diff --git a/src/components.rs b/src/components.rs index 5070f6e..898785f 100644 --- a/src/components.rs +++ b/src/components.rs @@ -209,7 +209,19 @@ pub struct Wand { #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Destructible {} +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Hidden {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct SingleActivation {} + #[derive(Component, Clone, ConvertSaveload)] pub struct ParticleLifetime { pub lifetime_ms: f32, } + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct EntryTrigger {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct EntityMoved {} diff --git a/src/gui.rs b/src/gui.rs index d4ae12e..7472c63 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,6 +1,6 @@ use super::{ - gamelog, rex_assets::RexAssets, CombatStats, Equipped, HungerClock, HungerState, InBackpack, Map, Name, Player, - Point, Position, RunState, State, Viewshed, + 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::*; @@ -61,13 +61,14 @@ 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 { return; } 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 c4d5a86..a1435c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod damage_system; use damage_system::*; mod hunger_system; mod melee_combat_system; +mod trigger_system; use melee_combat_system::MeleeCombatSystem; mod inventory_system; use inventory_system::*; @@ -65,27 +66,31 @@ pub struct State { impl State { fn run_systems(&mut self) { let mut vis = VisibilitySystem {}; - vis.run_now(&self.ecs); let mut mob = MonsterAI {}; - mob.run_now(&self.ecs); let mut mapindex = MapIndexingSystem {}; - mapindex.run_now(&self.ecs); - let mut inventory_system = ItemCollectionSystem {}; - inventory_system.run_now(&self.ecs); - let mut item_use_system = ItemUseSystem {}; - item_use_system.run_now(&self.ecs); - let mut item_drop_system = ItemDropSystem {}; - item_drop_system.run_now(&self.ecs); - let mut item_remove_system = ItemRemoveSystem {}; - item_remove_system.run_now(&self.ecs); + let mut trigger_system = trigger_system::TriggerSystem {}; let mut melee_system = MeleeCombatSystem {}; - melee_system.run_now(&self.ecs); let mut damage_system = DamageSystem {}; - damage_system.run_now(&self.ecs); + let mut inventory_system = ItemCollectionSystem {}; + let mut item_use_system = ItemUseSystem {}; + let mut item_drop_system = ItemDropSystem {}; + let mut item_remove_system = ItemRemoveSystem {}; let mut hunger_clock = hunger_system::HungerSystem {}; - hunger_clock.run_now(&self.ecs); let mut particle_system = particle_system::ParticleSpawnSystem {}; + + vis.run_now(&self.ecs); + mob.run_now(&self.ecs); + mapindex.run_now(&self.ecs); + trigger_system.run_now(&self.ecs); + melee_system.run_now(&self.ecs); + damage_system.run_now(&self.ecs); + inventory_system.run_now(&self.ecs); + item_use_system.run_now(&self.ecs); + item_drop_system.run_now(&self.ecs); + item_remove_system.run_now(&self.ecs); + hunger_clock.run_now(&self.ecs); particle_system.run_now(&self.ecs); + self.ecs.maintain(); } @@ -248,12 +253,13 @@ impl GameState for State { 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 map = self.ecs.fetch::(); let entities = self.ecs.entities(); - let mut data = (&positions, &renderables, &entities).join().collect::>(); + 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) in data.iter() { + 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 bg = render.bg.add(RGB::from_u8(26, 45, 45)).add(offsets); @@ -506,9 +512,13 @@ 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::(); + gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::>(); gs.ecs.register::(); diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs index a990815..a9c2e3b 100644 --- a/src/melee_combat_system.rs +++ b/src/melee_combat_system.rs @@ -127,14 +127,7 @@ impl<'a> System<'a> for MeleeCombatSystem { } let pos = positions.get(wants_melee.target); if let Some(pos) = pos { - particle_builder.request( - pos.x, - pos.y, - rltk::RGB::named(rltk::ORANGE), - rltk::RGB::named(rltk::BLACK), - rltk::to_cp437('‼'), - 150.0, - ); + particle_builder.damage_taken(pos.x, pos.y) } SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage); } diff --git a/src/monster_ai_system.rs b/src/monster_ai_system.rs index 1433673..8d7ae86 100644 --- a/src/monster_ai_system.rs +++ b/src/monster_ai_system.rs @@ -1,4 +1,6 @@ -use super::{gamelog, Confusion, Map, Monster, Name, ParticleBuilder, Position, RunState, Viewshed, WantsToMelee}; +use super::{ + gamelog, Confusion, EntityMoved, Map, Monster, Name, ParticleBuilder, Position, RunState, Viewshed, WantsToMelee, +}; use rltk::Point; use specs::prelude::*; @@ -19,6 +21,7 @@ impl<'a> System<'a> for MonsterAI { WriteStorage<'a, Confusion>, ReadStorage<'a, Name>, WriteExpect<'a, ParticleBuilder>, + WriteStorage<'a, EntityMoved>, ); fn run(&mut self, data: Self::SystemData) { @@ -35,6 +38,7 @@ impl<'a> System<'a> for MonsterAI { mut confused, name, mut particle_builder, + mut entity_moved, ) = data; if *runstate != RunState::MonsterTurn { @@ -88,6 +92,7 @@ impl<'a> System<'a> for MonsterAI { idx = map.xy_idx(pos.x, pos.y); map.blocked[idx] = true; viewshed.dirty = true; + entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); } } } diff --git a/src/particle_system.rs b/src/particle_system.rs index afbd8a0..c9b7634 100644 --- a/src/particle_system.rs +++ b/src/particle_system.rs @@ -55,6 +55,10 @@ impl ParticleBuilder { self.requests.push(ParticleRequest { x, y, fg, bg, glyph, lifetime }); } + pub fn damage_taken(&mut self, x: i32, y: i32) { + self.request(x, y, rltk::RGB::named(rltk::ORANGE), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('‼'), 200.0); + } + // Makes a particle request in the shape of an 'x'. Sort of. pub fn request_star(&mut self, x: i32, y: i32, fg: RGB, bg: RGB, glyph: rltk::FontCharType, lifetime: f32) { self.request(x, y, fg, bg, glyph, lifetime * 2.0); diff --git a/src/player.rs b/src/player.rs index bf45278..dcd7433 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,6 +1,6 @@ use super::{ - gamelog, CombatStats, HungerClock, HungerState, Item, Map, Monster, Name, Player, Position, RunState, State, - Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, MAPHEIGHT, MAPWIDTH, + gamelog, CombatStats, EntityMoved, Hidden, HungerClock, HungerState, Item, Map, Monster, Name, Player, Position, + RunState, State, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, MAPHEIGHT, MAPWIDTH, }; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use specs::prelude::*; @@ -11,6 +11,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let mut players = ecs.write_storage::(); let mut viewsheds = ecs.write_storage::(); let mut telepaths = ecs.write_storage::(); + let mut entity_moved = ecs.write_storage::(); let combat_stats = ecs.read_storage::(); let map = ecs.fetch::(); @@ -37,14 +38,18 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { if !map.blocked[destination_idx] { let names = ecs.read_storage::(); + let hidden = ecs.read_storage::(); // Push every entity name in the pile to a vector of strings let mut item_names: Vec = Vec::new(); let mut some = false; for entity in map.tile_content[destination_idx].iter() { - if let Some(name) = names.get(*entity) { - let item_name = &name.name; - item_names.push(item_name.to_string()); - some = true; + if let Some(_hidden) = hidden.get(*entity) { + } else { + if let Some(name) = names.get(*entity) { + let item_name = &name.name; + item_names.push(item_name.to_string()); + some = true; + } } } // If some names were found, append. Logger = logger is necessary @@ -74,6 +79,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let mut ppos = ecs.write_resource::(); ppos.x = pos.x; ppos.y = pos.y; + entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); } } } diff --git a/src/saveload_system.rs b/src/saveload_system.rs index e092981..ac2a1d7 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -55,8 +55,11 @@ pub fn save_game(ecs: &mut World) { Cursed, DefenceBonus, Destructible, + EntityMoved, + EntryTrigger, Equippable, Equipped, + Hidden, HungerClock, InBackpack, InflictsDamage, @@ -73,6 +76,7 @@ pub fn save_game(ecs: &mut World) { ProvidesNutrition, Ranged, Renderable, + SingleActivation, SufferDamage, Telepath, Viewshed, @@ -143,8 +147,11 @@ pub fn load_game(ecs: &mut World) { Cursed, DefenceBonus, Destructible, + EntityMoved, + EntryTrigger, Equippable, Equipped, + Hidden, HungerClock, InBackpack, InflictsDamage, @@ -161,6 +168,7 @@ pub fn load_game(ecs: &mut World) { ProvidesNutrition, Ranged, Renderable, + SingleActivation, SufferDamage, Telepath, Viewshed, diff --git a/src/spawner.rs b/src/spawner.rs index 8218b90..cf5e070 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,8 +1,8 @@ use super::{ random_table::RandomTable, BlocksTile, CombatStats, Confusion, Consumable, Cursed, DefenceBonus, Destructible, - EquipmentSlot, Equippable, HungerClock, HungerState, InflictsDamage, Item, MagicMapper, MeleePowerBonus, Mind, - Monster, Name, Player, Position, ProvidesHealing, ProvidesNutrition, Ranged, Rect, Renderable, SerializeMe, - Viewshed, Wand, AOE, MAPWIDTH, + EntryTrigger, EquipmentSlot, Equippable, Hidden, HungerClock, HungerState, InflictsDamage, Item, MagicMapper, + MeleePowerBonus, Mind, Monster, Name, Player, Position, ProvidesHealing, ProvidesNutrition, Ranged, Rect, + Renderable, SerializeMe, SingleActivation, Viewshed, Wand, AOE, MAPWIDTH, }; use rltk::{console, RandomNumberGenerator, RGB}; use specs::prelude::*; @@ -99,6 +99,7 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) { "mob" => spawn_table = mob_table(map_depth), "item" => spawn_table = item_table(map_depth), "food" => spawn_table = food_table(map_depth), + "trap" => spawn_table = trap_table(map_depth), _ => spawn_table = debug_table(), } spawn_points.insert(idx, spawn_table.roll(&mut rng)); @@ -138,16 +139,20 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) { // Wands "magic missile wand" => magic_missile_wand(ecs, x, y), "fireball wand" => fireball_wand(ecs, x, y), + "confusion wand" => confusion_wand(ecs, x, y), // Food "rations" => rations(ecs, x, y), + // Traps + "bear trap" => bear_trap(ecs, x, y), + "confusion trap" => confusion_trap(ecs, x, y), _ => console::log("Tried to spawn nothing. Bugfix needed!"), } } } -// 10 mobs : 3 items : 1 food +// 20 mobs : 6 items : 2 food : 1 trap fn category_table() -> RandomTable { - return RandomTable::new().add("mob", 9).add("item", 3).add("food", 1); + return RandomTable::new().add("mob", 20).add("item", 6).add("food", 2).add("trap", 1000); } fn debug_table() -> RandomTable { @@ -183,13 +188,18 @@ fn item_table(_map_depth: i32) -> RandomTable { .add("cursed magic map scroll", 2) // Wands .add("magic missile wand", 1) - .add("fireball wand", 1); + .add("fireball wand", 1) + .add("confusion wand", 1); } fn food_table(_map_depth: i32) -> RandomTable { return RandomTable::new().add("rations", 1); } +fn trap_table(_map_depth: i32) -> RandomTable { + return RandomTable::new().add("bear trap", 0).add("confusion trap", 1); +} + fn health_potion(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) @@ -478,7 +488,7 @@ fn magic_missile_wand(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { - glyph: rltk::to_cp437('?'), + glyph: rltk::to_cp437('/'), fg: RGB::named(rltk::BLUE), bg: RGB::named(rltk::BLACK), render_order: 2, @@ -492,3 +502,59 @@ fn magic_missile_wand(ecs: &mut World, x: i32, y: i32) { .marked::>() .build(); } + +fn confusion_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 confusion".to_string() }) + .with(Item {}) + .with(Wand { uses: 3, max_uses: 3 }) + .with(Destructible {}) + .with(Ranged { range: 10 }) + .with(Confusion { turns: 4 }) + .marked::>() + .build(); +} + +// TRAPS +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::GREY), + bg: RGB::named(rltk::BLACK), + render_order: 2, + }) + .with(Name { name: "bear trap".to_string() }) + .with(Hidden {}) + .with(EntryTrigger {}) + .with(SingleActivation {}) + .with(InflictsDamage { amount: 6 }) + .marked::>() + .build(); +} + +fn confusion_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::PURPLE), + bg: RGB::named(rltk::BLACK), + render_order: 2, + }) + .with(Name { name: "magic trap".to_string() }) + .with(Hidden {}) + .with(EntryTrigger {}) + .with(SingleActivation {}) + .with(Confusion { turns: 3 }) + .marked::>() + .build(); +} diff --git a/src/trigger_system.rs b/src/trigger_system.rs new file mode 100644 index 0000000..6a5c267 --- /dev/null +++ b/src/trigger_system.rs @@ -0,0 +1,88 @@ +use super::{ + gamelog, Confusion, EntityMoved, EntryTrigger, Hidden, InflictsDamage, Map, Name, ParticleBuilder, Position, + SingleActivation, SufferDamage, +}; +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>, + ReadStorage<'a, InflictsDamage>, + WriteStorage<'a, Confusion>, + WriteStorage<'a, SufferDamage>, + WriteStorage<'a, Hidden>, + ReadStorage<'a, SingleActivation>, + ReadStorage<'a, Name>, + WriteExpect<'a, ParticleBuilder>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + map, + mut entity_moved, + position, + entry_trigger, + inflicts_damage, + mut confusion, + mut inflict_damage, + mut hidden, + single_activation, + names, + mut particle_builder, + entities, + ) = data; + + // Iterate entities that moved, and their final position + let mut remove_entities: Vec = Vec::new(); + 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() { + if entity != *entity_id { + let maybe_trigger = entry_trigger.get(*entity_id); + match maybe_trigger { + None => {} + Some(_trigger) => { + // Something on this pos had a trigger + let name = names.get(*entity_id); + hidden.remove(*entity_id); + if let Some(name) = name { + gamelog::Logger::new().item_name(&name.name).append("triggers!").log(); + } + + let damage = inflicts_damage.get(*entity_id); + if let Some(damage) = damage { + particle_builder.damage_taken(pos.x, pos.y); + SufferDamage::new_damage(&mut inflict_damage, entity, damage.amount); + } + + let confuses = confusion.get(*entity_id); + if let Some(confuses) = confuses { + confusion + .insert(entity, Confusion { turns: confuses.turns }) + .expect("Unable to insert confusion"); + } + + let sa = single_activation.get(*entity_id); + if let Some(_sa) = sa { + remove_entities.push(*entity_id); + } + } + } + } + } + } + + for trap in remove_entities.iter() { + entities.delete(*trap).expect("Unable to delete trap"); + } + + entity_moved.clear(); + } +} diff --git a/src/visibility_system.rs b/src/visibility_system.rs index 96e92b9..9161fc9 100644 --- a/src/visibility_system.rs +++ b/src/visibility_system.rs @@ -1,4 +1,4 @@ -use super::{Map, Player, Position, Telepath, Viewshed}; +use super::{gamelog, Hidden, Map, Name, Player, Position, Telepath, Viewshed}; use rltk::{FieldOfViewAlg::SymmetricShadowcasting, Point}; use specs::prelude::*; @@ -7,15 +7,18 @@ pub struct VisibilitySystem {} impl<'a> System<'a> for VisibilitySystem { type SystemData = ( WriteExpect<'a, Map>, + WriteExpect<'a, rltk::RandomNumberGenerator>, Entities<'a>, WriteStorage<'a, Viewshed>, WriteStorage<'a, Telepath>, WriteStorage<'a, Position>, ReadStorage<'a, Player>, + WriteStorage<'a, Hidden>, + ReadStorage<'a, Name>, ); fn run(&mut self, data: Self::SystemData) { - let (mut map, entities, mut viewshed, mut telepath, pos, player) = data; + let (mut map, mut rng, entities, mut viewshed, mut telepath, pos, player, mut hidden, names) = data; for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() { if viewshed.dirty { @@ -44,6 +47,24 @@ impl<'a> System<'a> for VisibilitySystem { let idx = map.xy_idx(vis.x, vis.y); map.revealed_tiles[idx] = true; map.visible_tiles[idx] = true; + + // Reveal hidden things + for thing in map.tile_content[idx].iter() { + let is_hidden = hidden.get(*thing); + if let Some(_is_hidden) = is_hidden { + if rng.roll_dice(1, 20) == 1 { + let name = names.get(*thing); + if let Some(name) = name { + gamelog::Logger::new() + .append("You spot a") + .item_name_n(&name.name) + .period() + .log(); + } + hidden.remove(*thing); + } + } + } } } }