Refactor confusion effect, and update gui to show confusion status
This commit is contained in:
parent
af46a7631b
commit
ee5db23f6b
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Entity> = Vec::new();
|
||||
let mut not_confused: Vec<Entity> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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,
|
||||
}
|
||||
|
@ -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 {}
|
||||
|
@ -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::<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) {
|
||||
if let EffectType::Confusion { turns } = &effect.effect_type {
|
||||
ecs.write_storage::<Confusion>()
|
||||
.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::<SimpleMarker<SerializeMe>>()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -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,16 +10,36 @@ use crate::effects::{entity_position, targeting};
|
||||
use crate::{colors, GameLog, Map, RunState};
|
||||
|
||||
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
|
||||
let did_something = event_trigger(creator, item, targets, ecs);
|
||||
|
||||
// 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()
|
||||
.delete(item)
|
||||
.expect("Failed to delete consumable item");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger(creator: Option<Entity>, trigger: Entity, targets: &Targets, ecs: &mut World) {
|
||||
// The triggering item is no longer hidden
|
||||
@ -159,17 +179,19 @@ fn event_trigger(
|
||||
}
|
||||
|
||||
// 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(
|
||||
creator,
|
||||
EffectType::Confusion {
|
||||
turns: confusion.turns,
|
||||
turns: duration.turns,
|
||||
},
|
||||
targets.clone(),
|
||||
);
|
||||
|
||||
did_something = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Teleport
|
||||
if let Some(teleport) = ecs.read_storage::<TeleportTo>().get(entity) {
|
||||
|
28
src/gui.rs
28
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::<MagicItem>().get(item).is_some() {
|
||||
let dm = ecs.fetch::<MasterDungeonMap>();
|
||||
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()
|
||||
}
|
||||
} else {
|
||||
name.name.clone()
|
||||
}
|
||||
} else if let Some(obfuscated) = ecs.read_storage::<ObfuscatedName>().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::<HungerClock>();
|
||||
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::<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
|
||||
let log = ecs.fetch::<GameLog>();
|
||||
|
@ -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::<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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -28,6 +28,7 @@ pub struct Renderable {
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Consumable {
|
||||
pub effects: HashMap<String, String>,
|
||||
pub charges: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -294,27 +294,28 @@ macro_rules! apply_effects {
|
||||
"provides_healing" => {
|
||||
$eb = $eb.with(ProvidesHealing {
|
||||
heal_amount: effect.1.parse::<i32>().unwrap(),
|
||||
})
|
||||
});
|
||||
}
|
||||
"ranged" => {
|
||||
$eb = $eb.with(Ranged {
|
||||
range: effect.1.parse::<i32>().unwrap(),
|
||||
})
|
||||
});
|
||||
}
|
||||
"damage" => {
|
||||
$eb = $eb.with(InflictsDamage {
|
||||
damage: effect.1.parse::<i32>().unwrap(),
|
||||
})
|
||||
});
|
||||
}
|
||||
"area_of_effect" => {
|
||||
$eb = $eb.with(AreaOfEffect {
|
||||
radius: effect.1.parse::<i32>().unwrap(),
|
||||
})
|
||||
});
|
||||
}
|
||||
"confusion" => {
|
||||
$eb = $eb.with(Confusion {
|
||||
$eb = $eb.with(Confusion {});
|
||||
$eb = $eb.with(Duration {
|
||||
turns: effect.1.parse::<i32>().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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user