From 969ecf7feff3fe83f3d5d70cbfc4a8a41b67a140 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 25 Jan 2022 09:58:30 -0500 Subject: [PATCH] Setup parts of spell system --- src/components.rs | 22 ++++++++++++ src/gui.rs | 21 ++++++++++- src/main.rs | 3 ++ src/player.rs | 69 +++++++++++++++++++++++++++++++++---- src/raws.rs | 5 ++- src/raws/faction_structs.rs | 2 +- src/raws/rawmaster.rs | 63 +++++++++++++++++++++++++-------- src/raws/spell_structs.rs | 9 +++++ src/saveload_system.rs | 6 ++++ src/spawner.rs | 11 +++++- 10 files changed, 186 insertions(+), 25 deletions(-) create mode 100644 src/raws/spell_structs.rs diff --git a/src/components.rs b/src/components.rs index 40effcd..3db07f4 100644 --- a/src/components.rs +++ b/src/components.rs @@ -401,3 +401,25 @@ pub struct Duration { pub struct StatusEffect { pub target: Entity, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct KnownSpell { + pub display_name: String, + pub mana_cost: i32, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct KnownSpells { + pub spells: Vec, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct SpellTemplate { + pub mana_cost: i32, +} + +#[derive(Component, Debug, ConvertSaveload, Clone)] +pub struct WantsToCastSpell { + pub spell: Entity, + pub target: Option, +} diff --git a/src/gui.rs b/src/gui.rs index a4e14a2..860a51e 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -12,7 +12,8 @@ use tooltip::draw_tooltips; use crate::components::{ Attribute, Attributes, Consumable, CursedItem, Duration, Equipped, HungerClock, HungerState, - InBackpack, MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, StatusEffect, Viewshed, + InBackpack, KnownSpells, MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, StatusEffect, + Viewshed, }; use crate::game_log::GameLog; use crate::{camera, colors, Map, MasterDungeonMap, State}; @@ -243,6 +244,24 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { } } + // Spells + y += 1; + let known_spells_storage = ecs.read_storage::(); + let known_spells = &known_spells_storage.get(*player_entity).unwrap().spells; + let mut index = 1; + for spell in known_spells.iter() { + ctx.print_color(50, y, colors::CYAN, colors::BLACK, &format!("^{}", index)); + ctx.print_color( + 53, + y, + colors::CYAN, + colors::BLACK, + &format!("{} ({})", spell.display_name, spell.mana_cost), + ); + index += 1; + y += 1; + } + // Status y = 44; let hunger = ecs.read_storage::(); diff --git a/src/main.rs b/src/main.rs index e7add3a..515ab16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,6 +108,7 @@ fn init_state() -> State { InflictsDamage, Initiative, Item, + KnownSpells, LightSource, LootTable, MagicItem, @@ -136,12 +137,14 @@ fn init_state() -> State { Skills, SpawnParticleBurst, SpawnParticleLine, + SpellTemplate, StatusEffect, TeleportTo, TownPortal, Vendor, Viewshed, WantsToApproach, + WantsToCastSpell, WantsToDropItem, WantsToFlee, WantsToMelee, diff --git a/src/player.rs b/src/player.rs index a159ade..40e2dbe 100644 --- a/src/player.rs +++ b/src/player.rs @@ -9,7 +9,7 @@ use crate::components::{ }; use crate::game_log::GameLog; use crate::raws::{self, Reaction, RAWS}; -use crate::{spatial, Map, RunState, State, TileType, VendorMode}; +use crate::{spatial, Map, RunState, State, TileType, VendorMode, WantsToCastSpell}; pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState { let mut positions = ecs.write_storage::(); @@ -298,10 +298,53 @@ fn use_consumable_hotkey(gs: &mut State, key: i32) -> RunState { RunState::Ticking } -pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { - // Hotkeys - if ctx.shift && ctx.key.is_some() { - let key: Option = match ctx.key.unwrap() { +fn use_spell_hotkey(gs: &mut State, key: i32) -> RunState { + use crate::components::{KnownSpells, Ranged}; + use crate::raws::find_spell_entity; + + let player_entity = gs.ecs.fetch::(); + let known_spells_storage = gs.ecs.read_storage::(); + let known_spells = &known_spells_storage.get(*player_entity).unwrap().spells; + + if (key as usize) < known_spells.len() { + let pools = gs.ecs.read_storage::(); + let player_pools = pools.get(*player_entity).unwrap(); + if player_pools.mana.current >= known_spells[key as usize].mana_cost { + if let Some(spell_entity) = + find_spell_entity(&gs.ecs, &known_spells[key as usize].display_name) + { + if let Some(ranged) = gs.ecs.read_storage::().get(spell_entity) { + return RunState::ShowTargeting { + range: ranged.range, + item: spell_entity, + }; + } + + let mut intent = gs.ecs.write_storage::(); + intent + .insert( + *player_entity, + WantsToCastSpell { + spell: spell_entity, + target: None, + }, + ) + .expect("Unable to insert intent to cast spell"); + + return RunState::Ticking; + } + } else { + let mut gamelog = gs.ecs.fetch_mut::(); + gamelog.append("You don't have enough mana to cast that!"); + } + } + + RunState::Ticking +} + +fn get_number_key(ctx: &mut Rltk) -> Option { + match ctx.key { + Some(key) => match key { VirtualKeyCode::Key1 => Some(1), VirtualKeyCode::Key2 => Some(2), VirtualKeyCode::Key3 => Some(3), @@ -312,11 +355,25 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { 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); + } + } // Mac OS is special when it comes to the numpad. Instead of reporting // the keys as Numpad-specific numbers, it reports the number row scan diff --git a/src/raws.rs b/src/raws.rs index 34032ed..83d3655 100644 --- a/src/raws.rs +++ b/src/raws.rs @@ -5,6 +5,7 @@ mod mob_structs; mod prop_structs; mod rawmaster; mod spawn_table_structs; +mod spell_structs; use std::sync::Mutex; @@ -17,8 +18,9 @@ use mob_structs::*; use prop_structs::*; pub use rawmaster::*; use spawn_table_structs::*; +pub use spell_structs::Spell; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Default, Debug)] pub struct Raws { pub items: Vec, pub mobs: Vec, @@ -26,6 +28,7 @@ pub struct Raws { pub spawn_table: Vec, pub loot_tables: Vec, pub faction_table: Vec, + pub spells: Vec, } embedded_resource!(RAW_FILE, "../raws/spawns.json"); diff --git a/src/raws/faction_structs.rs b/src/raws/faction_structs.rs index d157b6d..c6c4310 100644 --- a/src/raws/faction_structs.rs +++ b/src/raws/faction_structs.rs @@ -8,7 +8,7 @@ pub struct FactionInfo { pub responses: HashMap, } -#[derive(PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum Reaction { Ignore, Attack, diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 7f4e09c..b5d75d9 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -40,6 +40,7 @@ pub enum SpawnType { Carried { by: Entity }, } +#[derive(Debug, Default)] pub struct RawMaster { raws: Raws, item_index: HashMap, @@ -47,25 +48,12 @@ pub struct RawMaster { prop_index: HashMap, loot_index: HashMap, faction_index: HashMap>, + spell_index: HashMap, } impl RawMaster { pub fn empty() -> RawMaster { - RawMaster { - raws: Raws { - items: Vec::new(), - mobs: Vec::new(), - props: Vec::new(), - spawn_table: Vec::new(), - loot_tables: Vec::new(), - faction_table: Vec::new(), - }, - item_index: HashMap::new(), - mob_index: HashMap::new(), - prop_index: HashMap::new(), - loot_index: HashMap::new(), - faction_index: HashMap::new(), - } + RawMaster::default() } pub fn load(&mut self, raws: Raws) { @@ -130,6 +118,10 @@ impl RawMaster { } self.faction_index.insert(faction.name.clone(), reactions); } + + for (i, spell) in self.raws.spells.iter().enumerate() { + self.spell_index.insert(spell.name.clone(), i); + } } } @@ -722,6 +714,26 @@ pub fn spawn_named_entity( None } +pub fn spawn_named_spell(raws: &RawMaster, ecs: &mut World, key: &str) -> Option { + if raws.spell_index.contains_key(key) { + let spell_template = &raws.raws.spells[raws.spell_index[key]]; + + let mut eb = ecs + .create_entity() + .marked::>() + .with(SpellTemplate { + mana_cost: spell_template.mana_cost, + }) + .with(Name::from(*spell_template.name)); + + apply_effects!(spell_template.effects, eb); + + return Some(eb.build()); + } + + None +} + pub fn get_spawn_table_for_depth(raws: &RawMaster, depth: i32) -> RandomTable { use super::SpawnTableEntry; @@ -761,3 +773,24 @@ pub fn get_item_drop( None } + +pub fn spawn_all_spells(ecs: &mut World) { + let raws = &RAWS.lock().unwrap(); + for spell in raws.raws.spells.iter() { + spawn_named_spell(raws, ecs, &spell.name); + } +} + +pub fn find_spell_entity(ecs: &World, name: &str) -> Option { + let names = ecs.read_storage::(); + let spell_templates = ecs.read_storage::(); + let entities = ecs.entities(); + + for (entity, sname, _template) in (&entities, &names, &spell_templates).join() { + if name == sname.name { + return Some(entity); + } + } + + None +} diff --git a/src/raws/spell_structs.rs b/src/raws/spell_structs.rs new file mode 100644 index 0000000..fafcfb5 --- /dev/null +++ b/src/raws/spell_structs.rs @@ -0,0 +1,9 @@ +use std::collections::HashMap; + +use ::serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct Spell { + pub name: String, + pub effects: HashMap, +} diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 090d622..ac4f397 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -87,6 +87,7 @@ pub fn save_game(ecs: &mut World) { InflictsDamage, Initiative, Item, + KnownSpells, LightSource, LootTable, MagicItem, @@ -114,12 +115,14 @@ pub fn save_game(ecs: &mut World) { Skills, SpawnParticleBurst, SpawnParticleLine, + SpellTemplate, StatusEffect, TeleportTo, TownPortal, Vendor, Viewshed, WantsToApproach, + WantsToCastSpell, WantsToDropItem, WantsToFlee, WantsToMelee, @@ -210,6 +213,7 @@ pub fn load_game(ecs: &mut World) { InflictsDamage, Initiative, Item, + KnownSpells, LightSource, LootTable, MagicItem, @@ -237,12 +241,14 @@ pub fn load_game(ecs: &mut World) { Skills, SpawnParticleBurst, SpawnParticleLine, + SpellTemplate, StatusEffect, TeleportTo, TownPortal, Vendor, Viewshed, WantsToApproach, + WantsToCastSpell, WantsToDropItem, WantsToFlee, WantsToMelee, diff --git a/src/spawner.rs b/src/spawner.rs index 80b0903..b941f8e 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -7,11 +7,14 @@ use ::specs::saveload::{MarkedBuilder, SimpleMarker}; use crate::components::*; use crate::gamesystem::{mana_at_level, player_hp_at_level}; use crate::random_table::RandomTable; -use crate::raws::{get_spawn_table_for_depth, spawn_named_entity, SpawnType, RAWS}; +use crate::raws::{ + get_spawn_table_for_depth, spawn_all_spells, spawn_named_entity, SpawnType, RAWS, +}; use crate::{colors, Map, MasterDungeonMap, Rect, TileType}; /// Spawns the player and returns their entity object pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { + spawn_all_spells(ecs); let player = ecs .create_entity() .with(Position { @@ -55,6 +58,12 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { .with(Initiative { current: 0 }) .with(Faction::from("Player")) .with(EquipmentChanged {}) + .with(KnownSpells { + spells: vec![KnownSpell { + display_name: "Zap".to_string(), + mana_cost: 1, + }], + }) .marked::>() .build();