atomising item use: hunger effect

This commit is contained in:
Llywelwyn 2023-08-17 01:16:35 +01:00
parent 18eae23a4c
commit 931f600625
13 changed files with 455 additions and 360 deletions

12
src/effects/hunger.rs Normal file
View file

@ -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::<HungerClock>().get_mut(target) {
hc.state = HungerState::Satiated;
hc.duration = SATIATED_DURATION;
}
}

View file

@ -5,10 +5,13 @@ use std::collections::VecDeque;
use std::sync::Mutex; use std::sync::Mutex;
mod damage; mod damage;
mod hunger;
mod particles; mod particles;
mod targeting; mod targeting;
mod triggers; mod triggers;
pub use targeting::aoe_tiles;
lazy_static! { lazy_static! {
pub static ref EFFECT_QUEUE: Mutex<VecDeque<EffectSpawner>> = Mutex::new(VecDeque::new()); pub static ref EFFECT_QUEUE: Mutex<VecDeque<EffectSpawner>> = Mutex::new(VecDeque::new());
} }
@ -19,6 +22,7 @@ pub enum EffectType {
Particle { glyph: FontCharType, fg: RGB, bg: RGB, lifespan: f32, delay: f32 }, Particle { glyph: FontCharType, fg: RGB, bg: RGB, lifespan: f32, delay: f32 },
EntityDeath, EntityDeath,
ItemUse { item: Entity }, ItemUse { item: Entity },
RestoreNutrition,
} }
#[derive(Clone)] #[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 /// Runs an effect on a given tile index
fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) { fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) {
if tile_effect_hits_entities(&effect.effect_type) { 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 // 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 /// Runs an effect on a given entity
fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
match &effect.effect_type { 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::EntityDeath => damage::entity_death(ecs, effect, target),
EffectType::RestoreNutrition => hunger::restore_food(ecs, effect, target),
_ => {} _ => {}
} }
} }

View file

@ -8,3 +8,13 @@ pub fn entity_position(ecs: &World, target: Entity) -> Option<usize> {
} }
return None; return None;
} }
pub fn aoe_tiles(map: &Map, target: rltk::Point, radius: i32) -> Vec<usize> {
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
}

View file

@ -1,5 +1,5 @@
use super::{Entity, Targets, World}; use super::{add_effect, EffectType, Entity, Targets, World};
use crate::{gamelog, Consumable}; use crate::{gamelog, gui::item_colour_ecs, gui::obfuscate_name, Consumable, ProvidesNutrition};
use specs::prelude::*; use specs::prelude::*;
pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs: &mut World) { pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs: &mut World) {
@ -12,5 +12,15 @@ pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs:
} }
fn event_trigger(source: Option<Entity>, entity: Entity, target: &Targets, ecs: &mut World) { fn event_trigger(source: Option<Entity>, entity: Entity, target: &Targets, ecs: &mut World) {
let logger = gamelog::Logger::new(); let mut logger = gamelog::Logger::new();
// Providing nutrition
if ecs.read_storage::<ProvidesNutrition>().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();
} }

View file

@ -151,8 +151,8 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
(&entities, &equipped, &renderables).join().filter(|item| item.1.owner == *player_entity) (&entities, &equipped, &renderables).join().filter(|item| item.1.owner == *player_entity)
{ {
equipment.push(( equipment.push((
get_item_display_name(ecs, entity).0, obfuscate_name(ecs, entity).0,
get_item_colour(ecs, entity), RGB::named(item_colour_ecs(ecs, entity)),
renderable.fg, renderable.fg,
renderable.glyph, renderable.glyph,
)); ));
@ -223,8 +223,8 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
(RGB::named(rltk::WHITE), rltk::to_cp437('-')) (RGB::named(rltk::WHITE), rltk::to_cp437('-'))
}; };
seen_entities.push(( seen_entities.push((
get_item_display_name(ecs, entity).0, obfuscate_name(ecs, entity).0,
get_item_colour(ecs, entity), RGB::named(item_colour_ecs(ecs, entity)),
render_fg, render_fg,
glyph, glyph,
)); ));
@ -387,7 +387,7 @@ pub fn get_max_inventory_width(inventory: &BTreeMap<UniqueInventoryItem, i32>) -
} }
// Inside the ECS // Inside the ECS
pub fn obfuscate_name( pub fn obfuscate_name_ecs(
item: Entity, item: Entity,
names: &ReadStorage<Name>, names: &ReadStorage<Name>,
magic_items: &ReadStorage<MagicItem>, magic_items: &ReadStorage<MagicItem>,
@ -423,7 +423,7 @@ pub fn obfuscate_name(
} }
// Outside the ECS // 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()); let (mut singular, mut plural) = ("nameless item (bug)".to_string(), "nameless items (bug)".to_string());
if let Some(name) = ecs.read_storage::<Name>().get(item) { if let Some(name) = ecs.read_storage::<Name>().get(item) {
if ecs.read_storage::<MagicItem>().get(item).is_some() { if ecs.read_storage::<MagicItem>().get(item).is_some() {
@ -449,30 +449,30 @@ pub fn get_item_display_name(ecs: &World, item: Entity) -> (String, String) {
return (singular, plural); 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::<MasterDungeonMap>(); let dm = ecs.fetch::<MasterDungeonMap>();
if let Some(name) = ecs.read_storage::<Name>().get(item) { if let Some(name) = ecs.read_storage::<Name>().get(item) {
if let Some(magic) = ecs.read_storage::<MagicItem>().get(item) { if let Some(magic) = ecs.read_storage::<MagicItem>().get(item) {
if dm.identified_items.contains(&name.name) { if dm.identified_items.contains(&name.name) {
// If identified magic item, use rarity colour // If identified magic item, use rarity colour
match magic.class { match magic.class {
MagicItemClass::Common => return RGB::named(rltk::WHITE), MagicItemClass::Common => return WHITE,
MagicItemClass::Uncommon => return RGB::named(rltk::GREEN), MagicItemClass::Uncommon => return GREEN,
MagicItemClass::Rare => return RGB::named(rltk::BLUE), MagicItemClass::Rare => return BLUE,
MagicItemClass::VeryRare => return RGB::named(rltk::PURPLE), MagicItemClass::VeryRare => return PURPLE,
MagicItemClass::Legendary => return RGB::named(rltk::GOLD), MagicItemClass::Legendary => return GOLD,
} }
} else { } else {
// Unidentified magic item // Unidentified magic item
return RGB::named(rltk::GREY); return GREY;
} }
} }
} }
// If nonmagic, just use white // If nonmagic, just use white
return RGB::named(rltk::WHITE); return WHITE;
} }
pub fn item_colour_u8( pub fn item_colour(
item: Entity, item: Entity,
names: &ReadStorage<Name>, names: &ReadStorage<Name>,
magic_items: &ReadStorage<MagicItem>, magic_items: &ReadStorage<MagicItem>,
@ -574,16 +574,14 @@ pub fn get_player_inventory(ecs: &World) -> (BTreeMap<UniqueInventoryItem, i32>,
(&entities, &backpack, &names, &renderables).join().filter(|item| item.1.owner == *player_entity) (&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. // 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 = let renderables =
((renderable.fg.r * 255.0) as u8, (renderable.fg.g * 255.0) as u8, (renderable.fg.b * 255.0) as u8); ((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) = let (singular, plural) = obfuscate_name(ecs, entity);
((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);
player_inventory player_inventory
.entry(UniqueInventoryItem { .entry(UniqueInventoryItem {
display_name: DisplayName { singular: singular.clone(), plural: plural }, display_name: DisplayName { singular: singular.clone(), plural: plural },
rgb: (r, g, b), rgb: item_colour,
renderables: renderables, renderables: renderables,
glyph: renderable.glyph, glyph: renderable.glyph,
name: name.name.clone(), 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 equippable: Vec<(Entity, String)> = Vec::new();
let mut width = 2; let mut width = 2;
for (entity, _pack) in (&entities, &backpack).join().filter(|item| item.1.owner == *player_entity) { 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(); let this_width = 5 + this_name.len();
width = if width > this_width { width } else { this_width }; width = if width > this_width { width } else { this_width };
equippable.push((entity, this_name.to_string())); 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 + 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); 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); ctx.print_color(x + 5, y, fg, RGB::named(rltk::BLACK), name);
y += 1; y += 1;
j += 1; j += 1;

View file

@ -72,7 +72,7 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
for (entity, position, renderable, _hidden) in (&entities, &positions, &renderables, !&hidden).join() { for (entity, position, renderable, _hidden) in (&entities, &positions, &renderables, !&hidden).join() {
if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 { if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 {
let mut tip = Tooltip::new(); 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 // Attributes
let attr = attributes.get(entity); let attr = attributes.get(entity);
if let Some(a) = attr { if let Some(a) = attr {

View file

@ -2,7 +2,7 @@ use super::{
gamelog, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, Position, Wand, gamelog, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, Position, Wand,
WantsToPickupItem, WantsToPickupItem,
}; };
use crate::gui::obfuscate_name; use crate::gui::obfuscate_name_ecs;
use specs::prelude::*; use specs::prelude::*;
pub struct ItemCollectionSystem {} pub struct ItemCollectionSystem {}
@ -48,7 +48,7 @@ impl<'a> System<'a> for ItemCollectionSystem {
.append("You pick up the") .append("You pick up the")
.item_name_n(format!( .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() .period()
.log(); .log();

View file

@ -2,7 +2,7 @@ use super::{
gamelog, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, Position, Wand, gamelog, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, Position, Wand,
WantsToDropItem, WantsToDropItem,
}; };
use crate::gui::obfuscate_name; use crate::gui::obfuscate_name_ecs;
use specs::prelude::*; use specs::prelude::*;
pub struct ItemDropSystem {} pub struct ItemDropSystem {}
@ -56,7 +56,7 @@ impl<'a> System<'a> for ItemDropSystem {
.append("You drop the") .append("You drop the")
.item_name_n(format!( .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() .period()
.log(); .log();

View file

@ -2,7 +2,7 @@ use super::{
gamelog, EquipmentChanged, Equippable, Equipped, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, gamelog, EquipmentChanged, Equippable, Equipped, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName,
WantsToUseItem, WantsToUseItem,
}; };
use crate::gui::{item_colour_u8, obfuscate_name}; use crate::gui::{item_colour, obfuscate_name_ecs};
use specs::prelude::*; use specs::prelude::*;
pub struct ItemEquipSystem {} pub struct ItemEquipSystem {}
@ -58,8 +58,8 @@ impl<'a> System<'a> for ItemEquipSystem {
if target == *player_entity { if target == *player_entity {
logger = logger logger = logger
.append("You remove your") .append("You remove your")
.append_n(obfuscate_name(*item, &names, &magic_items, &obfuscated_names, &dm, None).0) .append_n(obfuscate_name_ecs(*item, &names, &magic_items, &obfuscated_names, &dm, None).0)
.colour(item_colour_u8(*item, &names, &magic_items, &dm)) .colour(item_colour(*item, &names, &magic_items, &dm))
.period(); .period();
} }
} }
@ -73,10 +73,17 @@ impl<'a> System<'a> for ItemEquipSystem {
logger = logger logger = logger
.append("You equip the") .append("You equip the")
.append_n( .append_n(
obfuscate_name(wants_to_use_item.item, &names, &magic_items, &obfuscated_names, &dm, None) obfuscate_name_ecs(
.0, 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(); .period();
logger.log(); logger.log();
} }

View file

@ -1,5 +1,5 @@
use super::{gamelog, Equipped, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, WantsToRemoveItem}; 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::*; use specs::prelude::*;
pub struct ItemRemoveSystem {} pub struct ItemRemoveSystem {}
@ -37,8 +37,10 @@ impl<'a> System<'a> for ItemRemoveSystem {
if entity == *player_entity { if entity == *player_entity {
gamelog::Logger::new() gamelog::Logger::new()
.append("You unequip the") .append("You unequip the")
.append_n(obfuscate_name(to_remove.item, &names, &magic_items, &obfuscated_names, &dm, None).0) .append_n(
.colour(item_colour_u8(to_remove.item, &names, &magic_items, &dm)) 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() .period()
.log(); .log();
} }

View file

@ -1,330 +1,50 @@
use super::{ use super::{EquipmentChanged, IdentifiedItem, Map, Name, WantsToUseItem, AOE};
gamelog, Confusion, Consumable, Cursed, Destructible, Digger, HungerClock, HungerState, IdentifiedItem, use crate::effects::*;
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::*; 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 {} pub struct ItemUseSystem {}
impl<'a> System<'a> for ItemUseSystem { impl<'a> System<'a> for ItemUseSystem {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
WriteExpect<'a, Map>, WriteExpect<'a, Map>,
WriteExpect<'a, RandomNumberGenerator>,
Entities<'a>, Entities<'a>,
WriteStorage<'a, WantsToUseItem>, WriteStorage<'a, WantsToUseItem>,
NameComponents<'a>, ReadStorage<'a, Name>,
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, AOE>,
ReadStorage<'a, Digger>, WriteStorage<'a, EquipmentChanged>,
WriteStorage<'a, Confusion>, WriteStorage<'a, IdentifiedItem>,
ReadStorage<'a, MagicMapper>,
WriteExpect<'a, RunState>,
WriteStorage<'a, Viewshed>,
); );
#[allow(clippy::cognitive_complexity)]
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let ( let (player_entity, map, entities, mut wants_use, names, aoe, mut dirty, mut identified_item) = data;
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() { for (entity, useitem) in (&entities, &wants_use).join() {
let mut verb = "use"; dirty.insert(entity, EquipmentChanged {}).expect("Unable to insert");
let mut used_item = true; // Identify
let mut aoe_item = false; if entity == *player_entity {
identified_item
let mut logger = gamelog::Logger::new(); .insert(entity, IdentifiedItem { name: names.get(useitem.item).unwrap().name.clone() })
.expect("Unable to insert");
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;
} }
// Call the effects system
let item_being_used = names.get(wants_to_use.item).unwrap(); add_effect(
Some(entity),
let is_edible = provides_nutrition.get(wants_to_use.item); EffectType::ItemUse { item: useitem.item },
if let Some(_) = is_edible { match useitem.target {
verb = "eat"; None => Targets::Entity { target: *player_entity },
} Some(target) => {
if let Some(aoe) = aoe.get(useitem.item) {
logger = Targets::TileList { targets: aoe_tiles(&*map, target, aoe.radius) }
logger.append(format!("You {} the", verb)).item_name_n(format!("{}", &item_being_used.name)).period(); } else {
Targets::Tile { target: map.xy_idx(target.x, target.y) }
// TARGETING
let mut targets: Vec<Entity> = Vec::new();
let mut target_idxs: Vec<usize> = 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(); wants_use.clear();
} }
} }

View file

@ -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<Entity> = Vec::new();
let mut target_idxs: Vec<usize> = 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();
}
}

View file

@ -1,7 +1,7 @@
use super::{ use super::{
gamelog, gui::get_item_display_name, raws::Reaction, Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, gamelog, gui::obfuscate_name, raws::Reaction, Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, Faction,
Faction, Hidden, HungerClock, HungerState, Item, Map, Name, ParticleBuilder, Player, Pools, Position, Renderable, Hidden, HungerClock, HungerState, Item, Map, Name, ParticleBuilder, Player, Pools, Position, Renderable, RunState,
RunState, State, SufferDamage, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, State, SufferDamage, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem,
}; };
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
use specs::prelude::*; 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; let mut some = false;
crate::spatial::for_each_tile_content(destination_idx, |entity| { crate::spatial::for_each_tile_content(destination_idx, |entity| {
if !hidden.get(entity).is_some() && names.get(entity).is_some() { 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); item_names.push(item_name);
some = true; some = true;
} }