decoupled camera from map, wands of digging, wand use tracking
This commit is contained in:
parent
7aa440612e
commit
2ecfd25d95
9 changed files with 430 additions and 92 deletions
279
src/camera.rs
Normal file
279
src/camera.rs
Normal 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?
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
||||
|
|
|
|||
45
src/gui.rs
45
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::<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,34 +394,44 @@ 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));
|
||||
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 {
|
||||
return (ItemMenuResult::Cancel, None);
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
48
src/main.rs
48
src/main.rs
|
|
@ -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,54 +252,10 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
camera::render_camera(&self.ecs, ctx);
|
||||
gui::draw_ui(&self.ecs, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match new_runstate {
|
||||
RunState::PreRun => {
|
||||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,6 +122,11 @@ 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 {
|
||||
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);
|
||||
|
|
@ -125,6 +136,7 @@ pub fn open(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;
|
||||
} else {
|
||||
gamelog::Logger::new().append("It's already open.").log();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue