Create camera, and adjust drawing code to render arbitrary size maps

This commit is contained in:
Timothy Warren 2021-12-17 16:35:30 -05:00
parent 6af7476c51
commit f749d9a9e5
9 changed files with 247 additions and 60 deletions

168
src/camera.rs Normal file
View File

@ -0,0 +1,168 @@
use rltk::{Point, Rltk, RGB};
use specs::prelude::*;
use crate::{Hidden, Map, Position, Renderable, TileType};
const SHOW_BOUNDARIES: bool = true;
pub fn get_screen_bounds(ecs: &World, ctx: &mut Rltk) -> (i32, i32, i32, i32) {
let player_pos = ecs.fetch::<Point>();
let (x_chars, y_chars) = ctx.get_char_size();
let center_x = (x_chars / 2) as i32;
let center_y = (y_chars / 2) as i32;
let min_x = player_pos.x - center_x;
let max_x = min_x + x_chars as i32;
let min_y = player_pos.y - center_y;
let max_y = min_y + y_chars as i32;
(min_x, max_x, min_y, max_y)
}
pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
let map = ecs.fetch::<Map>();
let (min_x, max_x, min_y, max_y) = get_screen_bounds(ecs, ctx);
// Render the Map
let map_width = map.width - 1;
let map_height = map.height - 1;
let mut y = 0;
#[allow(clippy::explicit_counter_loop)]
for ty in min_y..max_y {
let mut x = 0;
for tx in min_x..max_x {
if tx > 0 && tx < map_width && ty > 0 && ty < map_height {
let idx = map.xy_idx(tx, ty);
if map.revealed_tiles[idx] {
let (glyph, fg, bg) = get_tile_glyph(idx, &*map);
ctx.set(x, y, fg, bg, glyph);
}
} else if SHOW_BOUNDARIES {
ctx.set(
x,
y,
RGB::named(rltk::GRAY),
RGB::named(rltk::BLACK),
rltk::to_cp437('·'),
);
}
x += 1;
}
y += 1;
}
// Render entities
let positions = ecs.read_storage::<Position>();
let renderables = ecs.read_storage::<Renderable>();
let hidden = ecs.read_storage::<Hidden>();
let map = ecs.fetch::<Map>();
let mut data = (&positions, &renderables, !&hidden)
.join()
.collect::<Vec<_>>();
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] {
let entity_screen_x = pos.x - min_x;
let entity_screen_y = pos.y - min_y;
if entity_screen_x > 0
&& entity_screen_x < map_width
&& entity_screen_y > 0
&& entity_screen_y < map_height
{
ctx.set(
entity_screen_x,
entity_screen_y,
render.fg,
render.bg,
render.glyph,
);
}
}
}
}
fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) {
let glyph;
let mut fg;
let mut bg = RGB::from_f32(0., 0., 0.);
match map.tiles[idx] {
TileType::Floor => {
glyph = rltk::to_cp437('.');
fg = RGB::from_f32(0., 0.5, 0.5);
}
TileType::Wall => {
let x = idx as i32 % map.width;
let y = idx as i32 / map.width;
glyph = wall_glyph(&*map, x, y);
fg = RGB::from_f32(0., 1.0, 0.);
}
TileType::DownStairs => {
glyph = rltk::to_cp437('>');
fg = RGB::from_f32(0., 1.0, 1.0);
}
};
if map.bloodstains.contains(&idx) {
bg = RGB::from_f32(0.75, 0., 0.);
}
if !map.visible_tiles[idx] {
fg = fg.to_greyscale();
// Don't show bloodstains out of visual range
bg = RGB::from_f32(0., 0., 0.);
}
(glyph, fg, bg)
}
fn wall_glyph(map: &Map, x: i32, y: i32) -> rltk::FontCharType {
if x < 1 || x > map.width - 2 || y < 1 || y > map.height - 2 {
return 35;
}
let mut mask = 0u8;
if is_revealed_and_wall(map, x, y - 1) {
mask += 1;
}
if is_revealed_and_wall(map, x, y + 1) {
mask += 2;
}
if is_revealed_and_wall(map, x - 1, y) {
mask += 4;
}
if is_revealed_and_wall(map, x + 1, y) {
mask += 8;
}
match mask {
0 => 9, // Pillar because we can't see neighbors
1 => 186, // Wall only to the north
2 => 186, // Wall only to the south
3 => 186, // Wall to the north and south
4 => 205, // Wall only to the west
5 => 188, // Wall to the north and west
6 => 187, // Wall to the south and west
7 => 185, // Wall to the north, south, and west
8 => 205, // Wall only to the east
9 => 200, // Wall to the north and east
10 => 201, // Wall to the sound and east
11 => 204, // Wall to the north, south, and east
12 => 205, // Wall to the east and west
13 => 202, // Wall to the east, west, and south
14 => 203, // Wall to the east, west, and north
15 => 206, // ╬ Wall on all sides
_ => 35, // We missed one?
}
}
fn is_revealed_and_wall(map: &Map, x: i32, y: i32) -> bool {
let idx = map.xy_idx(x, y);
map.tiles[idx] == TileType::Wall && map.revealed_tiles[idx]
}

View File

@ -6,7 +6,7 @@ use crate::components::{
}; };
use crate::game_log::GameLog; use crate::game_log::GameLog;
use crate::rex_assets::RexAssets; use crate::rex_assets::RexAssets;
use crate::{Equipped, Hidden, Map, RunState, State}; use crate::{camera, Equipped, Hidden, Map, RunState, State};
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
ctx.draw_box( ctx.draw_box(
@ -98,20 +98,32 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
} }
fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
let (min_x, _max_x, min_y, _max_y) = camera::get_screen_bounds(ecs, ctx);
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let names = ecs.read_storage::<Name>(); let names = ecs.read_storage::<Name>();
let positions = ecs.read_storage::<Position>(); let positions = ecs.read_storage::<Position>();
let hidden = ecs.read_storage::<Hidden>(); let hidden = ecs.read_storage::<Hidden>();
let mouse_pos = ctx.mouse_pos(); let mouse_pos = ctx.mouse_pos();
if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height { let mut mouse_map_pos = mouse_pos;
mouse_map_pos.0 += min_x;
mouse_map_pos.1 += min_y;
if mouse_map_pos.0 >= map.width - 1
|| mouse_map_pos.1 >= map.height - 1
|| mouse_map_pos.0 < 1
|| mouse_map_pos.1 < 1
{
return; return;
} }
if !map.visible_tiles[map.xy_idx(mouse_map_pos.0, mouse_map_pos.1)] {
return;
}
let mut tooltip: Vec<String> = Vec::new(); let mut tooltip: Vec<String> = Vec::new();
for (name, position, _hidden) in (&names, &positions, !&hidden).join() { for (name, position, _hidden) in (&names, &positions, !&hidden).join() {
let idx = map.xy_idx(position.x, position.y); if position.x == mouse_map_pos.0 && position.y == mouse_map_pos.1 {
if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] {
tooltip.push(name.name.to_string()); tooltip.push(name.name.to_string());
} }
} }
@ -475,6 +487,7 @@ pub fn ranged_target(
ctx: &mut Rltk, ctx: &mut Rltk,
range: i32, range: i32,
) -> (ItemMenuResult, Option<Point>) { ) -> (ItemMenuResult, Option<Point>) {
let (min_x, max_x, min_y, max_y) = camera::get_screen_bounds(&gs.ecs, ctx);
let player_entity = gs.ecs.fetch::<Entity>(); let player_entity = gs.ecs.fetch::<Entity>();
let player_pos = gs.ecs.fetch::<Point>(); let player_pos = gs.ecs.fetch::<Point>();
let viewsheds = gs.ecs.read_storage::<Viewshed>(); let viewsheds = gs.ecs.read_storage::<Viewshed>();
@ -493,19 +506,31 @@ pub fn ranged_target(
for idx in visible.visible_tiles.iter() { for idx in visible.visible_tiles.iter() {
let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx); let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx);
if distance <= range as f32 { if distance <= range as f32 {
let screen_x = idx.x - min_x;
let screen_y = idx.y - min_y;
if screen_x > 1
&& screen_x < (max_x - min_x) - 1
&& screen_y > 1
&& screen_y < (max_y - min_y) - 1
{
ctx.set_bg(idx.x, idx.y, RGB::named(rltk::BLUE)); ctx.set_bg(idx.x, idx.y, RGB::named(rltk::BLUE));
available_cells.push(idx); available_cells.push(idx);
} }
} }
}
} else { } else {
return (ItemMenuResult::Cancel, None); return (ItemMenuResult::Cancel, None);
} }
// Draw mouse cursor // Draw mouse cursor
let mouse_pos = ctx.mouse_pos(); let mouse_pos = ctx.mouse_pos();
let mut mouse_map_pos = mouse_pos;
mouse_map_pos.0 += min_x;
mouse_map_pos.1 += min_y;
let mut valid_target = false; let mut valid_target = false;
for idx in available_cells.iter() { for idx in available_cells.iter() {
if idx.x == mouse_pos.0 && idx.y == mouse_pos.1 { if idx.x == mouse_map_pos.0 && idx.y == mouse_map_pos.1 {
valid_target = true; valid_target = true;
} }
} }
@ -515,7 +540,7 @@ pub fn ranged_target(
if ctx.left_click { if ctx.left_click {
return ( return (
ItemMenuResult::Selected, ItemMenuResult::Selected,
Some(Point::new(mouse_pos.0, mouse_pos.1)), Some(Point::new(mouse_map_pos.0, mouse_map_pos.1)),
); );
} }
} else { } else {

View File

@ -2,6 +2,7 @@ use rltk::{GameState, Point, RandomNumberGenerator, Rltk};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
pub mod camera;
mod components; mod components;
mod damage_system; mod damage_system;
mod game_log; mod game_log;
@ -150,28 +151,10 @@ impl GameState for State {
RunState::MainMenu { .. } => {} RunState::MainMenu { .. } => {}
RunState::GameOver { .. } => {} RunState::GameOver { .. } => {}
_ => { _ => {
// Draw the UI camera::render_camera(&self.ecs, ctx);
draw_map(&self.ecs.fetch::<Map>(), ctx);
{
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let hidden = self.ecs.read_storage::<Hidden>();
let map = self.ecs.fetch::<Map>();
let mut data: Vec<_> = (&positions, &renderables, !&hidden).join().collect();
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] {
ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph)
}
}
gui::draw_ui(&self.ecs, ctx); gui::draw_ui(&self.ecs, ctx);
} }
} }
}
match newrunstate { match newrunstate {
RunState::MapGeneration => { RunState::MapGeneration => {
@ -345,12 +328,12 @@ impl GameState for State {
} }
RunState::MagicMapReveal { row } => { RunState::MagicMapReveal { row } => {
let mut map = self.ecs.fetch_mut::<Map>(); let mut map = self.ecs.fetch_mut::<Map>();
for x in 0..MAP_WIDTH { for x in 0..map.width {
let idx = map.xy_idx(x as i32, row); let idx = map.xy_idx(x as i32, row);
map.revealed_tiles[idx] = true; map.revealed_tiles[idx] = true;
} }
if row as usize == MAP_HEIGHT - 1 { if row == map.height - 1 {
newrunstate = RunState::MonsterTurn; newrunstate = RunState::MonsterTurn;
} else { } else {
newrunstate = RunState::MagicMapReveal { row: row + 1 }; newrunstate = RunState::MagicMapReveal { row: row + 1 };
@ -462,7 +445,7 @@ impl State {
self.mapgen_history.clear(); self.mapgen_history.clear();
let mut rng = self.ecs.write_resource::<RandomNumberGenerator>(); let mut rng = self.ecs.write_resource::<RandomNumberGenerator>();
let mut builder = map_builders::random_builder(new_depth, &mut rng); let mut builder = map_builders::random_builder(new_depth, &mut rng, 64, 64);
builder.build_map(&mut rng); builder.build_map(&mut rng);
std::mem::drop(rng); std::mem::drop(rng);
@ -550,7 +533,7 @@ fn main() -> rltk::BError {
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
gs.ecs.insert(Map::new(1)); gs.ecs.insert(Map::new(1, 64, 64));
gs.ecs.insert(Point::zero()); gs.ecs.insert(Point::zero());
gs.ecs.insert(rltk::RandomNumberGenerator::new()); gs.ecs.insert(rltk::RandomNumberGenerator::new());

View File

@ -4,10 +4,6 @@ use rltk::{Algorithm2D, BaseMap, Point, Rltk, SmallVec, RGB};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::prelude::*; use specs::prelude::*;
pub const MAP_WIDTH: usize = 80;
pub const MAP_HEIGHT: usize = 43;
pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
pub enum TileType { pub enum TileType {
Wall, Wall,
@ -60,15 +56,17 @@ impl Map {
} }
/// Generates an empty map, consisting entirely of solid walls /// Generates an empty map, consisting entirely of solid walls
pub fn new(new_depth: i32) -> Map { pub fn new(new_depth: i32, width: i32, height: i32) -> Map {
let map_tile_count = (width * height) as usize;
Map { Map {
tiles: vec![TileType::Wall; MAP_COUNT], tiles: vec![TileType::Wall; map_tile_count],
width: MAP_WIDTH as i32, width,
height: MAP_HEIGHT as i32, height,
revealed_tiles: vec![false; MAP_COUNT], revealed_tiles: vec![false; map_tile_count],
visible_tiles: vec![false; MAP_COUNT], visible_tiles: vec![false; map_tile_count],
blocked: vec![false; MAP_COUNT], blocked: vec![false; map_tile_count],
tile_content: vec![Vec::new(); MAP_COUNT], tile_content: vec![Vec::new(); map_tile_count],
depth: new_depth, depth: new_depth,
bloodstains: HashSet::new(), bloodstains: HashSet::new(),
view_blocked: HashSet::new(), view_blocked: HashSet::new(),
@ -173,7 +171,7 @@ pub fn draw_map(map: &Map, ctx: &mut Rltk) {
// Move to the next set of coordinates // Move to the next set of coordinates
x += 1; x += 1;
if x > MAP_WIDTH as i32 - 1 { if x > (map.width * map.height) as i32 - 1 {
x = 0; x = 0;
y += 1; y += 1;
} }

View File

@ -66,17 +66,21 @@ pub struct BuilderMap {
pub rooms: Option<Vec<Rect>>, pub rooms: Option<Vec<Rect>>,
pub corridors: Option<Vec<Vec<usize>>>, pub corridors: Option<Vec<Vec<usize>>>,
pub history: Vec<Map>, pub history: Vec<Map>,
pub width: i32,
pub height: i32,
} }
impl BuilderMap { impl BuilderMap {
fn new(new_depth: i32) -> BuilderMap { fn new(new_depth: i32, width: i32, height: i32) -> BuilderMap {
BuilderMap { BuilderMap {
spawn_list: Vec::new(), spawn_list: Vec::new(),
map: Map::new(new_depth), map: Map::new(new_depth, width, height),
starting_position: None, starting_position: None,
rooms: None, rooms: None,
corridors: None, corridors: None,
history: Vec::new(), history: Vec::new(),
width,
height,
} }
} }
@ -98,11 +102,11 @@ pub struct BuilderChain {
} }
impl BuilderChain { impl BuilderChain {
pub fn new(new_depth: i32) -> BuilderChain { pub fn new(new_depth: i32, width: i32, height: i32) -> BuilderChain {
BuilderChain { BuilderChain {
starter: None, starter: None,
builders: Vec::new(), builders: Vec::new(),
build_data: BuilderMap::new(new_depth), build_data: BuilderMap::new(new_depth, width, height),
} }
} }
@ -289,8 +293,13 @@ fn random_shape_builder(rng: &mut RandomNumberGenerator, builder: &mut BuilderCh
builder.with(DistantExit::new()); builder.with(DistantExit::new());
} }
pub fn random_builder(new_depth: i32, rng: &mut RandomNumberGenerator) -> BuilderChain { pub fn random_builder(
let mut builder = BuilderChain::new(new_depth); new_depth: i32,
rng: &mut RandomNumberGenerator,
width: i32,
height: i32,
) -> BuilderChain {
let mut builder = BuilderChain::new(new_depth, width, height);
match rng.roll_dice(1, 2) { match rng.roll_dice(1, 2) {
1 => random_room_builder(rng, &mut builder), 1 => random_room_builder(rng, &mut builder),

View File

@ -33,7 +33,7 @@ impl WaveformCollapseBuilder {
let constraints = patterns_to_constraints(patterns, CHUNK_SIZE); let constraints = patterns_to_constraints(patterns, CHUNK_SIZE);
self.render_tile_gallery(&constraints, CHUNK_SIZE, build_data); self.render_tile_gallery(&constraints, CHUNK_SIZE, build_data);
build_data.map = Map::new(build_data.map.depth); build_data.map = Map::new(build_data.map.depth, build_data.width, build_data.height);
loop { loop {
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map); let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map);
while !solver.iteration(&mut build_data.map, rng) { while !solver.iteration(&mut build_data.map, rng) {
@ -54,7 +54,7 @@ impl WaveformCollapseBuilder {
chunk_size: i32, chunk_size: i32,
build_data: &mut BuilderMap, build_data: &mut BuilderMap,
) { ) {
build_data.map = Map::new(0); build_data.map = Map::new(0, build_data.width, build_data.height);
let mut counter = 0; let mut counter = 0;
let mut x = 1; let mut x = 1;
let mut y = 1; let mut y = 1;
@ -71,7 +71,7 @@ impl WaveformCollapseBuilder {
if y + chunk_size > build_data.map.height { if y + chunk_size > build_data.map.height {
// Move to the next page // Move to the next page
build_data.take_snapshot(); build_data.take_snapshot();
build_data.map = Map::new(0); build_data.map = Map::new(0, build_data.width, build_data.height);
x = 1; x = 1;
y = 1; y = 1;

View File

@ -61,8 +61,8 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
} }
if !map.blocked[destination_idx] { if !map.blocked[destination_idx] {
pos.x = min(79, max(0, pos.x + delta_x)); pos.x = min(map.width - 1, max(0, pos.x + delta_x));
pos.y = min(49, max(0, pos.y + delta_y)); pos.y = min(map.height - 1, max(0, pos.y + delta_y));
entity_moved entity_moved
.insert(entity, EntityMoved {}) .insert(entity, EntityMoved {})
.expect("Failed to add EntityMoved flag to player"); .expect("Failed to add EntityMoved flag to player");

View File

@ -191,7 +191,7 @@ pub fn load_game(ecs: &mut World) {
for (e, h) in (&entities, &helper).join() { for (e, h) in (&entities, &helper).join() {
let mut worldmap = ecs.write_resource::<crate::map::Map>(); let mut worldmap = ecs.write_resource::<crate::map::Map>();
*worldmap = h.map.clone(); *worldmap = h.map.clone();
worldmap.tile_content = vec![Vec::new(); crate::map::MAP_COUNT]; worldmap.tile_content = vec![Vec::new(); (worldmap.height * worldmap.width) as usize];
deleteme = Some(e); deleteme = Some(e);
} }

View File

@ -5,7 +5,6 @@ use specs::prelude::*;
use specs::saveload::{MarkedBuilder, SimpleMarker}; use specs::saveload::{MarkedBuilder, SimpleMarker};
use crate::components::*; use crate::components::*;
use crate::map::MAP_WIDTH;
use crate::random_table::RandomTable; use crate::random_table::RandomTable;
use crate::{Map, Rect, TileType}; use crate::{Map, Rect, TileType};
@ -125,8 +124,13 @@ pub fn spawn_region(
/// Spawns a named entity (name in tuple.1) at the location in (tuple.0) /// Spawns a named entity (name in tuple.1) at the location in (tuple.0)
pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
let x = (*spawn.0 % MAP_WIDTH) as i32; let map = ecs.fetch::<Map>();
let y = (*spawn.0 / MAP_WIDTH) as i32; let width = map.width as usize;
let x = (*spawn.0 % width) as i32;
let y = (*spawn.0 / width) as i32;
// Drop this map reference to make the borrow checker happy
std::mem::drop(map);
match spawn.1.as_ref() { match spawn.1.as_ref() {
"Goblin" => goblin(ecs, x, y), "Goblin" => goblin(ecs, x, y),