From 2b7ec386a03747000876849c7ce7c3e49ea2d678 Mon Sep 17 00:00:00 2001 From: Benjamin Sherriff Date: Wed, 4 Oct 2023 10:00:39 -0400 Subject: [PATCH] Condensed schemas into json, will address later --- Cargo.toml | 1 + data/spells/cantrips.json | 125 ++++- migrations/000007_create_spells/up.sql | 27 +- src/db/classes/model.rs | 6 + src/db/conditions/mod.rs | 15 + src/db/schema.rs | 27 +- src/db/spells/mod.rs | 2 + src/db/spells/model.rs | 624 ++----------------------- src/db/spells/types.rs | 377 +++++++++++++++ 9 files changed, 557 insertions(+), 647 deletions(-) create mode 100644 src/db/spells/types.rs diff --git a/Cargo.toml b/Cargo.toml index fde71d0..03e4fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ env_logger = "0.10.0" diesel_migrations = { version = "2.1.0", features = ["postgres"] } r2d2 = "0.8.10" lazy_static = "1.4.0" +uuid = { version = "1.4.1", features = ["serde", "v4"] } [dependencies.serenity] version = "0.11.6" diff --git a/data/spells/cantrips.json b/data/spells/cantrips.json index 477d409..908160a 100644 --- a/data/spells/cantrips.json +++ b/data/spells/cantrips.json @@ -13,6 +13,13 @@ "amount": 60, "unit": "feet" }, + "saving_throw": [ + "dexterity" + ], + "damage_inflict": [ + "acid" + ], + "attack_type": "ranged", "components": { "verbal": true, "somatic": true, @@ -31,7 +38,8 @@ "description": { "entries": [ "You hurl a bubble of acid. Choose one creature within range, or choose two creatures within range that are within 5 feet of each other. A target must succeed on a Dexterity saving throw or take {@damage 1d6} acid damage.", - "This spell's damage increases by {@damage 1d6} when you reach 5th level ({@damage 2d6}), 11th level ({@damage 3d6}), and 17th level ({@damage 4d6})."] + "This spell's damage increases by {@damage 1d6} when you reach 5th level ({@damage 2d6}), 11th level ({@damage 3d6}), and 17th level ({@damage 4d6})." + ] } }, { @@ -46,6 +54,11 @@ "range": { "type": "self" }, + "damage_resistance": [ + "bludgeoning", + "piercing", + "slashing" + ], "components": { "verbal": true, "somatic": true, @@ -64,7 +77,8 @@ ], "description": { "entries": [ - "You extend your hand and trace a sigil of warding in the air. Until the end of your next turn, you have resistance against bludgeoning, piercing, and slashing damage dealt by weapon attacks."] + "You extend your hand and trace a sigil of warding in the air. Until the end of your next turn, you have resistance against bludgeoning, piercing, and slashing damage dealt by weapon attacks." + ] } }, { @@ -84,6 +98,10 @@ "amount": 5, "unit": "feet" }, + "damage_inflict": [ + "thunder" + ], + "attack_type": "melee", "components": { "verbal": false, "somatic": true, @@ -105,7 +123,8 @@ "description": { "entries": [ "You brandish the weapon used in the spell's casting and make a melee attack with it against one creature within 5 feet of you. On a hit, the target suffers the weapon attack's normal effects and then becomes sheathed in booming energy until the start of your next turn. If the target willingly moves 5 feet or more before then, the target takes 1d8 thunder damage, and the spell ends.", - "This spell's damage increases when you reach certain levels. At 5th level, the melee attack deals an extra {@damage 1d8} thunder damage to the target on a hit, and the damage the target takes for moving increases to {@damage 2d8}. Both damage rolls increase by 1d8 at 11th level ({@damage 2d8} and {@damage 3d8}) and again at 17th level ({@damage 3d8} and {@damage 4d8})."] + "This spell's damage increases when you reach certain levels. At 5th level, the melee attack deals an extra {@damage 1d8} thunder damage to the target on a hit, and the damage the target takes for moving increases to {@damage 2d8}. Both damage rolls increase by 1d8 at 11th level ({@damage 2d8} and {@damage 3d8}) and again at 17th level ({@damage 3d8} and {@damage 4d8})." + ] } }, { @@ -122,6 +141,10 @@ "amount": 120, "unit": "feet" }, + "damage_inflict": [ + "necrotic" + ], + "attack_type": "ranged", "components": { "verbal": true, "somatic": true, @@ -143,7 +166,8 @@ "entries": [ "You create a ghostly, skeletal hand in the space of a creature within range. Make a ranged spell attack against the creature to assail it with the chill of the grave. On a hit, the target takes {@damage 1d8} necrotic damage, and it can't regain hit points until the start of your next turn. Until then, the hand clings to the target.", "If you hit an undead target, it also has disadvantage on attack rolls against you until the end of your next turn.", - "This spell's damage increases by {@damage 1d8} when you reach 5th level ({@damage 2d8}), 11th level ({@damage 3d8}), and 17th level ({@damage 4d8})."] + "This spell's damage increases by {@damage 1d8} when you reach 5th level ({@damage 2d8}), 11th level ({@damage 3d8}), and 17th level ({@damage 4d8})." + ] } }, { @@ -197,7 +221,8 @@ "You cause simple shapes—such as the vague form of a creature, an inanimate object, or a location—to appear within the flames and animate as you like. The shapes last for 1 hour." ] }, - "If you cast this spell multiple times, you can have up to three non-instantaneous effects created by it active at a time, and you can dismiss such an effect as an action."] + "If you cast this spell multiple times, you can have up to three non-instantaneous effects created by it active at a time, and you can dismiss such an effect as an action." + ] } }, { @@ -240,7 +265,8 @@ "entries": [ "You create a bonfire on ground that you can see within range. Until the spell ends, the magic bonfire fills a 5-foot cube. Any creature in the bonfire's space when you cast the spell must succeed on a Dexterity saving throw or take {@damage 1d8} fire damage. A creature must also make the saving throw when it moves into the bonfire's space for the first time on a turn or ends its turn there.", "The bonfire ignites flammable objects in its area that aren't being worn or carried.", - "The spell's damage increases by {@damage 1d8} when you reach 5th level ({@damage 2d8}), 11th level ({@damage 3d8}), and 17th level ({@damage 4d8})."] + "The spell's damage increases by {@damage 1d8} when you reach 5th level ({@damage 2d8}), 11th level ({@damage 3d8}), and 17th level ({@damage 4d8})." + ] } }, { @@ -278,7 +304,92 @@ "description": { "entries": [ "You create up to four torch-amountd lights within range, making them appear as torches, lanterns, or glowing orbs that hover in the air for the duration. You can also combine the four lights into one glowing vaguely humanoid form of Medium amount. Whichever form you choose, each light sheds dim light in a 10-foot radius.", - "As a bonus action on your turn, you can move the lights up to 60 feet to a new spot within range. A light must be within 20 feet of another light created by this spell, and a light winks out if it exceeds the spell's range."] + "As a bonus action on your turn, you can move the lights up to 60 feet to a new spot within range. A light must be within 20 feet of another light created by this spell, and a light winks out if it exceeds the spell's range." + ] + } + }, + { + "name": "Druidcraft", + "school": "transmutation", + "level": 0, + "ritual": false, + "casting_time": { + "amount": 1, + "type": "action" + }, + "range": { + "type": "point", + "amount": 30, + "unit": "feet" + }, + "components": { + "verbal": true, + "somatic": true, + "material": false + }, + "durations": [ + { + "type": "instantaneous" + } + ], + "classes": ["druid"], + "sources": [ + { "source": "PHB", "page": 236 }, + { "source": "SRD", "page": 138 } + ], + "description": { + "entries": [ + "Whispering to the spirits of nature, you create one of the following effects within range:", + { + "type": "list", + "items": [ + "You create a tiny, harmless sensory effect that predicts what the weather will be at your location for the next 24 hours. The effect might manifest as a golden orb for clear skies, a cloud for rain, falling snowflakes for snow, and so on. This effect persists for 1 round.", + "You instantly make a flower blossom, a seed pod open, or a leaf bud bloom.", + "You create an instantaneous, harmless sensory effect, such as falling leaves, a puff of wind, the sound of a small animal, or the faint odor of skunk. The effect must fit in a 5-foot cube.", + "You instantly light or snuff out a candle, a torch, or a small campfire." + ] + } + ] + } + }, + { + "name": "Eldritch Blast", + "school": "evocation", + "level": 0, + "ritual": false, + "casting_time": { + "amount": 1, + "type": "action" + }, + "range": { + "type": "point", + "amount": 120, + "unit": "feet" + }, + "damage_inflict": [ + "force" + ], + "attack_type": "ranged", + "components": { + "verbal": true, + "somatic": true, + "material": false + }, + "durations": [ + { + "type": "instantaneous" + } + ], + "classes": ["warlock"], + "sources": [ + { "source": "PHB", "page": 237 }, + { "source": "SRD", "page": 139 } + ], + "description": { + "entries": [ + "A beam of crackling energy streaks toward a creature within range. Make a ranged spell attack against the target. On a hit, the target takes {@damage 1d10} force damage.", + "The spell creates more than one beam when you reach higher levels: two beams at 5th level, three beams at 11th level, and four beams at 17th level. You can direct the beams at the same target or at different ones. Make a separate attack roll for each beam." + ] } } ] \ No newline at end of file diff --git a/migrations/000007_create_spells/up.sql b/migrations/000007_create_spells/up.sql index 9e968e7..8dc9f9e 100644 --- a/migrations/000007_create_spells/up.sql +++ b/migrations/000007_create_spells/up.sql @@ -1,30 +1,5 @@ CREATE TABLE IF NOT EXISTS spells ( id INTEGER GENERATED ALWAYS AS IDENTITY, name TEXT NOT NULL, - school TEXT NOT NULL, - level INTEGER NOT NULL, - ritual BOOLEAN DEFAULT FALSE, - casting_time_amount INTEGER NOT NULL, - casting_time_unit TEXT NOT NULL, - saving_throw TEXT[], - attack_type TEXT, - damage_type TEXT, - conditions TEXT[], - range_type TEXT NOT NULL, - range_amount INTEGER, - range_unit TEXT, - area_type TEXT, - area_amount INTEGER, - area_unit TEXT, - components_verbal BOOLEAN DEFAULT FALSE, - components_somatic BOOLEAN DEFAULT FALSE, - components_material BOOLEAN DEFAULT FALSE, - components_materials_needed TEXT, - components_materials_cost INTEGER, - components_materials_consumed BOOLEAN DEFAULT FALSE, - durations JSONB NOT NULL, - classes TEXT[] NOT NULL, - sources JSONB NOT NULL, - tags TEXT[], - description JSONB NOT NULL + data JSONB NOT NULL ); \ No newline at end of file diff --git a/src/db/classes/model.rs b/src/db/classes/model.rs index c76b89a..4063296 100644 --- a/src/db/classes/model.rs +++ b/src/db/classes/model.rs @@ -4,11 +4,17 @@ use serde::{Serialize, Deserialize}; #[derive(Debug, Serialize, Deserialize)] pub enum AbilityType { + #[serde(rename = "strength")] Strength, + #[serde(rename = "dexterity")] Dexterity, + #[serde(rename = "constitution")] Constitution, + #[serde(rename = "intelligence")] Intelligence, + #[serde(rename = "wisdom")] Wisdom, + #[serde(rename = "charisma")] Charisma } diff --git a/src/db/conditions/mod.rs b/src/db/conditions/mod.rs index ca4e835..055953f 100644 --- a/src/db/conditions/mod.rs +++ b/src/db/conditions/mod.rs @@ -5,20 +5,35 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub enum ConditionType { + #[serde(rename = "blinded")] Blinded, + #[serde(rename = "charmed")] Charmed, + #[serde(rename = "deafened")] Deafened, + #[serde(rename = "exhaustion")] Exhaustion, + #[serde(rename = "frightened")] Frightened, + #[serde(rename = "grappled")] Grappled, + #[serde(rename = "incapacitated")] Incapacitated, + #[serde(rename = "invisible")] Invisible, + #[serde(rename = "paralyzed")] Paralyzed, + #[serde(rename = "petrified")] Petrified, + #[serde(rename = "poisoned")] Poisoned, + #[serde(rename = "prone")] Prone, + #[serde(rename = "restrained")] Restrained, + #[serde(rename = "stunned")] Stunned, + #[serde(rename = "unconscious")] Unconscious } diff --git a/src/db/schema.rs b/src/db/schema.rs index 17a7229..016753d 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -17,31 +17,6 @@ diesel::table! { spells (id) { id -> Integer, name -> Text, - school -> Text, - level -> Integer, - ritual -> Bool, - casting_time_amount -> Integer, - casting_time_unit -> Text, - saving_throw -> Nullable>, - attack_type -> Nullable, - damage_type -> Nullable, - conditions -> Nullable>, - range_type -> Text, - range_amount -> Nullable, - range_unit -> Nullable, - area_type -> Nullable, - area_amount -> Nullable, - area_unit -> Nullable, - components_verbal -> Bool, - components_somatic -> Bool, - components_material -> Bool, - components_materials_needed -> Nullable, - components_materials_cost -> Nullable, - components_materials_consumed -> Nullable, - durations -> Jsonb, - classes -> Array, - sources -> Jsonb, - tags -> Array, - description -> Jsonb + data -> Jsonb } } \ No newline at end of file diff --git a/src/db/spells/mod.rs b/src/db/spells/mod.rs index 6dfedea..29806d9 100644 --- a/src/db/spells/mod.rs +++ b/src/db/spells/mod.rs @@ -1,7 +1,9 @@ mod model; mod routes; +mod types; pub use model::*; +pub use types::*; pub use routes::init_routes; pub fn load_data() { diff --git a/src/db/spells/model.rs b/src/db/spells/model.rs index 05ed1fa..aa3cf2a 100644 --- a/src/db/spells/model.rs +++ b/src/db/spells/model.rs @@ -1,41 +1,16 @@ -use std::str::FromStr; - use diesel::prelude::*; -use serde::{Deserialize, Serialize, ser::SerializeMap}; +use serde::{Deserialize, Serialize}; -use crate::{db::{schema::spells, classes::AbilityType, conditions::ConditionType}, error_handler::ServiceError}; +use crate::{db::{schema::spells::{self}, classes::AbilityType, conditions::ConditionType}, error_handler::ServiceError}; + +use super::{SchoolType, CastingTime, CastingType, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description}; #[derive(Queryable, QueryableByName)] #[diesel(table_name = spells)] pub struct QuerySpell { pub id: i32, pub name: String, - pub school: String, - pub level: i32, - pub ritual: bool, - pub casting_time_amount: i32, - pub casting_time_unit: String, - pub saving_throw: Option>, - pub attack_type: Option, - pub damage_type: Option, - pub conditions: Option>, - pub range_type: String, - pub range_amount: Option, - pub range_unit: Option, - pub area_type: Option, - pub area_amount: Option, - pub area_unit: Option, - pub components_verbal: bool, - pub components_somatic: bool, - pub components_material: bool, - pub components_materials_needed: Option, - pub components_materials_cost: Option, - pub components_materials_consumed: Option, - pub durations: serde_json::Value, - pub classes: Vec, - pub sources: serde_json::Value, - pub tags: Vec, - pub description: serde_json::Value + pub data: serde_json::Value, } impl QuerySpell { @@ -74,32 +49,7 @@ impl QuerySpell { #[diesel(table_name = spells)] pub struct InsertSpell { pub name: String, - pub school: String, - pub level: i32, - pub ritual: bool, - pub casting_time_amount: i32, - pub casting_time_unit: String, - pub saving_throw: Option>, - pub attack_type: Option, - pub damage_type: Option, - pub conditions: Option>, - pub range_type: String, - pub range_amount: Option, - pub range_unit: Option, - pub area_type: Option, - pub area_amount: Option, - pub area_unit: Option, - pub components_verbal: bool, - pub components_somatic: bool, - pub components_material: bool, - pub components_materials_needed: Option, - pub components_materials_cost: Option, - pub components_materials_consumed: Option, - pub durations: serde_json::Value, - pub classes: Vec, - pub sources: serde_json::Value, - pub tags: Vec, - pub description: serde_json::Value + pub data: serde_json::Value } impl InsertSpell { @@ -128,7 +78,9 @@ pub struct Spell { #[serde(skip_serializing_if = "Option::is_none")] pub attack_type: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub damage_type: Option, + pub damage_inflict: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub damage_resist: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub conditions: Option>, pub range: Range, @@ -144,483 +96,31 @@ pub struct Spell { pub description: Option } -#[derive(Debug, Serialize, Deserialize)] -pub enum SchoolType { - #[serde(rename = "abjuration")] - Abjuration, - #[serde(rename = "conjuration")] - Conjuration, - #[serde(rename = "divination")] - Divination, - #[serde(rename = "enchantment")] - Enchantment, - #[serde(rename = "evocation")] - Evocation, - #[serde(rename = "illusion")] - Illusion, - #[serde(rename = "necromancy")] - Necromancy, - #[serde(rename = "transmutation")] - Transmutation -} - -impl SchoolType { - pub fn to_string(&self) -> String { - match self { - SchoolType::Abjuration => "abjuration".to_string(), - SchoolType::Conjuration => "conjuration".to_string(), - SchoolType::Divination => "divination".to_string(), - SchoolType::Enchantment => "enchantment".to_string(), - SchoolType::Evocation => "evocation".to_string(), - SchoolType::Illusion => "illusion".to_string(), - SchoolType::Necromancy => "necromancy".to_string(), - SchoolType::Transmutation => "transmutation".to_string() - } - } -} - -impl FromStr for SchoolType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "abjuration" => Ok(SchoolType::Abjuration), - "conjuration" => Ok(SchoolType::Conjuration), - "divination" => Ok(SchoolType::Divination), - "enchantment" => Ok(SchoolType::Enchantment), - "evocation" => Ok(SchoolType::Evocation), - "illusion" => Ok(SchoolType::Illusion), - "necromancy" => Ok(SchoolType::Necromancy), - "transmutation" => Ok(SchoolType::Transmutation), - _ => Err(()) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CastingTime { - pub amount: i32, - #[serde(rename = "type")] - pub casting_type: CastingType -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum CastingType { - #[serde(rename = "action")] - Action, - #[serde(rename = "bonus")] - BonusAction, - #[serde(rename = "reaction")] - Reaction, - #[serde(rename = "minutes")] - Minutes, - #[serde(rename = "hours")] - Hours -} - -impl CastingType { - pub fn to_string(&self) -> String { - match self { - CastingType::Action => "action".to_string(), - CastingType::BonusAction => "bonus".to_string(), - CastingType::Reaction => "reaction".to_string(), - CastingType::Minutes => "minutes".to_string(), - CastingType::Hours => "hours".to_string() - } - } -} - -impl FromStr for CastingType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "action" => Ok(CastingType::Action), - "bonus" => Ok(CastingType::BonusAction), - "reaction" => Ok(CastingType::Reaction), - "minutes" => Ok(CastingType::Minutes), - "hours" => Ok(CastingType::Hours), - _ => Err(()) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum SpellAttackType { - #[serde(rename = "melee")] - Melee, - #[serde(rename = "ranged")] - Ranged, -} - -impl SpellAttackType { - pub fn to_string(&self) -> String { - match self { - SpellAttackType::Melee => "melee".to_string(), - SpellAttackType::Ranged => "ranged".to_string() - } - } -} - -impl FromStr for SpellAttackType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "melee" => Ok(SpellAttackType::Melee), - "ranged" => Ok(SpellAttackType::Ranged), - _ => Err(()) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum SpellDamageType { - #[serde(rename = "acid")] - Acid, - #[serde(rename = "bludgeoning")] - Bludgeoning, - #[serde(rename = "cold")] - Cold, - #[serde(rename = "fire")] - Fire, - #[serde(rename = "force")] - Force, - #[serde(rename = "lightning")] - Lightning, - #[serde(rename = "necrotic")] - Necrotic, - #[serde(rename = "piercing")] - Piercing, - #[serde(rename = "poison")] - Poison, - #[serde(rename = "psychic")] - Psychic, - #[serde(rename = "radiant")] - Radiant, - #[serde(rename = "slashing")] - Slashing, - #[serde(rename = "thunder")] - Thunder -} - -impl SpellDamageType { - pub fn to_string(&self) -> String { - match self { - SpellDamageType::Acid => "acid".to_string(), - SpellDamageType::Bludgeoning => "bludgeoning".to_string(), - SpellDamageType::Cold => "cold".to_string(), - SpellDamageType::Fire => "fire".to_string(), - SpellDamageType::Force => "force".to_string(), - SpellDamageType::Lightning => "lightning".to_string(), - SpellDamageType::Necrotic => "necrotic".to_string(), - SpellDamageType::Piercing => "piercing".to_string(), - SpellDamageType::Poison => "poison".to_string(), - SpellDamageType::Psychic => "psychic".to_string(), - SpellDamageType::Radiant => "radiant".to_string(), - SpellDamageType::Slashing => "slashing".to_string(), - SpellDamageType::Thunder => "thunder".to_string() - } - } -} - -impl FromStr for SpellDamageType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "acid" => Ok(SpellDamageType::Acid), - "bludgeoning" => Ok(SpellDamageType::Bludgeoning), - "cold" => Ok(SpellDamageType::Cold), - "fire" => Ok(SpellDamageType::Fire), - "force" => Ok(SpellDamageType::Force), - "lightning" => Ok(SpellDamageType::Lightning), - "necrotic" => Ok(SpellDamageType::Necrotic), - "piercing" => Ok(SpellDamageType::Piercing), - "poison" => Ok(SpellDamageType::Poison), - "psychic" => Ok(SpellDamageType::Psychic), - "radiant" => Ok(SpellDamageType::Radiant), - "slashing" => Ok(SpellDamageType::Slashing), - "thunder" => Ok(SpellDamageType::Thunder), - _ => Err(()) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Range { - #[serde(rename = "type")] - pub range_type: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub amount: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Area { - #[serde(rename = "type")] - pub area_type: AreaType, - #[serde(skip_serializing_if = "Option::is_none")] - pub amount: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum AreaType { - #[serde(rename = "cone")] - Cone, - #[serde(rename = "cube")] - Cube, - #[serde(rename = "cylinder")] - Cylinder, - #[serde(rename = "line")] - Line, - #[serde(rename = "sphere")] - Sphere -} - -impl AreaType { - pub fn to_string(&self) -> String { - match self { - AreaType::Cone => "cone".to_string(), - AreaType::Cube => "cube".to_string(), - AreaType::Cylinder => "cylinder".to_string(), - AreaType::Line => "line".to_string(), - AreaType::Sphere => "sphere".to_string() - } - } -} - -impl FromStr for AreaType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "cone" => Ok(AreaType::Cone), - "cube" => Ok(AreaType::Cube), - "cylinder" => Ok(AreaType::Cylinder), - "line" => Ok(AreaType::Line), - "sphere" => Ok(AreaType::Sphere), - _ => Err(()) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Duration { - #[serde(rename = "type")] - pub duration_type: DurationType, - #[serde(skip_serializing_if = "Option::is_none")] - pub amount: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum DurationType { - #[serde(rename = "concentration")] - Concentration, - #[serde(rename = "instantaneous")] - Instantaneous, - #[serde(rename = "timed")] - Timed, - #[serde(rename = "dispelled")] - UntilDispelled, - #[serde(rename = "special")] - Special -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Source { - pub source: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub page: Option -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Description { - pub entries: Vec -} - -#[derive(Debug)] -pub struct Entry { - pub entry_type: String, - pub items: Vec -} - -impl<'de> Deserialize<'de> for Entry { - fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { - let value = serde_json::Value::deserialize(deserializer)?; - match value { - serde_json::Value::String(s) => Ok(Entry { - entry_type: "string".to_string(), - items: vec![s] - }), - serde_json::Value::Object(o) => { - let entry_type = match o.get("type") { - Some(t) => match t.as_str() { - Some(s) => s.to_string(), - None => return Err(serde::de::Error::custom("Invalid entry type")) - }, - None => return Err(serde::de::Error::custom("Missing entry type")) - }; - let items = match o.get("items") { - Some(i) => match i.as_array() { - Some(a) => { - let mut items = Vec::new(); - for item in a { - match item.as_str() { - Some(s) => items.push(s.to_string()), - None => return Err(serde::de::Error::custom("Invalid entry item")) - } - } - items - }, - None => return Err(serde::de::Error::custom("Invalid entry items")) - }, - None => return Err(serde::de::Error::custom("Missing entry items")) - }; - Ok(Entry { - entry_type, - items - }) - }, - _ => Err(serde::de::Error::custom("Invalid entry")) - } - } -} - -impl Serialize for Entry { - fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { - match self.entry_type.as_str() { - "string" => serializer.serialize_str(&self.items[0]), - _ => { - let mut map = serializer.serialize_map(Some(2))?; - map.serialize_entry("type", &self.entry_type)?; - map.serialize_entry("items", &self.items)?; - map.end() - } - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Components { - pub verbal: bool, - pub somatic: bool, - pub material: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub materials_needed: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub materials_cost: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub materials_consumed: Option -} - impl From for Spell { fn from(query: QuerySpell) -> Self { - return Self { - name: query.name, - school: match SchoolType::from_str(&query.school) { - Ok(school_type) => school_type, - Err(_) => { - log::error!("Failed to parse spell school type: {}", query.school); - SchoolType::Abjuration - } - }, - level: query.level, - ritual: query.ritual, - casting_time: CastingTime { - amount: query.casting_time_amount, - casting_type: match CastingType::from_str(&query.casting_time_unit) { - Ok(casting_type) => casting_type, - Err(_) => { - log::error!("Failed to parse spell casting type: {}", query.casting_time_unit); - CastingType::Action - } - } - }, - saving_throw: query.saving_throw.map(|saving_throw| saving_throw.iter().map(|ability_type| match AbilityType::from_str(&ability_type) { - Ok(ability_type) => ability_type, - Err(_) => { - log::error!("Failed to parse spell saving throw: {}", ability_type); - AbilityType::Strength - } - }).collect()), - attack_type: match query.attack_type { - Some(attack_type) => match SpellAttackType::from_str(&attack_type) { - Ok(attack_type) => Some(attack_type), - Err(_) => { - log::error!("Failed to parse spell attack type: {}", attack_type); - None - } - }, - None => None - }, - damage_type: query.damage_type.map(|damage_type| match SpellDamageType::from_str(&damage_type) { - Ok(damage_type) => damage_type, - Err(_) => { - log::error!("Failed to parse spell damage type: {}", damage_type); - SpellDamageType::Acid - } - }), - conditions: query.conditions.map(|conditions| conditions.iter().map(|condition_type| match ConditionType::from_str(&condition_type) { - Ok(condition_type) => condition_type, - Err(_) => { - log::error!("Failed to parse spell condition type: {}", condition_type); - ConditionType::Blinded - } - }).collect()), - range: Range { - range_type: query.range_type, - amount: query.range_amount, - unit: query.range_unit - }, - area: match query.area_type { - Some(area_type) => Some(Area { - area_type: match AreaType::from_str(&area_type) { - Ok(area_type) => area_type, - Err(_) => { - log::error!("Failed to parse spell area type: {}", area_type); - AreaType::Cone - } - }, - amount: query.area_amount, - unit: query.area_unit - }), - None => None - }, - components: Components { - verbal: query.components_verbal, - somatic: query.components_somatic, - material: query.components_material, - materials_needed: query.components_materials_needed, - materials_cost: query.components_materials_cost, - materials_consumed: query.components_materials_consumed - }, - durations: match serde_json::from_value(query.durations) { - Ok(durations) => durations, - Err(err) => { - log::error!("Failed to parse spell durations: {}", err); - Vec::new() - } - }, - classes: query.classes, - sources: match serde_json::from_value(query.sources) { - Ok(sources) => sources, - Err(err) => { - log::error!("Failed to parse spell sources: {}", err); - Vec::new() - } - }, - tags: Some(query.tags), - description: match serde_json::from_value(query.description) { - Ok(description) => description, - Err(err) => { - log::error!("Failed to parse spell description: {}", err); - None + return match serde_json::from_value(query.data) { + Ok(data) => data, + Err(err) => { + log::error!("Failed to parse spell: {}", err); + Self { + name: "".to_string(), + school: SchoolType::Abjuration, + level: 0, + ritual: false, + casting_time: CastingTime { amount: 0, casting_type: CastingType::Action }, + saving_throw: None, + attack_type: None, + damage_inflict: None, + damage_resist: None, + conditions: None, + range: Range { range_type: "".to_string(), amount: None, unit: None }, + area: None, + components: Components { verbal: false, somatic: false, material: false, materials_needed: None, materials_cost: None, materials_consumed: None }, + durations: vec![], + classes: vec![], + sources: vec![], + tags: None, + description: None, } } } @@ -630,66 +130,14 @@ impl From for Spell { impl Into for Spell { fn into(self) -> InsertSpell { return InsertSpell { - name: self.name, - school: self.school.to_string(), - level: self.level, - ritual: self.ritual, - casting_time_amount: self.casting_time.amount, - casting_time_unit: self.casting_time.casting_type.to_string(), - saving_throw: match self.saving_throw { - Some(saving_throw) => Some(saving_throw.iter().map(|ability_type| ability_type.to_string()).collect()), - None => None - }, - attack_type: match self.attack_type { - Some(attack_type) => Some(attack_type.to_string()), - None => None - }, - damage_type: match self.damage_type { - Some(damage_type) => Some(damage_type.to_string()), - None => None - }, - conditions: match self.conditions { - Some(conditions) => Some(conditions.iter().map(|condition_type| condition_type.to_string()).collect()), - None => None - }, - range_type: self.range.range_type.to_string(), - range_amount: self.range.amount, - range_unit: self.range.unit, - area_type: match &self.area { - Some(area) => Some(area.area_type.to_string()), - None => None - }, - area_amount: match &self.area { - Some(area) => area.amount, - None => None - }, - area_unit: match &self.area { - Some(area) => match &area.unit { - Some(unit) => Some(unit.to_string()), - None => None - }, - None => None - }, - components_verbal: self.components.verbal, - components_somatic: self.components.somatic, - components_material: self.components.material, - components_materials_needed: self.components.materials_needed, - components_materials_cost: self.components.materials_cost, - components_materials_consumed: self.components.materials_consumed, - durations: serde_json::to_value(self.durations).unwrap(), - classes: self.classes, - sources: self.sources.iter().map(|source| match source.page { Some(page) => format!("{} {}", source.source, page), None => source.source.to_string() }).collect(), - tags: match self.tags { - Some(tags) => tags, - None => Vec::new() - }, - description: match serde_json::to_value(self.description) { - Ok(description) => description, + name: self.name.to_string(), + data: match serde_json::to_value(&self) { + Ok(data) => data, Err(err) => { log::error!("Failed to serialize spell description: {}", err); serde_json::Value::Null } - } + }, } } } diff --git a/src/db/spells/types.rs b/src/db/spells/types.rs new file mode 100644 index 0000000..1b23398 --- /dev/null +++ b/src/db/spells/types.rs @@ -0,0 +1,377 @@ +use std::str::FromStr; +use serde::{Deserialize, Serialize, ser::SerializeMap}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum SchoolType { + #[serde(rename = "abjuration")] + Abjuration, + #[serde(rename = "conjuration")] + Conjuration, + #[serde(rename = "divination")] + Divination, + #[serde(rename = "enchantment")] + Enchantment, + #[serde(rename = "evocation")] + Evocation, + #[serde(rename = "illusion")] + Illusion, + #[serde(rename = "necromancy")] + Necromancy, + #[serde(rename = "transmutation")] + Transmutation +} + +impl SchoolType { + pub fn to_string(&self) -> String { + match self { + SchoolType::Abjuration => "abjuration".to_string(), + SchoolType::Conjuration => "conjuration".to_string(), + SchoolType::Divination => "divination".to_string(), + SchoolType::Enchantment => "enchantment".to_string(), + SchoolType::Evocation => "evocation".to_string(), + SchoolType::Illusion => "illusion".to_string(), + SchoolType::Necromancy => "necromancy".to_string(), + SchoolType::Transmutation => "transmutation".to_string() + } + } +} + +impl FromStr for SchoolType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "abjuration" => Ok(SchoolType::Abjuration), + "conjuration" => Ok(SchoolType::Conjuration), + "divination" => Ok(SchoolType::Divination), + "enchantment" => Ok(SchoolType::Enchantment), + "evocation" => Ok(SchoolType::Evocation), + "illusion" => Ok(SchoolType::Illusion), + "necromancy" => Ok(SchoolType::Necromancy), + "transmutation" => Ok(SchoolType::Transmutation), + _ => Err(()) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CastingTime { + pub amount: i32, + #[serde(rename = "type")] + pub casting_type: CastingType +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum CastingType { + #[serde(rename = "action")] + Action, + #[serde(rename = "bonus")] + BonusAction, + #[serde(rename = "reaction")] + Reaction, + #[serde(rename = "minutes")] + Minutes, + #[serde(rename = "hours")] + Hours +} + +impl CastingType { + pub fn to_string(&self) -> String { + match self { + CastingType::Action => "action".to_string(), + CastingType::BonusAction => "bonus".to_string(), + CastingType::Reaction => "reaction".to_string(), + CastingType::Minutes => "minutes".to_string(), + CastingType::Hours => "hours".to_string() + } + } +} + +impl FromStr for CastingType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "action" => Ok(CastingType::Action), + "bonus" => Ok(CastingType::BonusAction), + "reaction" => Ok(CastingType::Reaction), + "minutes" => Ok(CastingType::Minutes), + "hours" => Ok(CastingType::Hours), + _ => Err(()) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SpellAttackType { + #[serde(rename = "melee")] + Melee, + #[serde(rename = "ranged")] + Ranged, +} + +impl SpellAttackType { + pub fn to_string(&self) -> String { + match self { + SpellAttackType::Melee => "melee".to_string(), + SpellAttackType::Ranged => "ranged".to_string() + } + } +} + +impl FromStr for SpellAttackType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "melee" => Ok(SpellAttackType::Melee), + "ranged" => Ok(SpellAttackType::Ranged), + _ => Err(()) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SpellDamageType { + #[serde(rename = "acid")] + Acid, + #[serde(rename = "bludgeoning")] + Bludgeoning, + #[serde(rename = "cold")] + Cold, + #[serde(rename = "fire")] + Fire, + #[serde(rename = "force")] + Force, + #[serde(rename = "lightning")] + Lightning, + #[serde(rename = "necrotic")] + Necrotic, + #[serde(rename = "piercing")] + Piercing, + #[serde(rename = "poison")] + Poison, + #[serde(rename = "psychic")] + Psychic, + #[serde(rename = "radiant")] + Radiant, + #[serde(rename = "slashing")] + Slashing, + #[serde(rename = "thunder")] + Thunder +} + +impl SpellDamageType { + pub fn to_string(&self) -> String { + match self { + SpellDamageType::Acid => "acid".to_string(), + SpellDamageType::Bludgeoning => "bludgeoning".to_string(), + SpellDamageType::Cold => "cold".to_string(), + SpellDamageType::Fire => "fire".to_string(), + SpellDamageType::Force => "force".to_string(), + SpellDamageType::Lightning => "lightning".to_string(), + SpellDamageType::Necrotic => "necrotic".to_string(), + SpellDamageType::Piercing => "piercing".to_string(), + SpellDamageType::Poison => "poison".to_string(), + SpellDamageType::Psychic => "psychic".to_string(), + SpellDamageType::Radiant => "radiant".to_string(), + SpellDamageType::Slashing => "slashing".to_string(), + SpellDamageType::Thunder => "thunder".to_string() + } + } +} + +impl FromStr for SpellDamageType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "acid" => Ok(SpellDamageType::Acid), + "bludgeoning" => Ok(SpellDamageType::Bludgeoning), + "cold" => Ok(SpellDamageType::Cold), + "fire" => Ok(SpellDamageType::Fire), + "force" => Ok(SpellDamageType::Force), + "lightning" => Ok(SpellDamageType::Lightning), + "necrotic" => Ok(SpellDamageType::Necrotic), + "piercing" => Ok(SpellDamageType::Piercing), + "poison" => Ok(SpellDamageType::Poison), + "psychic" => Ok(SpellDamageType::Psychic), + "radiant" => Ok(SpellDamageType::Radiant), + "slashing" => Ok(SpellDamageType::Slashing), + "thunder" => Ok(SpellDamageType::Thunder), + _ => Err(()) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Range { + #[serde(rename = "type")] + pub range_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unit: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Area { + #[serde(rename = "type")] + pub area_type: AreaType, + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unit: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AreaType { + #[serde(rename = "cone")] + Cone, + #[serde(rename = "cube")] + Cube, + #[serde(rename = "cylinder")] + Cylinder, + #[serde(rename = "line")] + Line, + #[serde(rename = "sphere")] + Sphere +} + +impl AreaType { + pub fn to_string(&self) -> String { + match self { + AreaType::Cone => "cone".to_string(), + AreaType::Cube => "cube".to_string(), + AreaType::Cylinder => "cylinder".to_string(), + AreaType::Line => "line".to_string(), + AreaType::Sphere => "sphere".to_string() + } + } +} + +impl FromStr for AreaType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "cone" => Ok(AreaType::Cone), + "cube" => Ok(AreaType::Cube), + "cylinder" => Ok(AreaType::Cylinder), + "line" => Ok(AreaType::Line), + "sphere" => Ok(AreaType::Sphere), + _ => Err(()) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Duration { + #[serde(rename = "type")] + pub duration_type: DurationType, + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unit: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum DurationType { + #[serde(rename = "concentration")] + Concentration, + #[serde(rename = "instantaneous")] + Instantaneous, + #[serde(rename = "timed")] + Timed, + #[serde(rename = "dispelled")] + UntilDispelled, + #[serde(rename = "special")] + Special +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Source { + pub source: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Description { + pub entries: Vec +} + +#[derive(Debug)] +pub struct Entry { + pub entry_type: String, + pub items: Vec +} + +impl<'de> Deserialize<'de> for Entry { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + let value = serde_json::Value::deserialize(deserializer)?; + match value { + serde_json::Value::String(s) => Ok(Entry { + entry_type: "string".to_string(), + items: vec![s] + }), + serde_json::Value::Object(o) => { + let entry_type = match o.get("type") { + Some(t) => match t.as_str() { + Some(s) => s.to_string(), + None => return Err(serde::de::Error::custom("Invalid entry type")) + }, + None => return Err(serde::de::Error::custom("Missing entry type")) + }; + let items = match o.get("items") { + Some(i) => match i.as_array() { + Some(a) => { + let mut items = Vec::new(); + for item in a { + match item.as_str() { + Some(s) => items.push(s.to_string()), + None => return Err(serde::de::Error::custom("Invalid entry item")) + } + } + items + }, + None => return Err(serde::de::Error::custom("Invalid entry items")) + }, + None => return Err(serde::de::Error::custom("Missing entry items")) + }; + Ok(Entry { + entry_type, + items + }) + }, + _ => Err(serde::de::Error::custom("Invalid entry")) + } + } +} + +impl Serialize for Entry { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + match self.entry_type.as_str() { + "string" => serializer.serialize_str(&self.items[0]), + _ => { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("type", &self.entry_type)?; + map.serialize_entry("items", &self.items)?; + map.end() + } + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Components { + pub verbal: bool, + pub somatic: bool, + pub material: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub materials_needed: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub materials_cost: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub materials_consumed: Option +} \ No newline at end of file