From 66f5a8d82638f0233a6ba6024e955827c111e7cc Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Thu, 17 Aug 2023 09:49:58 +0100 Subject: [PATCH] refactors wands/items with charges --- raws/items.json | 8 +- src/ai/turn_status_system.rs | 4 +- src/components.rs | 2 +- src/effects/damage.rs | 5 +- src/effects/mod.rs | 4 + src/effects/triggers.rs | 41 +++- src/gui/mod.rs | 10 +- src/inventory/collection_system.rs | 6 +- src/inventory/drop_system.rs | 6 +- src/inventory/use_system_backup.rs | 330 ----------------------------- src/main.rs | 2 +- src/raws/rawmaster.rs | 2 +- src/saveload_system.rs | 4 +- 13 files changed, 67 insertions(+), 357 deletions(-) delete mode 100644 src/inventory/use_system_backup.rs diff --git a/raws/items.json b/raws/items.json index aa5eabb..91656dd 100644 --- a/raws/items.json +++ b/raws/items.json @@ -313,7 +313,7 @@ "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "weight": 2, "value": 100, - "flags": ["WAND"], + "flags": ["CHARGES"], "effects": { "ranged": "12", "damage": "10" }, "magic": { "class": "uncommon", "naming": "wand" } }, @@ -323,7 +323,7 @@ "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "weight": 2, "value": 300, - "flags": ["WAND"], + "flags": ["CHARGES"], "effects": { "ranged": "10", "damage": "15", "aoe": "3" }, "magic": { "class": "rare", "naming": "wand" } }, @@ -333,7 +333,7 @@ "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "weight": 2, "value": 200, - "flags": ["WAND"], + "flags": ["CHARGES"], "effects": { "ranged": "10", "confusion": "4" }, "magic": { "class": "uncommon", "naming": "wand" } }, @@ -343,7 +343,7 @@ "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "weight": 2, "value": 300, - "flags": ["WAND"], + "flags": ["CHARGES"], "effects": { "ranged": "10", "digger": "" }, "magic": { "class": "rare", "naming": "wand" } }, diff --git a/src/ai/turn_status_system.rs b/src/ai/turn_status_system.rs index f7b369c..0945bfc 100644 --- a/src/ai/turn_status_system.rs +++ b/src/ai/turn_status_system.rs @@ -41,7 +41,7 @@ impl<'a> System<'a> for TurnStatusSystem { if entity == *player_entity { logger = logger .colour(renderable_colour(&renderables, entity)) - .append(&name.name) + .append("You") .colour(WHITE) .append("snap out of it."); log = true; @@ -70,7 +70,7 @@ impl<'a> System<'a> for TurnStatusSystem { if entity == *player_entity { logger = logger .colour(renderable_colour(&renderables, entity)) - .append(&name.name) + .append("You") .colour(WHITE) .append("are confused!"); log = true; diff --git a/src/components.rs b/src/components.rs index 0d947df..7dfa8d9 100644 --- a/src/components.rs +++ b/src/components.rs @@ -382,7 +382,7 @@ pub struct Chasing { pub struct Consumable {} #[derive(Component, Debug, ConvertSaveload)] -pub struct Wand { +pub struct Charges { pub uses: i32, pub max_uses: i32, } diff --git a/src/effects/damage.rs b/src/effects/damage.rs index 71cfbd6..c400e50 100644 --- a/src/effects/damage.rs +++ b/src/effects/damage.rs @@ -2,7 +2,8 @@ use super::{add_effect, targeting, EffectSpawner, EffectType, Entity, Targets, W use crate::{ gamelog, gamesystem::{hp_per_level, mana_per_level}, - Attributes, Confusion, GrantsXP, Map, Player, Pools, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME, + Attributes, Confusion, Destructible, GrantsXP, Map, Player, Pools, DEFAULT_PARTICLE_LIFETIME, + LONG_PARTICLE_LIFETIME, }; use rltk::prelude::*; use specs::prelude::*; @@ -30,6 +31,8 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) { } } } + } else if let Some(_destructible) = ecs.read_storage::().get(target) { + add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target }); } } diff --git a/src/effects/mod.rs b/src/effects/mod.rs index d0482fd..5c106f1 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -26,6 +26,7 @@ pub enum EffectType { EntityDeath, ItemUse { item: Entity }, RestoreNutrition { buc: i32 }, + TriggerFire { trigger: Entity }, } #[derive(Clone)] @@ -67,6 +68,9 @@ fn target_applicator(ecs: &mut World, effect: &EffectSpawner) { if let EffectType::ItemUse { item } = effect.effect_type { triggers::item_trigger(effect.source, item, &effect.target, ecs); return; + } else if let EffectType::TriggerFire { trigger } = effect.effect_type { + triggers::trigger(effect.source, trigger, &effect.target, ecs); + return; } // Otherwise, just match the effect and enact it directly. match &effect.target { diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index 16f175e..e2ae764 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -1,13 +1,26 @@ use super::{add_effect, spatial, EffectType, Entity, Targets, World}; use crate::{ - gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, gui::renderable_colour, Confusion, Consumable, Cursed, - InflictsDamage, MagicMapper, Player, Prop, ProvidesHealing, ProvidesNutrition, RandomNumberGenerator, Renderable, - RunState, + gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, gui::renderable_colour, Charges, Confusion, Consumable, + Cursed, Destructible, Hidden, InflictsDamage, Item, MagicMapper, Player, Prop, ProvidesHealing, ProvidesNutrition, + RandomNumberGenerator, Renderable, RunState, SingleActivation, }; use rltk::prelude::*; use specs::prelude::*; pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: &mut World) { + // Check if the item has charges, etc. + if let Some(has_charges) = ecs.write_storage::().get_mut(item) { + if has_charges.uses <= 0 { + let mut rng = ecs.write_resource::(); + if rng.roll_dice(1, 121) != 1 { + gamelog::Logger::new().append("The wand does nothing.").log(); + return; + } + gamelog::Logger::new().colour(rltk::YELLOW).append("You wrest one last charge from the worn-out wand."); + ecs.write_storage::().insert(item, Consumable {}).expect("Could not insert consumable"); + } + has_charges.uses -= 1; + } // Use the item via the generic system event_trigger(source, item, target, ecs); // If it's a consumable, delete it @@ -16,6 +29,17 @@ pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: } } +pub fn trigger(source: Option, trigger: Entity, target: &Targets, ecs: &mut World) { + // Remove hidden from the trigger + ecs.write_storage::().remove(trigger); + // Use the trigger via the generic system + event_trigger(source, trigger, target, ecs); + // If it was single-activation, delete it + if ecs.read_storage::().get(trigger).is_some() { + ecs.entities().delete(trigger).expect("Failed to delete entity with a SingleActivation"); + } +} + pub const BLESSED: i32 = 2; pub const UNCURSED: i32 = 1; pub const CURSED: i32 = 0; @@ -87,7 +111,7 @@ fn handle_healing(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::L let roll = rng.roll_dice(healing_item.n_dice, healing_item.sides) + healing_item.modifier; add_effect(event.source, EffectType::Healing { amount: roll }, event.target.clone()); for target in get_entity_targets(&event.target) { - if ecs.read_storage::().get(target).is_some() { + if ecs.read_storage::().get(target).is_some() || ecs.read_storage::().get(target).is_some() { continue; } let renderables = ecs.read_storage::(); @@ -128,6 +152,15 @@ fn handle_damage(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Lo .append(obfuscate_name_ecs(ecs, target).0) .colour(WHITE) .append("are hit!"); + } else if ecs.read_storage::().get(target).is_some() { + if ecs.read_storage::().get(target).is_some() { + logger = logger + .append("The") + .colour(renderable_colour(&renderables, target)) + .append(obfuscate_name_ecs(ecs, target).0) + .colour(WHITE) + .append("is ruined!"); + } } else { logger = logger .append("The") diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 7332454..fe79585 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,8 +1,8 @@ use super::{ ai::CARRY_CAPACITY_PER_STRENGTH, camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes, - Burden, Equipped, Hidden, HungerClock, HungerState, InBackpack, MagicItem, MagicItemClass, Map, MasterDungeonMap, - Name, ObfuscatedName, Player, Point, Pools, Position, Prop, Renderable, RunState, Skill, Skills, State, Viewshed, - Wand, + Burden, Charges, Equipped, Hidden, HungerClock, HungerState, InBackpack, MagicItem, MagicItemClass, Map, + MasterDungeonMap, Name, ObfuscatedName, Player, Point, Pools, Position, Prop, Renderable, RunState, Skill, Skills, + State, Viewshed, }; use rltk::prelude::*; use specs::prelude::*; @@ -393,7 +393,7 @@ pub fn obfuscate_name( magic_items: &ReadStorage, obfuscated_names: &ReadStorage, dm: &MasterDungeonMap, - wand: Option<&ReadStorage>, + wand: Option<&ReadStorage>, ) -> (String, String) { let (mut singular, mut plural) = ("nameless item (bug)".to_string(), "nameless items (bug)".to_string()); if let Some(name) = names.get(item) { @@ -439,7 +439,7 @@ pub fn obfuscate_name_ecs(ecs: &World, item: Entity) -> (String, String) { (singular, plural) = (name.name.clone(), name.plural.clone()); } } - if let Some(wand) = ecs.read_storage::().get(item) { + if let Some(wand) = ecs.read_storage::().get(item) { let used = wand.max_uses - wand.uses; for _i in 0..used { singular.push_str("*"); diff --git a/src/inventory/collection_system.rs b/src/inventory/collection_system.rs index dfcf5eb..936b7be 100644 --- a/src/inventory/collection_system.rs +++ b/src/inventory/collection_system.rs @@ -1,6 +1,6 @@ use crate::{ - gamelog, gui::obfuscate_name, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, - Position, Wand, WantsToPickupItem, + gamelog, gui::obfuscate_name, Charges, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, + ObfuscatedName, Position, WantsToPickupItem, }; use specs::prelude::*; @@ -18,7 +18,7 @@ impl<'a> System<'a> for ItemCollectionSystem { ReadStorage<'a, MagicItem>, ReadStorage<'a, ObfuscatedName>, ReadExpect<'a, MasterDungeonMap>, - ReadStorage<'a, Wand>, + ReadStorage<'a, Charges>, ); fn run(&mut self, data: Self::SystemData) { diff --git a/src/inventory/drop_system.rs b/src/inventory/drop_system.rs index 0a9df3f..2d1238c 100644 --- a/src/inventory/drop_system.rs +++ b/src/inventory/drop_system.rs @@ -1,6 +1,6 @@ use crate::{ - gamelog, gui::obfuscate_name, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, ObfuscatedName, - Position, Wand, WantsToDropItem, + gamelog, gui::obfuscate_name, Charges, EquipmentChanged, InBackpack, MagicItem, MasterDungeonMap, Name, + ObfuscatedName, Position, WantsToDropItem, }; use specs::prelude::*; @@ -19,7 +19,7 @@ impl<'a> System<'a> for ItemDropSystem { ReadStorage<'a, MagicItem>, ReadStorage<'a, ObfuscatedName>, ReadExpect<'a, MasterDungeonMap>, - ReadStorage<'a, Wand>, + ReadStorage<'a, Charges>, ); fn run(&mut self, data: Self::SystemData) { diff --git a/src/inventory/use_system_backup.rs b/src/inventory/use_system_backup.rs deleted file mode 100644 index 286b8c4..0000000 --- a/src/inventory/use_system_backup.rs +++ /dev/null @@ -1,330 +0,0 @@ -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/main.rs b/src/main.rs index 5f22f58..2458337 100644 --- a/src/main.rs +++ b/src/main.rs @@ -576,7 +576,7 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); - gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 5c5d8bc..53b9562 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -160,7 +160,7 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn "EQUIP_HANDS" => eb = eb.with(Equippable { slot: EquipmentSlot::Hands }), "EQUIP_NECK" => eb = eb.with(Equippable { slot: EquipmentSlot::Neck }), "EQUIP_BACK" => eb = eb.with(Equippable { slot: EquipmentSlot::Back }), - "WAND" => eb = eb.with(Wand { uses: 3, max_uses: 3 }), + "CHARGES" => eb = eb.with(Charges { uses: 3, max_uses: 3 }), "FOOD" => eb = eb.with(ProvidesNutrition {}), "STRENGTH" => weapon_type = 0, "DEXTERITY" => weapon_type = 2, diff --git a/src/saveload_system.rs b/src/saveload_system.rs index c8e32f2..39a5477 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -104,7 +104,7 @@ pub fn save_game(ecs: &mut World) { TakingTurn, Telepath, Viewshed, - Wand, + Charges, WantsToApproach, WantsToDropItem, WantsToFlee, @@ -221,7 +221,7 @@ pub fn load_game(ecs: &mut World) { TakingTurn, Telepath, Viewshed, - Wand, + Charges, WantsToApproach, WantsToDropItem, WantsToFlee,