Add a basic hidden trap mechanism

This commit is contained in:
Timothy Warren 2021-11-29 16:00:07 -05:00
parent 32874bf9f5
commit 804904dd4b
8 changed files with 145 additions and 7 deletions

View File

@ -199,6 +199,15 @@ pub struct ProvidesFood {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct MagicMapper {} pub struct MagicMapper {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Hidden {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntryTrigger {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntityMoved {}
// Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an // Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an
// Entity. // Entity.

View File

@ -1,7 +1,7 @@
use crate::components::{ use crate::components::{
CombatStats, HungerClock, HungerState, InBackpack, Name, Player, Position, Viewshed, CombatStats, HungerClock, HungerState, InBackpack, Name, Player, Position, Viewshed,
}; };
use crate::{game_log::GameLog, rex_assets::RexAssets, Equipped, Map, RunState, State}; use crate::{game_log::GameLog, rex_assets::RexAssets, Equipped, Hidden, Map, RunState, State};
use rltk::{Point, Rltk, VirtualKeyCode, RGB}; use rltk::{Point, Rltk, VirtualKeyCode, RGB};
use specs::prelude::*; use specs::prelude::*;
@ -98,6 +98,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let names = ecs.read_storage::<Name>(); let names = ecs.read_storage::<Name>();
let positions = ecs.read_storage::<Position>(); let positions = ecs.read_storage::<Position>();
let hidden = ecs.read_storage::<Hidden>();
let mouse_pos = ctx.mouse_pos(); let mouse_pos = ctx.mouse_pos();
if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height { if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height {
@ -105,7 +106,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
} }
let mut tooltip: Vec<String> = Vec::new(); let mut tooltip: Vec<String> = Vec::new();
for (name, position) in (&names, &positions).join() { for (name, position, _hidden) in (&names, &positions, !&hidden).join() {
let idx = map.xy_idx(position.x, position.y); 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.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] {
tooltip.push(name.name.to_string()); tooltip.push(name.name.to_string());

View File

@ -19,6 +19,7 @@ mod rect;
mod rex_assets; mod rex_assets;
pub mod saveload_system; pub mod saveload_system;
mod spawner; mod spawner;
mod trigger_system;
mod visibility_system; mod visibility_system;
use crate::inventory_system::ItemRemoveSystem; use crate::inventory_system::ItemRemoveSystem;
@ -85,6 +86,9 @@ impl State {
let mut mob = MonsterAI {}; let mut mob = MonsterAI {};
mob.run_now(&self.ecs); mob.run_now(&self.ecs);
let mut triggers = trigger_system::TriggerSystem {};
triggers.run_now(&self.ecs);
let mut mapindex = MapIndexingSystem {}; let mut mapindex = MapIndexingSystem {};
mapindex.run_now(&self.ecs); mapindex.run_now(&self.ecs);
@ -129,17 +133,19 @@ impl GameState for State {
match newrunstate { match newrunstate {
RunState::MainMenu { .. } => {} RunState::MainMenu { .. } => {}
RunState::GameOver { .. } => {}
_ => { _ => {
// Draw the UI // Draw the UI
draw_map(&self.ecs, ctx); draw_map(&self.ecs, ctx);
{ {
let positions = self.ecs.read_storage::<Position>(); let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>(); let renderables = self.ecs.read_storage::<Renderable>();
let hidden = self.ecs.read_storage::<Hidden>();
let map = self.ecs.fetch::<Map>(); let map = self.ecs.fetch::<Map>();
let mut data: Vec<_> = (&positions, &renderables).join().collect(); let mut data: Vec<_> = (&positions, &renderables, !&hidden).join().collect();
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render) in data.iter() { for (pos, render, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y); let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] { if map.visible_tiles[idx] {
@ -511,6 +517,9 @@ fn main() -> rltk::BError {
HungerClock, HungerClock,
ProvidesFood, ProvidesFood,
MagicMapper, MagicMapper,
Hidden,
EntryTrigger,
EntityMoved,
); );
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());

View File

@ -1,5 +1,5 @@
use crate::components::{Confusion, Monster, Position, Viewshed, WantsToMelee}; use crate::components::{Confusion, Monster, Position, Viewshed, WantsToMelee};
use crate::{particle_system::ParticleBuilder, Map, RunState}; use crate::{particle_system::ParticleBuilder, EntityMoved, Map, RunState};
use rltk::{Point, RGB}; use rltk::{Point, RGB};
use specs::prelude::*; use specs::prelude::*;
@ -19,6 +19,7 @@ impl<'a> System<'a> for MonsterAI {
WriteStorage<'a, WantsToMelee>, WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, Confusion>, WriteStorage<'a, Confusion>,
WriteExpect<'a, ParticleBuilder>, WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, EntityMoved>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -34,6 +35,7 @@ impl<'a> System<'a> for MonsterAI {
mut wants_to_melee, mut wants_to_melee,
mut confused, mut confused,
mut particle_builder, mut particle_builder,
mut entity_moved,
) = data; ) = data;
if *runstate != RunState::MonsterTurn { if *runstate != RunState::MonsterTurn {
@ -91,6 +93,10 @@ impl<'a> System<'a> for MonsterAI {
pos.x = path.steps[1] as i32 % map.width; pos.x = path.steps[1] as i32 % map.width;
pos.y = path.steps[1] as i32 / map.width; pos.y = path.steps[1] as i32 / map.width;
entity_moved
.insert(entity, EntityMoved {})
.expect("Unable to add EntityMoved flag to monster");
idx = map.xy_idx(pos.x, pos.y); idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = true; map.blocked[idx] = true;
viewshed.dirty = true; viewshed.dirty = true;

View File

@ -1,6 +1,6 @@
use crate::components::{ use crate::components::{
CombatStats, HungerClock, HungerState, Item, Monster, Player, Position, Viewshed, WantsToMelee, CombatStats, EntityMoved, HungerClock, HungerState, Item, Monster, Player, Position, Viewshed,
WantsToPickupItem, WantsToMelee, WantsToPickupItem,
}; };
use crate::{game_log::GameLog, Map, RunState, State, TileType}; use crate::{game_log::GameLog, Map, RunState, State, TileType};
use rltk::{Point, Rltk, VirtualKeyCode}; use rltk::{Point, Rltk, VirtualKeyCode};
@ -15,6 +15,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let combat_stats = ecs.read_storage::<CombatStats>(); let combat_stats = ecs.read_storage::<CombatStats>();
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let mut wants_to_melee = ecs.write_storage::<WantsToMelee>(); let mut wants_to_melee = ecs.write_storage::<WantsToMelee>();
let mut entity_moved = ecs.write_storage::<EntityMoved>();
for (entity, _player, pos, viewshed) in for (entity, _player, pos, viewshed) in
(&entities, &players, &mut positions, &mut viewsheds).join() (&entities, &players, &mut positions, &mut viewsheds).join()
@ -45,6 +46,9 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
if !map.blocked[destination_idx] { if !map.blocked[destination_idx] {
pos.x = min(79, max(0, pos.x + delta_x)); pos.x = min(79, max(0, pos.x + delta_x));
pos.y = min(49, max(0, pos.y + delta_y)); pos.y = min(49, max(0, pos.y + delta_y));
entity_moved
.insert(entity, EntityMoved {})
.expect("Failed to add EntityMoved flag to player");
viewshed.dirty = true; viewshed.dirty = true;
let mut ppos = ecs.write_resource::<Point>(); let mut ppos = ecs.write_resource::<Point>();

View File

@ -79,6 +79,9 @@ pub fn save_game(ecs: &mut World) {
HungerClock, HungerClock,
ProvidesFood, ProvidesFood,
MagicMapper, MagicMapper,
Hidden,
EntryTrigger,
EntityMoved,
); );
} }
@ -164,6 +167,9 @@ pub fn load_game(ecs: &mut World) {
HungerClock, HungerClock,
ProvidesFood, ProvidesFood,
MagicMapper, MagicMapper,
Hidden,
EntryTrigger,
EntityMoved,
); );
} }

View File

@ -51,6 +51,7 @@ fn room_table(map_depth: i32) -> RandomTable {
.add("Tower Shield", map_depth - 1) .add("Tower Shield", map_depth - 1)
.add("Rations", 10) .add("Rations", 10)
.add("Magic Mapping Scroll", 2) .add("Magic Mapping Scroll", 2)
.add("Bear Trap", 2)
} }
/// fills a room with stuff! /// fills a room with stuff!
@ -101,6 +102,7 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
"Tower Shield" => tower_shield(ecs, x, y), "Tower Shield" => tower_shield(ecs, x, y),
"Rations" => rations(ecs, x, y), "Rations" => rations(ecs, x, y),
"Magic Mapping Scroll" => magic_mapping_scroll(ecs, x, y), "Magic Mapping Scroll" => magic_mapping_scroll(ecs, x, y),
"Bear Trap" => bear_trap(ecs, x, y),
_ => {} _ => {}
} }
} }
@ -318,3 +320,20 @@ fn magic_mapping_scroll(ecs: &mut World, x: i32, y: i32) {
.marked::<SimpleMarker<SerializeMe>>() .marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
fn bear_trap(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('^'),
fg: RGB::named(rltk::RED),
bg: RGB::named(rltk::BLACK),
render_order: 2,
})
.with(Name::from("Bear Trap"))
.with(Hidden {})
.with(EntryTrigger {})
.with(InflictsDamage { damage: 6 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}

84
src/trigger_system.rs Normal file
View File

@ -0,0 +1,84 @@
use crate::components::{
EntityMoved, EntryTrigger, Hidden, InflictsDamage, Name, Position, SufferDamage,
};
use crate::{game_log::GameLog, particle_system::ParticleBuilder, Map};
use specs::prelude::*;
pub struct TriggerSystem {}
impl<'a> System<'a> for TriggerSystem {
#[allow(clippy::type_complexity)]
type SystemData = (
ReadExpect<'a, Map>,
WriteStorage<'a, EntityMoved>,
ReadStorage<'a, Position>,
ReadStorage<'a, EntryTrigger>,
WriteStorage<'a, Hidden>,
ReadStorage<'a, Name>,
Entities<'a>,
WriteExpect<'a, GameLog>,
ReadStorage<'a, InflictsDamage>,
WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, SufferDamage>,
);
fn run(&mut self, data: Self::SystemData) {
let (
map,
mut entity_moved,
position,
entry_trigger,
mut hidden,
names,
entities,
mut log,
inflicts_damage,
mut particle_builder,
mut inflict_damage,
) = data;
// Iterate the entities that moved and their final position
for (entity, mut _entity_moved, pos) in (&entities, &mut entity_moved, &position).join() {
let idx = map.xy_idx(pos.x, pos.y);
for entity_id in map.tile_content[idx].iter() {
// Do not bother to check yourself for being a trap!
if entity != *entity_id {
match entry_trigger.get(*entity_id) {
None => {}
Some(_trigger) => {
// We triggered it
if let Some(name) = names.get(*entity_id) {
log.append(format!("{} triggers!", &name.name));
}
// The trap is no longer hidden
hidden.remove(*entity_id);
// If the trap is damage inflicting, do it
if let Some(damage) = inflicts_damage.get(*entity_id) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.0,
);
SufferDamage::new_damage(
&mut inflict_damage,
entity,
damage.damage,
);
}
}
}
}
}
}
// Remove all entity movement markers
entity_moved.clear();
}
}