inventory_system.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. extern crate specs;
  2. use specs::prelude::*;
  3. use super::{WantsToPickupItem, Name, InBackpack, Position, gamelog::GameLog, WantsToUseItem,
  4. Consumable, ProvidesHealing, CombatStats, WantsToDropItem, InflictsDamage, Map, SufferDamage,
  5. AreaOfEffect, Confusion, Equippable, Equipped, WantsToRemoveItem, particle_system::ParticleBuilder,
  6. ProvidesFood, HungerClock, HungerState, MagicMapper, RunState};
  7. pub struct ItemCollectionSystem {}
  8. impl<'a> System<'a> for ItemCollectionSystem {
  9. #[allow(clippy::type_complexity)]
  10. type SystemData = ( ReadExpect<'a, Entity>,
  11. WriteExpect<'a, GameLog>,
  12. WriteStorage<'a, WantsToPickupItem>,
  13. WriteStorage<'a, Position>,
  14. ReadStorage<'a, Name>,
  15. WriteStorage<'a, InBackpack>
  16. );
  17. fn run(&mut self, data : Self::SystemData) {
  18. let (player_entity, mut gamelog, mut wants_pickup, mut positions, names, mut backpack) = data;
  19. for pickup in wants_pickup.join() {
  20. positions.remove(pickup.item);
  21. backpack.insert(pickup.item, InBackpack{ owner: pickup.collected_by }).expect("Unable to insert backpack entry");
  22. if pickup.collected_by == *player_entity {
  23. gamelog.entries.insert(0, format!("You pick up the {}.", names.get(pickup.item).unwrap().name));
  24. }
  25. }
  26. wants_pickup.clear();
  27. }
  28. }
  29. pub struct ItemUseSystem {}
  30. impl<'a> System<'a> for ItemUseSystem {
  31. #[allow(clippy::type_complexity)]
  32. type SystemData = ( ReadExpect<'a, Entity>,
  33. WriteExpect<'a, GameLog>,
  34. WriteExpect<'a, Map>,
  35. Entities<'a>,
  36. WriteStorage<'a, WantsToUseItem>,
  37. ReadStorage<'a, Name>,
  38. ReadStorage<'a, Consumable>,
  39. ReadStorage<'a, ProvidesHealing>,
  40. ReadStorage<'a, InflictsDamage>,
  41. WriteStorage<'a, CombatStats>,
  42. WriteStorage<'a, SufferDamage>,
  43. ReadStorage<'a, AreaOfEffect>,
  44. WriteStorage<'a, Confusion>,
  45. ReadStorage<'a, Equippable>,
  46. WriteStorage<'a, Equipped>,
  47. WriteStorage<'a, InBackpack>,
  48. WriteExpect<'a, ParticleBuilder>,
  49. ReadStorage<'a, Position>,
  50. ReadStorage<'a, ProvidesFood>,
  51. WriteStorage<'a, HungerClock>,
  52. ReadStorage<'a, MagicMapper>,
  53. WriteExpect<'a, RunState>
  54. );
  55. #[allow(clippy::cognitive_complexity)]
  56. fn run(&mut self, data : Self::SystemData) {
  57. let (player_entity, mut gamelog, map, entities, mut wants_use, names,
  58. consumables, healing, inflict_damage, mut combat_stats, mut suffer_damage,
  59. aoe, mut confused, equippable, mut equipped, mut backpack, mut particle_builder, positions,
  60. provides_food, mut hunger_clocks, magic_mapper, mut runstate) = data;
  61. for (entity, useitem) in (&entities, &wants_use).join() {
  62. let mut used_item = true;
  63. // Targeting
  64. let mut targets : Vec<Entity> = Vec::new();
  65. match useitem.target {
  66. None => { targets.push( *player_entity ); }
  67. Some(target) => {
  68. let area_effect = aoe.get(useitem.item);
  69. match area_effect {
  70. None => {
  71. // Single target in tile
  72. let idx = map.xy_idx(target.x, target.y);
  73. for mob in map.tile_content[idx].iter() {
  74. targets.push(*mob);
  75. }
  76. }
  77. Some(area_effect) => {
  78. // AoE
  79. let blast_tiles = rltk::field_of_view(target, area_effect.radius, &*map);
  80. for tile_idx in blast_tiles.iter() {
  81. let idx = map.xy_idx(tile_idx.x, tile_idx.y);
  82. for mob in map.tile_content[idx].iter() {
  83. targets.push(*mob);
  84. }
  85. particle_builder.request(tile_idx.x, tile_idx.y, rltk::RGB::named(rltk::ORANGE), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('░'), 200.0);
  86. }
  87. }
  88. }
  89. }
  90. }
  91. // If it is equippable, then we want to equip it - and unequip whatever else was in that slot
  92. let item_equippable = equippable.get(useitem.item);
  93. match item_equippable {
  94. None => {}
  95. Some(can_equip) => {
  96. let target_slot = can_equip.slot;
  97. let target = targets[0];
  98. // Remove any items the target has in the item's slot
  99. let mut to_unequip : Vec<Entity> = Vec::new();
  100. for (item_entity, already_equipped, name) in (&entities, &equipped, &names).join() {
  101. if already_equipped.owner == target && already_equipped.slot == target_slot {
  102. to_unequip.push(item_entity);
  103. if target == *player_entity {
  104. gamelog.entries.insert(0, format!("You unequip {}.", name.name));
  105. }
  106. }
  107. }
  108. for item in to_unequip.iter() {
  109. equipped.remove(*item);
  110. backpack.insert(*item, InBackpack{ owner: target }).expect("Unable to insert backpack entry");
  111. }
  112. // Wield the item
  113. equipped.insert(useitem.item, Equipped{ owner: target, slot: target_slot }).expect("Unable to insert equipped component");
  114. backpack.remove(useitem.item);
  115. if target == *player_entity {
  116. gamelog.entries.insert(0, format!("You equip {}.", names.get(useitem.item).unwrap().name));
  117. }
  118. }
  119. }
  120. // It it is edible, eat it!
  121. let item_edible = provides_food.get(useitem.item);
  122. match item_edible {
  123. None => {}
  124. Some(_) => {
  125. used_item = true;
  126. let target = targets[0];
  127. let hc = hunger_clocks.get_mut(target);
  128. if let Some(hc) = hc {
  129. hc.state = HungerState::WellFed;
  130. hc.duration = 20;
  131. gamelog.entries.insert(0, format!("You eat the {}.", names.get(useitem.item).unwrap().name));
  132. }
  133. }
  134. }
  135. // If its a magic mapper...
  136. let is_mapper = magic_mapper.get(useitem.item);
  137. match is_mapper {
  138. None => {}
  139. Some(_) => {
  140. used_item = true;
  141. gamelog.entries.insert(0, "The map is revealed to you!".to_string());
  142. *runstate = RunState::MagicMapReveal{ row : 0};
  143. }
  144. }
  145. // If it heals, apply the healing
  146. let item_heals = healing.get(useitem.item);
  147. match item_heals {
  148. None => {}
  149. Some(healer) => {
  150. used_item = false;
  151. for target in targets.iter() {
  152. let stats = combat_stats.get_mut(*target);
  153. if let Some(stats) = stats {
  154. stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
  155. if entity == *player_entity {
  156. gamelog.entries.insert(0, format!("You use the {}, healing {} hp.", names.get(useitem.item).unwrap().name, healer.heal_amount));
  157. }
  158. used_item = true;
  159. let pos = positions.get(*target);
  160. if let Some(pos) = pos {
  161. particle_builder.request(pos.x, pos.y, rltk::RGB::named(rltk::GREEN), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('♥'), 200.0);
  162. }
  163. }
  164. }
  165. }
  166. }
  167. // If it inflicts damage, apply it to the target cell
  168. let item_damages = inflict_damage.get(useitem.item);
  169. match item_damages {
  170. None => {}
  171. Some(damage) => {
  172. used_item = false;
  173. for mob in targets.iter() {
  174. suffer_damage.insert(*mob, SufferDamage{ amount : damage.damage }).expect("Unable to insert");
  175. if entity == *player_entity {
  176. let mob_name = names.get(*mob).unwrap();
  177. let item_name = names.get(useitem.item).unwrap();
  178. gamelog.entries.insert(0, format!("You use {} on {}, inflicting {} hp.", item_name.name, mob_name.name, damage.damage));
  179. let pos = positions.get(*mob);
  180. if let Some(pos) = pos {
  181. particle_builder.request(pos.x, pos.y, rltk::RGB::named(rltk::RED), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('‼'), 200.0);
  182. }
  183. }
  184. used_item = true;
  185. }
  186. }
  187. }
  188. // Can it pass along confusion? Note the use of scopes to escape from the borrow checker!
  189. let mut add_confusion = Vec::new();
  190. {
  191. let causes_confusion = confused.get(useitem.item);
  192. match causes_confusion {
  193. None => {}
  194. Some(confusion) => {
  195. used_item = false;
  196. for mob in targets.iter() {
  197. add_confusion.push((*mob, confusion.turns ));
  198. if entity == *player_entity {
  199. let mob_name = names.get(*mob).unwrap();
  200. let item_name = names.get(useitem.item).unwrap();
  201. gamelog.entries.insert(0, format!("You use {} on {}, confusing them.", item_name.name, mob_name.name));
  202. let pos = positions.get(*mob);
  203. if let Some(pos) = pos {
  204. particle_builder.request(pos.x, pos.y, rltk::RGB::named(rltk::MAGENTA), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('?'), 200.0);
  205. }
  206. }
  207. }
  208. }
  209. }
  210. }
  211. for mob in add_confusion.iter() {
  212. confused.insert(mob.0, Confusion{ turns: mob.1 }).expect("Unable to insert status");
  213. }
  214. // If its a consumable, we delete it on use
  215. if used_item {
  216. let consumable = consumables.get(useitem.item);
  217. match consumable {
  218. None => {}
  219. Some(_) => {
  220. entities.delete(useitem.item).expect("Delete failed");
  221. }
  222. }
  223. }
  224. }
  225. wants_use.clear();
  226. }
  227. }
  228. pub struct ItemDropSystem {}
  229. impl<'a> System<'a> for ItemDropSystem {
  230. #[allow(clippy::type_complexity)]
  231. type SystemData = ( ReadExpect<'a, Entity>,
  232. WriteExpect<'a, GameLog>,
  233. Entities<'a>,
  234. WriteStorage<'a, WantsToDropItem>,
  235. ReadStorage<'a, Name>,
  236. WriteStorage<'a, Position>,
  237. WriteStorage<'a, InBackpack>
  238. );
  239. fn run(&mut self, data : Self::SystemData) {
  240. let (player_entity, mut gamelog, entities, mut wants_drop, names, mut positions, mut backpack) = data;
  241. for (entity, to_drop) in (&entities, &wants_drop).join() {
  242. let mut dropper_pos : Position = Position{x:0, y:0};
  243. {
  244. let dropped_pos = positions.get(entity).unwrap();
  245. dropper_pos.x = dropped_pos.x;
  246. dropper_pos.y = dropped_pos.y;
  247. }
  248. positions.insert(to_drop.item, Position{ x : dropper_pos.x, y : dropper_pos.y }).expect("Unable to insert position");
  249. backpack.remove(to_drop.item);
  250. if entity == *player_entity {
  251. gamelog.entries.insert(0, format!("You drop up the {}.", names.get(to_drop.item).unwrap().name));
  252. }
  253. }
  254. wants_drop.clear();
  255. }
  256. }
  257. pub struct ItemRemoveSystem {}
  258. impl<'a> System<'a> for ItemRemoveSystem {
  259. #[allow(clippy::type_complexity)]
  260. type SystemData = (
  261. Entities<'a>,
  262. WriteStorage<'a, WantsToRemoveItem>,
  263. WriteStorage<'a, Equipped>,
  264. WriteStorage<'a, InBackpack>
  265. );
  266. fn run(&mut self, data : Self::SystemData) {
  267. let (entities, mut wants_remove, mut equipped, mut backpack) = data;
  268. for (entity, to_remove) in (&entities, &wants_remove).join() {
  269. equipped.remove(to_remove.item);
  270. backpack.insert(to_remove.item, InBackpack{ owner: entity }).expect("Unable to insert backpack");
  271. }
  272. wants_remove.clear();
  273. }
  274. }