diff --git a/src/inventory_system.rs b/src/inventory_system.rs index eb46b13..7d7abbb 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -1,9 +1,11 @@ +mod item_use_system; + use ::specs::prelude::*; +pub use item_use_system::ItemUseSystem; use crate::components::*; use crate::game_log::GameLog; -use crate::particle_system::ParticleBuilder; -use crate::{colors, raws, spatial, Map, MasterDungeonMap, RunState}; +use crate::{raws, MasterDungeonMap}; fn obfuscate_name( item: Entity, @@ -86,345 +88,6 @@ impl<'a> System<'a> for ItemCollectionSystem { } } -pub struct ItemUseSystem {} - -impl<'a> System<'a> for ItemUseSystem { - #[allow(clippy::type_complexity)] - type SystemData = ( - ReadExpect<'a, Entity>, - WriteExpect<'a, GameLog>, - WriteExpect<'a, Map>, - Entities<'a>, - WriteStorage<'a, WantsToUseItem>, - ReadStorage<'a, Name>, - ReadStorage<'a, Consumable>, - ReadStorage<'a, ProvidesHealing>, - ReadStorage<'a, InflictsDamage>, - WriteStorage<'a, Pools>, - WriteStorage<'a, SufferDamage>, - ReadStorage<'a, AreaOfEffect>, - WriteStorage<'a, Confusion>, - ReadStorage<'a, Equippable>, - WriteStorage<'a, Equipped>, - WriteStorage<'a, InBackpack>, - WriteExpect<'a, ParticleBuilder>, - ReadStorage<'a, Position>, - ReadStorage<'a, ProvidesFood>, - WriteStorage<'a, HungerClock>, - ReadStorage<'a, MagicMapper>, - WriteExpect<'a, RunState>, - WriteStorage<'a, EquipmentChanged>, - ReadStorage<'a, TownPortal>, - WriteStorage<'a, IdentifiedItem>, - ); - - #[allow(clippy::cognitive_complexity)] - fn run(&mut self, data: Self::SystemData) { - let ( - player_entity, - mut gamelog, - map, - entities, - mut wants_use, - names, - consumables, - healing, - inflict_damage, - mut combat_stats, - mut suffer_damage, - aoe, - mut confused, - equippable, - mut equipped, - mut backpack, - mut particle_builder, - positions, - provides_food, - mut hunger_clocks, - magic_mapper, - mut runstate, - mut dirty, - town_portal, - mut identified_item, - ) = data; - - for (entity, useitem) in (&entities, &wants_use).join() { - dirty - .insert(entity, EquipmentChanged {}) - .expect("Unable to insert equipment change"); - let mut used_item = true; - - // Targeting - let mut targets: Vec = Vec::new(); - match useitem.target { - None => { - targets.push(*player_entity); - } - Some(target) => { - match aoe.get(useitem.item) { - None => { - // Single target in tile - let idx = map.xy_idx(target.x, target.y); - spatial::for_each_tile_content(idx, |mob| targets.push(mob)); - } - Some(area_effect) => { - // AoE - 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); - spatial::for_each_tile_content(idx, |mob| targets.push(mob)); - - particle_builder.request( - tile_idx.x, - tile_idx.y, - colors::ORANGE, - colors::BLACK, - rltk::to_cp437('░'), - 200.0, - ); - } - } - } - } - } - - // Identify - if entity == *player_entity { - identified_item - .insert( - entity, - IdentifiedItem { - name: names.get(useitem.item).unwrap().name.clone(), - }, - ) - .expect("Unable to identify item"); - } - - // If it is equippable, then we want to equip it - and unequip whatever else was in that slot - match equippable.get(useitem.item) { - None => {} - Some(can_equip) => { - let target_slot = can_equip.slot; - let target = targets[0]; - - // Remove any items the target has in the item's slot - let mut to_unequip: Vec = Vec::new(); - for (item_entity, already_equipped, name) in - (&entities, &equipped, &names).join() - { - if already_equipped.owner == target && already_equipped.slot == target_slot - { - to_unequip.push(item_entity); - if target == *player_entity { - gamelog.append(format!("You unequip {}.", name.name)); - } - } - } - for item in to_unequip.iter() { - equipped.remove(*item); - backpack - .insert(*item, InBackpack { owner: target }) - .expect("Unable to put unequipped item back in backpack"); - } - - // Wield the item - equipped - .insert( - useitem.item, - Equipped { - owner: target, - slot: target_slot, - }, - ) - .expect("Failed to equip item"); - backpack.remove(useitem.item); - if target == *player_entity { - gamelog.append(format!( - "You equip {}.", - names.get(useitem.item).unwrap().name - )); - } - } - } - - // If it is edible, eat it! - match provides_food.get(useitem.item) { - None => {} - Some(_) => { - used_item = true; - let target = targets[0]; - - if let Some(hc) = hunger_clocks.get_mut(target) { - hc.state = HungerState::WellFed; - hc.duration = 20; - - gamelog.append(format!( - "You eat the {}.", - names.get(useitem.item).unwrap().name - )); - } - } - } - - // If its a magic mapper... - match magic_mapper.get(useitem.item) { - None => {} - Some(_) => { - used_item = true; - gamelog.append("The map is revealed to you!"); - - *runstate = RunState::MagicMapReveal { row: 0 }; - } - } - - // If it's a town portal... - if town_portal.get(useitem.item).is_some() { - if map.depth == 1 { - gamelog.append("You are already in town, so the scroll does nothing"); - } else { - used_item = true; - gamelog.append("You are teleported back to town!"); - - *runstate = RunState::TownPortal; - } - } - - // If the item heals, apply the healing - match healing.get(useitem.item) { - None => {} - Some(healer) => { - used_item = false; - - for target in targets.iter() { - if let Some(stats) = combat_stats.get_mut(*target) { - stats.hit_points.current = i32::min( - stats.hit_points.max, - stats.hit_points.current + healer.heal_amount, - ); - if entity == *player_entity { - gamelog.append(format!( - "You drink the {}, healing {} hp.", - names.get(useitem.item).unwrap().name, - healer.heal_amount - )); - } - - used_item = true; - - // Visually show healing - if let Some(pos) = positions.get(*target) { - particle_builder.request( - pos.x, - pos.y, - colors::GREEN, - colors::BLACK, - rltk::to_cp437('♥'), - 200.0, - ); - } - } - } - } - } - - // If it inflicts damage, apply it to the target cell - match inflict_damage.get(useitem.item) { - None => {} - Some(damage) => { - used_item = false; - - for mob in targets.iter() { - SufferDamage::new_damage(&mut suffer_damage, *mob, damage.damage, true); - if entity == *player_entity { - let mob_name = names.get(*mob).unwrap(); - let item_name = names.get(useitem.item).unwrap(); - - gamelog.append(format!( - "You use {} on {}, inflicting {} hp.", - item_name.name, mob_name.name, damage.damage - )); - - if let Some(pos) = positions.get(*mob) { - particle_builder.request( - pos.x, - pos.y, - colors::RED, - colors::BLACK, - rltk::to_cp437('‼'), - 200.0, - ); - } - } - - used_item = true; - } - } - } - - // Can it pass along confusion? Note the use of scopes - // to escape from the borrow checker! - let mut add_confusion = Vec::new(); - { - match confused.get(useitem.item) { - None => {} - Some(confusion) => { - used_item = false; - - for mob in targets.iter() { - add_confusion.push((*mob, confusion.turns)); - if entity == *player_entity { - let mob_name = names.get(*mob).unwrap(); - let item_name = names.get(useitem.item).unwrap(); - - gamelog.append(format!( - "You use {} on {}, confusing them.", - item_name.name, mob_name.name - )); - - if let Some(pos) = positions.get(*mob) { - particle_builder.request( - pos.x, - pos.y, - colors::MAGENTA, - colors::BLACK, - rltk::to_cp437('?'), - 200.0, - ); - } - } - } - } - } - } - for mob in add_confusion.iter() { - confused - .insert(mob.0, Confusion { turns: mob.1 }) - .expect("Unable to add confused status"); - } - - // If it's a consumable, delete it on use - if used_item { - let consumable = consumables.get(useitem.item); - match consumable { - None => {} - Some(_) => { - entities - .delete(useitem.item) - .expect("Failed to consume item"); - } - } - } - } - - wants_use.clear(); - } -} - pub struct ItemDropSystem {} impl<'a> System<'a> for ItemDropSystem { diff --git a/src/inventory_system/item_use_system.rs b/src/inventory_system/item_use_system.rs new file mode 100644 index 0000000..219d59d --- /dev/null +++ b/src/inventory_system/item_use_system.rs @@ -0,0 +1,345 @@ +use ::specs::prelude::*; + +use crate::components::*; +use crate::game_log::GameLog; +use crate::particle_system::ParticleBuilder; +use crate::{colors, spatial, Map, RunState}; + +pub struct ItemUseSystem {} + +impl<'a> System<'a> for ItemUseSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + ReadExpect<'a, Entity>, + WriteExpect<'a, GameLog>, + WriteExpect<'a, Map>, + Entities<'a>, + WriteStorage<'a, WantsToUseItem>, + ReadStorage<'a, Name>, + ReadStorage<'a, Consumable>, + ReadStorage<'a, ProvidesHealing>, + ReadStorage<'a, InflictsDamage>, + WriteStorage<'a, Pools>, + WriteStorage<'a, SufferDamage>, + ReadStorage<'a, AreaOfEffect>, + WriteStorage<'a, Confusion>, + ReadStorage<'a, Equippable>, + WriteStorage<'a, Equipped>, + WriteStorage<'a, InBackpack>, + WriteExpect<'a, ParticleBuilder>, + ReadStorage<'a, Position>, + ReadStorage<'a, ProvidesFood>, + WriteStorage<'a, HungerClock>, + ReadStorage<'a, MagicMapper>, + WriteExpect<'a, RunState>, + WriteStorage<'a, EquipmentChanged>, + ReadStorage<'a, TownPortal>, + WriteStorage<'a, IdentifiedItem>, + ); + + #[allow(clippy::cognitive_complexity)] + fn run(&mut self, data: Self::SystemData) { + let ( + player_entity, + mut gamelog, + map, + entities, + mut wants_use, + names, + consumables, + healing, + inflict_damage, + mut combat_stats, + mut suffer_damage, + aoe, + mut confused, + equippable, + mut equipped, + mut backpack, + mut particle_builder, + positions, + provides_food, + mut hunger_clocks, + magic_mapper, + mut runstate, + mut dirty, + town_portal, + mut identified_item, + ) = data; + + for (entity, useitem) in (&entities, &wants_use).join() { + dirty + .insert(entity, EquipmentChanged {}) + .expect("Unable to insert equipment change"); + let mut used_item = true; + + // Targeting + let mut targets: Vec = Vec::new(); + match useitem.target { + None => { + targets.push(*player_entity); + } + Some(target) => { + match aoe.get(useitem.item) { + None => { + // Single target in tile + let idx = map.xy_idx(target.x, target.y); + spatial::for_each_tile_content(idx, |mob| targets.push(mob)); + } + Some(area_effect) => { + // AoE + 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); + spatial::for_each_tile_content(idx, |mob| targets.push(mob)); + + particle_builder.request( + tile_idx.x, + tile_idx.y, + colors::ORANGE, + colors::BLACK, + rltk::to_cp437('░'), + 200.0, + ); + } + } + } + } + } + + // Identify + if entity == *player_entity { + identified_item + .insert( + entity, + IdentifiedItem { + name: names.get(useitem.item).unwrap().name.clone(), + }, + ) + .expect("Unable to identify item"); + } + + // If it is equippable, then we want to equip it - and unequip whatever else was in that slot + match equippable.get(useitem.item) { + None => {} + Some(can_equip) => { + let target_slot = can_equip.slot; + let target = targets[0]; + + // Remove any items the target has in the item's slot + let mut to_unequip: Vec = Vec::new(); + for (item_entity, already_equipped, name) in + (&entities, &equipped, &names).join() + { + if already_equipped.owner == target && already_equipped.slot == target_slot + { + to_unequip.push(item_entity); + if target == *player_entity { + gamelog.append(format!("You unequip {}.", name.name)); + } + } + } + for item in to_unequip.iter() { + equipped.remove(*item); + backpack + .insert(*item, InBackpack { owner: target }) + .expect("Unable to put unequipped item back in backpack"); + } + + // Wield the item + equipped + .insert( + useitem.item, + Equipped { + owner: target, + slot: target_slot, + }, + ) + .expect("Failed to equip item"); + backpack.remove(useitem.item); + if target == *player_entity { + gamelog.append(format!( + "You equip {}.", + names.get(useitem.item).unwrap().name + )); + } + } + } + + // If it is edible, eat it! + match provides_food.get(useitem.item) { + None => {} + Some(_) => { + used_item = true; + let target = targets[0]; + + if let Some(hc) = hunger_clocks.get_mut(target) { + hc.state = HungerState::WellFed; + hc.duration = 20; + + gamelog.append(format!( + "You eat the {}.", + names.get(useitem.item).unwrap().name + )); + } + } + } + + // If its a magic mapper... + match magic_mapper.get(useitem.item) { + None => {} + Some(_) => { + used_item = true; + gamelog.append("The map is revealed to you!"); + + *runstate = RunState::MagicMapReveal { row: 0 }; + } + } + + // If it's a town portal... + if town_portal.get(useitem.item).is_some() { + if map.depth == 1 { + gamelog.append("You are already in town, so the scroll does nothing"); + } else { + used_item = true; + gamelog.append("You are teleported back to town!"); + + *runstate = RunState::TownPortal; + } + } + + // If the item heals, apply the healing + match healing.get(useitem.item) { + None => {} + Some(healer) => { + used_item = false; + + for target in targets.iter() { + if let Some(stats) = combat_stats.get_mut(*target) { + stats.hit_points.current = i32::min( + stats.hit_points.max, + stats.hit_points.current + healer.heal_amount, + ); + if entity == *player_entity { + gamelog.append(format!( + "You drink the {}, healing {} hp.", + names.get(useitem.item).unwrap().name, + healer.heal_amount + )); + } + + used_item = true; + + // Visually show healing + if let Some(pos) = positions.get(*target) { + particle_builder.request( + pos.x, + pos.y, + colors::GREEN, + colors::BLACK, + rltk::to_cp437('♥'), + 200.0, + ); + } + } + } + } + } + + // If it inflicts damage, apply it to the target cell + match inflict_damage.get(useitem.item) { + None => {} + Some(damage) => { + used_item = false; + + for mob in targets.iter() { + SufferDamage::new_damage(&mut suffer_damage, *mob, damage.damage, true); + if entity == *player_entity { + let mob_name = names.get(*mob).unwrap(); + let item_name = names.get(useitem.item).unwrap(); + + gamelog.append(format!( + "You use {} on {}, inflicting {} hp.", + item_name.name, mob_name.name, damage.damage + )); + + if let Some(pos) = positions.get(*mob) { + particle_builder.request( + pos.x, + pos.y, + colors::RED, + colors::BLACK, + rltk::to_cp437('‼'), + 200.0, + ); + } + } + + used_item = true; + } + } + } + + // Can it pass along confusion? Note the use of scopes + // to escape from the borrow checker! + let mut add_confusion = Vec::new(); + { + match confused.get(useitem.item) { + None => {} + Some(confusion) => { + used_item = false; + + for mob in targets.iter() { + add_confusion.push((*mob, confusion.turns)); + if entity == *player_entity { + let mob_name = names.get(*mob).unwrap(); + let item_name = names.get(useitem.item).unwrap(); + + gamelog.append(format!( + "You use {} on {}, confusing them.", + item_name.name, mob_name.name + )); + + if let Some(pos) = positions.get(*mob) { + particle_builder.request( + pos.x, + pos.y, + colors::MAGENTA, + colors::BLACK, + rltk::to_cp437('?'), + 200.0, + ); + } + } + } + } + } + } + for mob in add_confusion.iter() { + confused + .insert(mob.0, Confusion { turns: mob.1 }) + .expect("Unable to add confused status"); + } + + // If it's a consumable, delete it on use + if used_item { + let consumable = consumables.get(useitem.item); + match consumable { + None => {} + Some(_) => { + entities + .delete(useitem.item) + .expect("Failed to consume item"); + } + } + } + } + + wants_use.clear(); + } +}