basic trap implementation

confusion on player is NYI
This commit is contained in:
Llywelwyn 2023-07-15 09:59:20 +01:00
parent 4dffdd361d
commit d316a55818
11 changed files with 257 additions and 43 deletions

View file

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

View file

@ -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::<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 {
return;
}
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());

View file

@ -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::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let minds = self.ecs.read_storage::<Mind>();
let hidden = self.ecs.read_storage::<Hidden>();
let map = self.ecs.fetch::<Map>();
let entities = self.ecs.entities();
let mut data = (&positions, &renderables, &entities).join().collect::<Vec<_>>();
let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::<Vec<_>>();
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::<WantsToRemoveItem>();
gs.ecs.register::<WantsToUseItem>();
gs.ecs.register::<Consumable>();
gs.ecs.register::<SingleActivation>();
gs.ecs.register::<Wand>();
gs.ecs.register::<ProvidesNutrition>();
gs.ecs.register::<Destructible>();
gs.ecs.register::<Hidden>();
gs.ecs.register::<EntryTrigger>();
gs.ecs.register::<EntityMoved>();
gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>();

View file

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

View file

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

View file

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

View file

@ -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::<Player>();
let mut viewsheds = ecs.write_storage::<Viewshed>();
let mut telepaths = ecs.write_storage::<Telepath>();
let mut entity_moved = ecs.write_storage::<EntityMoved>();
let combat_stats = ecs.read_storage::<CombatStats>();
let map = ecs.fetch::<Map>();
@ -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::<Name>();
let hidden = ecs.read_storage::<Hidden>();
// Push every entity name in the pile to a vector of strings
let mut item_names: Vec<String> = 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::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
}
}
}

View file

@ -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,

View file

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

88
src/trigger_system.rs Normal file
View file

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

View file

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