From 804904dd4b9652f0133496f761d0b6a6d877c7f4 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Mon, 29 Nov 2021 16:00:07 -0500 Subject: [PATCH] Add a basic hidden trap mechanism --- src/components.rs | 9 +++++ src/gui.rs | 5 ++- src/main.rs | 13 ++++++- src/monster_ai_system.rs | 8 +++- src/player.rs | 8 +++- src/saveload_system.rs | 6 +++ src/spawner.rs | 19 +++++++++ src/trigger_system.rs | 84 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 src/trigger_system.rs diff --git a/src/components.rs b/src/components.rs index c9fbb53..446f6dc 100644 --- a/src/components.rs +++ b/src/components.rs @@ -199,6 +199,15 @@ pub struct ProvidesFood {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] 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 // Entity. diff --git a/src/gui.rs b/src/gui.rs index c2498da..135366d 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,7 +1,7 @@ use crate::components::{ 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 specs::prelude::*; @@ -98,6 +98,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { 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 { @@ -105,7 +106,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { } let mut tooltip: Vec = 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); if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] { tooltip.push(name.name.to_string()); diff --git a/src/main.rs b/src/main.rs index 5414cc7..aadd33b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ mod rect; mod rex_assets; pub mod saveload_system; mod spawner; +mod trigger_system; mod visibility_system; use crate::inventory_system::ItemRemoveSystem; @@ -85,6 +86,9 @@ impl State { let mut mob = MonsterAI {}; mob.run_now(&self.ecs); + let mut triggers = trigger_system::TriggerSystem {}; + triggers.run_now(&self.ecs); + let mut mapindex = MapIndexingSystem {}; mapindex.run_now(&self.ecs); @@ -129,17 +133,19 @@ impl GameState for State { match newrunstate { RunState::MainMenu { .. } => {} + RunState::GameOver { .. } => {} _ => { // Draw the UI draw_map(&self.ecs, ctx); { let positions = self.ecs.read_storage::(); let renderables = self.ecs.read_storage::(); + let hidden = self.ecs.read_storage::(); let map = self.ecs.fetch::(); - 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)); - for (pos, render) in data.iter() { + for (pos, render, _hidden) in data.iter() { let idx = map.xy_idx(pos.x, pos.y); if map.visible_tiles[idx] { @@ -511,6 +517,9 @@ fn main() -> rltk::BError { HungerClock, ProvidesFood, MagicMapper, + Hidden, + EntryTrigger, + EntityMoved, ); gs.ecs.insert(SimpleMarkerAllocator::::new()); diff --git a/src/monster_ai_system.rs b/src/monster_ai_system.rs index 8d34748..11ea224 100644 --- a/src/monster_ai_system.rs +++ b/src/monster_ai_system.rs @@ -1,5 +1,5 @@ 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 specs::prelude::*; @@ -19,6 +19,7 @@ impl<'a> System<'a> for MonsterAI { WriteStorage<'a, WantsToMelee>, WriteStorage<'a, Confusion>, WriteExpect<'a, ParticleBuilder>, + WriteStorage<'a, EntityMoved>, ); fn run(&mut self, data: Self::SystemData) { @@ -34,6 +35,7 @@ impl<'a> System<'a> for MonsterAI { mut wants_to_melee, mut confused, mut particle_builder, + mut entity_moved, ) = data; if *runstate != RunState::MonsterTurn { @@ -91,6 +93,10 @@ impl<'a> System<'a> for MonsterAI { pos.x = 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); map.blocked[idx] = true; viewshed.dirty = true; diff --git a/src/player.rs b/src/player.rs index fb41edb..5f18569 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,6 +1,6 @@ use crate::components::{ - CombatStats, HungerClock, HungerState, Item, Monster, Player, Position, Viewshed, WantsToMelee, - WantsToPickupItem, + CombatStats, EntityMoved, HungerClock, HungerState, Item, Monster, Player, Position, Viewshed, + WantsToMelee, WantsToPickupItem, }; use crate::{game_log::GameLog, Map, RunState, State, TileType}; 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::(); let map = ecs.fetch::(); let mut wants_to_melee = ecs.write_storage::(); + let mut entity_moved = ecs.write_storage::(); for (entity, _player, pos, viewshed) in (&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] { pos.x = min(79, max(0, pos.x + delta_x)); 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; let mut ppos = ecs.write_resource::(); diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 856146e..8d47df6 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -79,6 +79,9 @@ pub fn save_game(ecs: &mut World) { HungerClock, ProvidesFood, MagicMapper, + Hidden, + EntryTrigger, + EntityMoved, ); } @@ -164,6 +167,9 @@ pub fn load_game(ecs: &mut World) { HungerClock, ProvidesFood, MagicMapper, + Hidden, + EntryTrigger, + EntityMoved, ); } diff --git a/src/spawner.rs b/src/spawner.rs index 10d1af2..59a122c 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -51,6 +51,7 @@ fn room_table(map_depth: i32) -> RandomTable { .add("Tower Shield", map_depth - 1) .add("Rations", 10) .add("Magic Mapping Scroll", 2) + .add("Bear Trap", 2) } /// 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), "Rations" => rations(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::>() .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::>() + .build(); +} diff --git a/src/trigger_system.rs b/src/trigger_system.rs new file mode 100644 index 0000000..d5dce58 --- /dev/null +++ b/src/trigger_system.rs @@ -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(); + } +}