diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..bfb64c2 --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,313 @@ +use super::{Door, Hidden, Map, Mind, Position, Renderable, TileType}; +use rltk::{Point, Rltk, RGB}; +use specs::prelude::*; +use std::ops::{Add, Mul}; + +const SHOW_BOUNDARIES: bool = false; + +pub fn get_screen_bounds(ecs: &World, ctx: &mut Rltk) -> (i32, i32, i32, i32) { + let player_pos = ecs.fetch::(); + let (x_chars, y_chars) = ctx.get_char_size(); + + let centre_x = (x_chars / 2) as i32; + let centre_y = (y_chars / 2) as i32; + + let min_x = player_pos.x - centre_x; + let min_y = player_pos.y - centre_y; + let max_x = min_x + x_chars as i32; + 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::(); + let (min_x, max_x, min_y, max_y) = get_screen_bounds(ecs, ctx); + + // Might need to -1 here? + let map_width = map.width; + let map_height = map.height; + + // Render map + let mut y = 0; + for t_y in min_y..max_y { + let mut x = 0; + for t_x in min_x..max_x { + if t_x >= 0 && t_x < map.width && t_y >= 0 && t_y < map_height { + let idx = map.xy_idx(t_x, t_y); + 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::DARKSLATEGRAY), RGB::named(rltk::BLACK), rltk::to_cp437('#')); + } + x += 1; + } + y += 1; + } + + // Render entities + { + let positions = ecs.read_storage::(); + let renderables = ecs.read_storage::(); + let minds = ecs.read_storage::(); + let hidden = ecs.read_storage::(); + let doors = ecs.write_storage::(); + let map = ecs.fetch::(); + let entities = ecs.entities(); + + let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::>(); + data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); + for (pos, render, ent, _hidden) in data.iter() { + let idx = map.xy_idx(pos.x, pos.y); + let entity_offset_x = pos.x - min_x; + let entity_offset_y = pos.y - min_y; + if entity_offset_x > 0 && entity_offset_x < map_width && entity_offset_y > 0 && entity_offset_y < map_height + { + let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]); + let mut draw = false; + let mut fg = render.fg; + let mut bg = render.bg.add(RGB::from_u8(26, 45, 45)).add(offsets); + // Get bloodstain colours + if map.bloodstains.contains(&idx) { + bg = bg.add(RGB::from_f32(0.6, 0., 0.)); + } + // Draw entities on visible tiles + if map.visible_tiles[idx] { + draw = true; + } + // Draw entities with minds within telepath range + if map.telepath_tiles[idx] { + let has_mind = minds.get(*ent); + if let Some(_) = has_mind { + draw = true; + } + } + // Draw all doors + let is_door = doors.get(*ent); + if let Some(_) = is_door { + if map.revealed_tiles[idx] { + if !map.visible_tiles[idx] { + fg = fg.mul(0.6); + bg = bg.mul(0.6); + } + draw = true; + } + } + if draw { + ctx.set(entity_offset_x, entity_offset_y, fg, bg, render.glyph); + } + } + } + } +} + +fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { + let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]); + let glyph; + let mut fg = offsets.mul(2.0); + let mut bg = offsets.add(RGB::from_u8(26, 45, 45)); + + match map.tiles[idx] { + TileType::Floor => { + glyph = rltk::to_cp437('.'); + fg = fg.add(RGB::from_f32(0.1, 0.8, 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 = fg.add(RGB::from_f32(0.6, 0.5, 0.25)); + } + TileType::DownStair => { + glyph = rltk::to_cp437('>'); + fg = RGB::from_f32(0., 1., 1.); + } + } + if map.bloodstains.contains(&idx) { + bg = bg.add(RGB::from_f32(0.6, 0., 0.)); + } + if !map.visible_tiles[idx] { + fg = fg.mul(0.6); + bg = bg.mul(0.6); + } + + return (glyph, fg, bg); +} + +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] +} + +fn wall_glyph(map: &Map, x: i32, y: i32) -> rltk::FontCharType { + if x < 1 || x > map.width - 2 || y < 1 || y > map.height - 2 as i32 { + return 35; + } + let mut mask: u8 = 0; + let diagonals_matter: Vec = vec![7, 11, 13, 14, 15]; + + if is_revealed_and_wall(map, x, y - 1) { + // N + mask += 1; + } + if is_revealed_and_wall(map, x, y + 1) { + // S + mask += 2; + } + if is_revealed_and_wall(map, x - 1, y) { + // W + mask += 4; + } + if is_revealed_and_wall(map, x + 1, y) { + // E + mask += 8; + } + + if diagonals_matter.contains(&mask) { + if is_revealed_and_wall(map, x + 1, y - 1) { + // Top right + mask += 16; + } + if is_revealed_and_wall(map, x - 1, y - 1) { + // Top left + mask += 32; + } + if is_revealed_and_wall(map, x + 1, y + 1) { + // Bottom right + mask += 64; + } + if is_revealed_and_wall(map, x - 1, y + 1) { + // Bottom left + mask += 128; + } + } + + match mask { + 0 => 254, // ■ (254) square pillar; but maybe ○ (9) looks better + 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 south 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 north + 14 => 203, // Wall to the east, west, and south + 15 => 206, // ╬ Wall on all sides + 29 => 202, + 31 => 206, + 45 => 202, + 46 => 203, + 47 => 206, + 55 => 185, + 59 => 204, + 63 => 203, + 87 => 185, + 126 => 203, + 143 => 206, + 77 => 202, + 171 => 204, + 187 => 204, + 215 => 185, + 190 => 203, + 237 => 202, + 30 => 203, + 110 => 203, + 111 => 206, + 119 => 185, + 142 => 203, + 158 => 203, + 235 => 204, + 93 => 202, + 109 => 202, + 94 => 203, + 174 => 203, + 159 => 206, + 221 => 202, + 157 => 202, + 79 => 206, + 95 => 185, + 23 => 185, // NSW and NSE + 1 diagonal + 39 => 185, + 71 => 185, + 103 => 185, + 135 => 185, + 151 => 185, + 199 => 185, + 78 => 203, + 27 => 204, + 43 => 204, + 75 => 204, + 107 => 204, + 139 => 204, + 155 => 204, + 173 => 202, + 141 => 202, + 205 => 202, + 175 => 204, + 203 => 204, + 61 => 205, // NEW cases + 125 => 205, // NEW cases + 189 => 205, // NEW cases + 206 => 205, + 207 => 202, + 222 => 205, + 238 => 205, + 253 => 205, + 254 => 205, + 167 => 186, // NSW, NW, SW + 91 => 186, // NSE, NE, SE + 183 => 186, // NSW, NW, SW, NE + 123 => 186, // NSE, NE, SE, NW + 231 => 186, // NSW, NW, SW, SE + 219 => 186, // NSE, NE, SE, SW + 247 => 186, + 251 => 186, + 127 => 187, // Everything except NE + 191 => 201, // Everything except NW + 223 => 188, // Everything except SE + 239 => 200, // Everything except SW + _ => 35, // We missed one? + } +} + +pub fn render_debug_map(map: &Map, ctx: &mut Rltk) { + let player_pos = Point::new(map.width / 2, map.height / 2); + 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; + + let map_width = map.width - 1; + let map_height = map.height - 1; + + let mut y = 0; + 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; + } +} diff --git a/src/components.rs b/src/components.rs index eb195e1..e050e41 100644 --- a/src/components.rs +++ b/src/components.rs @@ -235,6 +235,9 @@ pub struct Wand { #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Destructible {} +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Digger {} + #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Hidden {} diff --git a/src/gui.rs b/src/gui.rs index 388ebe5..7b648d9 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,6 +1,6 @@ use super::{ - gamelog, rex_assets::RexAssets, CombatStats, Equipped, Hidden, HungerClock, HungerState, InBackpack, Map, Name, - Player, Point, Position, RunState, State, Viewshed, + camera, gamelog, rex_assets::RexAssets, CombatStats, Equipped, Hidden, HungerClock, HungerState, InBackpack, Map, + Name, Player, Point, Position, RunState, State, Viewshed, }; use rltk::{Rltk, VirtualKeyCode, RGB}; use specs::prelude::*; @@ -83,19 +83,31 @@ pub fn get_input_direction( } 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::(); let names = ecs.read_storage::(); let positions = ecs.read_storage::(); let hidden = ecs.read_storage::(); let mouse_pos = ctx.mouse_pos(); - if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height { + let mut mouse_pos_adjusted = mouse_pos; + mouse_pos_adjusted.0 += min_x; + mouse_pos_adjusted.1 += min_y; + if mouse_pos_adjusted.0 >= map.width + || mouse_pos_adjusted.1 >= map.height + || mouse_pos_adjusted.1 < 0 // Might need to be 1, and -1 from map height/width. + || mouse_pos_adjusted.0 < 0 + { + return; + } + if !(map.visible_tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)] + || map.telepath_tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)]) + { return; } let mut tooltip: Vec = 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_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 { tooltip.push(name.name.to_string()); } } @@ -367,11 +379,12 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti } pub fn ranged_target(gs: &mut State, ctx: &mut Rltk, range: i32, aoe: i32) -> (ItemMenuResult, Option) { + let (min_x, max_x, min_y, max_y) = camera::get_screen_bounds(&gs.ecs, ctx); let player_entity = gs.ecs.fetch::(); let player_pos = gs.ecs.fetch::(); let viewsheds = gs.ecs.read_storage::(); - ctx.print_color(5, 0, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "select target"); + ctx.print_color(1, 1, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Targeting which tile? [mouse input]"); // Highlight available cells let mut available_cells = Vec::new(); @@ -381,8 +394,12 @@ pub fn ranged_target(gs: &mut State, ctx: &mut Rltk, range: i32, aoe: i32) -> (I for idx in visible.visible_tiles.iter() { let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx); if distance <= range as f32 { - ctx.set_bg(idx.x, idx.y, RGB::named(rltk::BLUE)); - available_cells.push(idx); + let screen_x = idx.x - min_x; + let screen_y = idx.y - min_y; + if screen_x > 0 && screen_x < (max_x - min_x) && screen_y > 0 && screen_y < (max_y - min_y) { + ctx.set_bg(screen_x, screen_y, RGB::named(rltk::BLUE)); + available_cells.push(idx); + } } } } else { @@ -391,24 +408,30 @@ pub fn ranged_target(gs: &mut State, ctx: &mut Rltk, range: i32, aoe: i32) -> (I // Draw mouse cursor let mouse_pos = ctx.mouse_pos(); + let mut mouse_pos_adjusted = mouse_pos; + mouse_pos_adjusted.0 += min_x; + mouse_pos_adjusted.1 += min_y; let map = gs.ecs.fetch::(); 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_pos_adjusted.0 && idx.y == mouse_pos_adjusted.1 { valid_target = true; } } if valid_target { if aoe > 0 { - let mut blast_tiles = rltk::field_of_view(Point::new(mouse_pos.0, mouse_pos.1), aoe, &*map); + // We adjust for camera position when getting FOV, but then we need to adjust back + // when iterating through the tiles themselves, by taking away min_x/min_y. + let mut blast_tiles = + rltk::field_of_view(Point::new(mouse_pos_adjusted.0, mouse_pos_adjusted.1), aoe, &*map); blast_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1); for tile in blast_tiles.iter() { - ctx.set_bg(tile.x, tile.y, RGB::named(rltk::DARKCYAN)); + ctx.set_bg(tile.x - min_x, tile.y - min_y, RGB::named(rltk::DARKCYAN)); } } ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::CYAN)); if ctx.left_click { - return (ItemMenuResult::Selected, Some(Point::new(mouse_pos.0, mouse_pos.1))); + return (ItemMenuResult::Selected, Some(Point::new(mouse_pos_adjusted.0, mouse_pos_adjusted.1))); } } else { ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::RED)); @@ -580,7 +603,6 @@ pub fn game_over(ctx: &mut Rltk) -> YesNoResult { RGB::named(rltk::BLACK), format!("- forgot the controls {} time(s)", crate::gamelog::get_event_count("looked_for_help")), ); - y += 1; } match ctx.key { diff --git a/src/inventory_system.rs b/src/inventory_system.rs index b8c13e2..ce4ef82 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -1,8 +1,8 @@ use super::{ - gamelog, CombatStats, Confusion, Consumable, Cursed, Destructible, Equippable, Equipped, HungerClock, HungerState, - InBackpack, InflictsDamage, MagicMapper, Map, Name, ParticleBuilder, Point, Position, ProvidesHealing, - ProvidesNutrition, RandomNumberGenerator, RunState, SufferDamage, Wand, WantsToDropItem, WantsToPickupItem, - WantsToRemoveItem, WantsToUseItem, AOE, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME, + gamelog, CombatStats, Confusion, Consumable, Cursed, Destructible, Digger, Equippable, Equipped, HungerClock, + HungerState, InBackpack, InflictsDamage, MagicMapper, Map, Name, ParticleBuilder, Point, Position, ProvidesHealing, + ProvidesNutrition, RandomNumberGenerator, RunState, SufferDamage, TileType, Viewshed, Wand, WantsToDropItem, + WantsToPickupItem, WantsToRemoveItem, WantsToUseItem, AOE, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME, }; use specs::prelude::*; @@ -38,16 +38,21 @@ impl<'a> System<'a> for ItemCollectionSystem { } } +// Grouping together components because of type complexity issues - SystemData was too large. +// This is a temporary solution that'll be fixed once inventory use is refactored into separate +// systems. +type EquipComponents<'a> = (ReadStorage<'a, Equippable>, WriteStorage<'a, Equipped>); + pub struct ItemUseSystem {} impl<'a> System<'a> for ItemUseSystem { #[allow(clippy::type_complexity)] type SystemData = ( ReadExpect<'a, Entity>, - ReadExpect<'a, Map>, + WriteExpect<'a, Map>, WriteExpect<'a, RandomNumberGenerator>, Entities<'a>, WriteStorage<'a, WantsToUseItem>, - ReadStorage<'a, Name>, + WriteStorage<'a, Name>, WriteStorage<'a, Consumable>, WriteStorage<'a, Wand>, ReadStorage<'a, Destructible>, @@ -61,22 +66,23 @@ impl<'a> System<'a> for ItemUseSystem { ReadStorage<'a, Position>, ReadStorage<'a, InflictsDamage>, ReadStorage<'a, AOE>, + ReadStorage<'a, Digger>, WriteStorage<'a, Confusion>, ReadStorage<'a, MagicMapper>, WriteExpect<'a, RunState>, - ReadStorage<'a, Equippable>, - WriteStorage<'a, Equipped>, + EquipComponents<'a>, WriteStorage<'a, InBackpack>, + WriteStorage<'a, Viewshed>, ); fn run(&mut self, data: Self::SystemData) { let ( player_entity, - map, + mut map, mut rng, entities, mut wants_to_use, - names, + mut names, mut consumables, mut wands, destructibles, @@ -90,23 +96,24 @@ impl<'a> System<'a> for ItemUseSystem { positions, inflicts_damage, aoe, + digger, mut confused, magic_mapper, mut runstate, - equippable, - mut equipped, + (equippable, mut equipped), mut backpack, + mut viewsheds, ) = data; for (entity, wants_to_use) in (&entities, &wants_to_use).join() { let mut verb = "use"; let mut used_item = true; let mut aoe_item = false; - let item_being_used = names.get(wants_to_use.item).unwrap(); let is_cursed = cursed_items.get(wants_to_use.item); let wand = wands.get_mut(wants_to_use.item); if let Some(wand) = wand { + let name = names.get_mut(wants_to_use.item).unwrap(); // If want has no uses, roll 1d121. On a 121, wrest the wand, then delete it. if wand.uses == 0 { if rng.roll_dice(1, 121) != 121 { @@ -120,9 +127,14 @@ impl<'a> System<'a> for ItemUseSystem { consumables.insert(wants_to_use.item, Consumable {}).expect("Could not insert consumable"); } verb = "zap"; + // TODO: Change this to track uses better, after adding in identification. + name.name.push_str("*"); + name.plural.push_str("*"); wand.uses -= 1; } + let item_being_used = names.get(wants_to_use.item).unwrap(); + let is_edible = provides_nutrition.get(wants_to_use.item); if let Some(_) = is_edible { verb = "eat"; @@ -140,9 +152,14 @@ impl<'a> System<'a> for ItemUseSystem { // TARGETING let mut targets: Vec = Vec::new(); + let mut target_idxs: Vec = Vec::new(); match wants_to_use.target { None => { targets.push(*player_entity); + let pos = positions.get(*player_entity); + if let Some(pos) = pos { + target_idxs.push(map.xy_idx(pos.x, pos.y)); + } } Some(mut target) => { let area_effect = aoe.get(wants_to_use.item); @@ -150,6 +167,7 @@ impl<'a> System<'a> for ItemUseSystem { None => { // Single target in a tile let idx = map.xy_idx(target.x, target.y); + target_idxs.push(idx); for mob in map.tile_content[idx].iter() { targets.push(*mob); } @@ -177,6 +195,7 @@ impl<'a> System<'a> for ItemUseSystem { blast_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1); for tile_idx in blast_tiles.iter() { let idx = map.xy_idx(tile_idx.x, tile_idx.y); + target_idxs.push(idx); for mob in map.tile_content[idx].iter() { targets.push(*mob); } @@ -250,13 +269,7 @@ impl<'a> System<'a> for ItemUseSystem { if let Some(stats) = stats { stats.hp = i32::min(stats.max_hp, stats.hp + heal.amount); if entity == *player_entity { - gamelog::Logger::new() - .append("Quaffing, you heal") - .colour(rltk::GREEN) - .append(heal.amount) - .colour(rltk::WHITE) - .append("hit points.") - .log(); + gamelog::Logger::new().append("Quaffing, you recover some vigour.").log(); } let pos = positions.get(entity); if let Some(pos) = pos { @@ -371,6 +384,27 @@ impl<'a> System<'a> for ItemUseSystem { } } + let is_digger = digger.get(wants_to_use.item); + match is_digger { + None => {} + Some(_) => { + used_item = true; + for idx in target_idxs { + if map.tiles[idx] == TileType::Wall { + map.tiles[idx] = TileType::Floor; + } + for viewshed in (&mut viewsheds).join() { + if viewshed + .visible_tiles + .contains(&Point::new(idx % map.width as usize, idx / map.width as usize)) + { + viewshed.dirty = true; + } + } + } + } + } + // ITEM DELETION AFTER USE if used_item { let consumable = consumables.get(wants_to_use.item); diff --git a/src/main.rs b/src/main.rs index a856c5c..b43e90d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use rltk::{GameState, Point, RandomNumberGenerator, Rltk, RGB}; +use rltk::{GameState, Point, RandomNumberGenerator, Rltk}; use specs::prelude::*; use specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; -use std::ops::{Add, Mul}; extern crate serde; +pub mod camera; mod components; pub use components::*; mod map; @@ -81,7 +81,7 @@ impl State { self.mapgen_timer = 0.0; self.mapgen_history.clear(); let mut rng = self.ecs.write_resource::(); - 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); self.mapgen_history = builder.build_data.history.clone(); @@ -251,52 +251,8 @@ impl GameState for State { RunState::MainMenu { .. } => {} _ => { // Draw map and ui - draw_map(&self.ecs.fetch::(), ctx); - { - let positions = self.ecs.read_storage::(); - let renderables = self.ecs.read_storage::(); - let minds = self.ecs.read_storage::(); - let hidden = self.ecs.read_storage::(); - let doors = self.ecs.write_storage::(); - let map = self.ecs.fetch::(); - let entities = self.ecs.entities(); - - let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::>(); - data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); - for (pos, render, ent, _hidden) in data.iter() { - let idx = map.xy_idx(pos.x, pos.y); - let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]); - let mut fg = render.fg; - let mut bg = render.bg.add(RGB::from_u8(26, 45, 45)).add(offsets); - // Get bloodstain colours - if map.bloodstains.contains(&idx) { - bg = bg.add(RGB::from_f32(0.6, 0., 0.)); - } - // Draw entities on visible tiles - if map.visible_tiles[idx] { - ctx.set(pos.x, pos.y, fg, bg, render.glyph); - } - // Draw entities with minds within telepath range - if map.telepath_tiles[idx] { - let has_mind = minds.get(*ent); - if let Some(_) = has_mind { - ctx.set(pos.x, pos.y, render.fg, RGB::named(rltk::BLACK), render.glyph); - } - } - // Draw all doors - let is_door = doors.get(*ent); - if let Some(_) = is_door { - if map.revealed_tiles[idx] { - if !map.visible_tiles[idx] { - fg = fg.mul(0.6); - bg = bg.mul(0.6); - } - ctx.set(pos.x, pos.y, fg, bg, render.glyph); - } - } - } - gui::draw_ui(&self.ecs, ctx); - } + camera::render_camera(&self.ecs, ctx); + gui::draw_ui(&self.ecs, ctx); } } @@ -459,12 +415,12 @@ impl GameState for State { // Could probably toss this into a function somewhere, and/or // have multiple simple animations for it. - for x in 0..MAPWIDTH { + for x in 0..map.width { let idx; if x % 2 == 0 { idx = map.xy_idx(x as i32, row); } else { - idx = map.xy_idx(x as i32, (MAPHEIGHT as i32 - 1) - (row)); + idx = map.xy_idx(x as i32, (map.height as i32 - 1) - (row)); } if !cursed { map.revealed_tiles[idx] = true; @@ -483,7 +439,7 @@ impl GameState for State { } } - if row as usize == MAPHEIGHT - 1 { + if row as usize == map.height as usize - 1 { new_runstate = RunState::MonsterTurn; } else { new_runstate = RunState::MagicMapReveal { row: row + 1, cursed: cursed }; @@ -495,7 +451,7 @@ impl GameState for State { } if self.mapgen_history.len() != 0 { ctx.cls(); - draw_map(&self.mapgen_history[self.mapgen_index], ctx); + camera::render_debug_map(&self.mapgen_history[self.mapgen_index], ctx); self.mapgen_timer += ctx.frame_time_ms; if self.mapgen_timer > 300.0 { @@ -566,6 +522,7 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); @@ -587,7 +544,7 @@ fn main() -> rltk::BError { gs.ecs.insert(SimpleMarkerAllocator::::new()); let player_entity = spawner::player(&mut gs.ecs, 0, 0); - gs.ecs.insert(Map::new(1)); + gs.ecs.insert(Map::new(1, 64, 64)); gs.ecs.insert(Point::new(0, 0)); gs.ecs.insert(player_entity); gs.ecs.insert(rltk::RandomNumberGenerator::new()); diff --git a/src/map.rs b/src/map.rs index 213ac28..0ff42de 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,8 +1,7 @@ -use rltk::{Algorithm2D, BaseMap, Point, Rltk, RGB}; +use rltk::{Algorithm2D, BaseMap, Point}; use serde::{Deserialize, Serialize}; use specs::prelude::*; use std::collections::HashSet; -use std::ops::{Add, Mul}; #[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] pub enum TileType { @@ -11,9 +10,9 @@ pub enum TileType { DownStair, } -pub const MAPWIDTH: usize = 80; -pub const MAPHEIGHT: usize = 43; -pub const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH; +// FIXME: If the map size gets too small, entities stop being rendered starting from the right. +// i.e. on a map size of 40*40, only entities to the left of the player are rendered. +// on a map size of 42*42, the player can see entities up to 2 tiles to their right. #[derive(Default, Serialize, Deserialize, Clone)] pub struct Map { @@ -43,23 +42,24 @@ impl Map { (y as usize) * (self.width as usize) + (x as usize) } - 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; let mut map = Map { - tiles: vec![TileType::Wall; MAPCOUNT], - width: MAPWIDTH as i32, - height: MAPHEIGHT as i32, - revealed_tiles: vec![false; MAPCOUNT], - visible_tiles: vec![false; MAPCOUNT], - lit_tiles: vec![true; MAPCOUNT], // NYI: Light sources. Once those exist, we can set this to false. - telepath_tiles: vec![false; MAPCOUNT], - red_offset: vec![0; MAPCOUNT], - green_offset: vec![0; MAPCOUNT], - blue_offset: vec![0; MAPCOUNT], - blocked: vec![false; MAPCOUNT], + tiles: vec![TileType::Wall; map_tile_count], + width: width, + height: height, + revealed_tiles: vec![false; map_tile_count], + visible_tiles: vec![false; map_tile_count], + lit_tiles: vec![true; map_tile_count], // NYI: Light sources. Once those exist, we can set this to false. + telepath_tiles: vec![false; map_tile_count], + red_offset: vec![0; map_tile_count], + green_offset: vec![0; map_tile_count], + blue_offset: vec![0; map_tile_count], + blocked: vec![false; map_tile_count], depth: new_depth, bloodstains: HashSet::new(), view_blocked: HashSet::new(), - tile_content: vec![Vec::new(); MAPCOUNT], + tile_content: vec![Vec::new(); map_tile_count], }; const MAX_OFFSET: u8 = 32; @@ -160,192 +160,3 @@ impl BaseMap for Map { exits } } - -pub fn draw_map(map: &Map, ctx: &mut Rltk) { - let mut y = 0; - let mut x = 0; - - for (idx, tile) in map.tiles.iter().enumerate() { - // Get our colour offsets. Credit to Brogue for the inspiration here. - let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]); - if map.revealed_tiles[idx] { - let mut fg = offsets.mul(2.0); - // Right now, everything always has the same background. It's a - // very dark green, just to distinguish it slightly from the - // black that is tiles we've *never* seen. - let mut bg = offsets.add(RGB::from_u8(26, 45, 45)); - let glyph; - match tile { - TileType::Floor => { - glyph = rltk::to_cp437('.'); - fg = fg.add(RGB::from_f32(0.1, 0.8, 0.5)); - } - TileType::Wall => { - glyph = wall_glyph(&*map, x, y); - fg = fg.add(RGB::from_f32(0.6, 0.5, 0.25)); - } - TileType::DownStair => { - glyph = rltk::to_cp437('>'); - fg = RGB::from_f32(0., 1., 1.); - } - } - if map.bloodstains.contains(&idx) { - bg = bg.add(RGB::from_f32(0.6, 0., 0.)); - } - if !map.visible_tiles[idx] { - fg = fg.mul(0.6); - bg = bg.mul(0.6); - } - ctx.set(x, y, fg, bg, glyph); - } - - // Move the coordinates - x += 1; - if x > (MAPWIDTH as i32) - 1 { - x = 0; - y += 1; - } - } -} - -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] -} - -fn wall_glyph(map: &Map, x: i32, y: i32) -> rltk::FontCharType { - if x < 1 || x > map.width - 2 || y < 1 || y > map.height - 2 as i32 { - return 35; - } - let mut mask: u8 = 0; - let diagonals_matter: Vec = vec![7, 11, 13, 14, 15]; - - if is_revealed_and_wall(map, x, y - 1) { - // N - mask += 1; - } - if is_revealed_and_wall(map, x, y + 1) { - // S - mask += 2; - } - if is_revealed_and_wall(map, x - 1, y) { - // W - mask += 4; - } - if is_revealed_and_wall(map, x + 1, y) { - // E - mask += 8; - } - - if diagonals_matter.contains(&mask) { - if is_revealed_and_wall(map, x + 1, y - 1) { - // Top right - mask += 16; - } - if is_revealed_and_wall(map, x - 1, y - 1) { - // Top left - mask += 32; - } - if is_revealed_and_wall(map, x + 1, y + 1) { - // Bottom right - mask += 64; - } - if is_revealed_and_wall(map, x - 1, y + 1) { - // Bottom left - mask += 128; - } - } - - match mask { - 0 => 254, // ■ (254) square pillar; but maybe ○ (9) looks better - 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 south 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 north - 14 => 203, // Wall to the east, west, and south - 15 => 206, // ╬ Wall on all sides - 29 => 202, - 31 => 206, - 45 => 202, - 46 => 203, - 47 => 206, - 55 => 185, - 59 => 204, - 63 => 203, - 87 => 185, - 126 => 203, - 143 => 206, - 77 => 202, - 171 => 204, - 187 => 204, - 215 => 185, - 190 => 203, - 237 => 202, - 30 => 203, - 110 => 203, - 111 => 206, - 119 => 185, - 142 => 203, - 158 => 203, - 235 => 204, - 93 => 202, - 109 => 202, - 94 => 203, - 174 => 203, - 159 => 206, - 221 => 202, - 157 => 202, - 79 => 206, - 95 => 185, - 23 => 185, // NSW and NSE + 1 diagonal - 39 => 185, - 71 => 185, - 103 => 185, - 135 => 185, - 151 => 185, - 199 => 185, - 78 => 203, - 27 => 204, - 43 => 204, - 75 => 204, - 107 => 204, - 139 => 204, - 155 => 204, - 173 => 202, - 141 => 202, - 205 => 202, - 175 => 204, - 203 => 204, - 61 => 205, // NEW cases - 125 => 205, // NEW cases - 189 => 205, // NEW cases - 206 => 205, - 207 => 202, - 222 => 205, - 238 => 205, - 253 => 205, - 254 => 205, - 167 => 186, // NSW, NW, SW - 91 => 186, // NSE, NE, SE - 183 => 186, // NSW, NW, SW, NE - 123 => 186, // NSE, NE, SE, NW - 231 => 186, // NSW, NW, SW, SE - 219 => 186, // NSE, NE, SE, SW - 247 => 186, - 251 => 186, - 127 => 187, // Everything except NE - 191 => 201, // Everything except NW - 223 => 188, // Everything except SE - 239 => 200, // Everything except SW - _ => 35, // We missed one? - } -} diff --git a/src/map_builders/common.rs b/src/map_builders/common.rs index dd945c8..80c5343 100644 --- a/src/map_builders/common.rs +++ b/src/map_builders/common.rs @@ -1,6 +1,7 @@ use super::{Map, Rect, TileType}; use std::cmp::{max, min}; +#[allow(dead_code)] pub fn apply_room_to_map(map: &mut Map, room: &Rect) { for y in room.y1 + 1..=room.y2 { for x in room.x1 + 1..=room.x2 { diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index b55fe36..3ecb2e2 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -65,6 +65,8 @@ pub struct BuilderMap { pub rooms: Option>, pub corridors: Option>>, pub history: Vec, + pub width: i32, + pub height: i32, } impl BuilderMap { @@ -86,17 +88,19 @@ 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 { 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: width, + height: height, }, } } @@ -166,7 +170,7 @@ fn random_room_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut Buil let build_roll = rng.roll_dice(1, 3); // Start with a room builder. match build_roll { - 1 => builder.start_with(SimpleMapBuilder::new()), + 1 => builder.start_with(SimpleMapBuilder::new(None)), 2 => builder.start_with(BspDungeonBuilder::new()), _ => builder.start_with(BspInteriorBuilder::new()), } @@ -271,8 +275,8 @@ fn random_shape_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut Bui builder.with(DistantExit::new()); } -pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain { - /*let mut builder = BuilderChain::new(new_depth); +pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { + let mut builder = BuilderChain::new(new_depth, width, height); let type_roll = rng.roll_dice(1, 2); match type_roll { 1 => random_room_builder(rng, &mut builder), @@ -298,14 +302,5 @@ pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> builder.with(DoorPlacement::new()); builder.with(PrefabBuilder::vaults()); - builder*/ - - let mut builder = BuilderChain::new(new_depth); - builder.start_with(BspInteriorBuilder::new()); - builder.with(DoorPlacement::new()); - builder.with(RoomBasedSpawner::new()); - builder.with(PrefabBuilder::vaults()); - builder.with(RoomBasedStairs::new()); - builder.with(RoomBasedStartingPosition::new()); builder } diff --git a/src/map_builders/prefab_builder/mod.rs b/src/map_builders/prefab_builder/mod.rs index f0735ab..127ccec 100644 --- a/src/map_builders/prefab_builder/mod.rs +++ b/src/map_builders/prefab_builder/mod.rs @@ -117,6 +117,11 @@ impl PrefabBuilder { build_data.spawn_list.push((idx, scroll_table(build_data.map.depth).roll(rng))); // Placeholder for scroll spawn } + ')' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, equipment_table(build_data.map.depth).roll(rng))); + // Placeholder for scroll spawn + } _ => { rltk::console::log(format!("Unknown glyph '{}' when loading prefab", (ch as u8) as char)); } diff --git a/src/map_builders/room_sorter.rs b/src/map_builders/room_sorter.rs index 0fd5eba..7cd082d 100644 --- a/src/map_builders/room_sorter.rs +++ b/src/map_builders/room_sorter.rs @@ -1,6 +1,7 @@ use super::{BuilderMap, MetaMapBuilder, Rect}; use rltk::RandomNumberGenerator; +#[allow(dead_code)] pub enum RoomSort { LEFTMOST, RIGHTMOST, diff --git a/src/map_builders/simple_map.rs b/src/map_builders/simple_map.rs index 8c54d50..d0b03b9 100644 --- a/src/map_builders/simple_map.rs +++ b/src/map_builders/simple_map.rs @@ -1,7 +1,9 @@ -use super::{apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect}; +use super::{BuilderMap, InitialMapBuilder, Rect}; use rltk::RandomNumberGenerator; -pub struct SimpleMapBuilder {} +pub struct SimpleMapBuilder { + room_params: (i32, i32, i32), +} impl InitialMapBuilder for SimpleMapBuilder { #[allow(dead_code)] @@ -12,19 +14,28 @@ impl InitialMapBuilder for SimpleMapBuilder { impl SimpleMapBuilder { #[allow(dead_code)] - pub fn new() -> Box { - Box::new(SimpleMapBuilder {}) + pub fn new(room_params: Option<(i32, i32, i32)>) -> Box { + const DEFAULT_MAX_ROOMS: i32 = 40; + const DEFAULT_MIN_SIZE: i32 = 6; + const DEFAULT_MAX_SIZE: i32 = 16; + + let (max_rooms, min_size, max_size); + + if let Some(room_params) = room_params { + (max_rooms, min_size, max_size) = (room_params.0, room_params.1, room_params.2) + } else { + (max_rooms, min_size, max_size) = (DEFAULT_MAX_ROOMS, DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE) + } + + Box::new(SimpleMapBuilder { room_params: (max_rooms, min_size, max_size) }) } fn build_rooms(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { - const MAX_ROOMS: i32 = 30; - const MIN_SIZE: i32 = 6; - const MAX_SIZE: i32 = 10; let mut rooms: Vec = Vec::new(); - for _i in 0..MAX_ROOMS { - let w = rng.range(MIN_SIZE, MAX_SIZE); - let h = rng.range(MIN_SIZE, MAX_SIZE); + for _i in 0..self.room_params.0 { + let w = rng.range(self.room_params.1, self.room_params.2); + let h = rng.range(self.room_params.1, self.room_params.2); let x = rng.roll_dice(1, build_data.map.width - w - 1) - 1; let y = rng.roll_dice(1, build_data.map.height - h - 1) - 1; let new_room = Rect::new(x, y, w, h); diff --git a/src/map_builders/wfc/mod.rs b/src/map_builders/wfc/mod.rs index d2f6159..9bada30 100644 --- a/src/map_builders/wfc/mod.rs +++ b/src/map_builders/wfc/mod.rs @@ -31,7 +31,7 @@ impl WaveFunctionCollapseBuilder { 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.map.width, build_data.map.height); loop { let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map); while !solver.iteration(&mut build_data.map, rng) { @@ -46,7 +46,7 @@ impl WaveFunctionCollapseBuilder { } fn render_tile_gallery(&mut self, constraints: &[MapChunk], 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; @@ -62,7 +62,7 @@ impl WaveFunctionCollapseBuilder { 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; diff --git a/src/player.rs b/src/player.rs index 00fb02b..5255b25 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,7 @@ use super::{ gamelog, BlocksTile, BlocksVisibility, CombatStats, Door, EntityMoved, Hidden, HungerClock, HungerState, Item, Map, Monster, Name, Player, Position, Renderable, RunState, State, SufferDamage, Telepath, TileType, Viewshed, - WantsToMelee, WantsToPickupItem, MAPHEIGHT, MAPWIDTH, + WantsToMelee, WantsToPickupItem, }; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use specs::prelude::*; @@ -19,11 +19,12 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { let mut blocks_movement = ecs.write_storage::(); let mut renderables = ecs.write_storage::(); let names = ecs.read_storage::(); + let mut rng = ecs.write_resource::(); let mut result = RunState::AwaitingInput; let mut door_pos: Option = None; - for (_entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { + for (_entity, _player, pos) in (&entities, &mut players, &mut positions).join() { let delta_x = i; let delta_y = j; @@ -45,6 +46,10 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { if let Some(name) = names.get(*potential_target) { gamelog::Logger::new().append("The").item_name(&name.name).append("is blocked.").log(); } + } else if rng.roll_dice(1, 6) == 1 { + if let Some(name) = names.get(*potential_target) { + gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log(); + } } else { door.open = false; blocks_visibility @@ -59,8 +64,8 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { } render_data.glyph = rltk::to_cp437('+'); // Nethack open door, maybe just use '/' instead. door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); - result = RunState::PlayerTurn; } + result = RunState::PlayerTurn; } else { gamelog::Logger::new().append("It's already closed.").log(); } @@ -94,11 +99,12 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { let mut blocks_movement = ecs.write_storage::(); let mut renderables = ecs.write_storage::(); let names = ecs.read_storage::(); + let mut rng = ecs.write_resource::(); let mut result = RunState::AwaitingInput; let mut door_pos: Option = None; - for (_entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { + for (_entity, _player, pos) in (&entities, &mut players, &mut positions).join() { let delta_x = i; let delta_y = j; @@ -116,15 +122,21 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { let door = doors.get_mut(*potential_target); if let Some(door) = door { if door.open == false { - door.open = true; - blocks_visibility.remove(*potential_target); - blocks_movement.remove(*potential_target); - let render_data = renderables.get_mut(*potential_target).unwrap(); - if let Some(name) = names.get(*potential_target) { - gamelog::Logger::new().append("You open the").item_name_n(&name.name).period().log(); + if rng.roll_dice(1, 6) == 1 { + if let Some(name) = names.get(*potential_target) { + gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log(); + } + } else { + door.open = true; + blocks_visibility.remove(*potential_target); + blocks_movement.remove(*potential_target); + let render_data = renderables.get_mut(*potential_target).unwrap(); + if let Some(name) = names.get(*potential_target) { + gamelog::Logger::new().append("You open the").item_name_n(&name.name).period().log(); + } + render_data.glyph = rltk::to_cp437('▓'); // Nethack open door, maybe just use '/' instead. + door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); } - render_data.glyph = rltk::to_cp437('▓'); // Nethack open door, maybe just use '/' instead. - door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); result = RunState::PlayerTurn; } else { gamelog::Logger::new().append("It's already open.").log(); @@ -161,7 +173,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { let names = ecs.read_storage::(); let mut rng = ecs.write_resource::(); - for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { + for (entity, _player, pos) in (&entities, &mut players, &mut positions).join() { let delta_x = i; let delta_y = j; @@ -323,8 +335,8 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { } 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)); + pos.x = min(map.width - 1, max(0, pos.x + delta_x)); + pos.y = min(map.height - 1, max(0, pos.y + delta_y)); // Dirty viewsheds, and check only now if telepath viewshed exists viewshed.dirty = true; diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 0bc56a9..293ab0d 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -57,6 +57,7 @@ pub fn save_game(ecs: &mut World) { Cursed, DefenceBonus, Destructible, + Digger, Door, EntityMoved, EntryTrigger, @@ -152,6 +153,7 @@ pub fn load_game(ecs: &mut World) { Cursed, DefenceBonus, Destructible, + Digger, Door, EntityMoved, EntryTrigger, @@ -197,7 +199,7 @@ pub fn load_game(ecs: &mut World) { for (e, h) in (&entities, &helper).join() { let mut worldmap = ecs.write_resource::(); *worldmap = h.map.clone(); - worldmap.tile_content = vec![Vec::new(); super::map::MAPCOUNT]; + worldmap.tile_content = vec![Vec::new(); (worldmap.width * worldmap.height) as usize]; deleteme = Some(e); crate::gamelog::restore_log(&mut h.log.clone()); crate::gamelog::load_events(h.events.clone()); diff --git a/src/spawner.rs b/src/spawner.rs index 3bf7891..2d24231 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,9 +1,9 @@ use super::{ random_table::RandomTable, Attribute, Attributes, BlocksTile, BlocksVisibility, CombatStats, Confusion, Consumable, - Cursed, DefenceBonus, Destructible, Door, EntryTrigger, EquipmentSlot, Equippable, Hidden, HungerClock, + Cursed, DefenceBonus, Destructible, Digger, Door, EntryTrigger, EquipmentSlot, Equippable, Hidden, HungerClock, HungerState, InflictsDamage, Item, MagicMapper, Map, MeleePowerBonus, Mind, Monster, Name, Player, Position, ProvidesHealing, ProvidesNutrition, Ranged, Rect, Renderable, SerializeMe, SingleActivation, TileType, Viewshed, - Wand, AOE, MAPWIDTH, + Wand, AOE, }; use rltk::{console, RandomNumberGenerator, RGB}; use specs::prelude::*; @@ -141,8 +141,11 @@ pub fn spawn_region( } pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { - let x = (*spawn.0 % MAPWIDTH) as i32; - let y = (*spawn.0 / MAPWIDTH) as i32; + let map = ecs.fetch::(); + let width = map.width as usize; + std::mem::drop(map); + let x = (*spawn.0 % width) as i32; + let y = (*spawn.0 / width) as i32; match spawn.1.as_ref() { // Monsters @@ -168,6 +171,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { "magic missile wand" => magic_missile_wand(ecs, x, y), "fireball wand" => fireball_wand(ecs, x, y), "confusion wand" => confusion_wand(ecs, x, y), + "digging wand" => digging_wand(ecs, x, y), // Food "rations" => rations(ecs, x, y), "apple" => apple(ecs, x, y), @@ -180,7 +184,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { } } -// 20 mobs : 6 items : 2 food : 1 trap +// 12 mobs : 6 items : 2 food : 1 trap fn category_table() -> RandomTable { return RandomTable::new().add("mob", 12).add("item", 6).add("food", 2).add("trap", 1); } @@ -230,7 +234,11 @@ pub fn scroll_table(_map_depth: i32) -> RandomTable { } pub fn wand_table(_map_depth: i32) -> RandomTable { - return RandomTable::new().add("magic missile wand", 1).add("fireball wand", 1).add("confusion wand", 1); + return RandomTable::new() + .add("magic missile wand", 1) + .add("fireball wand", 1) + .add("confusion wand", 1) + .add("digging wand", 1); } pub fn food_table(_map_depth: i32) -> RandomTable { @@ -600,6 +608,25 @@ fn confusion_wand(ecs: &mut World, x: i32, y: i32) { .build(); } +fn digging_wand(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('/'), + fg: RGB::named(rltk::PURPLE), + bg: RGB::named(rltk::BLACK), + render_order: 2, + }) + .with(Name { name: "wand of digging".to_string(), plural: "wands of digging".to_string() }) + .with(Item {}) + .with(Wand { uses: 3, max_uses: 3 }) + .with(Destructible {}) + .with(Ranged { range: 10 }) + .with(Digger {}) + .marked::>() + .build(); +} + // TRAPS fn bear_trap(ecs: &mut World, x: i32, y: i32) { ecs.create_entity()