Create camera, and adjust drawing code to render arbitrary size maps
This commit is contained in:
parent
6af7476c51
commit
f749d9a9e5
168
src/camera.rs
Normal file
168
src/camera.rs
Normal 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]
|
||||
}
|
37
src/gui.rs
37
src/gui.rs
@ -6,7 +6,7 @@ use crate::components::{
|
||||
};
|
||||
use crate::game_log::GameLog;
|
||||
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) {
|
||||
ctx.draw_box(
|
||||
@ -98,20 +98,32 @@ pub fn draw_ui(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 names = ecs.read_storage::<Name>();
|
||||
let positions = ecs.read_storage::<Position>();
|
||||
let hidden = ecs.read_storage::<Hidden>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if !map.visible_tiles[map.xy_idx(mouse_map_pos.0, mouse_map_pos.1)] {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tooltip: Vec<String> = Vec::new();
|
||||
|
||||
for (name, position, _hidden) in (&names, &positions, !&hidden).join() {
|
||||
let idx = map.xy_idx(position.x, position.y);
|
||||
if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] {
|
||||
if position.x == mouse_map_pos.0 && position.y == mouse_map_pos.1 {
|
||||
tooltip.push(name.name.to_string());
|
||||
}
|
||||
}
|
||||
@ -475,6 +487,7 @@ pub fn ranged_target(
|
||||
ctx: &mut Rltk,
|
||||
range: i32,
|
||||
) -> (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_pos = gs.ecs.fetch::<Point>();
|
||||
let viewsheds = gs.ecs.read_storage::<Viewshed>();
|
||||
@ -493,19 +506,31 @@ pub fn ranged_target(
|
||||
for idx in visible.visible_tiles.iter() {
|
||||
let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx);
|
||||
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));
|
||||
available_cells.push(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return (ItemMenuResult::Cancel, None);
|
||||
}
|
||||
|
||||
// Draw mouse cursor
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -515,7 +540,7 @@ pub fn ranged_target(
|
||||
if ctx.left_click {
|
||||
return (
|
||||
ItemMenuResult::Selected,
|
||||
Some(Point::new(mouse_pos.0, mouse_pos.1)),
|
||||
Some(Point::new(mouse_map_pos.0, mouse_map_pos.1)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
29
src/main.rs
29
src/main.rs
@ -2,6 +2,7 @@ use rltk::{GameState, Point, RandomNumberGenerator, Rltk};
|
||||
use specs::prelude::*;
|
||||
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
|
||||
|
||||
pub mod camera;
|
||||
mod components;
|
||||
mod damage_system;
|
||||
mod game_log;
|
||||
@ -150,28 +151,10 @@ impl GameState for State {
|
||||
RunState::MainMenu { .. } => {}
|
||||
RunState::GameOver { .. } => {}
|
||||
_ => {
|
||||
// Draw the UI
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
camera::render_camera(&self.ecs, ctx);
|
||||
gui::draw_ui(&self.ecs, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match newrunstate {
|
||||
RunState::MapGeneration => {
|
||||
@ -345,12 +328,12 @@ impl GameState for State {
|
||||
}
|
||||
RunState::MagicMapReveal { row } => {
|
||||
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);
|
||||
map.revealed_tiles[idx] = true;
|
||||
}
|
||||
|
||||
if row as usize == MAP_HEIGHT - 1 {
|
||||
if row == map.height - 1 {
|
||||
newrunstate = RunState::MonsterTurn;
|
||||
} else {
|
||||
newrunstate = RunState::MagicMapReveal { row: row + 1 };
|
||||
@ -462,7 +445,7 @@ impl State {
|
||||
self.mapgen_history.clear();
|
||||
|
||||
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);
|
||||
|
||||
std::mem::drop(rng);
|
||||
@ -550,7 +533,7 @@ fn main() -> rltk::BError {
|
||||
|
||||
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(rltk::RandomNumberGenerator::new());
|
||||
|
||||
|
24
src/map.rs
24
src/map.rs
@ -4,10 +4,6 @@ use rltk::{Algorithm2D, BaseMap, Point, Rltk, SmallVec, RGB};
|
||||
use serde::{Deserialize, Serialize};
|
||||
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)]
|
||||
pub enum TileType {
|
||||
Wall,
|
||||
@ -60,15 +56,17 @@ impl Map {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
tiles: vec![TileType::Wall; MAP_COUNT],
|
||||
width: MAP_WIDTH as i32,
|
||||
height: MAP_HEIGHT as i32,
|
||||
revealed_tiles: vec![false; MAP_COUNT],
|
||||
visible_tiles: vec![false; MAP_COUNT],
|
||||
blocked: vec![false; MAP_COUNT],
|
||||
tile_content: vec![Vec::new(); MAP_COUNT],
|
||||
tiles: vec![TileType::Wall; map_tile_count],
|
||||
width,
|
||||
height,
|
||||
revealed_tiles: vec![false; map_tile_count],
|
||||
visible_tiles: vec![false; map_tile_count],
|
||||
blocked: vec![false; map_tile_count],
|
||||
tile_content: vec![Vec::new(); map_tile_count],
|
||||
depth: new_depth,
|
||||
bloodstains: 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
|
||||
x += 1;
|
||||
if x > MAP_WIDTH as i32 - 1 {
|
||||
if x > (map.width * map.height) as i32 - 1 {
|
||||
x = 0;
|
||||
y += 1;
|
||||
}
|
||||
|
@ -66,17 +66,21 @@ pub struct BuilderMap {
|
||||
pub rooms: Option<Vec<Rect>>,
|
||||
pub corridors: Option<Vec<Vec<usize>>>,
|
||||
pub history: Vec<Map>,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
impl BuilderMap {
|
||||
fn new(new_depth: i32) -> BuilderMap {
|
||||
fn new(new_depth: i32, width: i32, height: i32) -> BuilderMap {
|
||||
BuilderMap {
|
||||
spawn_list: Vec::new(),
|
||||
map: Map::new(new_depth),
|
||||
map: Map::new(new_depth, width, height),
|
||||
starting_position: None,
|
||||
rooms: None,
|
||||
corridors: None,
|
||||
history: Vec::new(),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,11 +102,11 @@ pub struct BuilderChain {
|
||||
}
|
||||
|
||||
impl BuilderChain {
|
||||
pub fn new(new_depth: i32) -> BuilderChain {
|
||||
pub fn new(new_depth: i32, width: i32, height: i32) -> BuilderChain {
|
||||
BuilderChain {
|
||||
starter: None,
|
||||
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());
|
||||
}
|
||||
|
||||
pub fn random_builder(new_depth: i32, rng: &mut RandomNumberGenerator) -> BuilderChain {
|
||||
let mut builder = BuilderChain::new(new_depth);
|
||||
pub fn random_builder(
|
||||
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) {
|
||||
1 => random_room_builder(rng, &mut builder),
|
||||
|
@ -33,7 +33,7 @@ impl WaveformCollapseBuilder {
|
||||
let constraints = patterns_to_constraints(patterns, CHUNK_SIZE);
|
||||
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 {
|
||||
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map);
|
||||
while !solver.iteration(&mut build_data.map, rng) {
|
||||
@ -54,7 +54,7 @@ impl WaveformCollapseBuilder {
|
||||
chunk_size: i32,
|
||||
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 x = 1;
|
||||
let mut y = 1;
|
||||
@ -71,7 +71,7 @@ impl WaveformCollapseBuilder {
|
||||
if y + chunk_size > build_data.map.height {
|
||||
// Move to the next page
|
||||
build_data.take_snapshot();
|
||||
build_data.map = Map::new(0);
|
||||
build_data.map = Map::new(0, build_data.width, build_data.height);
|
||||
|
||||
x = 1;
|
||||
y = 1;
|
||||
|
@ -61,8 +61,8 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
|
||||
}
|
||||
|
||||
if !map.blocked[destination_idx] {
|
||||
pos.x = min(79, max(0, pos.x + delta_x));
|
||||
pos.y = min(49, max(0, pos.y + delta_y));
|
||||
pos.x = min(map.width - 1, max(0, pos.x + delta_x));
|
||||
pos.y = min(map.height - 1, max(0, pos.y + delta_y));
|
||||
entity_moved
|
||||
.insert(entity, EntityMoved {})
|
||||
.expect("Failed to add EntityMoved flag to player");
|
||||
|
@ -191,7 +191,7 @@ pub fn load_game(ecs: &mut World) {
|
||||
for (e, h) in (&entities, &helper).join() {
|
||||
let mut worldmap = ecs.write_resource::<crate::map::Map>();
|
||||
*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);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ use specs::prelude::*;
|
||||
use specs::saveload::{MarkedBuilder, SimpleMarker};
|
||||
|
||||
use crate::components::*;
|
||||
use crate::map::MAP_WIDTH;
|
||||
use crate::random_table::RandomTable;
|
||||
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)
|
||||
pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
||||
let x = (*spawn.0 % MAP_WIDTH) as i32;
|
||||
let y = (*spawn.0 / MAP_WIDTH) as i32;
|
||||
let map = ecs.fetch::<Map>();
|
||||
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() {
|
||||
"Goblin" => goblin(ecs, x, y),
|
||||
|
Loading…
Reference in New Issue
Block a user