diff --git a/src/components.rs b/src/components.rs index cd854ae..098a066 100644 --- a/src/components.rs +++ b/src/components.rs @@ -217,3 +217,14 @@ 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 {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct SingleActivation {} diff --git a/src/damage_system.rs b/src/damage_system.rs index 64ebfbb..6389f1f 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -1,4 +1,3 @@ - use specs::prelude::*; use crate::components::{CombatStats, Player, SufferDamage}; diff --git a/src/gui.rs b/src/gui.rs index 6f859e0..6b9a99c 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,15 +1,15 @@ -use rltk::{Point, VirtualKeyCode}; -use rltk::RGB; use rltk::Rltk; +use rltk::RGB; +use rltk::{Point, VirtualKeyCode}; use specs::prelude::*; -use crate::{CombatStats, InBackpack, RunState, State, Viewshed}; -use crate::{Equipped, Map}; -use crate::{HungerClock, HungerState, Name}; use crate::gamelog::GameLog; +use crate::rex_assets::RexAssets; use crate::Player; use crate::Position; -use crate::rex_assets::RexAssets; +use crate::{CombatStats, Hidden, InBackpack, RunState, State, Viewshed}; +use crate::{Equipped, Map}; +use crate::{HungerClock, HungerState, Name}; pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { ctx.draw_box( @@ -100,6 +100,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 { @@ -107,7 +108,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()); @@ -446,10 +447,32 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { let assets = gs.ecs.fetch::(); ctx.render_xp_sprite(&assets.menu, 0, 0); - ctx.draw_box_double(24, 18, 31, 10, RGB::named(rltk::WHEAT), RGB::named(rltk::BLACK)); - ctx.print_color_centered(20, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Rust Roguelike Tutorial"); - ctx.print_color_centered(21, RGB::named(rltk::CYAN), RGB::named(rltk::BLACK), "by Herbert Wolverson"); - ctx.print_color_centered(22, RGB::named(rltk::GRAY), RGB::named(rltk::BLACK), "Use Up/Down Arrows and Enter"); + ctx.draw_box_double( + 24, + 18, + 31, + 10, + RGB::named(rltk::WHEAT), + RGB::named(rltk::BLACK), + ); + ctx.print_color_centered( + 20, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "Rust Roguelike Tutorial", + ); + ctx.print_color_centered( + 21, + RGB::named(rltk::CYAN), + RGB::named(rltk::BLACK), + "by Herbert Wolverson", + ); + ctx.print_color_centered( + 22, + RGB::named(rltk::GRAY), + RGB::named(rltk::BLACK), + "Use Up/Down Arrows and Enter", + ); let mut y = 24; if let RunState::MainMenu { diff --git a/src/hunger_system.rs b/src/hunger_system.rs index 06c4273..cbe4dce 100644 --- a/src/hunger_system.rs +++ b/src/hunger_system.rs @@ -1,6 +1,5 @@ use specs::prelude::*; - use crate::{GameLog, Heals, HungerClock, HungerState, RunState, SufferDamage}; pub struct HungerSystem {} diff --git a/src/inventory_system.rs b/src/inventory_system.rs index 23bb51a..95715f3 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -1,8 +1,13 @@ use specs::prelude::*; -use crate::{AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, HungerClock, HungerState, InBackpack, InflictsDamage, MagicMapper, Map, Name, Position, ProvidesFood, ProvidesHealing, Ranged, RunState, SufferDamage, WantsToDropItem, WantsToPickupItem, WantsToRemoveItem, WantsToUseItem}; use crate::gamelog::GameLog; use crate::particle_system::ParticleBuilder; +use crate::{ + AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, HungerClock, + HungerState, InBackpack, InflictsDamage, MagicMapper, Map, Name, Position, ProvidesFood, + ProvidesHealing, Ranged, RunState, SufferDamage, WantsToDropItem, WantsToPickupItem, + WantsToRemoveItem, WantsToUseItem, +}; pub struct ItemCollectionSystem {} @@ -71,7 +76,7 @@ impl<'a> System<'a> for ItemUseSystem { ReadStorage<'a, ProvidesFood>, WriteStorage<'a, HungerClock>, ReadStorage<'a, MagicMapper>, - WriteExpect<'a, RunState> + WriteExpect<'a, RunState>, ); fn run(&mut self, data: Self::SystemData) { @@ -98,7 +103,7 @@ impl<'a> System<'a> for ItemUseSystem { provides_food, mut hunger_clocks, magic_mapper, - mut run_state + mut run_state, ) = data; for (entity, use_item) in (&entities, &wants_use).join() { @@ -144,8 +149,10 @@ impl<'a> System<'a> for ItemUseSystem { if let Some(_mapper) = magic_mapper.get(use_item.item) { used_item = true; - game_log.entries.push("The map is revealed to you!".to_string()); - *run_state = RunState::MagicMapReveal {row: 0}; + game_log + .entries + .push("The map is revealed to you!".to_string()); + *run_state = RunState::MagicMapReveal { row: 0 }; } if let Some(_item_edible) = provides_food.get(use_item.item) { diff --git a/src/main.rs b/src/main.rs index b79d08f..11bee94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use crate::inventory_system::{ ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem, }; use crate::particle_system::ParticleSpawnSystem; +use crate::trigger_system::TriggerSystem; mod components; mod damage_system; @@ -36,10 +37,11 @@ mod particle_system; mod player; mod random_table; mod rect; +mod rex_assets; mod save_load_system; mod spawner; mod visibility_system; -mod rex_assets; +mod trigger_system; #[derive(PartialEq, Copy, Clone)] pub enum RunState { @@ -60,7 +62,9 @@ pub enum RunState { NextLevel, ShowRemoveItem, GameOver, - MagicMapReveal { row: i32 }, + MagicMapReveal { + row: i32, + }, } pub struct State { @@ -210,6 +214,9 @@ impl State { let mut mob = MonsterAI {}; mob.run_now(&self.ecs); + let mut trigger = TriggerSystem {}; + trigger.run_now(&self.ecs); + let mut map_index = MapIndexingSystem {}; map_index.run_now(&self.ecs); @@ -262,11 +269,14 @@ impl GameState for State { 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 = (&positions, &renderables).join().collect::>(); + let mut data = (&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] { ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) @@ -287,8 +297,12 @@ impl GameState for State { self.run_systems(); new_run_state = RunState::MonsterTurn; match *self.ecs.fetch::() { - RunState::MagicMapReveal { .. } => { new_run_state = RunState::MagicMapReveal { row: 0 } } - _ => { new_run_state = RunState::MonsterTurn; } + RunState::MagicMapReveal { .. } => { + new_run_state = RunState::MagicMapReveal { row: 0 } + } + _ => { + new_run_state = RunState::MonsterTurn; + } } } RunState::MonsterTurn => { @@ -422,9 +436,7 @@ impl GameState for State { } } } - RunState::MagicMapReveal { - row - } => { + RunState::MagicMapReveal { row } => { let mut map = self.ecs.fetch_mut::(); for x in 0..MAP_WIDTH { let idx = map.xy_idx(x as i32, row); @@ -433,7 +445,7 @@ impl GameState for State { if row as usize == MAP_HEIGHT - 1 { new_run_state = RunState::MonsterTurn; } else { - new_run_state = RunState::MagicMapReveal {row: row + 1} + new_run_state = RunState::MagicMapReveal { row: row + 1 } } } } @@ -493,6 +505,10 @@ 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.insert(SimpleMarkerAllocator::::new()); diff --git a/src/monster_ai_system.rs b/src/monster_ai_system.rs index 665a8fa..21a756e 100644 --- a/src/monster_ai_system.rs +++ b/src/monster_ai_system.rs @@ -5,7 +5,7 @@ use crate::particle_system::ParticleBuilder; use crate::{ components::{Monster, Position, Viewshed, WantsToMelee}, map::Map, - Confusion, RunState, + Confusion, EntityMoved, RunState, }; pub struct MonsterAI {} @@ -24,6 +24,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) { @@ -39,6 +40,7 @@ impl<'a> System<'a> for MonsterAI { mut wants_to_melee, mut confused, mut particle_builder, + mut entity_moved, ) = data; if *runstate != RunState::MonsterTurn { @@ -96,6 +98,10 @@ 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 entity moved"); } } } diff --git a/src/player.rs b/src/player.rs index 7b62ca2..3ae26cb 100644 --- a/src/player.rs +++ b/src/player.rs @@ -8,7 +8,8 @@ use crate::gamelog::GameLog; use crate::{ components::{CombatStats, Player, Position, Viewshed, WantsToMelee}, map::Map, - HungerClock, HungerState, Item, Monster, RunState, State, TileType, WantsToPickupItem, + EntityMoved, HungerClock, HungerState, Item, Monster, RunState, State, TileType, + WantsToPickupItem, }; pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { @@ -19,6 +20,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let entities = ecs.entities(); let mut wants_to_melee = ecs.write_storage::(); let map = ecs.fetch::(); + let mut entity_moved = ecs.write_storage::(); for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() @@ -54,6 +56,10 @@ 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/rex_assets.rs b/src/rex_assets.rs index 24d43ac..15aad12 100644 --- a/src/rex_assets.rs +++ b/src/rex_assets.rs @@ -12,7 +12,7 @@ impl RexAssets { rltk::link_resource!(SMALL_DUNGEON, "../resources/SmallDungeon_80x50.xp"); RexAssets { - menu: XpFile::from_resource("../resources/SmallDungeon_80x50.xp").unwrap() + menu: XpFile::from_resource("../resources/SmallDungeon_80x50.xp").unwrap(), } } -} \ No newline at end of file +} diff --git a/src/save_load_system.rs b/src/save_load_system.rs index 3d4d0fe..f37a00e 100644 --- a/src/save_load_system.rs +++ b/src/save_load_system.rs @@ -81,7 +81,11 @@ pub fn save_game(ecs: &mut World) { HungerClock, Heals, ProvidesFood, - MagicMapper + MagicMapper, + Hidden, + EntryTrigger, + EntityMoved, + SingleActivation ); } @@ -165,7 +169,11 @@ pub fn load_game(ecs: &mut World) { HungerClock, Heals, ProvidesFood, - MagicMapper + MagicMapper, + Hidden, + EntryTrigger, + EntityMoved, + SingleActivation ); } diff --git a/src/spawner.rs b/src/spawner.rs index 5228356..4a1a6d9 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -4,7 +4,7 @@ use rltk::{FontCharType, RandomNumberGenerator, RGB}; use specs::prelude::*; use specs::saveload::{MarkedBuilder, SimpleMarker}; -use crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EquipmentSlot, Equippable, HungerClock, HungerState, InflictsDamage, Item, MagicMapper, MAP_WIDTH, MAX_MONSTER, MeleePowerBonus, Monster, Name, Player, Position, ProvidesFood, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed}; +use crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EntryTrigger, EquipmentSlot, Equippable, Hidden, HungerClock, HungerState, InflictsDamage, Item, MagicMapper, MAP_WIDTH, MAX_MONSTER, MeleePowerBonus, Monster, Name, Player, Position, ProvidesFood, ProvidesHealing, Ranged, Renderable, SerializeMe, SingleActivation, Viewshed}; use crate::random_table::RandomTable; use crate::rect::Rect; @@ -127,6 +127,7 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) { "Sabatons" => sabatons(ecs, x, y), "Rations" => rations(ecs, x, y), "Magic Mapping Scroll" => magic_mapper_scroll(ecs, x, y), + "Bear Trap" => bear_trap(ecs, x, y), _ => {} } } @@ -230,6 +231,7 @@ pub fn room_table(map_depth: i32) -> RandomTable { .add("Sabatons", map_depth - 4) .add("Rations", 10) .add("Magic Mapping Scroll", 2) + .add("Bear Trap", 2) } fn dagger(ecs: &mut World, x: i32, y: i32) { @@ -438,4 +440,24 @@ fn magic_mapper_scroll(ecs: &mut World, x: i32, y: i32) { .with(MagicMapper {}) .marked::>() .build(); -} \ No newline at end of file +} + +fn bear_trap(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('^'), + render_order: 2, + fg: RGB::named(rltk::RED), + bg: RGB::named(rltk::BLACK), + }) + .with(Name { + name: "Bear Trap".to_string(), + }) + .with(Hidden {}) + .with(EntryTrigger {}) + .with(InflictsDamage { damage: 6 }) + .with(SingleActivation {}) + .marked::>() + .build(); +} diff --git a/src/trigger_system.rs b/src/trigger_system.rs new file mode 100644 index 0000000..df72e72 --- /dev/null +++ b/src/trigger_system.rs @@ -0,0 +1,62 @@ +use specs::prelude::*; + +use crate::{EntityMoved, EntryTrigger, GameLog, Hidden, InflictsDamage, Map, Name, Position, SingleActivation, SufferDamage}; +use crate::particle_system::ParticleBuilder; + +pub struct TriggerSystem {} + +impl<'a> System<'a> for TriggerSystem { + 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>, + WriteStorage<'a, SingleActivation> + ); + + fn run(&mut self, data: Self::SystemData) { + let (map, mut entity_moved, position, entry_trigger, mut hidden, names, entities, mut game_log, inflicts_damage, mut particle_builder, mut inflict_damage, mut single_activation) = data; + + 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_id == entity { + continue; + } + + if let Some(_trigger) = entry_trigger.get(*entity_id) { + if let Some(name) = names.get(*entity_id) { + game_log.entries.push(format!("{} triggers", name.name)) + } + + hidden.remove(*entity_id); + + 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); + } + + if let Some(_sa) = single_activation.get(*entity_id) { + remove_entities.push(*entity_id); + } + } + } + } + + for trap in remove_entities.iter() { + entities.delete(*trap).expect("Could not delete trap"); + } + + entity_moved.clear(); + } +} + diff --git a/src/visibility_system.rs b/src/visibility_system.rs index e49cff1..6e989c0 100644 --- a/src/visibility_system.rs +++ b/src/visibility_system.rs @@ -1,10 +1,7 @@ -use rltk::{field_of_view, Point}; +use rltk::{field_of_view, Point, RandomNumberGenerator}; use specs::prelude::*; -use crate::{ - components::{Player, Position, Viewshed}, - map::Map, -}; +use crate::{components::{Player, Position, Viewshed}, GameLog, Hidden, map::Map, Name}; pub struct VisibilitySystem {} @@ -15,10 +12,14 @@ impl<'a> System<'a> for VisibilitySystem { WriteStorage<'a, Viewshed>, WriteStorage<'a, Position>, ReadStorage<'a, Player>, + WriteStorage<'a, Hidden>, + WriteExpect<'a, RandomNumberGenerator>, + ReadStorage<'a, Name>, + WriteExpect<'a, GameLog> ); fn run(&mut self, data: Self::SystemData) { - let (mut map, entities, mut viewshed, pos, player) = data; + let (mut map, entities, mut viewshed, pos, player, mut hidden, mut rng, names, mut game_log) = data; for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() { if !viewshed.dirty { continue; @@ -39,6 +40,17 @@ 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; + + for e in map.tile_content[idx].iter() { + if let Some(_maybe_hidden) = hidden.get(*e) { + if rng.roll_dice(1, 24) == 1 { + if let Some(name) = names.get(*e) { + game_log.entries.push(format!("You spotted a {}.", &name.name)); + } + hidden.remove(*e); + } + } + } } } }