From 911873127db63d286c90d52938cab4139bcfe182 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Thu, 17 Aug 2023 03:12:42 +0100 Subject: [PATCH] atomising item use: damage and healing, and refactors --- src/effects/damage.rs | 20 +++++ src/effects/mod.rs | 3 + src/effects/triggers.rs | 113 ++++++++++++++++++++++++----- src/gui/mod.rs | 12 +-- src/gui/tooltip.rs | 2 +- src/inventory/collection_system.rs | 4 +- src/inventory/drop_system.rs | 4 +- src/inventory/equip_system.rs | 15 +--- src/inventory/remove_system.rs | 6 +- src/main.rs | 2 +- src/player.rs | 8 +- src/spawner.rs | 35 +++++++++ 12 files changed, 176 insertions(+), 48 deletions(-) diff --git a/src/effects/damage.rs b/src/effects/damage.rs index 600ac9e..1b0b1c4 100644 --- a/src/effects/damage.rs +++ b/src/effects/damage.rs @@ -33,6 +33,26 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) { } } +pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) { + let mut pools = ecs.write_storage::(); + if let Some(pool) = pools.get_mut(target) { + if let EffectType::Healing { amount } = heal.effect_type { + pool.hit_points.current = i32::min(pool.hit_points.max, pool.hit_points.current + amount); + add_effect( + None, + EffectType::Particle { + glyph: to_cp437('♥'), + fg: RGB::named(BLUE), + bg: RGB::named(BLACK), + lifespan: DEFAULT_PARTICLE_LIFETIME, + delay: 0.0, + }, + Targets::Entity { target }, + ); + } + } +} + pub fn bloodstain(ecs: &mut World, target: usize) { let mut map = ecs.fetch_mut::(); // If the current tile isn't bloody, bloody it. diff --git a/src/effects/mod.rs b/src/effects/mod.rs index 5659819..51e9fd8 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -19,6 +19,7 @@ lazy_static! { pub enum EffectType { Damage { amount: i32 }, + Healing { amount: i32 }, Bloodstain, Particle { glyph: FontCharType, fg: RGB, bg: RGB, lifespan: f32, delay: f32 }, EntityDeath, @@ -95,6 +96,7 @@ fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) { fn tile_effect_hits_entities(effect: &EffectType) -> bool { match effect { EffectType::Damage { .. } => true, + EffectType::Healing { .. } => true, EffectType::RestoreNutrition { .. } => true, _ => false, } @@ -104,6 +106,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool { fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { match &effect.effect_type { EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target), + EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target), EffectType::Bloodstain { .. } => { if let Some(pos) = targeting::entity_position(ecs, target) { damage::bloodstain(ecs, pos) diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index 17b1663..d53e2ed 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -1,5 +1,9 @@ use super::{add_effect, EffectType, Entity, Targets, World}; -use crate::{gamelog, gui::item_colour_ecs, gui::obfuscate_name, Consumable, Cursed, ProvidesNutrition}; +use crate::{ + gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, Consumable, Cursed, InflictsDamage, MagicMapper, + ProvidesHealing, ProvidesNutrition, RandomNumberGenerator, RunState, +}; +use rltk::prelude::*; use specs::prelude::*; pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: &mut World) { @@ -15,24 +19,99 @@ pub const BLESSED: i32 = 2; pub const UNCURSED: i32 = 1; pub const CURSED: i32 = 0; +struct EventInfo { + source: Option, + entity: Entity, + target: Targets, + buc: i32, + log: bool, +} + +// TODO: Currently, items can only be used by the player, and so this system is only built for that. +// It does almost no sanity-checking to make sure the logs only appear if the effect is taking +// place on the player -- once monsters can use an item, their item usage will make logs for +// the player saying they were the one who used the item. This will need refactoring then. fn event_trigger(source: Option, entity: Entity, target: &Targets, ecs: &mut World) { - let mut logger = gamelog::Logger::new(); - let mut log = false; - // Check BUC -- - // TODO: Replace this with a system checking for blessed, uncursed, or cursed. let buc = if ecs.read_storage::().get(entity).is_some() { CURSED } else { UNCURSED }; - // Providing nutrition - if ecs.read_storage::().get(entity).is_some() { - add_effect(source, EffectType::RestoreNutrition { buc }, target.clone()); - logger = logger - .append("You eat the") - .append_n(obfuscate_name(ecs, entity).0) - .colour(item_colour_ecs(ecs, entity)) - .period() - .buc(buc, Some("Blech! Rotten."), Some("Delicious.")); - log = true; - } - if log { + let mut event = EventInfo { source, entity, target: target.clone(), buc, log: false }; + let mut logger = gamelog::Logger::new(); + // PROVIDES NUTRITION + logger = handle_restore_nutrition(ecs, &mut event, logger); + // MAGIC MAPPER + logger = handle_magic_mapper(ecs, &mut event, logger); + // DOES HEALING + logger = handle_healing(ecs, &mut event, logger); + // DOES DAMAGE + logger = handle_damage(ecs, &mut event, logger); + if event.log { logger.log(); } } + +fn handle_restore_nutrition(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger) -> gamelog::Logger { + if ecs.read_storage::().get(event.entity).is_some() { + add_effect(event.source, EffectType::RestoreNutrition { buc: event.buc }, event.target.clone()); + logger = logger + .append("You eat the") + .colour(item_colour_ecs(ecs, event.entity)) + .append_n(obfuscate_name_ecs(ecs, event.entity).0) + .colour(WHITE) + .period() + .buc(event.buc, Some("Blech! Rotten."), Some("Delicious.")); + event.log = true; + } + return logger; +} + +fn handle_magic_mapper(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger) -> gamelog::Logger { + if ecs.read_storage::().get(event.entity).is_some() { + let mut runstate = ecs.fetch_mut::(); + let cursed = if event.buc == CURSED { true } else { false }; + *runstate = RunState::MagicMapReveal { row: 0, cursed: cursed }; + logger = logger.append("You recall your surroundings!").buc( + event.buc, + Some("... but forget where you last were."), + None, + ); + event.log = true; + } + return logger; +} + +fn handle_healing(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger) -> gamelog::Logger { + if let Some(healing_item) = ecs.read_storage::().get(event.entity) { + let mut rng = ecs.write_resource::(); + let roll = rng.roll_dice(healing_item.n_dice, healing_item.sides) + healing_item.modifier; + add_effect(event.source, EffectType::Healing { amount: roll }, event.target.clone()); + logger = logger.append("You recover some vigour."); + event.log = true; + } + return logger; +} + +fn handle_damage(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger) -> gamelog::Logger { + if let Some(damage_item) = ecs.read_storage::().get(event.entity) { + let mut rng = ecs.write_resource::(); + let roll = rng.roll_dice(damage_item.n_dice, damage_item.sides) + damage_item.modifier; + add_effect(event.source, EffectType::Damage { amount: roll }, event.target.clone()); + for target in get_entity_targets(&event.target) { + logger = logger + .append("The") + .colour(item_colour_ecs(ecs, target)) + .append(obfuscate_name_ecs(ecs, target).0) + .append("is hit!"); + event.log = true; + } + } + return logger; +} + +fn get_entity_targets(target: &Targets) -> Vec { + let mut entities: Vec = Vec::new(); + match target { + Targets::Entity { target } => entities.push(*target), + Targets::EntityList { targets } => targets.iter().for_each(|target| entities.push(*target)), + _ => {} + } + return entities; +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 1f36562..832139e 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -151,7 +151,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { (&entities, &equipped, &renderables).join().filter(|item| item.1.owner == *player_entity) { equipment.push(( - obfuscate_name(ecs, entity).0, + obfuscate_name_ecs(ecs, entity).0, RGB::named(item_colour_ecs(ecs, entity)), renderable.fg, renderable.glyph, @@ -223,7 +223,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { (RGB::named(rltk::WHITE), rltk::to_cp437('-')) }; seen_entities.push(( - obfuscate_name(ecs, entity).0, + obfuscate_name_ecs(ecs, entity).0, RGB::named(item_colour_ecs(ecs, entity)), render_fg, glyph, @@ -387,7 +387,7 @@ pub fn get_max_inventory_width(inventory: &BTreeMap) - } // Inside the ECS -pub fn obfuscate_name_ecs( +pub fn obfuscate_name( item: Entity, names: &ReadStorage, magic_items: &ReadStorage, @@ -423,7 +423,7 @@ pub fn obfuscate_name_ecs( } // Outside the ECS -pub fn obfuscate_name(ecs: &World, item: Entity) -> (String, String) { +pub fn obfuscate_name_ecs(ecs: &World, item: Entity) -> (String, String) { let (mut singular, mut plural) = ("nameless item (bug)".to_string(), "nameless items (bug)".to_string()); if let Some(name) = ecs.read_storage::().get(item) { if ecs.read_storage::().get(item).is_some() { @@ -577,7 +577,7 @@ pub fn get_player_inventory(ecs: &World) -> (BTreeMap, let item_colour = item_colour_ecs(ecs, entity); let renderables = ((renderable.fg.r * 255.0) as u8, (renderable.fg.g * 255.0) as u8, (renderable.fg.b * 255.0) as u8); - let (singular, plural) = obfuscate_name(ecs, entity); + let (singular, plural) = obfuscate_name_ecs(ecs, entity); player_inventory .entry(UniqueInventoryItem { display_name: DisplayName { singular: singular.clone(), plural: plural }, @@ -684,7 +684,7 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti let mut equippable: Vec<(Entity, String)> = Vec::new(); let mut width = 2; for (entity, _pack) in (&entities, &backpack).join().filter(|item| item.1.owner == *player_entity) { - let this_name = &obfuscate_name(&gs.ecs, entity).0; + let this_name = &obfuscate_name_ecs(&gs.ecs, entity).0; let this_width = 5 + this_name.len(); width = if width > this_width { width } else { this_width }; equippable.push((entity, this_name.to_string())); diff --git a/src/gui/tooltip.rs b/src/gui/tooltip.rs index 1416956..70d0be3 100644 --- a/src/gui/tooltip.rs +++ b/src/gui/tooltip.rs @@ -72,7 +72,7 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { for (entity, position, renderable, _hidden) in (&entities, &positions, &renderables, !&hidden).join() { if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 { let mut tip = Tooltip::new(); - tip.add(crate::gui::obfuscate_name(ecs, entity).0, renderable.fg); + tip.add(crate::gui::obfuscate_name_ecs(ecs, entity).0, renderable.fg); // Attributes let attr = attributes.get(entity); if let Some(a) = attr { diff --git a/src/inventory/collection_system.rs b/src/inventory/collection_system.rs index df38ab4..3c54c35 100644 --- a/src/inventory/collection_system.rs +++ b/src/inventory/collection_system.rs @@ -2,7 +2,7 @@ use super::{ gamelog, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, Position, Wand, WantsToPickupItem, }; -use crate::gui::obfuscate_name_ecs; +use crate::gui::obfuscate_name; use specs::prelude::*; pub struct ItemCollectionSystem {} @@ -48,7 +48,7 @@ impl<'a> System<'a> for ItemCollectionSystem { .append("You pick up the") .item_name_n(format!( "{}", - obfuscate_name_ecs(pickup.item, &names, &magic_items, &obfuscated_names, &dm, Some(&wands)).0 + obfuscate_name(pickup.item, &names, &magic_items, &obfuscated_names, &dm, Some(&wands)).0 )) .period() .log(); diff --git a/src/inventory/drop_system.rs b/src/inventory/drop_system.rs index 417a1a4..1f23442 100644 --- a/src/inventory/drop_system.rs +++ b/src/inventory/drop_system.rs @@ -2,7 +2,7 @@ use super::{ gamelog, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, Position, Wand, WantsToDropItem, }; -use crate::gui::obfuscate_name_ecs; +use crate::gui::obfuscate_name; use specs::prelude::*; pub struct ItemDropSystem {} @@ -56,7 +56,7 @@ impl<'a> System<'a> for ItemDropSystem { .append("You drop the") .item_name_n(format!( "{}", - obfuscate_name_ecs(to_drop.item, &names, &magic_items, &obfuscated_names, &dm, Some(&wands)).0 + obfuscate_name(to_drop.item, &names, &magic_items, &obfuscated_names, &dm, Some(&wands)).0 )) .period() .log(); diff --git a/src/inventory/equip_system.rs b/src/inventory/equip_system.rs index f956d9e..8f22342 100644 --- a/src/inventory/equip_system.rs +++ b/src/inventory/equip_system.rs @@ -2,7 +2,7 @@ use super::{ gamelog, EquipmentChanged, Equippable, Equipped, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, WantsToUseItem, }; -use crate::gui::{item_colour, obfuscate_name_ecs}; +use crate::gui::{item_colour, obfuscate_name}; use specs::prelude::*; pub struct ItemEquipSystem {} @@ -58,7 +58,7 @@ impl<'a> System<'a> for ItemEquipSystem { if target == *player_entity { logger = logger .append("You remove your") - .append_n(obfuscate_name_ecs(*item, &names, &magic_items, &obfuscated_names, &dm, None).0) + .append_n(obfuscate_name(*item, &names, &magic_items, &obfuscated_names, &dm, None).0) .colour(item_colour(*item, &names, &magic_items, &dm)) .period(); } @@ -73,15 +73,8 @@ impl<'a> System<'a> for ItemEquipSystem { logger = logger .append("You equip the") .append_n( - obfuscate_name_ecs( - wants_to_use_item.item, - &names, - &magic_items, - &obfuscated_names, - &dm, - None, - ) - .0, + obfuscate_name(wants_to_use_item.item, &names, &magic_items, &obfuscated_names, &dm, None) + .0, ) .colour(item_colour(wants_to_use_item.item, &names, &magic_items, &dm)) .period(); diff --git a/src/inventory/remove_system.rs b/src/inventory/remove_system.rs index dcda643..9078617 100644 --- a/src/inventory/remove_system.rs +++ b/src/inventory/remove_system.rs @@ -1,5 +1,5 @@ use super::{gamelog, Equipped, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, WantsToRemoveItem}; -use crate::gui::{item_colour, obfuscate_name_ecs}; +use crate::gui::{item_colour, obfuscate_name}; use specs::prelude::*; pub struct ItemRemoveSystem {} @@ -37,9 +37,7 @@ impl<'a> System<'a> for ItemRemoveSystem { if entity == *player_entity { gamelog::Logger::new() .append("You unequip the") - .append_n( - obfuscate_name_ecs(to_remove.item, &names, &magic_items, &obfuscated_names, &dm, None).0, - ) + .append_n(obfuscate_name(to_remove.item, &names, &magic_items, &obfuscated_names, &dm, None).0) .colour(item_colour(to_remove.item, &names, &magic_items, &dm)) .period() .log(); diff --git a/src/main.rs b/src/main.rs index bffa226..7af3817 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,8 +112,8 @@ impl State { regen_system.run_now(&self.ecs); encumbrance_system.run_now(&self.ecs); energy.run_now(&self.ecs); - turn_status_system.run_now(&self.ecs); quip_system.run_now(&self.ecs); + turn_status_system.run_now(&self.ecs); self.run_ai(); trigger_system.run_now(&self.ecs); inventory_system.run_now(&self.ecs); diff --git a/src/player.rs b/src/player.rs index 246a48f..44c56d5 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,7 @@ use super::{ - gamelog, gui::obfuscate_name, raws::Reaction, Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, Faction, - Hidden, HungerClock, HungerState, Item, Map, Name, ParticleBuilder, Player, Pools, Position, Renderable, RunState, - State, SufferDamage, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, + gamelog, gui::obfuscate_name_ecs, raws::Reaction, Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, + Faction, Hidden, HungerClock, HungerState, Item, Map, Name, ParticleBuilder, Player, Pools, Position, Renderable, + RunState, State, SufferDamage, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, }; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use specs::prelude::*; @@ -356,7 +356,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState let mut some = false; crate::spatial::for_each_tile_content(destination_idx, |entity| { if !hidden.get(entity).is_some() && names.get(entity).is_some() { - let item_name = obfuscate_name(ecs, entity).0; + let item_name = obfuscate_name_ecs(ecs, entity).0; item_names.push(item_name); some = true; } diff --git a/src/spawner.rs b/src/spawner.rs index 1fee052..2b40980 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -70,6 +70,13 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { raws::SpawnType::Equipped { by: player }, 0, ); + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + "food_rations", + raws::SpawnType::Carried { by: player }, + 0, + ); raws::spawn_named_entity( &raws::RAWS.lock().unwrap(), ecs, @@ -77,6 +84,34 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { raws::SpawnType::Carried { by: player }, 0, ); + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + "food_apple", + raws::SpawnType::Carried { by: player }, + 0, + ); + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + "scroll_confusion", + raws::SpawnType::Carried { by: player }, + 0, + ); + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + "scroll_confusion", + raws::SpawnType::Carried { by: player }, + 0, + ); + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + "scroll_confusion", + raws::SpawnType::Carried { by: player }, + 0, + ); return player; }