mapgen visualisation

This commit is contained in:
Llywelwyn 2023-07-15 13:38:51 +01:00
parent 011b26088e
commit 0728a1db41
6 changed files with 144 additions and 81 deletions

View file

@ -43,6 +43,9 @@ rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
rltk::embedded_resource!(SCANLINESFS, "../resources/scanlines.fs"); rltk::embedded_resource!(SCANLINESFS, "../resources/scanlines.fs");
rltk::embedded_resource!(SCANLINESVS, "../resources/scanlines.vs"); rltk::embedded_resource!(SCANLINESVS, "../resources/scanlines.vs");
//Consts
pub const SHOW_MAPGEN: bool = true;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum RunState { pub enum RunState {
AwaitingInput, AwaitingInput,
@ -58,22 +61,33 @@ pub enum RunState {
GameOver, GameOver,
NextLevel, NextLevel,
MagicMapReveal { row: i32, cursed: bool }, MagicMapReveal { row: i32, cursed: bool },
MapGeneration,
} }
pub struct State { pub struct State {
pub ecs: World, pub ecs: World,
mapgen_next_state: Option<RunState>,
mapgen_history: Vec<Map>,
mapgen_index: usize,
mapgen_timer: f32,
} }
impl State { impl State {
fn generate_world_map(&mut self, new_depth: i32) { fn generate_world_map(&mut self, new_depth: i32) {
// Visualisation stuff
self.mapgen_index = 0;
self.mapgen_timer = 0.0;
self.mapgen_history.clear();
// Create new builder // Create new builder
let mut builder = map_builders::random_builder(new_depth); let mut builder = map_builders::random_builder(new_depth);
let player_start; let player_start;
// Scope for borrow checker
{ {
// Fetch RNG from resources // Fetch RNG from resources
let mut rng = self.ecs.write_resource::<RandomNumberGenerator>(); let mut rng = self.ecs.write_resource::<RandomNumberGenerator>();
// Build a new map using RNG (to retain seed) // Build a new map using RNG (to retain seed)
builder.build_map(&mut rng); builder.build_map(&mut rng);
self.mapgen_history = builder.get_snapshot_history();
let mut worldmap_resource = self.ecs.write_resource::<Map>(); let mut worldmap_resource = self.ecs.write_resource::<Map>();
*worldmap_resource = builder.get_map(); *worldmap_resource = builder.get_map();
player_start = builder.get_starting_pos(); player_start = builder.get_starting_pos();
@ -236,7 +250,7 @@ impl GameState for State {
RunState::MainMenu { .. } => {} RunState::MainMenu { .. } => {}
_ => { _ => {
// Draw map and ui // Draw map and ui
draw_map(&self.ecs, ctx); draw_map(&self.ecs.fetch::<Map>(), ctx);
{ {
let positions = self.ecs.read_storage::<Position>(); let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>(); let renderables = self.ecs.read_storage::<Renderable>();
@ -442,6 +456,23 @@ impl GameState for State {
new_runstate = RunState::MagicMapReveal { row: row + 1, cursed: cursed }; new_runstate = RunState::MagicMapReveal { row: row + 1, cursed: cursed };
} }
} }
RunState::MapGeneration => {
if !SHOW_MAPGEN {
new_runstate = self.mapgen_next_state.unwrap();
} else {
ctx.cls();
draw_map(&self.mapgen_history[self.mapgen_index], ctx);
self.mapgen_timer += ctx.frame_time_ms;
if self.mapgen_timer > 300.0 {
self.mapgen_timer = 0.0;
self.mapgen_index += 1;
if self.mapgen_index >= self.mapgen_history.len() {
new_runstate = self.mapgen_next_state.unwrap();
}
}
}
}
} }
{ {
@ -467,7 +498,13 @@ fn main() -> rltk::BError {
context.with_post_scanlines(false); context.with_post_scanlines(false);
//context.screen_burn_color(RGB::named((150, 255, 255))); //context.screen_burn_color(RGB::named((150, 255, 255)));
let mut gs = State { ecs: World::new() }; let mut gs = State {
ecs: World::new(),
mapgen_next_state: Some(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame }),
mapgen_index: 0,
mapgen_history: Vec::new(),
mapgen_timer: 0.0,
};
gs.ecs.register::<Position>(); gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>(); gs.ecs.register::<Renderable>();
@ -517,7 +554,7 @@ fn main() -> rltk::BError {
gs.ecs.insert(Point::new(0, 0)); gs.ecs.insert(Point::new(0, 0));
gs.ecs.insert(player_entity); gs.ecs.insert(player_entity);
gs.ecs.insert(rltk::RandomNumberGenerator::new()); gs.ecs.insert(rltk::RandomNumberGenerator::new());
gs.ecs.insert(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame }); gs.ecs.insert(RunState::MapGeneration {});
gs.ecs.insert(particle_system::ParticleBuilder::new()); gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.insert(rex_assets::RexAssets::new()); gs.ecs.insert(rex_assets::RexAssets::new());

View file

@ -139,9 +139,7 @@ impl BaseMap for Map {
} }
} }
pub fn draw_map(ecs: &World, ctx: &mut Rltk) { pub fn draw_map(map: &Map, ctx: &mut Rltk) {
let map = ecs.fetch::<Map>();
let mut y = 0; let mut y = 0;
let mut x = 0; let mut x = 0;
for (idx, tile) in map.tiles.iter().enumerate() { for (idx, tile) in map.tiles.iter().enumerate() {

View file

@ -1,4 +1,4 @@
use super::{spawner, Map, Position, Rect, TileType}; use super::{spawner, Map, Position, Rect, TileType, SHOW_MAPGEN};
mod simple_map; mod simple_map;
use simple_map::SimpleMapBuilder; use simple_map::SimpleMapBuilder;
mod common; mod common;
@ -11,6 +11,8 @@ pub trait MapBuilder {
fn spawn_entities(&mut self, ecs: &mut World); fn spawn_entities(&mut self, ecs: &mut World);
fn get_map(&mut self) -> Map; fn get_map(&mut self) -> Map;
fn get_starting_pos(&mut self) -> Position; fn get_starting_pos(&mut self) -> Position;
fn get_snapshot_history(&self) -> Vec<Map>;
fn take_snapshot(&mut self);
} }
pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> { pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {

View file

@ -1,8 +1,8 @@
use super::{ use super::{
apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect, apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect,
TileType, TileType, SHOW_MAPGEN,
}; };
use rltk::RandomNumberGenerator; use rltk::{console, RandomNumberGenerator};
use specs::prelude::*; use specs::prelude::*;
pub struct SimpleMapBuilder { pub struct SimpleMapBuilder {
@ -10,24 +10,36 @@ pub struct SimpleMapBuilder {
starting_position: Position, starting_position: Position,
depth: i32, depth: i32,
rooms: Vec<Rect>, rooms: Vec<Rect>,
history: Vec<Map>,
} }
impl MapBuilder for SimpleMapBuilder { impl MapBuilder for SimpleMapBuilder {
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
return self.rooms_and_corridors(rng); return self.rooms_and_corridors(rng);
} }
fn spawn_entities(&mut self, ecs: &mut World) { fn spawn_entities(&mut self, ecs: &mut World) {
for room in self.rooms.iter().skip(1) { for room in self.rooms.iter().skip(1) {
return spawner::spawn_room(ecs, room, self.depth); spawner::spawn_room(ecs, room, self.depth);
}
}
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
} }
} }
} }
@ -39,6 +51,7 @@ impl SimpleMapBuilder {
starting_position: Position { x: 0, y: 0 }, starting_position: Position { x: 0, y: 0 },
depth: new_depth, depth: new_depth,
rooms: Vec::new(), rooms: Vec::new(),
history: Vec::new(),
} }
} }
@ -89,6 +102,8 @@ impl SimpleMapBuilder {
} }
self.rooms.push(new_room); self.rooms.push(new_room);
console::log("pushed new room");
self.take_snapshot();
} }
} }

View file

@ -6,7 +6,7 @@ use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
use specs::prelude::*; use specs::prelude::*;
use std::cmp::{max, min}; use std::cmp::{max, min};
pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let mut positions = ecs.write_storage::<Position>(); let mut positions = ecs.write_storage::<Position>();
let mut players = ecs.write_storage::<Player>(); let mut players = ecs.write_storage::<Player>();
let mut viewsheds = ecs.write_storage::<Viewshed>(); let mut viewsheds = ecs.write_storage::<Viewshed>();
@ -24,7 +24,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
|| pos.y + delta_y < 1 || pos.y + delta_y < 1
|| pos.y + delta_y > map.height - 1 || pos.y + delta_y > map.height - 1
{ {
return; return false;
} }
let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y);
@ -32,56 +32,62 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let target = combat_stats.get(*potential_target); let target = combat_stats.get(*potential_target);
if let Some(_target) = target { if let Some(_target) = target {
wants_to_melee.insert(entity, WantsToMelee { target: *potential_target }).expect("Add target failed."); wants_to_melee.insert(entity, WantsToMelee { target: *potential_target }).expect("Add target failed.");
return; return true;
} }
} }
if !map.blocked[destination_idx] { if map.blocked[destination_idx] {
let names = ecs.read_storage::<Name>(); return false;
let hidden = ecs.read_storage::<Hidden>();
// Push every entity name in the pile to a vector of strings
let mut item_names: Vec<String> = Vec::new();
let mut some = false;
for entity in map.tile_content[destination_idx].iter() {
if let Some(_hidden) = hidden.get(*entity) {
} else {
if let Some(name) = names.get(*entity) {
let item_name = &name.name;
item_names.push(item_name.to_string());
some = true;
}
}
}
// If some names were found, append. Logger = logger is necessary
// makes logger called a mutable self. It's not the most efficient
// but it happens infrequently enough (once per player turn at most)
// that it shouldn't matter.
if some {
let mut logger = gamelog::Logger::new().append("You see a");
for i in 0..item_names.len() {
if i > 0 && i < item_names.len() {
logger = logger.append(", a");
}
logger = logger.item_name_n(&item_names[i]);
}
logger.period().log();
}
pos.x = min((MAPWIDTH as i32) - 1, max(0, pos.x + delta_x));
pos.y = min((MAPHEIGHT as i32) - 1, max(0, pos.y + delta_y));
// Dirty viewsheds, and check only now if telepath viewshed exists
viewshed.dirty = true;
let is_telepath = telepaths.get_mut(entity);
if let Some(telepathy) = is_telepath {
telepathy.dirty = true;
}
let mut ppos = ecs.write_resource::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
} }
let names = ecs.read_storage::<Name>();
let hidden = ecs.read_storage::<Hidden>();
// Push every entity name in the pile to a vector of strings
let mut item_names: Vec<String> = Vec::new();
let mut some = false;
for entity in map.tile_content[destination_idx].iter() {
if let Some(_hidden) = hidden.get(*entity) {
} else {
if let Some(name) = names.get(*entity) {
let item_name = &name.name;
item_names.push(item_name.to_string());
some = true;
}
}
}
// If some names were found, append. Logger = logger is necessary
// makes logger called a mutable self. It's not the most efficient
// but it happens infrequently enough (once per player turn at most)
// that it shouldn't matter.
if some {
let mut logger = gamelog::Logger::new().append("You see a");
for i in 0..item_names.len() {
if i > 0 && i < item_names.len() {
logger = logger.append(", a");
}
logger = logger.item_name_n(&item_names[i]);
}
logger.period().log();
}
pos.x = min((MAPWIDTH as i32) - 1, max(0, pos.x + delta_x));
pos.y = min((MAPHEIGHT as i32) - 1, max(0, pos.y + delta_y));
// Dirty viewsheds, and check only now if telepath viewshed exists
viewshed.dirty = true;
let is_telepath = telepaths.get_mut(entity);
if let Some(telepathy) = is_telepath {
telepathy.dirty = true;
}
let mut ppos = ecs.write_resource::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
return true;
} }
return false;
} }
fn get_item(ecs: &mut World) { fn get_item(ecs: &mut World) {
@ -111,27 +117,28 @@ fn get_item(ecs: &mut World) {
pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
// Player movement // Player movement
let mut result = false;
match ctx.key { match ctx.key {
None => return RunState::AwaitingInput, None => return RunState::AwaitingInput,
Some(key) => match key { Some(key) => match key {
// Cardinals // Cardinals
VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => { VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => {
try_move_player(-1, 0, &mut gs.ecs); result = try_move_player(-1, 0, &mut gs.ecs);
} }
VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => { VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => {
try_move_player(1, 0, &mut gs.ecs); result = try_move_player(1, 0, &mut gs.ecs);
} }
VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => { VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => {
try_move_player(0, -1, &mut gs.ecs); result = try_move_player(0, -1, &mut gs.ecs);
} }
VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => { VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => {
try_move_player(0, 1, &mut gs.ecs); result = try_move_player(0, 1, &mut gs.ecs);
} }
// Diagonals // Diagonals
VirtualKeyCode::Numpad9 | VirtualKeyCode::U => try_move_player(1, -1, &mut gs.ecs), VirtualKeyCode::Numpad9 | VirtualKeyCode::U => result = try_move_player(1, -1, &mut gs.ecs),
VirtualKeyCode::Numpad7 | VirtualKeyCode::Y => try_move_player(-1, -1, &mut gs.ecs), VirtualKeyCode::Numpad7 | VirtualKeyCode::Y => result = try_move_player(-1, -1, &mut gs.ecs),
VirtualKeyCode::Numpad3 | VirtualKeyCode::N => try_move_player(1, 1, &mut gs.ecs), VirtualKeyCode::Numpad3 | VirtualKeyCode::N => result = try_move_player(1, 1, &mut gs.ecs),
VirtualKeyCode::Numpad1 | VirtualKeyCode::B => try_move_player(-1, 1, &mut gs.ecs), VirtualKeyCode::Numpad1 | VirtualKeyCode::B => result = try_move_player(-1, 1, &mut gs.ecs),
// Depth // Depth
VirtualKeyCode::Period => { VirtualKeyCode::Period => {
if ctx.shift { if ctx.shift {
@ -158,7 +165,11 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
} }
}, },
} }
RunState::PlayerTurn if result {
return RunState::PlayerTurn;
} else {
return RunState::AwaitingInput;
}
} }
pub fn try_next_level(ecs: &mut World) -> bool { pub fn try_next_level(ecs: &mut World) -> bool {

View file

@ -78,12 +78,10 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
// Scope for borrow checker // Scope for borrow checker
{ {
let mut rng = ecs.write_resource::<RandomNumberGenerator>(); let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let num_spawns = rng.roll_dice(1, MAX_ENTITIES + 3) + (map_depth - 1) - 3; let num_spawns = rng.roll_dice(1, MAX_ENTITIES + 2) - 2;
// With a MAX_ENTITIES of 4, this means each room has between: console::log(format!("room of: {}", num_spawns));
// d1: -2 to 4 // [-1, 0, 1, 2, 3, 4] things in a room
// d2: -1 to 5 // 2/6 chance for nothing, 4/6 for something
// d3: 0 to 6
// etc.
for _i in 0..num_spawns { for _i in 0..num_spawns {
let mut added = false; let mut added = false;
@ -104,8 +102,10 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
} }
spawn_points.insert(idx, spawn_table.roll(&mut rng)); spawn_points.insert(idx, spawn_table.roll(&mut rng));
added = true; added = true;
console::log(format!("added spawnpoint"));
} else { } else {
tries += 1; tries += 1;
console::log(format!("failed {} times", tries));
} }
} }
} }