decoupled camera from map, wands of digging, wand use tracking

This commit is contained in:
Llywelwyn 2023-07-24 18:14:38 +01:00
parent 7aa440612e
commit 2ecfd25d95
9 changed files with 430 additions and 92 deletions

279
src/camera.rs Normal file
View file

@ -0,0 +1,279 @@
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 = 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 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::<Map>();
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::<Position>();
let renderables = ecs.read_storage::<Renderable>();
let minds = ecs.read_storage::<Mind>();
let hidden = ecs.read_storage::<Hidden>();
let doors = ecs.write_storage::<Door>();
let map = ecs.fetch::<Map>();
let entities = ecs.entities();
let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::<Vec<_>>();
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<u8> = 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?
}
}

View file

@ -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 {}

View file

@ -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::<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_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<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_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<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>();
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::<Map>();
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));

View file

@ -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<Entity> = Vec::new();
let mut target_idxs: Vec<usize> = 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);

View file

@ -4,6 +4,7 @@ use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
use std::ops::{Add, Mul};
extern crate serde;
pub mod camera;
mod components;
pub use components::*;
mod map;
@ -251,52 +252,8 @@ impl GameState for State {
RunState::MainMenu { .. } => {}
_ => {
// Draw map and ui
draw_map(&self.ecs.fetch::<Map>(), ctx);
{
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let minds = self.ecs.read_storage::<Mind>();
let hidden = self.ecs.read_storage::<Hidden>();
let doors = self.ecs.write_storage::<Door>();
let map = self.ecs.fetch::<Map>();
let entities = self.ecs.entities();
let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::<Vec<_>>();
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);
}
}
@ -566,6 +523,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<InflictsDamage>();
gs.ecs.register::<Ranged>();
gs.ecs.register::<AOE>();
gs.ecs.register::<Digger>();
gs.ecs.register::<Confusion>();
gs.ecs.register::<MagicMapper>();
gs.ecs.register::<InBackpack>();

View file

@ -11,8 +11,11 @@ pub enum TileType {
DownStair,
}
pub const MAPWIDTH: usize = 80;
pub const MAPHEIGHT: usize = 43;
// 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.
pub const MAPWIDTH: usize = 64;
pub const MAPHEIGHT: usize = 64;
pub const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH;
#[derive(Default, Serialize, Deserialize, Clone)]

View file

@ -19,6 +19,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
let mut blocks_movement = ecs.write_storage::<BlocksTile>();
let mut renderables = ecs.write_storage::<Renderable>();
let names = ecs.read_storage::<Name>();
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let mut result = RunState::AwaitingInput;
let mut door_pos: Option<Point> = None;
@ -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,6 +99,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
let mut blocks_movement = ecs.write_storage::<BlocksTile>();
let mut renderables = ecs.write_storage::<Renderable>();
let names = ecs.read_storage::<Name>();
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let mut result = RunState::AwaitingInput;
let mut door_pos: Option<Point> = None;
@ -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();

View file

@ -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,

View file

@ -1,6 +1,6 @@
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,
@ -168,6 +168,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 +181,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 +231,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 +605,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::<SimpleMarker<SerializeMe>>()
.build();
}
// TRAPS
fn bear_trap(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()