From e6a01791bfdccd7363946088615766202e43f54b Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 25 Jan 2022 11:15:32 -0500 Subject: [PATCH] Implement system to use spells --- src/effects.rs | 5 ++ src/effects/targeting.rs | 9 +++- src/effects/triggers.rs | 21 ++++++-- src/inventory_system.rs | 2 +- src/inventory_system/use_system.rs | 77 +++++++++++++++++++++++++++++- src/player.rs | 28 ++++------- src/raws/rawmaster.rs | 2 +- src/raws/spell_structs.rs | 1 + src/spawner.rs | 16 ++++--- src/state.rs | 42 +++++++++++----- 10 files changed, 159 insertions(+), 44 deletions(-) diff --git a/src/effects.rs b/src/effects.rs index 34395ef..433a1c9 100644 --- a/src/effects.rs +++ b/src/effects.rs @@ -35,6 +35,9 @@ pub enum EffectType { ItemUse { item: Entity, }, + SpellUse { + spell: Entity, + }, WellFed, Healing { amount: i32, @@ -98,6 +101,8 @@ pub fn run_effects_queue(ecs: &mut World) { fn target_applicator(ecs: &mut World, effect: &EffectSpawner) { if let EffectType::ItemUse { item } = effect.effect_type { triggers::item_trigger(effect.creator, item, &effect.targets, ecs); + } else if let EffectType::SpellUse { spell } = effect.effect_type { + triggers::spell_trigger(effect.creator, spell, &effect.targets, ecs); } else if let EffectType::TriggerFire { trigger } = effect.effect_type { triggers::trigger(effect.creator, trigger, &effect.targets, ecs); } else { diff --git a/src/effects/targeting.rs b/src/effects/targeting.rs index acc28af..78a7d52 100644 --- a/src/effects/targeting.rs +++ b/src/effects/targeting.rs @@ -23,7 +23,7 @@ pub fn aoe_tiles(map: &Map, target: ::rltk::Point, radius: i32) -> Vec { result } -pub fn find_item_position(ecs: &World, target: Entity) -> Option { +pub fn find_item_position(ecs: &World, target: Entity, creator: Option) -> Option { let positions = ecs.read_storage::(); let map = ecs.fetch::(); @@ -46,6 +46,13 @@ pub fn find_item_position(ecs: &World, target: Entity) -> Option { } } + // Maybe the creator has a position? + if let Some(creator) = creator { + if let Some(pos) = positions.get(creator) { + return Some(map.xy_idx(pos.x, pos.y) as i32); + } + } + // No idea - give up None } diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index a460e6a..bbe4c8a 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -3,8 +3,8 @@ use ::specs::prelude::*; use super::{add_effect, EffectType, Targets}; use crate::components::{ AttributeBonus, Confusion, Consumable, Duration, Hidden, InflictsDamage, MagicMapper, Name, - ProvidesFood, ProvidesHealing, ProvidesRemoveCurse, SingleActivation, SpawnParticleBurst, - SpawnParticleLine, TeleportTo, TownPortal, + Pools, ProvidesFood, ProvidesHealing, ProvidesRemoveCurse, SingleActivation, + SpawnParticleBurst, SpawnParticleLine, SpellTemplate, TeleportTo, TownPortal, }; use crate::effects::{entity_position, targeting}; use crate::{colors, GameLog, Map, RunState}; @@ -41,6 +41,21 @@ pub fn item_trigger(creator: Option, item: Entity, targets: &Targets, ec } } +pub fn spell_trigger(creator: Option, spell: Entity, targets: &Targets, ecs: &mut World) { + if let Some(template) = ecs.read_storage::().get(spell) { + let mut pools = ecs.write_storage::(); + if let Some(caster) = creator { + if let Some(pool) = pools.get_mut(caster) { + if template.mana_cost <= pool.mana.current { + pool.mana.current -= template.mana_cost; + } + } + } + } + + event_trigger(creator, spell, targets, ecs); +} + pub fn trigger(creator: Option, trigger: Entity, targets: &Targets, ecs: &mut World) { // The triggering item is no longer hidden ecs.write_storage::().remove(trigger); @@ -86,7 +101,7 @@ fn event_trigger( // Line particle spawn if let Some(part) = ecs.read_storage::().get(entity) { - if let Some(start_pos) = targeting::find_item_position(ecs, entity) { + if let Some(start_pos) = targeting::find_item_position(ecs, entity, creator) { match targets { Targets::Tile { tile_idx } => spawn_line_particles(ecs, start_pos, *tile_idx, part), Targets::Tiles { tiles } => tiles diff --git a/src/inventory_system.rs b/src/inventory_system.rs index 3e40f43..d39af02 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -11,7 +11,7 @@ pub use drop_system::ItemDropSystem; pub use identification_system::ItemIdentificationSystem; pub use remove_system::ItemRemoveSystem; pub use use_equip::ItemEquipOnUse; -pub use use_system::ItemUseSystem; +pub use use_system::{ItemUseSystem, SpellUseSystem}; use crate::components::{MagicItem, Name, ObfuscatedName}; use crate::MasterDungeonMap; diff --git a/src/inventory_system/use_system.rs b/src/inventory_system/use_system.rs index 43cd927..3032976 100644 --- a/src/inventory_system/use_system.rs +++ b/src/inventory_system/use_system.rs @@ -1,6 +1,8 @@ use ::specs::prelude::*; -use crate::components::{AreaOfEffect, EquipmentChanged, IdentifiedItem, Name, WantsToUseItem}; +use crate::components::{ + AreaOfEffect, EquipmentChanged, IdentifiedItem, Name, WantsToCastSpell, WantsToUseItem, +}; use crate::effects::{add_effect, aoe_tiles, EffectType, Targets}; use crate::Map; @@ -75,3 +77,76 @@ impl<'a> System<'a> for ItemUseSystem { wants_use.clear(); } } + +pub struct SpellUseSystem {} + +impl<'a> System<'a> for SpellUseSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + ReadExpect<'a, Entity>, + WriteExpect<'a, Map>, + Entities<'a>, + WriteStorage<'a, WantsToCastSpell>, + ReadStorage<'a, Name>, + ReadStorage<'a, AreaOfEffect>, + WriteStorage<'a, EquipmentChanged>, + WriteStorage<'a, IdentifiedItem>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + player_entity, + map, + entities, + mut wants_use, + names, + aoe, + mut dirty, + mut identified_item, + ) = data; + + for (entity, useitem) in (&entities, &wants_use).join() { + dirty + .insert(entity, EquipmentChanged {}) + .expect("Unable to insert EquipmentChanged tag"); + + // Identify + if entity == *player_entity { + identified_item + .insert( + entity, + IdentifiedItem { + name: names.get(useitem.spell).unwrap().name.clone(), + }, + ) + .expect("Unable to insert item identification"); + } + + // Call the effects system + add_effect( + Some(entity), + EffectType::SpellUse { + spell: useitem.spell, + }, + match useitem.target { + None => Targets::Single { + target: *player_entity, + }, + Some(target) => { + if let Some(aoe) = aoe.get(useitem.spell) { + Targets::Tiles { + tiles: aoe_tiles(&*map, target, aoe.radius), + } + } else { + Targets::Tile { + tile_idx: map.xy_idx(target.x, target.y) as i32, + } + } + } + }, + ) + } + + wants_use.clear(); + } +} diff --git a/src/player.rs b/src/player.rs index 40e2dbe..8b7d2f1 100644 --- a/src/player.rs +++ b/src/player.rs @@ -342,9 +342,10 @@ fn use_spell_hotkey(gs: &mut State, key: i32) -> RunState { RunState::Ticking } -fn get_number_key(ctx: &mut Rltk) -> Option { - match ctx.key { - Some(key) => match key { +pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { + // Hotkeys + if (ctx.shift || ctx.control) && ctx.key.is_some() { + let key: Option = match ctx.key.unwrap() { VirtualKeyCode::Key1 => Some(1), VirtualKeyCode::Key2 => Some(2), VirtualKeyCode::Key3 => Some(3), @@ -355,23 +356,14 @@ fn get_number_key(ctx: &mut Rltk) -> Option { VirtualKeyCode::Key8 => Some(8), VirtualKeyCode::Key9 => Some(9), _ => None, - }, - None => None, - } -} + }; -pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { - // Hotkeys - if ctx.shift && ctx.key.is_some() { - let key: Option = get_number_key(ctx); if let Some(key) = key { - return use_consumable_hotkey(gs, key - 1); - } - } - if ctx.control && ctx.key.is_some() { - let key: Option = get_number_key(ctx); - if let Some(key) = key { - return use_spell_hotkey(gs, key - 1); + if ctx.shift { + return use_consumable_hotkey(gs, key - 1); + } else if ctx.control { + return use_spell_hotkey(gs, key - 1); + } } } diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index b5d75d9..9ad8217 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -724,7 +724,7 @@ pub fn spawn_named_spell(raws: &RawMaster, ecs: &mut World, key: &str) -> Option .with(SpellTemplate { mana_cost: spell_template.mana_cost, }) - .with(Name::from(*spell_template.name)); + .with(Name::from(spell_template.name.clone())); apply_effects!(spell_template.effects, eb); diff --git a/src/raws/spell_structs.rs b/src/raws/spell_structs.rs index fafcfb5..34b038e 100644 --- a/src/raws/spell_structs.rs +++ b/src/raws/spell_structs.rs @@ -5,5 +5,6 @@ use ::serde::Deserialize; #[derive(Deserialize, Debug)] pub struct Spell { pub name: String, + pub mana_cost: i32, pub effects: HashMap, } diff --git a/src/spawner.rs b/src/spawner.rs index b941f8e..1101508 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -51,13 +51,13 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { gold: 0., god_mode: false, }) + .with(EquipmentChanged {}) .with(LightSource { color: colors::TORCH_LIGHT, range: 8, }) .with(Initiative { current: 0 }) .with(Faction::from("Player")) - .with(EquipmentChanged {}) .with(KnownSpells { spells: vec![KnownSpell { display_name: "Zap".to_string(), @@ -195,15 +195,15 @@ pub fn spawn_region( /// Spawns a named entity (name in tuple.1) at the location in (tuple.0) pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { - let map = ecs.fetch::(); - let width = map.width as usize; + let width: usize; + { + width = ecs.fetch::().width as usize; + } + let (idx, name) = *spawn; let x = (*idx % width) as i32; let y = (*idx / width) as i32; - // Drop this map reference to make the borrow checker happy - std::mem::drop(map); - let item_result = spawn_named_entity( &RAWS.lock().unwrap(), ecs, @@ -214,7 +214,9 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { return; } - ::rltk::console::log(format!("WARNING: We don't know how to spawn [{}]!", name)); + if !name.is_empty() { + ::rltk::console::log(format!("WARNING: We don't know how to spawn [{}]!", name)); + } } pub fn spawn_town_portal(ecs: &mut World) { diff --git a/src/state.rs b/src/state.rs index 5e7643d..e285968 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use crate::gui::{self, show_cheat_mode, CheatMenuResult, MainMenuSelection}; use crate::hunger_system::HungerSystem; use crate::inventory_system::{ ItemCollectionSystem, ItemDropSystem, ItemEquipOnUse, ItemIdentificationSystem, - ItemRemoveSystem, ItemUseSystem, + ItemRemoveSystem, ItemUseSystem, SpellUseSystem, }; use crate::lighting_system::LightingSystem; use crate::map::{self, *}; @@ -133,6 +133,9 @@ impl State { let mut itemuse = ItemUseSystem {}; itemuse.run_now(&self.ecs); + let mut spelluse = SpellUseSystem {}; + spelluse.run_now(&self.ecs); + let mut item_id = ItemIdentificationSystem {}; item_id.run_now(&self.ecs); @@ -348,19 +351,34 @@ impl GameState for State { gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { - let mut intent = self.ecs.write_storage::(); + if self.ecs.read_storage::().get(item).is_some() { + let mut intent = self.ecs.write_storage::(); + intent + .insert( + *self.ecs.fetch::(), + WantsToCastSpell { + spell: item, + target: result.1, + }, + ) + .expect("failed to add intent to cast spell"); - intent - .insert( - *self.ecs.fetch::(), - WantsToUseItem { - item, - target: result.1, - }, - ) - .expect("failed to add intent to use item"); + newrunstate = RunState::Ticking; + } else { + let mut intent = self.ecs.write_storage::(); - newrunstate = RunState::Ticking; + intent + .insert( + *self.ecs.fetch::(), + WantsToUseItem { + item, + target: result.1, + }, + ) + .expect("failed to add intent to use item"); + + newrunstate = RunState::Ticking; + } } } }