blackout/src/map.rs

492 lines
19 KiB
Rust
Raw Normal View History

2021-05-13 17:25:45 +00:00
use std::collections::{HashMap, HashSet};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
2021-05-13 17:25:45 +00:00
use derive_more::{Deref, DerefMut};
2021-06-09 19:53:48 +00:00
pub use mapgen::Map;
2021-07-13 17:22:50 +00:00
use mapgen::{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;
use crate::{
2021-06-30 14:43:26 +00:00
core::{Coordinates, Player, PointLike},
2021-05-13 17:25:45 +00:00
exploration::{ExplorationType, Mappable},
log::Log,
utils::target_and_other,
visibility::BlocksVisibility,
2021-05-13 17:25:45 +00:00
};
impl From<mapgen::geometry::Point> for Coordinates {
fn from(point: mapgen::geometry::Point) -> Self {
Self((point.x as f32, point.y as f32))
}
}
2021-06-29 14:29:49 +00:00
#[derive(Clone, Debug, Default, Reflect)]
#[reflect(Component)]
2021-07-19 16:30:27 +00:00
pub struct Area;
2021-06-29 14:29:49 +00:00
#[derive(Clone, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct MapObstruction;
#[derive(Clone, Debug, Default, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Portal;
2021-05-13 17:25:45 +00:00
#[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct SpawnColliderPerTile(pub bool);
#[derive(Clone, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct SpawnColliders(pub bool);
impl Default for SpawnColliders {
fn default() -> Self {
Self(true)
}
}
#[derive(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
}
}
#[derive(Clone, Debug)]
pub struct MapConfig {
pub describe_undescribed_areas: bool,
2021-05-13 17:25:45 +00:00
pub speak_area_descriptions: bool,
pub start_revealed: bool,
}
impl Default for MapConfig {
fn default() -> Self {
Self {
describe_undescribed_areas: false,
2021-05-13 17:25:45 +00:00
speak_area_descriptions: true,
start_revealed: false,
}
}
}
#[derive(Bundle)]
2021-05-20 19:54:13 +00:00
pub struct PortalBundle {
2021-05-13 17:25:45 +00:00
pub coordinates: Coordinates,
2021-05-20 19:54:13 +00:00
pub portal: Portal,
2021-05-13 17:25:45 +00:00
pub exploration_type: ExplorationType,
pub mappable: Mappable,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
2021-05-20 19:54:13 +00:00
impl Default for PortalBundle {
2021-05-13 17:25:45 +00:00
fn default() -> Self {
Self {
coordinates: Default::default(),
2021-05-20 19:54:13 +00:00
portal: Default::default(),
exploration_type: ExplorationType::Portal,
2021-05-13 17:25:45 +00:00
mappable: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
}
}
}
#[derive(Bundle, Clone, Default)]
pub struct MapBundle {
pub map: Map,
pub spawn_colliders: SpawnColliders,
pub spawn_collider_per_tile: SpawnColliderPerTile,
pub spawn_portals: SpawnPortals,
2021-05-13 17:25:45 +00:00
pub children: Children,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
pub struct GridBuilder {
width_in_rooms: u32,
height_in_rooms: u32,
room_width: u32,
room_height: u32,
}
impl GridBuilder {
pub fn new(
width_in_rooms: u32,
height_in_rooms: u32,
room_width: u32,
room_height: u32,
) -> Box<GridBuilder> {
Box::new(GridBuilder {
width_in_rooms,
height_in_rooms,
room_width,
room_height,
})
}
}
impl MapFilter for GridBuilder {
2021-06-09 19:53:48 +00:00
fn modify_map(&self, _rng: &mut StdRng, map: &Map) -> Map {
2021-05-13 17:25:45 +00:00
let mut map = map.clone();
let mut generator = RbGenerator::new(None);
let 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;
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) {
let half_width = self.room_width / 2;
let half_height = self.room_height / 2;
use maze_generator::prelude::Direction::*;
if field.has_passage(&North) {
let x = x_offset + half_width;
let y = y_offset + self.room_height;
2021-07-13 17:22:50 +00:00
map.set_tile(x as usize, y as usize, Tile::floor());
2021-05-13 17:25:45 +00:00
}
if field.has_passage(&South) {
let x = x_offset + half_width;
let y = y_offset;
2021-07-13 17:22:50 +00:00
map.set_tile(x as usize, y as usize, Tile::floor());
2021-05-13 17:25:45 +00:00
}
if field.has_passage(&East) {
let x = x_offset + self.room_width;
let y = y_offset + half_height;
2021-07-13 17:22:50 +00:00
map.set_tile(x as usize, y as usize, Tile::floor());
2021-05-13 17:25:45 +00:00
}
if field.has_passage(&West) {
let x = x_offset;
let y = y_offset + half_height;
2021-07-13 17:22:50 +00:00
map.set_tile(x as usize, y as usize, Tile::floor());
2021-05-13 17:25:45 +00:00
}
}
}
}
map
}
}
fn spawn_colliders(
mut commands: Commands,
maps: Query<(Entity, &Map, &SpawnColliders, &SpawnColliderPerTile), Changed<SpawnColliders>>,
) {
for (map_entity, map, spawn_colliders, spawn_collider_per_tile) in maps.iter() {
if **spawn_colliders {
commands.entity(map_entity).remove::<SpawnColliders>();
commands.entity(map_entity).remove::<SpawnColliderPerTile>();
commands.entity(map_entity).insert_bundle(RigidBodyBundle {
body_type: RigidBodyType::Static,
..Default::default()
});
if **spawn_collider_per_tile {
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_bundle(ColliderBundle {
shape: ColliderShape::cuboid(0.5, 0.5),
..Default::default()
})
.insert(ColliderParent {
handle: map_entity.handle(),
pos_wrt_parent: Vec2::new(x as f32 + 0.5, y as f32 + 0.5)
.into(),
})
.insert(MapObstruction)
.id();
if tile.blocks_visibility() {
commands.entity(id).insert(BlocksVisibility);
}
}
}
}
} else {
let mut has_body = vec![vec![false; map.height as usize]; map.width as usize];
let mut bottom_left = None;
let mut bottom_right = None;
for y in 0..map.height {
for x in 0..map.width {
// println!("Checking ({}, {})", x, y);
if bottom_left.is_some() {
if has_body[x][y] {
// println!("Hit another body, setting bottom right");
bottom_right = Some((x, y));
} else if map.at(x, y).is_walkable() {
/*println!(
"Hit an empty tile, setting bottom right to ({}, {})",
x - 1,
y
);*/
bottom_right = Some((x - 1, y));
}
}
if map.at(x, y).is_blocked() && !has_body[x][y] {
//println!("Blocked, setting has_body");
has_body[x][y] = true;
if bottom_left.is_none() {
//println!("Setting bottom left");
bottom_left = Some((x, y));
}
if bottom_left.is_some() && x == map.width - 1 {
//println!("Hit right edge, setting bottom right");
bottom_right = Some((x, y));
}
}
if let (Some(bl), Some(br)) = (bottom_left, bottom_right) {
//println!("Got bottom, checking if can extend up");
let mut top_left = bl.clone();
let mut top_right = br.clone();
if y != map.height - 1 {
let mut can_extend_up = true;
for y in bl.1 + 1..map.height {
for x in bl.0..=br.0 {
//println!("Extension check: ({}, {})", x, y);
if map.at(x, y).is_walkable() {
//println!("Can't, empty tile");
can_extend_up = false;
break;
}
}
if can_extend_up {
//println!("Can extend up, setting has_body");
top_left.1 += 1;
top_right.1 += 1;
for x in top_left.0..=top_right.0 {
has_body[x][top_left.1] = true;
}
} else {
break;
}
}
}
let mut width = br.0 as f32 - bl.0 as f32;
if width == 0. {
width = 1.;
}
let half_width = width / 2.;
let mut height = top_left.1 as f32 - bl.1 as f32;
if height == 0. {
height = 1.;
}
let half_height = height / 2.;
/*println!(
"Top left: {:?}\ntop right: {:?}\nbottom left: {:?}\nbottom right: {:?}",
top_left, top_right, bl, br
);*/
let center = (bl.0 as f32 + half_width, br.1 as f32 + half_height);
let x = center.0;
let y = center.1;
/*println!(
"Create shape at {:?} of width {} and height {}",
center, width, height
);*/
let id = commands
.spawn_bundle(ColliderBundle {
shape: ColliderShape::cuboid(half_width, half_height),
..Default::default()
})
.insert(ColliderParent {
handle: map_entity.handle(),
pos_wrt_parent: Vec2::new(x, y).into(),
})
.insert(MapObstruction)
.id();
if map.at(x as usize, y as usize).blocks_visibility() {
commands.entity(id).insert(BlocksVisibility);
}
bottom_left = None;
bottom_right = None;
}
}
}
}
for room in &map.rooms {
let shape =
ColliderShape::cuboid((room.width() / 2) as f32, (room.height() / 2) as f32);
let position = Vec2::new(room.center().x(), room.center().y()).into();
commands
.spawn_bundle(ColliderBundle {
collider_type: ColliderType::Sensor,
shape: shape.clone(),
flags: ActiveEvents::INTERSECTION_EVENTS.into(),
..Default::default()
})
.insert(ColliderParent {
handle: map_entity.handle(),
pos_wrt_parent: position,
})
.insert(shape.compute_aabb(&position))
2021-07-19 16:30:27 +00:00
.insert(Area);
}
}
}
}
fn spawn_portals(
2021-05-13 17:25:45 +00:00
mut commands: Commands,
map: Query<(Entity, &Map, &SpawnPortals), Changed<SpawnPortals>>,
2021-05-13 17:25:45 +00:00
) {
for (entity, map, spawn_portals) in map.iter() {
if **spawn_portals {
commands.entity(entity).remove::<SpawnPortals>();
2021-05-20 19:54:13 +00:00
let mut portals: Vec<(f32, f32)> = vec![];
2021-06-09 19:53:48 +00:00
for x in 1..map.width {
for y in 1..map.height {
2021-05-20 19:54:13 +00:00
let mut spawn_portal = false;
2021-06-09 19:53:48 +00:00
if map.get_available_exits(x, y).len() > 2 {
let idx = (x, y).to_index(map.width);
2021-07-13 17:22:50 +00:00
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())
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-07-13 17:22:50 +00:00
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())
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 {
2021-05-13 17:25:45 +00:00
let x = x as f32;
let y = y as f32;
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
}
}
}
}
2021-05-20 19:54:13 +00:00
for portal in portals {
let x = portal.0 as f32;
let y = portal.1 as f32;
let coordinates = Coordinates((x, y));
let portal = commands
.spawn_bundle(PortalBundle {
2021-05-20 19:54:13 +00:00
coordinates,
2021-05-13 17:25:45 +00:00
..Default::default()
})
.id();
commands.entity(entity).push_children(&[portal]);
}
}
}
}
fn spawn_portal_colliders(
mut commands: Commands,
map: Query<(Entity, &SpawnColliders), With<Map>>,
portals: Query<(Entity, &Coordinates), Without<ColliderShape>>,
) {
for (map_entity, spawn_colliders) in map.iter() {
if **spawn_colliders {
for (portal_entity, coordinates) in portals.iter() {
commands
.entity(portal_entity)
.insert_bundle(ColliderBundle {
collider_type: ColliderType::Sensor,
shape: ColliderShape::cuboid(0.5, 0.5),
flags: ActiveEvents::INTERSECTION_EVENTS.into(),
..Default::default()
})
.insert(ColliderPositionSync::Discrete)
.insert(ColliderParent {
handle: map_entity.handle(),
pos_wrt_parent: Vec2::new(coordinates.x() + 0.5, coordinates.y() + 0.5)
.into(),
});
2021-05-13 17:25:45 +00:00
}
}
}
}
fn area_description(
mut events: EventReader<IntersectionEvent>,
2021-07-19 16:30:27 +00:00
areas: Query<&Area>,
players: Query<&Player>,
names: Query<&Name>,
config: Res<MapConfig>,
shapes: Query<&ColliderShape>,
2021-05-13 17:25:45 +00:00
mut log: Query<&mut Log>,
) {
for event in events.iter() {
if event.intersecting {
if let Some((area, other)) =
target_and_other(event.collider1.entity(), event.collider2.entity(), &|v| {
areas.get(v).is_ok()
})
{
if players.get(other).is_ok() {
if let Ok(mut log) = log.single_mut() {
if let Ok(name) = names.get(area) {
log.push(name.to_string());
} else if config.describe_undescribed_areas {
if let Ok(shape) = shapes.get(area) {
let aabb = shape.compute_local_aabb();
log.push(format!(
"{}-by-{} area",
aabb.extents().x,
aabb.extents().y
));
}
}
2021-05-13 17:25:45 +00:00
}
}
}
}
}
}
pub struct MapPlugin;
impl Plugin for MapPlugin {
fn build(&self, app: &mut AppBuilder) {
if !app.world().contains_resource::<MapConfig>() {
app.insert_resource(MapConfig::default());
}
let config = app.world().get_resource::<MapConfig>().unwrap().clone();
2021-05-20 19:54:13 +00:00
app.register_type::<Portal>()
.add_system(spawn_colliders.system())
.add_system(spawn_portals.system())
.add_system(spawn_portal_colliders.system());
2021-05-13 17:25:45 +00:00
if config.speak_area_descriptions {
app.add_system_to_stage(CoreStage::PostUpdate, area_description.system());
}
}
}