Refactor confusion effect, and update gui to show confusion status

This commit is contained in:
Timothy Warren 2022-01-24 10:58:37 -05:00
parent af46a7631b
commit ee5db23f6b
12 changed files with 178 additions and 56 deletions

View File

@ -1,7 +1,9 @@
use ::rltk::{DistanceAlg, Point, RandomNumberGenerator}; use ::rltk::{DistanceAlg, Point, RandomNumberGenerator};
use ::specs::prelude::*; 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; use crate::RunState;
pub struct InitiativeSystem {} pub struct InitiativeSystem {}
@ -19,6 +21,9 @@ impl<'a> System<'a> for InitiativeSystem {
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
ReadExpect<'a, Point>, ReadExpect<'a, Point>,
ReadStorage<'a, Pools>, ReadStorage<'a, Pools>,
WriteStorage<'a, Duration>,
WriteStorage<'a, EquipmentChanged>,
ReadStorage<'a, StatusEffect>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -33,6 +38,9 @@ impl<'a> System<'a> for InitiativeSystem {
player, player,
player_pos, player_pos,
pools, pools,
mut durations,
mut dirty,
statuses,
) = data; ) = data;
if *runstate != RunState::Ticking { 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");
}
}
}
} }
} }

View File

@ -1,7 +1,10 @@
use std::collections::HashSet;
use ::specs::prelude::*; use ::specs::prelude::*;
use crate::components::{Confusion, MyTurn}; use crate::components::{Confusion, MyTurn, StatusEffect};
use crate::RunState; use crate::effects::{add_effect, EffectType, Targets};
use crate::{colors, RunState};
pub struct TurnStatusSystem {} pub struct TurnStatusSystem {}
@ -9,37 +12,50 @@ impl<'a> System<'a> for TurnStatusSystem {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
WriteStorage<'a, MyTurn>, WriteStorage<'a, MyTurn>,
WriteStorage<'a, Confusion>, ReadStorage<'a, Confusion>,
Entities<'a>, Entities<'a>,
ReadExpect<'a, RunState>, ReadExpect<'a, RunState>,
ReadStorage<'a, StatusEffect>,
); );
fn run(&mut self, data: Self::SystemData) { 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 { if *runstate != RunState::Ticking {
return; 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<Entity> = Vec::new(); let mut not_my_turn: Vec<Entity> = Vec::new();
let mut not_confused: Vec<Entity> = Vec::new(); for (effect_entity, status_effect) in (&entities, &statuses).join() {
if entity_turns.contains(&status_effect.target) {
for (entity, _turn, confused) in (&entities, &mut turns, &mut confusion).join() { // Skip turn for confusion
confused.turns -= 1; if confusion.get(effect_entity).is_some() {
add_effect(
if confused.turns < 1 { None,
not_confused.push(entity) EffectType::Particle {
} else { glyph: ::rltk::to_cp437('?'),
not_my_turn.push(entity); 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 { for e in not_my_turn {
turns.remove(e); turns.remove(e);
} }
for e in not_confused {
confusion.remove(e);
}
} }
} }

View File

@ -106,11 +106,6 @@ pub struct AreaOfEffect {
pub radius: i32, pub radius: i32,
} }
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Confusion {
pub turns: i32,
}
#[derive(Component, Debug, ConvertSaveload, Clone)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct ProvidesHealing { pub struct ProvidesHealing {
pub heal_amount: i32, pub heal_amount: i32,
@ -390,3 +385,19 @@ pub struct AttributeBonus {
pub quickness: Option<i32>, pub quickness: Option<i32>,
pub intelligence: Option<i32>, pub intelligence: Option<i32>,
} }
#[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,
}

View File

@ -7,9 +7,6 @@ use ::specs_derive::*;
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Player {} pub struct Player {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Consumable {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct BlocksTile {} pub struct BlocksTile {}
@ -51,3 +48,6 @@ pub struct ProvidesRemoveCurse {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ProvidesIdentification {} pub struct ProvidesIdentification {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Confusion {}

View File

@ -1,10 +1,11 @@
use ::rltk::Point; use ::rltk::Point;
use ::specs::prelude::*; use ::specs::prelude::*;
use ::specs::saveload::{MarkedBuilder, SimpleMarker};
use super::{add_effect, entity_position, EffectSpawner, EffectType, Targets}; 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::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) { pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
let mut pools = ecs.write_storage::<Pools>(); let mut pools = ecs.write_storage::<Pools>();
@ -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) { pub fn add_confusion(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
if let EffectType::Confusion { turns } = &effect.effect_type { if let EffectType::Confusion { turns } = &effect.effect_type {
ecs.write_storage::<Confusion>() ecs.create_entity()
.insert(target, Confusion { turns: *turns }) .with(StatusEffect { target })
.expect("Unable to make target confused"); .with(Confusion {})
.with(Duration { turns: *turns })
.with(Name::from("Confusion"))
.marked::<SimpleMarker<SerializeMe>>()
.build();
} }
} }

View File

@ -2,7 +2,7 @@ use ::specs::prelude::*;
use super::{add_effect, EffectType, Targets}; use super::{add_effect, EffectType, Targets};
use crate::components::{ use crate::components::{
Confusion, Consumable, Hidden, InflictsDamage, MagicMapper, Name, ProvidesFood, Confusion, Consumable, Duration, Hidden, InflictsDamage, MagicMapper, Name, ProvidesFood,
ProvidesHealing, ProvidesRemoveCurse, SingleActivation, SpawnParticleBurst, SpawnParticleLine, ProvidesHealing, ProvidesRemoveCurse, SingleActivation, SpawnParticleBurst, SpawnParticleLine,
TeleportTo, TownPortal, TeleportTo, TownPortal,
}; };
@ -10,15 +10,35 @@ use crate::effects::{entity_position, targeting};
use crate::{colors, GameLog, Map, RunState}; use crate::{colors, GameLog, Map, RunState};
pub fn item_trigger(creator: Option<Entity>, item: Entity, targets: &Targets, ecs: &mut World) { pub fn item_trigger(creator: Option<Entity>, item: Entity, targets: &Targets, ecs: &mut World) {
// Check charges
if let Some(c) = ecs.write_storage::<Consumable>().get_mut(item) {
if c.charges < 1 {
// Cancel
let mut gamelog = ecs.fetch_mut::<GameLog>();
gamelog.append(format!(
"{} is out of charges!",
ecs.read_storage::<Name>().get(item).unwrap().name
));
return;
} else {
c.charges -= 1;
}
}
// Use the item via the generic system // Use the item via the generic system
let did_something = event_trigger(creator, item, targets, ecs); let did_something = event_trigger(creator, item, targets, ecs);
// If it was a consumable, then it gets deleted // If it was a consumable, then it gets deleted
if did_something && ecs.read_storage::<Consumable>().get(item).is_some() { if did_something {
if let Some(c) = ecs.read_storage::<Consumable>().get(item) {
if c.max_charges == 0 {
ecs.entities() ecs.entities()
.delete(item) .delete(item)
.expect("Failed to delete consumable item"); .expect("Failed to delete consumable item");
} }
}
}
} }
pub fn trigger(creator: Option<Entity>, trigger: Entity, targets: &Targets, ecs: &mut World) { pub fn trigger(creator: Option<Entity>, trigger: Entity, targets: &Targets, ecs: &mut World) {
@ -159,17 +179,19 @@ fn event_trigger(
} }
// Confusion // Confusion
if let Some(confusion) = ecs.read_storage::<Confusion>().get(entity) { if ecs.read_storage::<Confusion>().get(entity).is_some() {
if let Some(duration) = ecs.read_storage::<Duration>().get(entity) {
add_effect( add_effect(
creator, creator,
EffectType::Confusion { EffectType::Confusion {
turns: confusion.turns, turns: duration.turns,
}, },
targets.clone(), targets.clone(),
); );
did_something = true; did_something = true;
} }
}
// Teleport // Teleport
if let Some(teleport) = ecs.read_storage::<TeleportTo>().get(entity) { if let Some(teleport) = ecs.read_storage::<TeleportTo>().get(entity) {

View File

@ -11,8 +11,8 @@ pub use menu::*;
use tooltip::draw_tooltips; use tooltip::draw_tooltips;
use crate::components::{ use crate::components::{
Attribute, Attributes, Consumable, CursedItem, Equipped, HungerClock, HungerState, InBackpack, Attribute, Attributes, Consumable, CursedItem, Duration, Equipped, HungerClock, HungerState,
MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, Viewshed, InBackpack, MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, StatusEffect, Viewshed,
}; };
use crate::game_log::GameLog; use crate::game_log::GameLog;
use crate::{camera, colors, Map, MasterDungeonMap, State}; 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::<MagicItem>().get(item).is_some() { if ecs.read_storage::<MagicItem>().get(item).is_some() {
let dm = ecs.fetch::<MasterDungeonMap>(); let dm = ecs.fetch::<MasterDungeonMap>();
if dm.identified_items.contains(&name.name) { if dm.identified_items.contains(&name.name) {
if let Some(c) = ecs.read_storage::<Consumable>().get(item) {
if c.max_charges > 1 {
format!("{} ({})", name.name.clone(), c.charges)
} else {
name.name.clone() name.name.clone()
}
} else {
name.name.clone()
}
} else if let Some(obfuscated) = ecs.read_storage::<ObfuscatedName>().get(item) { } else if let Some(obfuscated) = ecs.read_storage::<ObfuscatedName>().get(item) {
obfuscated.name.clone() obfuscated.name.clone()
} else { } else {
@ -236,6 +244,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
} }
// Status // Status
y = 44;
let hunger = ecs.read_storage::<HungerClock>(); let hunger = ecs.read_storage::<HungerClock>();
let hc = hunger.get(*player_entity).unwrap(); let hc = hunger.get(*player_entity).unwrap();
match hc.state { 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::Hungry => ctx.print_color(50, 44, colors::ORANGE, colors::BLACK, "Hungry"),
HungerState::Starving => ctx.print_color(50, 44, colors::RED, colors::BLACK, "Starving"), HungerState::Starving => ctx.print_color(50, 44, colors::RED, colors::BLACK, "Starving"),
} }
let statuses = ecs.read_storage::<StatusEffect>();
let durations = ecs.read_storage::<Duration>();
let names = ecs.read_storage::<Name>();
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 // Draw the log
let log = ecs.fetch::<GameLog>(); let log = ecs.fetch::<GameLog>();

View File

@ -1,7 +1,7 @@
use ::rltk::Rltk; use ::rltk::Rltk;
use ::specs::prelude::*; 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}; use crate::{camera, colors, Map};
struct Tooltip { struct Tooltip {
@ -126,6 +126,16 @@ pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
tip.add(format!("Level: {}", stat.level)); tip.add(format!("Level: {}", stat.level));
} }
// Status effects
let statuses = ecs.read_storage::<StatusEffect>();
let durations = ecs.read_storage::<Duration>();
let names = ecs.read_storage::<Name>();
for (status, duration, name) in (&statuses, &durations, &names).join() {
if status.target == entity {
tip.add(format!("{} ({})", name.name, duration.turns));
}
}
tip_boxes.push(tip); tip_boxes.push(tip);
} }
} }

View File

@ -94,6 +94,7 @@ fn init_state() -> State {
CursedItem, CursedItem,
Door, Door,
DMSerializationHelper, DMSerializationHelper,
Duration,
EntityMoved, EntityMoved,
EntryTrigger, EntryTrigger,
Equippable, Equippable,
@ -135,6 +136,7 @@ fn init_state() -> State {
Skills, Skills,
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
StatusEffect,
TeleportTo, TeleportTo,
TownPortal, TownPortal,
Vendor, Vendor,

View File

@ -28,6 +28,7 @@ pub struct Renderable {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Consumable { pub struct Consumable {
pub effects: HashMap<String, String>, pub effects: HashMap<String, String>,
pub charges: Option<i32>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]

View File

@ -294,27 +294,28 @@ macro_rules! apply_effects {
"provides_healing" => { "provides_healing" => {
$eb = $eb.with(ProvidesHealing { $eb = $eb.with(ProvidesHealing {
heal_amount: effect.1.parse::<i32>().unwrap(), heal_amount: effect.1.parse::<i32>().unwrap(),
}) });
} }
"ranged" => { "ranged" => {
$eb = $eb.with(Ranged { $eb = $eb.with(Ranged {
range: effect.1.parse::<i32>().unwrap(), range: effect.1.parse::<i32>().unwrap(),
}) });
} }
"damage" => { "damage" => {
$eb = $eb.with(InflictsDamage { $eb = $eb.with(InflictsDamage {
damage: effect.1.parse::<i32>().unwrap(), damage: effect.1.parse::<i32>().unwrap(),
}) });
} }
"area_of_effect" => { "area_of_effect" => {
$eb = $eb.with(AreaOfEffect { $eb = $eb.with(AreaOfEffect {
radius: effect.1.parse::<i32>().unwrap(), radius: effect.1.parse::<i32>().unwrap(),
}) });
} }
"confusion" => { "confusion" => {
$eb = $eb.with(Confusion { $eb = $eb.with(Confusion {});
$eb = $eb.with(Duration {
turns: effect.1.parse::<i32>().unwrap(), turns: effect.1.parse::<i32>().unwrap(),
}) });
} }
"magic_mapping" => $eb = $eb.with(MagicMapper {}), "magic_mapping" => $eb = $eb.with(MagicMapper {}),
"town_portal" => $eb = $eb.with(TownPortal {}), "town_portal" => $eb = $eb.with(TownPortal {}),
@ -368,7 +369,11 @@ pub fn spawn_named_item(
}); });
if let Some(consumable) = &item_template.consumable { 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); apply_effects!(consumable.effects, eb);
} }

View File

@ -73,6 +73,7 @@ pub fn save_game(ecs: &mut World) {
CursedItem, CursedItem,
Door, Door,
DMSerializationHelper, DMSerializationHelper,
Duration,
EntityMoved, EntityMoved,
EntryTrigger, EntryTrigger,
Equippable, Equippable,
@ -113,6 +114,7 @@ pub fn save_game(ecs: &mut World) {
Skills, Skills,
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
StatusEffect,
TeleportTo, TeleportTo,
TownPortal, TownPortal,
Vendor, Vendor,
@ -194,6 +196,7 @@ pub fn load_game(ecs: &mut World) {
CursedItem, CursedItem,
Door, Door,
DMSerializationHelper, DMSerializationHelper,
Duration,
EntityMoved, EntityMoved,
EntryTrigger, EntryTrigger,
Equippable, Equippable,
@ -234,6 +237,7 @@ pub fn load_game(ecs: &mut World) {
Skills, Skills,
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
StatusEffect,
TeleportTo, TeleportTo,
TownPortal, TownPortal,
Vendor, Vendor,