diff --git a/src/effects/hunger.rs b/src/effects/hunger.rs new file mode 100644 index 0000000..ed9b731 --- /dev/null +++ b/src/effects/hunger.rs @@ -0,0 +1,12 @@ +use super::EffectSpawner; +use crate::{HungerClock, HungerState}; +use specs::prelude::*; + +const SATIATED_DURATION: i32 = 200; + +pub fn restore_food(ecs: &mut World, _damage: &EffectSpawner, target: Entity) { + if let Some(hc) = ecs.write_storage::().get_mut(target) { + hc.state = HungerState::Satiated; + hc.duration = SATIATED_DURATION; + } +} diff --git a/src/effects/mod.rs b/src/effects/mod.rs index 40ff625..a564cf6 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -5,10 +5,13 @@ use std::collections::VecDeque; use std::sync::Mutex; mod damage; +mod hunger; mod particles; mod targeting; mod triggers; +pub use targeting::aoe_tiles; + lazy_static! { pub static ref EFFECT_QUEUE: Mutex> = Mutex::new(VecDeque::new()); } @@ -19,6 +22,7 @@ pub enum EffectType { Particle { glyph: FontCharType, fg: RGB, bg: RGB, lifespan: f32, delay: f32 }, EntityDeath, ItemUse { item: Entity }, + RestoreNutrition, } #[derive(Clone)] @@ -70,14 +74,6 @@ fn target_applicator(ecs: &mut World, effect: &EffectSpawner) { } } -/// Checks if a given effect affects entities or not. -fn tile_effect_hits_entities(effect: &EffectType) -> bool { - match effect { - EffectType::Damage { .. } => true, - _ => false, - } -} - /// Runs an effect on a given tile index fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) { if tile_effect_hits_entities(&effect.effect_type) { @@ -94,6 +90,15 @@ fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) { // Run the effect } +/// Checks if a given effect affects entities or not. +fn tile_effect_hits_entities(effect: &EffectType) -> bool { + match effect { + EffectType::Damage { .. } => true, + EffectType::RestoreNutrition => true, + _ => false, + } +} + /// Runs an effect on a given entity fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { match &effect.effect_type { @@ -109,6 +114,7 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { } } EffectType::EntityDeath => damage::entity_death(ecs, effect, target), + EffectType::RestoreNutrition => hunger::restore_food(ecs, effect, target), _ => {} } } diff --git a/src/effects/targeting.rs b/src/effects/targeting.rs index 1b620ad..65d56bd 100644 --- a/src/effects/targeting.rs +++ b/src/effects/targeting.rs @@ -8,3 +8,13 @@ pub fn entity_position(ecs: &World, target: Entity) -> Option { } return None; } + +pub fn aoe_tiles(map: &Map, target: rltk::Point, radius: i32) -> Vec { + let mut blast_tiles = rltk::field_of_view(target, radius, &*map); + blast_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1); + let mut result = Vec::new(); + for t in blast_tiles.iter() { + result.push(map.xy_idx(t.x, t.y)); + } + result +} diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index 22242c6..fac7578 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -1,5 +1,5 @@ -use super::{Entity, Targets, World}; -use crate::{gamelog, Consumable}; +use super::{add_effect, EffectType, Entity, Targets, World}; +use crate::{gamelog, gui::item_colour_ecs, gui::obfuscate_name, Consumable, ProvidesNutrition}; use specs::prelude::*; pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: &mut World) { @@ -12,5 +12,15 @@ pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: } fn event_trigger(source: Option, entity: Entity, target: &Targets, ecs: &mut World) { - let logger = gamelog::Logger::new(); + let mut logger = gamelog::Logger::new(); + // Providing nutrition + if ecs.read_storage::().get(entity).is_some() { + add_effect(source, EffectType::RestoreNutrition, target.clone()); + logger = logger + .append("You eat the") + .append_n(obfuscate_name(ecs, entity).0) + .colour(item_colour_ecs(ecs, entity)) + .period(); + } + logger.log(); } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 9baa021..1f36562 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -151,8 +151,8 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { (&entities, &equipped, &renderables).join().filter(|item| item.1.owner == *player_entity) { equipment.push(( - get_item_display_name(ecs, entity).0, - get_item_colour(ecs, entity), + obfuscate_name(ecs, entity).0, + RGB::named(item_colour_ecs(ecs, entity)), renderable.fg, renderable.glyph, )); @@ -223,8 +223,8 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { (RGB::named(rltk::WHITE), rltk::to_cp437('-')) }; seen_entities.push(( - get_item_display_name(ecs, entity).0, - get_item_colour(ecs, entity), + obfuscate_name(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( +pub fn obfuscate_name_ecs( item: Entity, names: &ReadStorage, magic_items: &ReadStorage, @@ -423,7 +423,7 @@ pub fn obfuscate_name( } // Outside the ECS -pub fn get_item_display_name(ecs: &World, item: Entity) -> (String, String) { +pub fn obfuscate_name(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() { @@ -449,30 +449,30 @@ pub fn get_item_display_name(ecs: &World, item: Entity) -> (String, String) { return (singular, plural); } -pub fn get_item_colour(ecs: &World, item: Entity) -> RGB { +pub fn item_colour_ecs(ecs: &World, item: Entity) -> (u8, u8, u8) { let dm = ecs.fetch::(); if let Some(name) = ecs.read_storage::().get(item) { if let Some(magic) = ecs.read_storage::().get(item) { if dm.identified_items.contains(&name.name) { // If identified magic item, use rarity colour match magic.class { - MagicItemClass::Common => return RGB::named(rltk::WHITE), - MagicItemClass::Uncommon => return RGB::named(rltk::GREEN), - MagicItemClass::Rare => return RGB::named(rltk::BLUE), - MagicItemClass::VeryRare => return RGB::named(rltk::PURPLE), - MagicItemClass::Legendary => return RGB::named(rltk::GOLD), + MagicItemClass::Common => return WHITE, + MagicItemClass::Uncommon => return GREEN, + MagicItemClass::Rare => return BLUE, + MagicItemClass::VeryRare => return PURPLE, + MagicItemClass::Legendary => return GOLD, } } else { // Unidentified magic item - return RGB::named(rltk::GREY); + return GREY; } } } // If nonmagic, just use white - return RGB::named(rltk::WHITE); + return WHITE; } -pub fn item_colour_u8( +pub fn item_colour( item: Entity, names: &ReadStorage, magic_items: &ReadStorage, @@ -574,16 +574,14 @@ pub fn get_player_inventory(ecs: &World) -> (BTreeMap, (&entities, &backpack, &names, &renderables).join().filter(|item| item.1.owner == *player_entity) { // RGB can't be used as a key. This is converting the RGB (tuple of f32) into a tuple of u8s. - let item_colour = get_item_colour(ecs, entity); + 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 (r, g, b): (u8, u8, u8) = - ((item_colour.r * 255.0) as u8, (item_colour.g * 255.0) as u8, (item_colour.b * 255.0) as u8); - let (singular, plural) = get_item_display_name(ecs, entity); + let (singular, plural) = obfuscate_name(ecs, entity); player_inventory .entry(UniqueInventoryItem { display_name: DisplayName { singular: singular.clone(), plural: plural }, - rgb: (r, g, b), + rgb: item_colour, renderables: renderables, glyph: renderable.glyph, name: name.name.clone(), @@ -686,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 = &get_item_display_name(&gs.ecs, entity).0; + let this_name = &obfuscate_name(&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())); @@ -708,7 +706,7 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti }; ctx.set(x + 1, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97 + j as rltk::FontCharType); ctx.set(x + 3, y, fg, RGB::named(rltk::BLACK), glyph); - fg = get_item_colour(&gs.ecs, *e); + fg = RGB::named(item_colour_ecs(&gs.ecs, *e)); ctx.print_color(x + 5, y, fg, RGB::named(rltk::BLACK), name); y += 1; j += 1; diff --git a/src/gui/tooltip.rs b/src/gui/tooltip.rs index 6304e63..1416956 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::get_item_display_name(ecs, entity).0, renderable.fg); + tip.add(crate::gui::obfuscate_name(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 3c54c35..df38ab4 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; +use crate::gui::obfuscate_name_ecs; 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(pickup.item, &names, &magic_items, &obfuscated_names, &dm, Some(&wands)).0 + obfuscate_name_ecs(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 1f23442..417a1a4 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; +use crate::gui::obfuscate_name_ecs; 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(to_drop.item, &names, &magic_items, &obfuscated_names, &dm, Some(&wands)).0 + obfuscate_name_ecs(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 d4c53f3..f956d9e 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_u8, obfuscate_name}; +use crate::gui::{item_colour, obfuscate_name_ecs}; use specs::prelude::*; pub struct ItemEquipSystem {} @@ -58,8 +58,8 @@ impl<'a> System<'a> for ItemEquipSystem { if target == *player_entity { logger = logger .append("You remove your") - .append_n(obfuscate_name(*item, &names, &magic_items, &obfuscated_names, &dm, None).0) - .colour(item_colour_u8(*item, &names, &magic_items, &dm)) + .append_n(obfuscate_name_ecs(*item, &names, &magic_items, &obfuscated_names, &dm, None).0) + .colour(item_colour(*item, &names, &magic_items, &dm)) .period(); } } @@ -73,10 +73,17 @@ impl<'a> System<'a> for ItemEquipSystem { logger = logger .append("You equip the") .append_n( - obfuscate_name(wants_to_use_item.item, &names, &magic_items, &obfuscated_names, &dm, None) - .0, + obfuscate_name_ecs( + wants_to_use_item.item, + &names, + &magic_items, + &obfuscated_names, + &dm, + None, + ) + .0, ) - .colour(item_colour_u8(wants_to_use_item.item, &names, &magic_items, &dm)) + .colour(item_colour(wants_to_use_item.item, &names, &magic_items, &dm)) .period(); logger.log(); } diff --git a/src/inventory/remove_system.rs b/src/inventory/remove_system.rs index 2d2df45..dcda643 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_u8, obfuscate_name}; +use crate::gui::{item_colour, obfuscate_name_ecs}; use specs::prelude::*; pub struct ItemRemoveSystem {} @@ -37,8 +37,10 @@ impl<'a> System<'a> for ItemRemoveSystem { if entity == *player_entity { gamelog::Logger::new() .append("You unequip the") - .append_n(obfuscate_name(to_remove.item, &names, &magic_items, &obfuscated_names, &dm, None).0) - .colour(item_colour_u8(to_remove.item, &names, &magic_items, &dm)) + .append_n( + obfuscate_name_ecs(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/inventory/use_system.rs b/src/inventory/use_system.rs index 286b8c4..e9d6212 100644 --- a/src/inventory/use_system.rs +++ b/src/inventory/use_system.rs @@ -1,330 +1,50 @@ -use super::{ - gamelog, Confusion, Consumable, Cursed, Destructible, Digger, HungerClock, HungerState, IdentifiedItem, - InflictsDamage, MagicMapper, Map, Name, ParticleBuilder, Point, Pools, Position, ProvidesHealing, - ProvidesNutrition, RandomNumberGenerator, RunState, SufferDamage, TileType, Viewshed, Wand, WantsToUseItem, AOE, - DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME, -}; +use super::{EquipmentChanged, IdentifiedItem, Map, Name, WantsToUseItem, AOE}; +use crate::effects::*; use specs::prelude::*; -// Grouping together components because of type complexity issues - SystemData was too large. -// This is a temporary solution that'll be fixed once inventory use is refactored into separate -// systems. -type NameComponents<'a> = (ReadStorage<'a, Name>, WriteStorage<'a, IdentifiedItem>); - pub struct ItemUseSystem {} + impl<'a> System<'a> for ItemUseSystem { #[allow(clippy::type_complexity)] type SystemData = ( ReadExpect<'a, Entity>, WriteExpect<'a, Map>, - WriteExpect<'a, RandomNumberGenerator>, Entities<'a>, WriteStorage<'a, WantsToUseItem>, - NameComponents<'a>, - WriteStorage<'a, Consumable>, - WriteStorage<'a, Wand>, - ReadStorage<'a, Destructible>, - ReadStorage<'a, Cursed>, - ReadStorage<'a, ProvidesHealing>, - ReadStorage<'a, ProvidesNutrition>, - WriteStorage<'a, HungerClock>, - WriteStorage<'a, Pools>, - WriteStorage<'a, SufferDamage>, - WriteExpect<'a, ParticleBuilder>, - ReadStorage<'a, Position>, - ReadStorage<'a, InflictsDamage>, + ReadStorage<'a, Name>, ReadStorage<'a, AOE>, - ReadStorage<'a, Digger>, - WriteStorage<'a, Confusion>, - ReadStorage<'a, MagicMapper>, - WriteExpect<'a, RunState>, - WriteStorage<'a, Viewshed>, + WriteStorage<'a, EquipmentChanged>, + WriteStorage<'a, IdentifiedItem>, ); + #[allow(clippy::cognitive_complexity)] fn run(&mut self, data: Self::SystemData) { - let ( - player_entity, - mut map, - mut rng, - entities, - mut wants_to_use, - (names, mut identified_items), - mut consumables, - mut wands, - destructibles, - cursed_items, - provides_healing, - provides_nutrition, - mut hunger_clock, - mut combat_stats, - mut suffer_damage, - mut particle_builder, - positions, - inflicts_damage, - aoe, - digger, - mut confused, - magic_mapper, - mut runstate, - mut viewsheds, - ) = data; + let (player_entity, map, entities, mut wants_use, names, aoe, mut dirty, mut identified_item) = data; - for (entity, wants_to_use) in (&entities, &wants_to_use).join() { - let mut verb = "use"; - let mut used_item = true; - let mut aoe_item = false; - - let mut logger = gamelog::Logger::new(); - - let is_cursed = cursed_items.get(wants_to_use.item); - let wand = wands.get_mut(wants_to_use.item); - if let Some(wand) = wand { - // If want has no uses, roll 1d121. On a 121, wrest the wand, then delete it. - if wand.uses == 0 { - if rng.roll_dice(1, 121) != 121 { - gamelog::Logger::new().append("The wand does nothing.").log(); - break; - } - logger = logger.colour(rltk::YELLOW).append("You wrest one last charge from the worn-out wand."); - consumables.insert(wants_to_use.item, Consumable {}).expect("Could not insert consumable"); - } - verb = "zap"; - wand.uses -= 1; + for (entity, useitem) in (&entities, &wants_use).join() { + dirty.insert(entity, EquipmentChanged {}).expect("Unable to insert"); + // Identify + if entity == *player_entity { + identified_item + .insert(entity, IdentifiedItem { name: names.get(useitem.item).unwrap().name.clone() }) + .expect("Unable to insert"); } - - let item_being_used = names.get(wants_to_use.item).unwrap(); - - let is_edible = provides_nutrition.get(wants_to_use.item); - if let Some(_) = is_edible { - verb = "eat"; - } - - logger = - logger.append(format!("You {} the", verb)).item_name_n(format!("{}", &item_being_used.name)).period(); - - // TARGETING - let mut targets: Vec = Vec::new(); - let mut target_idxs: Vec = Vec::new(); - match wants_to_use.target { - None => { - targets.push(*player_entity); - let pos = positions.get(*player_entity); - if let Some(pos) = pos { - target_idxs.push(map.xy_idx(pos.x, pos.y)); - } - } - Some(mut target) => { - let area_effect = aoe.get(wants_to_use.item); - match area_effect { - None => { - // Single target in a tile - let idx = map.xy_idx(target.x, target.y); - target_idxs.push(idx); - crate::spatial::for_each_tile_content(idx, |mob| targets.push(mob)); - } - Some(area_effect) => { - // If item with a targeted AOE is cursed, get the position - // of the player and set them to be the new target. - match is_cursed { - None => {} - Some(_) => { - let pos = positions.get(*player_entity); - if let Some(pos) = pos { - target = Point::new(pos.x, pos.y); - } - logger = logger - .append("The") - .item_name(&item_being_used.name) - .colour(rltk::WHITE) - .append("disobeys!"); - } - } - // AOE - aoe_item = true; - let mut blast_tiles = rltk::field_of_view(target, area_effect.radius, &*map); - blast_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1); - for tile_idx in blast_tiles.iter() { - let idx = map.xy_idx(tile_idx.x, tile_idx.y); - target_idxs.push(idx); - crate::spatial::for_each_tile_content(idx, |mob| targets.push(mob)); - particle_builder.request( - tile_idx.x, - tile_idx.y, - rltk::RGB::named(rltk::ORANGE), - rltk::RGB::named(rltk::BLACK), - rltk::to_cp437('░'), - LONG_PARTICLE_LIFETIME, - ); - } + // Call the effects system + add_effect( + Some(entity), + EffectType::ItemUse { item: useitem.item }, + match useitem.target { + None => Targets::Entity { target: *player_entity }, + Some(target) => { + if let Some(aoe) = aoe.get(useitem.item) { + Targets::TileList { targets: aoe_tiles(&*map, target, aoe.radius) } + } else { + Targets::Tile { target: map.xy_idx(target.x, target.y) } } } - } - } - - // EDIBLE - match is_edible { - None => {} - Some(_) => { - let target = targets[0]; - let hc = hunger_clock.get_mut(target); - if let Some(hc) = hc { - hc.state = HungerState::Satiated; - hc.duration = 200; - } - } - } - - // HEALING ITEM - let item_heals = provides_healing.get(wants_to_use.item); - match item_heals { - None => {} - Some(heal) => { - for target in targets.iter() { - let stats = combat_stats.get_mut(*target); - if let Some(stats) = stats { - let roll = rng.roll_dice(heal.n_dice, heal.sides) + heal.modifier; - stats.hit_points.current = i32::min(stats.hit_points.max, stats.hit_points.current + roll); - if entity == *player_entity { - logger = logger.append("You recover some vigour."); - } - let pos = positions.get(entity); - if let Some(pos) = pos { - particle_builder.heal(pos.x, pos.y); - } - } - } - } - } - - let mut damage_logger = gamelog::Logger::new(); - let mut needs_damage_log = false; - - // DAMAGING ITEM - let item_damages = inflicts_damage.get(wants_to_use.item); - match item_damages { - None => {} - Some(damage) => { - let target_point = wants_to_use.target.unwrap(); - let damage_roll = rng.roll_dice(damage.n_dice, damage.sides) + damage.modifier; - if !aoe_item { - particle_builder.request_rainbow_star( - target_point.x, - target_point.y, - rltk::to_cp437('*'), - DEFAULT_PARTICLE_LIFETIME, - ); - } - for mob in targets.iter() { - let destructible = destructibles.get(*mob); - let entity_name = names.get(*mob).unwrap(); - match destructible { - None => { - SufferDamage::new_damage(&mut suffer_damage, *mob, damage_roll, true); - if entity == *player_entity { - damage_logger = - damage_logger.append("The").npc_name(&entity_name.name).append("is hit!"); - needs_damage_log = true; - } - } - Some(_destructible) => { - damage_logger = damage_logger - .append("The") - .item_name(&entity_name.name) - .colour(rltk::WHITE) - .append("is destroyed!"); - needs_damage_log = true; - entities.delete(*mob).expect("Delete failed"); - } - } - - used_item = true; - } - } - } - - // CONFUSION - let mut add_confusion = Vec::new(); - { - let causes_confusion = confused.get(wants_to_use.item); - match causes_confusion { - None => {} - Some(confusion) => { - for mob in targets.iter() { - add_confusion.push((*mob, confusion.turns)); - // Gamelog entry for this is handled turn-by-turn in AI. - } - } - } - } - for mob in add_confusion.iter() { - confused.insert(mob.0, Confusion { turns: mob.1 }).expect("Unable to insert status"); - } - - // MAGIC MAPPERS - let is_mapper = magic_mapper.get(wants_to_use.item); - match is_mapper { - None => {} - Some(_) => { - used_item = true; - match is_cursed { - None => { - logger = logger - .append("You feel") - .colour(rltk::GREEN) - .append("a sense of acuity towards your surroundings."); - *runstate = RunState::MagicMapReveal { row: 0, cursed: false }; - } - Some(_) => { - logger = logger.append("You").colour(rltk::RED).append("forget where you last were."); - *runstate = RunState::MagicMapReveal { row: 0, cursed: true }; - } - } - } - } - - let is_digger = digger.get(wants_to_use.item); - match is_digger { - None => {} - Some(_) => { - used_item = true; - for idx in target_idxs { - if map.tiles[idx] == TileType::Wall { - map.tiles[idx] = TileType::Floor; - } - for viewshed in (&mut viewsheds).join() { - if viewshed - .visible_tiles - .contains(&Point::new(idx % map.width as usize, idx / map.width as usize)) - { - viewshed.dirty = true; - } - } - } - } - } - - // ITEM DELETION AFTER USE - if used_item { - // Identify - if entity == *player_entity { - identified_items - .insert(entity, IdentifiedItem { name: item_being_used.name.clone() }) - .expect("Unable to insert"); - } - let consumable = consumables.get(wants_to_use.item); - match consumable { - None => {} - Some(_) => { - entities.delete(wants_to_use.item).expect("Delete failed"); - } - } - } - - logger.log(); - if needs_damage_log { - damage_logger.log(); - } + }, + ); } - wants_to_use.clear(); + wants_use.clear(); } } diff --git a/src/inventory/use_system_backup.rs b/src/inventory/use_system_backup.rs new file mode 100644 index 0000000..286b8c4 --- /dev/null +++ b/src/inventory/use_system_backup.rs @@ -0,0 +1,330 @@ +use super::{ + gamelog, Confusion, Consumable, Cursed, Destructible, Digger, HungerClock, HungerState, IdentifiedItem, + InflictsDamage, MagicMapper, Map, Name, ParticleBuilder, Point, Pools, Position, ProvidesHealing, + ProvidesNutrition, RandomNumberGenerator, RunState, SufferDamage, TileType, Viewshed, Wand, WantsToUseItem, AOE, + DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME, +}; +use specs::prelude::*; + +// Grouping together components because of type complexity issues - SystemData was too large. +// This is a temporary solution that'll be fixed once inventory use is refactored into separate +// systems. +type NameComponents<'a> = (ReadStorage<'a, Name>, WriteStorage<'a, IdentifiedItem>); + +pub struct ItemUseSystem {} +impl<'a> System<'a> for ItemUseSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + ReadExpect<'a, Entity>, + WriteExpect<'a, Map>, + WriteExpect<'a, RandomNumberGenerator>, + Entities<'a>, + WriteStorage<'a, WantsToUseItem>, + NameComponents<'a>, + WriteStorage<'a, Consumable>, + WriteStorage<'a, Wand>, + ReadStorage<'a, Destructible>, + ReadStorage<'a, Cursed>, + ReadStorage<'a, ProvidesHealing>, + ReadStorage<'a, ProvidesNutrition>, + WriteStorage<'a, HungerClock>, + WriteStorage<'a, Pools>, + WriteStorage<'a, SufferDamage>, + WriteExpect<'a, ParticleBuilder>, + ReadStorage<'a, Position>, + ReadStorage<'a, InflictsDamage>, + ReadStorage<'a, AOE>, + ReadStorage<'a, Digger>, + WriteStorage<'a, Confusion>, + ReadStorage<'a, MagicMapper>, + WriteExpect<'a, RunState>, + WriteStorage<'a, Viewshed>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + player_entity, + mut map, + mut rng, + entities, + mut wants_to_use, + (names, mut identified_items), + mut consumables, + mut wands, + destructibles, + cursed_items, + provides_healing, + provides_nutrition, + mut hunger_clock, + mut combat_stats, + mut suffer_damage, + mut particle_builder, + positions, + inflicts_damage, + aoe, + digger, + mut confused, + magic_mapper, + mut runstate, + mut viewsheds, + ) = data; + + for (entity, wants_to_use) in (&entities, &wants_to_use).join() { + let mut verb = "use"; + let mut used_item = true; + let mut aoe_item = false; + + let mut logger = gamelog::Logger::new(); + + let is_cursed = cursed_items.get(wants_to_use.item); + let wand = wands.get_mut(wants_to_use.item); + if let Some(wand) = wand { + // If want has no uses, roll 1d121. On a 121, wrest the wand, then delete it. + if wand.uses == 0 { + if rng.roll_dice(1, 121) != 121 { + gamelog::Logger::new().append("The wand does nothing.").log(); + break; + } + logger = logger.colour(rltk::YELLOW).append("You wrest one last charge from the worn-out wand."); + consumables.insert(wants_to_use.item, Consumable {}).expect("Could not insert consumable"); + } + verb = "zap"; + wand.uses -= 1; + } + + let item_being_used = names.get(wants_to_use.item).unwrap(); + + let is_edible = provides_nutrition.get(wants_to_use.item); + if let Some(_) = is_edible { + verb = "eat"; + } + + logger = + logger.append(format!("You {} the", verb)).item_name_n(format!("{}", &item_being_used.name)).period(); + + // TARGETING + let mut targets: Vec = Vec::new(); + let mut target_idxs: Vec = Vec::new(); + match wants_to_use.target { + None => { + targets.push(*player_entity); + let pos = positions.get(*player_entity); + if let Some(pos) = pos { + target_idxs.push(map.xy_idx(pos.x, pos.y)); + } + } + Some(mut target) => { + let area_effect = aoe.get(wants_to_use.item); + match area_effect { + None => { + // Single target in a tile + let idx = map.xy_idx(target.x, target.y); + target_idxs.push(idx); + crate::spatial::for_each_tile_content(idx, |mob| targets.push(mob)); + } + Some(area_effect) => { + // If item with a targeted AOE is cursed, get the position + // of the player and set them to be the new target. + match is_cursed { + None => {} + Some(_) => { + let pos = positions.get(*player_entity); + if let Some(pos) = pos { + target = Point::new(pos.x, pos.y); + } + logger = logger + .append("The") + .item_name(&item_being_used.name) + .colour(rltk::WHITE) + .append("disobeys!"); + } + } + // AOE + aoe_item = true; + let mut blast_tiles = rltk::field_of_view(target, area_effect.radius, &*map); + blast_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1); + for tile_idx in blast_tiles.iter() { + let idx = map.xy_idx(tile_idx.x, tile_idx.y); + target_idxs.push(idx); + crate::spatial::for_each_tile_content(idx, |mob| targets.push(mob)); + particle_builder.request( + tile_idx.x, + tile_idx.y, + rltk::RGB::named(rltk::ORANGE), + rltk::RGB::named(rltk::BLACK), + rltk::to_cp437('░'), + LONG_PARTICLE_LIFETIME, + ); + } + } + } + } + } + + // EDIBLE + match is_edible { + None => {} + Some(_) => { + let target = targets[0]; + let hc = hunger_clock.get_mut(target); + if let Some(hc) = hc { + hc.state = HungerState::Satiated; + hc.duration = 200; + } + } + } + + // HEALING ITEM + let item_heals = provides_healing.get(wants_to_use.item); + match item_heals { + None => {} + Some(heal) => { + for target in targets.iter() { + let stats = combat_stats.get_mut(*target); + if let Some(stats) = stats { + let roll = rng.roll_dice(heal.n_dice, heal.sides) + heal.modifier; + stats.hit_points.current = i32::min(stats.hit_points.max, stats.hit_points.current + roll); + if entity == *player_entity { + logger = logger.append("You recover some vigour."); + } + let pos = positions.get(entity); + if let Some(pos) = pos { + particle_builder.heal(pos.x, pos.y); + } + } + } + } + } + + let mut damage_logger = gamelog::Logger::new(); + let mut needs_damage_log = false; + + // DAMAGING ITEM + let item_damages = inflicts_damage.get(wants_to_use.item); + match item_damages { + None => {} + Some(damage) => { + let target_point = wants_to_use.target.unwrap(); + let damage_roll = rng.roll_dice(damage.n_dice, damage.sides) + damage.modifier; + if !aoe_item { + particle_builder.request_rainbow_star( + target_point.x, + target_point.y, + rltk::to_cp437('*'), + DEFAULT_PARTICLE_LIFETIME, + ); + } + for mob in targets.iter() { + let destructible = destructibles.get(*mob); + let entity_name = names.get(*mob).unwrap(); + match destructible { + None => { + SufferDamage::new_damage(&mut suffer_damage, *mob, damage_roll, true); + if entity == *player_entity { + damage_logger = + damage_logger.append("The").npc_name(&entity_name.name).append("is hit!"); + needs_damage_log = true; + } + } + Some(_destructible) => { + damage_logger = damage_logger + .append("The") + .item_name(&entity_name.name) + .colour(rltk::WHITE) + .append("is destroyed!"); + needs_damage_log = true; + entities.delete(*mob).expect("Delete failed"); + } + } + + used_item = true; + } + } + } + + // CONFUSION + let mut add_confusion = Vec::new(); + { + let causes_confusion = confused.get(wants_to_use.item); + match causes_confusion { + None => {} + Some(confusion) => { + for mob in targets.iter() { + add_confusion.push((*mob, confusion.turns)); + // Gamelog entry for this is handled turn-by-turn in AI. + } + } + } + } + for mob in add_confusion.iter() { + confused.insert(mob.0, Confusion { turns: mob.1 }).expect("Unable to insert status"); + } + + // MAGIC MAPPERS + let is_mapper = magic_mapper.get(wants_to_use.item); + match is_mapper { + None => {} + Some(_) => { + used_item = true; + match is_cursed { + None => { + logger = logger + .append("You feel") + .colour(rltk::GREEN) + .append("a sense of acuity towards your surroundings."); + *runstate = RunState::MagicMapReveal { row: 0, cursed: false }; + } + Some(_) => { + logger = logger.append("You").colour(rltk::RED).append("forget where you last were."); + *runstate = RunState::MagicMapReveal { row: 0, cursed: true }; + } + } + } + } + + let is_digger = digger.get(wants_to_use.item); + match is_digger { + None => {} + Some(_) => { + used_item = true; + for idx in target_idxs { + if map.tiles[idx] == TileType::Wall { + map.tiles[idx] = TileType::Floor; + } + for viewshed in (&mut viewsheds).join() { + if viewshed + .visible_tiles + .contains(&Point::new(idx % map.width as usize, idx / map.width as usize)) + { + viewshed.dirty = true; + } + } + } + } + } + + // ITEM DELETION AFTER USE + if used_item { + // Identify + if entity == *player_entity { + identified_items + .insert(entity, IdentifiedItem { name: item_being_used.name.clone() }) + .expect("Unable to insert"); + } + let consumable = consumables.get(wants_to_use.item); + match consumable { + None => {} + Some(_) => { + entities.delete(wants_to_use.item).expect("Delete failed"); + } + } + } + + logger.log(); + if needs_damage_log { + damage_logger.log(); + } + } + wants_to_use.clear(); + } +} diff --git a/src/player.rs b/src/player.rs index 1c95998..246a48f 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,7 @@ use super::{ - gamelog, gui::get_item_display_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, 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 = get_item_display_name(ecs, entity).0; + let item_name = obfuscate_name(ecs, entity).0; item_names.push(item_name); some = true; }