From ee5db23f6b98c17afb40d888ab3f19f367b4d044 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Mon, 24 Jan 2022 10:58:37 -0500 Subject: [PATCH] Refactor confusion effect, and update gui to show confusion status --- src/ai/initiative_system.rs | 24 +++++++++++++++++- src/ai/turn_status.rs | 50 ++++++++++++++++++++++++------------- src/components.rs | 21 ++++++++++++---- src/components/tags.rs | 6 ++--- src/effects/damage.rs | 15 +++++++---- src/effects/triggers.rs | 50 ++++++++++++++++++++++++++----------- src/gui.rs | 30 +++++++++++++++++++--- src/gui/tooltip.rs | 12 ++++++++- src/main.rs | 2 ++ src/raws/item_structs.rs | 1 + src/raws/rawmaster.rs | 19 ++++++++------ src/saveload_system.rs | 4 +++ 12 files changed, 178 insertions(+), 56 deletions(-) diff --git a/src/ai/initiative_system.rs b/src/ai/initiative_system.rs index 535857f..b13d450 100644 --- a/src/ai/initiative_system.rs +++ b/src/ai/initiative_system.rs @@ -1,7 +1,9 @@ use ::rltk::{DistanceAlg, Point, RandomNumberGenerator}; use ::specs::prelude::*; -use crate::components::{Attributes, Initiative, MyTurn, Pools, Position}; +use crate::components::{ + Attributes, Duration, EquipmentChanged, Initiative, MyTurn, Pools, Position, StatusEffect, +}; use crate::RunState; pub struct InitiativeSystem {} @@ -19,6 +21,9 @@ impl<'a> System<'a> for InitiativeSystem { ReadExpect<'a, Entity>, ReadExpect<'a, Point>, ReadStorage<'a, Pools>, + WriteStorage<'a, Duration>, + WriteStorage<'a, EquipmentChanged>, + ReadStorage<'a, StatusEffect>, ); fn run(&mut self, data: Self::SystemData) { @@ -33,6 +38,9 @@ impl<'a> System<'a> for InitiativeSystem { player, player_pos, pools, + mut durations, + mut dirty, + statuses, ) = data; if *runstate != RunState::Ticking { @@ -82,5 +90,19 @@ impl<'a> System<'a> for InitiativeSystem { } } } + + if *runstate == RunState::AwaitingInput { + for (effect_entity, duration, status) in (&entities, &mut durations, &statuses).join() { + duration.turns -= 1; + if duration.turns < 1 { + dirty + .insert(status.target, EquipmentChanged {}) + .expect("Unable to insert EquipmentChanged tag"); + entities + .delete(effect_entity) + .expect("Unable to delete status effect tag entity"); + } + } + } } } diff --git a/src/ai/turn_status.rs b/src/ai/turn_status.rs index ad1c571..1838998 100644 --- a/src/ai/turn_status.rs +++ b/src/ai/turn_status.rs @@ -1,7 +1,10 @@ +use std::collections::HashSet; + use ::specs::prelude::*; -use crate::components::{Confusion, MyTurn}; -use crate::RunState; +use crate::components::{Confusion, MyTurn, StatusEffect}; +use crate::effects::{add_effect, EffectType, Targets}; +use crate::{colors, RunState}; pub struct TurnStatusSystem {} @@ -9,37 +12,50 @@ impl<'a> System<'a> for TurnStatusSystem { #[allow(clippy::type_complexity)] type SystemData = ( WriteStorage<'a, MyTurn>, - WriteStorage<'a, Confusion>, + ReadStorage<'a, Confusion>, Entities<'a>, ReadExpect<'a, RunState>, + ReadStorage<'a, StatusEffect>, ); fn run(&mut self, data: Self::SystemData) { - let (mut turns, mut confusion, entities, runstate) = data; + let (mut turns, confusion, entities, runstate, statuses) = data; if *runstate != RunState::Ticking { return; } + // Collect a set of all entities whose turn it is + let mut entity_turns = HashSet::new(); + for (entity, _turn) in (&entities, &turns).join() { + entity_turns.insert(entity); + } + + // Find status effects affecting entities whose turn it is let mut not_my_turn: Vec = Vec::new(); - let mut not_confused: Vec = Vec::new(); - - for (entity, _turn, confused) in (&entities, &mut turns, &mut confusion).join() { - confused.turns -= 1; - - if confused.turns < 1 { - not_confused.push(entity) - } else { - not_my_turn.push(entity); + for (effect_entity, status_effect) in (&entities, &statuses).join() { + if entity_turns.contains(&status_effect.target) { + // Skip turn for confusion + if confusion.get(effect_entity).is_some() { + add_effect( + None, + EffectType::Particle { + glyph: ::rltk::to_cp437('?'), + fg: colors::CYAN, + bg: colors::BLACK, + lifespan: 200.0, + }, + Targets::Single { + target: status_effect.target, + }, + ); + not_my_turn.push(status_effect.target); + } } } for e in not_my_turn { turns.remove(e); } - - for e in not_confused { - confusion.remove(e); - } } } diff --git a/src/components.rs b/src/components.rs index f75e38c..40effcd 100644 --- a/src/components.rs +++ b/src/components.rs @@ -106,11 +106,6 @@ pub struct AreaOfEffect { pub radius: i32, } -#[derive(Component, Debug, ConvertSaveload, Clone)] -pub struct Confusion { - pub turns: i32, -} - #[derive(Component, Debug, ConvertSaveload, Clone)] pub struct ProvidesHealing { pub heal_amount: i32, @@ -390,3 +385,19 @@ pub struct AttributeBonus { pub quickness: Option, pub intelligence: Option, } + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Consumable { + pub max_charges: i32, + pub charges: i32, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Duration { + pub turns: i32, +} + +#[derive(Component, Debug, ConvertSaveload, Clone)] +pub struct StatusEffect { + pub target: Entity, +} diff --git a/src/components/tags.rs b/src/components/tags.rs index 500574e..6f0eebd 100644 --- a/src/components/tags.rs +++ b/src/components/tags.rs @@ -7,9 +7,6 @@ use ::specs_derive::*; #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Player {} -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Consumable {} - #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct BlocksTile {} @@ -51,3 +48,6 @@ pub struct ProvidesRemoveCurse {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct ProvidesIdentification {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Confusion {} diff --git a/src/effects/damage.rs b/src/effects/damage.rs index de521bd..9093a09 100644 --- a/src/effects/damage.rs +++ b/src/effects/damage.rs @@ -1,10 +1,11 @@ use ::rltk::Point; use ::specs::prelude::*; +use ::specs::saveload::{MarkedBuilder, SimpleMarker}; use super::{add_effect, entity_position, EffectSpawner, EffectType, Targets}; -use crate::components::{Attributes, Confusion, Player, Pools}; +use crate::components::{Attributes, Confusion, Duration, Name, Player, Pools, SerializeMe}; use crate::gamesystem::{mana_at_level, player_hp_at_level}; -use crate::{colors, GameLog, Map}; +use crate::{colors, GameLog, Map, StatusEffect}; pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) { let mut pools = ecs.write_storage::(); @@ -130,8 +131,12 @@ pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) { pub fn add_confusion(ecs: &mut World, effect: &EffectSpawner, target: Entity) { if let EffectType::Confusion { turns } = &effect.effect_type { - ecs.write_storage::() - .insert(target, Confusion { turns: *turns }) - .expect("Unable to make target confused"); + ecs.create_entity() + .with(StatusEffect { target }) + .with(Confusion {}) + .with(Duration { turns: *turns }) + .with(Name::from("Confusion")) + .marked::>() + .build(); } } diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index 3f398d4..2968e38 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -2,7 +2,7 @@ use ::specs::prelude::*; use super::{add_effect, EffectType, Targets}; use crate::components::{ - Confusion, Consumable, Hidden, InflictsDamage, MagicMapper, Name, ProvidesFood, + Confusion, Consumable, Duration, Hidden, InflictsDamage, MagicMapper, Name, ProvidesFood, ProvidesHealing, ProvidesRemoveCurse, SingleActivation, SpawnParticleBurst, SpawnParticleLine, TeleportTo, TownPortal, }; @@ -10,14 +10,34 @@ use crate::effects::{entity_position, targeting}; use crate::{colors, GameLog, Map, RunState}; pub fn item_trigger(creator: Option, item: Entity, targets: &Targets, ecs: &mut World) { + // Check charges + if let Some(c) = ecs.write_storage::().get_mut(item) { + if c.charges < 1 { + // Cancel + let mut gamelog = ecs.fetch_mut::(); + gamelog.append(format!( + "{} is out of charges!", + ecs.read_storage::().get(item).unwrap().name + )); + + return; + } else { + c.charges -= 1; + } + } + // Use the item via the generic system let did_something = event_trigger(creator, item, targets, ecs); // If it was a consumable, then it gets deleted - if did_something && ecs.read_storage::().get(item).is_some() { - ecs.entities() - .delete(item) - .expect("Failed to delete consumable item"); + if did_something { + if let Some(c) = ecs.read_storage::().get(item) { + if c.max_charges == 0 { + ecs.entities() + .delete(item) + .expect("Failed to delete consumable item"); + } + } } } @@ -159,16 +179,18 @@ fn event_trigger( } // Confusion - if let Some(confusion) = ecs.read_storage::().get(entity) { - add_effect( - creator, - EffectType::Confusion { - turns: confusion.turns, - }, - targets.clone(), - ); + if ecs.read_storage::().get(entity).is_some() { + if let Some(duration) = ecs.read_storage::().get(entity) { + add_effect( + creator, + EffectType::Confusion { + turns: duration.turns, + }, + targets.clone(), + ); - did_something = true; + did_something = true; + } } // Teleport diff --git a/src/gui.rs b/src/gui.rs index d93a2d4..585996f 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -11,8 +11,8 @@ pub use menu::*; use tooltip::draw_tooltips; use crate::components::{ - Attribute, Attributes, Consumable, CursedItem, Equipped, HungerClock, HungerState, InBackpack, - MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, Viewshed, + Attribute, Attributes, Consumable, CursedItem, Duration, Equipped, HungerClock, HungerState, + InBackpack, MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, StatusEffect, Viewshed, }; use crate::game_log::GameLog; use crate::{camera, colors, Map, MasterDungeonMap, State}; @@ -45,7 +45,15 @@ pub fn get_item_display_name(ecs: &World, item: Entity) -> String { if ecs.read_storage::().get(item).is_some() { let dm = ecs.fetch::(); if dm.identified_items.contains(&name.name) { - name.name.clone() + if let Some(c) = ecs.read_storage::().get(item) { + if c.max_charges > 1 { + format!("{} ({})", name.name.clone(), c.charges) + } else { + name.name.clone() + } + } else { + name.name.clone() + } } else if let Some(obfuscated) = ecs.read_storage::().get(item) { obfuscated.name.clone() } else { @@ -236,6 +244,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { } // Status + y = 44; let hunger = ecs.read_storage::(); let hc = hunger.get(*player_entity).unwrap(); match hc.state { @@ -244,6 +253,21 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { HungerState::Hungry => ctx.print_color(50, 44, colors::ORANGE, colors::BLACK, "Hungry"), HungerState::Starving => ctx.print_color(50, 44, colors::RED, colors::BLACK, "Starving"), } + let statuses = ecs.read_storage::(); + let durations = ecs.read_storage::(); + let names = ecs.read_storage::(); + for (status, duration, name) in (&statuses, &durations, &names).join() { + if status.target == *player_entity { + ctx.print_color( + 50, + y, + colors::RED, + colors::BLACK, + &format!("{} ({})", name.name, duration.turns), + ); + y -= 1; + } + } // Draw the log let log = ecs.fetch::(); diff --git a/src/gui/tooltip.rs b/src/gui/tooltip.rs index 45cf9cc..273d895 100644 --- a/src/gui/tooltip.rs +++ b/src/gui/tooltip.rs @@ -1,7 +1,7 @@ use ::rltk::Rltk; use ::specs::prelude::*; -use crate::components::{Attributes, Hidden, Pools, Position}; +use crate::components::{Attributes, Duration, Hidden, Name, Pools, Position, StatusEffect}; use crate::{camera, colors, Map}; struct Tooltip { @@ -126,6 +126,16 @@ pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { tip.add(format!("Level: {}", stat.level)); } + // Status effects + let statuses = ecs.read_storage::(); + let durations = ecs.read_storage::(); + let names = ecs.read_storage::(); + for (status, duration, name) in (&statuses, &durations, &names).join() { + if status.target == entity { + tip.add(format!("{} ({})", name.name, duration.turns)); + } + } + tip_boxes.push(tip); } } diff --git a/src/main.rs b/src/main.rs index 9864be2..e7add3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,6 +94,7 @@ fn init_state() -> State { CursedItem, Door, DMSerializationHelper, + Duration, EntityMoved, EntryTrigger, Equippable, @@ -135,6 +136,7 @@ fn init_state() -> State { Skills, SpawnParticleBurst, SpawnParticleLine, + StatusEffect, TeleportTo, TownPortal, Vendor, diff --git a/src/raws/item_structs.rs b/src/raws/item_structs.rs index ffcb118..45ae687 100644 --- a/src/raws/item_structs.rs +++ b/src/raws/item_structs.rs @@ -28,6 +28,7 @@ pub struct Renderable { #[derive(Deserialize, Debug)] pub struct Consumable { pub effects: HashMap, + pub charges: Option, } #[derive(Deserialize, Debug)] diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 2edcc81..7f4e09c 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -294,27 +294,28 @@ macro_rules! apply_effects { "provides_healing" => { $eb = $eb.with(ProvidesHealing { heal_amount: effect.1.parse::().unwrap(), - }) + }); } "ranged" => { $eb = $eb.with(Ranged { range: effect.1.parse::().unwrap(), - }) + }); } "damage" => { $eb = $eb.with(InflictsDamage { damage: effect.1.parse::().unwrap(), - }) + }); } "area_of_effect" => { $eb = $eb.with(AreaOfEffect { radius: effect.1.parse::().unwrap(), - }) + }); } "confusion" => { - $eb = $eb.with(Confusion { + $eb = $eb.with(Confusion {}); + $eb = $eb.with(Duration { turns: effect.1.parse::().unwrap(), - }) + }); } "magic_mapping" => $eb = $eb.with(MagicMapper {}), "town_portal" => $eb = $eb.with(TownPortal {}), @@ -368,7 +369,11 @@ pub fn spawn_named_item( }); if let Some(consumable) = &item_template.consumable { - eb = eb.with(Consumable {}); + let max_charges = consumable.charges.unwrap_or(1); + eb = eb.with(Consumable { + max_charges, + charges: max_charges, + }); apply_effects!(consumable.effects, eb); } diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 5b3b0ab..090d622 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -73,6 +73,7 @@ pub fn save_game(ecs: &mut World) { CursedItem, Door, DMSerializationHelper, + Duration, EntityMoved, EntryTrigger, Equippable, @@ -113,6 +114,7 @@ pub fn save_game(ecs: &mut World) { Skills, SpawnParticleBurst, SpawnParticleLine, + StatusEffect, TeleportTo, TownPortal, Vendor, @@ -194,6 +196,7 @@ pub fn load_game(ecs: &mut World) { CursedItem, Door, DMSerializationHelper, + Duration, EntityMoved, EntryTrigger, Equippable, @@ -234,6 +237,7 @@ pub fn load_game(ecs: &mut World) { Skills, SpawnParticleBurst, SpawnParticleLine, + StatusEffect, TeleportTo, TownPortal, Vendor,