Add traps

This commit is contained in:
Kasper Juul Hermansen 2022-01-29 16:07:01 +01:00
parent 835a416013
commit 7c67291031
Signed by: kjuulh
GPG Key ID: DCD9397082D97069
13 changed files with 213 additions and 42 deletions

View File

@ -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 {}

View File

@ -1,4 +1,3 @@
use specs::prelude::*;
use crate::components::{CombatStats, Player, SufferDamage};

View File

@ -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::<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 {
@ -107,7 +108,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
}
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);
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::<RexAssets>();
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 {

View File

@ -1,6 +1,5 @@
use specs::prelude::*;
use crate::{GameLog, Heals, HungerClock, HungerState, RunState, SufferDamage};
pub struct HungerSystem {}

View File

@ -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) {

View File

@ -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::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let hidden = self.ecs.read_storage::<Hidden>();
let map = self.ecs.fetch::<Map>();
let mut data = (&positions, &renderables).join().collect::<Vec<_>>();
let mut data = (&positions, &renderables, !&hidden)
.join()
.collect::<Vec<_>>();
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>() {
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::<Map>();
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::<Heals>();
gs.ecs.register::<ProvidesFood>();
gs.ecs.register::<MagicMapper>();
gs.ecs.register::<Hidden>();
gs.ecs.register::<EntryTrigger>();
gs.ecs.register::<EntityMoved>();
gs.ecs.register::<SingleActivation>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());

View File

@ -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");
}
}
}

View File

@ -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::<WantsToMelee>();
let map = ecs.fetch::<Map>();
let mut entity_moved = ecs.write_storage::<EntityMoved>();
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::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
entity_moved
.insert(entity, EntityMoved {})
.expect("Unable to insert marker");
}
}
}

View File

@ -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(),
}
}
}
}

View File

@ -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
);
}

View File

@ -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::<SimpleMarker<SerializeMe>>()
.build();
}
}
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::<SimpleMarker<SerializeMe>>()
.build();
}

62
src/trigger_system.rs Normal file
View File

@ -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<Entity> = 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();
}
}

View File

@ -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);
}
}
}
}
}
}