Updated spells schema
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,4 +8,3 @@ audio/
|
|||||||
logs/
|
logs/
|
||||||
settings.json
|
settings.json
|
||||||
app/
|
app/
|
||||||
data/
|
|
||||||
|
|||||||
37
data/spells/cantrips.json
Normal file
37
data/spells/cantrips.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Acid Splash",
|
||||||
|
"school": "conjuration",
|
||||||
|
"level": 0,
|
||||||
|
"ritual": false,
|
||||||
|
"casting_time": {
|
||||||
|
"amount": 1,
|
||||||
|
"type": "action"
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"type": "point",
|
||||||
|
"amount": 60,
|
||||||
|
"unit": "feet"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"verbal": true,
|
||||||
|
"somatic": true,
|
||||||
|
"material": false
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"type": "instantaneous"
|
||||||
|
},
|
||||||
|
"classes": ["artificer", "sorcerer", "wizard"],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"source": "PHB",
|
||||||
|
"page": 211
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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})."]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
0
data/spells/level_1.json
Normal file
0
data/spells/level_1.json
Normal file
@@ -4,15 +4,29 @@ CREATE TABLE IF NOT EXISTS spells (
|
|||||||
school TEXT NOT NULL,
|
school TEXT NOT NULL,
|
||||||
level INTEGER NOT NULL,
|
level INTEGER NOT NULL,
|
||||||
ritual BOOLEAN DEFAULT FALSE,
|
ritual BOOLEAN DEFAULT FALSE,
|
||||||
casting_time TEXT NOT NULL,
|
casting_time_amount INTEGER NOT NULL,
|
||||||
range TEXT 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_verbal BOOLEAN DEFAULT FALSE,
|
||||||
components_somatic BOOLEAN DEFAULT FALSE,
|
components_somatic BOOLEAN DEFAULT FALSE,
|
||||||
components_material BOOLEAN DEFAULT FALSE,
|
components_material BOOLEAN DEFAULT FALSE,
|
||||||
components_materials_needed TEXT,
|
components_materials_needed TEXT,
|
||||||
duration TEXT NOT NULL,
|
components_materials_cost INTEGER,
|
||||||
|
components_materials_consumed BOOLEAN DEFAULT FALSE,
|
||||||
|
duration_type TEXT NOT NULL,
|
||||||
|
duration_amount INTEGER,
|
||||||
|
duration_unit TEXT,
|
||||||
classes TEXT[] NOT NULL,
|
classes TEXT[] NOT NULL,
|
||||||
sources TEXT[] NOT NULL,
|
sources TEXT[] NOT NULL,
|
||||||
tags TEXT[],
|
tags TEXT[],
|
||||||
description TEXT NOT NULL
|
description JSONB NOT NULL
|
||||||
);
|
);
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use diesel::{prelude::*, insert_into};
|
use diesel::{prelude::*, insert_into};
|
||||||
use log::{error, debug, trace, warn};
|
use log::{error, debug, trace, warn};
|
||||||
|
|
||||||
@@ -13,12 +9,15 @@ use serenity::model::prelude::{ChannelType, PermissionOverwrite, PermissionOverw
|
|||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
|
|
||||||
use crate::db::{connection, messages::{MessageDB, NewMessageDB}};
|
use crate::db::{connection, messages::{MessageDB, NewMessageDB}};
|
||||||
|
use crate::error_handler::ServiceError;
|
||||||
|
|
||||||
pub struct OAI {
|
pub struct OAI {
|
||||||
pub client: reqwest::Client,
|
pub client: reqwest::Client,
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
pub max_attempts: i64,
|
pub max_attempts: i64,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
|
pub max_tokens: i64,
|
||||||
|
pub default_model: GPTModel,
|
||||||
pub max_context_questions: i64
|
pub max_context_questions: i64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ enum GPTRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
enum GPTModel {
|
pub enum GPTModel {
|
||||||
#[serde(rename = "gpt-3.5-turbo")]
|
#[serde(rename = "gpt-3.5-turbo")]
|
||||||
GPT35Turbo,
|
GPT35Turbo,
|
||||||
#[serde(rename = "gpt-3.5-turbo-0613")]
|
#[serde(rename = "gpt-3.5-turbo-0613")]
|
||||||
@@ -110,26 +109,18 @@ struct Choice {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct ResponseError {
|
struct ResponseError {
|
||||||
code: Option<i64>,
|
error: Option<ErrorDetails>,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
param: Option<String>,
|
param: Option<String>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
error_type: Option<String>
|
error_type: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct OAIError {
|
struct ErrorDetails {
|
||||||
pub message: String
|
code: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for OAIError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "OAIError: {}", self.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for OAIError {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
enum ResponseEvent {
|
enum ResponseEvent {
|
||||||
ChatCompletionResponse(ChatCompletionResponse),
|
ChatCompletionResponse(ChatCompletionResponse),
|
||||||
@@ -137,7 +128,7 @@ enum ResponseEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OAI {
|
impl OAI {
|
||||||
async fn get_request(&self, request: ChatCompletionRequest) -> Result<ChatCompletionResponse, OAIError> {
|
async fn get_request(&self, request: ChatCompletionRequest) -> Result<ChatCompletionResponse, ServiceError> {
|
||||||
let uri = format!("{}/chat/completions", self.base_url);
|
let uri = format!("{}/chat/completions", self.base_url);
|
||||||
let body = serde_json::to_string(&request).unwrap();
|
let body = serde_json::to_string(&request).unwrap();
|
||||||
trace!("Sending request to {}: {}", uri, body);
|
trace!("Sending request to {}: {}", uri, body);
|
||||||
@@ -150,35 +141,39 @@ impl OAI {
|
|||||||
.send()
|
.send()
|
||||||
.await {
|
.await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(err) => return Err(OAIError {
|
Err(err) => return Err(ServiceError {
|
||||||
message: format!("Could not send request to OpenAI: {}", err),
|
message: format!("Could not send request to OpenAI: {}", err),
|
||||||
|
status: 500
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.json::<Value>()
|
.json::<Value>()
|
||||||
.await {
|
.await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(err) => return Err(OAIError {
|
Err(err) => return Err(ServiceError {
|
||||||
message: format!("Could not read response from OpenAI: {}", err)
|
message: format!("Could not read response from OpenAI: {}", err),
|
||||||
|
status: 500
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("Received response from OpenAI: {:?}", value);
|
trace!("Received response from OpenAI: {:?}", value);
|
||||||
|
|
||||||
// let response = match serde_json::from_value::<OAIResponseEvent>(value) {
|
// let response = match serde_json::from_value::<ResponseEvent>(value) {
|
||||||
// Ok(r) => {
|
// Ok(r) => {
|
||||||
// match r {
|
// match r {
|
||||||
// OAIResponseEvent::OAIResponse(r) => r,
|
// ResponseEvent::ChatCompletionResponse(r) => r,
|
||||||
// OAIResponseEvent::OAIError(e) => return Err(OAIError { message: e.message.unwrap_or("Unknown error".to_string()) })
|
// ResponseEvent::ResponseError(e) => return Err(ServiceError { message: e.message.unwrap_or("Unknown error".to_string()), status: 500 }),
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// Err(err) => return Err(OAIError {
|
// Err(err) => return Err(ServiceError {
|
||||||
// message: format!("Could not parse response from OpenAI: {}", err)
|
// message: format!("Could not parse response from OpenAI: {}", err),
|
||||||
|
// status: 500
|
||||||
// })
|
// })
|
||||||
// };
|
// };
|
||||||
let response = match serde_json::from_value::<ChatCompletionResponse>(value) {
|
let response = match serde_json::from_value::<ChatCompletionResponse>(value) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(err) => return Err(OAIError {
|
Err(err) => return Err(ServiceError {
|
||||||
message: format!("Could not parse response from OpenAI: {}", err)
|
message: format!("Could not parse response from OpenAI: {}", err),
|
||||||
|
status: 500
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,20 +204,6 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) {
|
|||||||
.limit(oai.max_context_questions)
|
.limit(oai.max_context_questions)
|
||||||
.load(&mut connection);
|
.load(&mut connection);
|
||||||
|
|
||||||
let previous_messages = match result {
|
|
||||||
Ok(r) => {
|
|
||||||
let mut previous_message = "".to_string();
|
|
||||||
for message in r {
|
|
||||||
previous_message = format!("{}You: {}\n Siren: {}\n", previous_message, message.request, message.response);
|
|
||||||
}
|
|
||||||
Some(ChatCompletionMessage { role: GPTRole::User, content: previous_message })
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("Could not load previous messages: {}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut messages = vec![
|
let mut messages = vec![
|
||||||
ChatCompletionMessage {
|
ChatCompletionMessage {
|
||||||
role: GPTRole::System,
|
role: GPTRole::System,
|
||||||
@@ -230,25 +211,34 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if let Some(mut previous) = previous_messages {
|
match result {
|
||||||
previous.content = format!("{}You: {}\nSiren: ", previous.content, parsed_content);
|
Ok(r) => {
|
||||||
messages.push(previous);
|
for message in r {
|
||||||
} else {
|
messages.push(
|
||||||
messages.push(ChatCompletionMessage {
|
ChatCompletionMessage {
|
||||||
role: GPTRole::User,
|
role: GPTRole::User,
|
||||||
content: format!("You: {}, Siren: ", parsed_content)
|
content: format!("{}", message.request)
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
let model = "gpt-3.5-turbo".to_string();
|
messages.push(
|
||||||
|
ChatCompletionMessage {
|
||||||
|
role: GPTRole::Assistant,
|
||||||
|
content: format!("{}", message.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => error!("Could not load previous messages: {}", err)
|
||||||
|
};
|
||||||
|
messages.push(ChatCompletionMessage { role: GPTRole::User, content: parsed_content.clone() });
|
||||||
|
|
||||||
let request = ChatCompletionRequest {
|
let request = ChatCompletionRequest {
|
||||||
model: GPTModel::GPT35Turbo,
|
model: oai.default_model.clone(),
|
||||||
messages,
|
messages,
|
||||||
temperature: Some(0.5),
|
temperature: Some(0.5),
|
||||||
top_p: None,
|
top_p: None,
|
||||||
n: None,
|
n: None,
|
||||||
max_tokens: Some(1000),
|
max_tokens: Some(oai.max_tokens),
|
||||||
presence_penalty: Some(0.6),
|
presence_penalty: Some(0.6),
|
||||||
frequency_penalty: Some(0.0),
|
frequency_penalty: Some(0.0),
|
||||||
user: Some(msg.author.name.clone())
|
user: Some(msg.author.name.clone())
|
||||||
@@ -289,7 +279,7 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) {
|
|||||||
channel_id: response_channel.0 as i64,
|
channel_id: response_channel.0 as i64,
|
||||||
user_id: author_id.0 as i64,
|
user_id: author_id.0 as i64,
|
||||||
created: r.created,
|
created: r.created,
|
||||||
model: &model,
|
model: &serde_json::to_string(&r.model).unwrap(),
|
||||||
request: &parsed_content,
|
request: &parsed_content,
|
||||||
response: &res,
|
response: &res,
|
||||||
request_tags: vec![],
|
request_tags: vec![],
|
||||||
@@ -305,7 +295,7 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) {
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Could not get response from OpenAI: {}", err.message);
|
error!("Could not get response from OpenAI: {}", err.message);
|
||||||
err.message
|
"There was an error processing your message. Please try again later.".to_string()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug!("Writing response: \"{}\"", response);
|
debug!("Writing response: \"{}\"", response);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod model;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
41
src/db/classes/model.rs
Normal file
41
src/db/classes/model.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum AbilityType {
|
||||||
|
Strength,
|
||||||
|
Dexterity,
|
||||||
|
Constitution,
|
||||||
|
Intelligence,
|
||||||
|
Wisdom,
|
||||||
|
Charisma
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbilityType {
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
AbilityType::Strength => "Strength".to_string(),
|
||||||
|
AbilityType::Dexterity => "Dexterity".to_string(),
|
||||||
|
AbilityType::Constitution => "Constitution".to_string(),
|
||||||
|
AbilityType::Intelligence => "Intelligence".to_string(),
|
||||||
|
AbilityType::Wisdom => "Wisdom".to_string(),
|
||||||
|
AbilityType::Charisma => "Charisma".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for AbilityType {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"Strength" => Ok(AbilityType::Strength),
|
||||||
|
"Dexterity" => Ok(AbilityType::Dexterity),
|
||||||
|
"Constitution" => Ok(AbilityType::Constitution),
|
||||||
|
"Intelligence" => Ok(AbilityType::Intelligence),
|
||||||
|
"Wisdom" => Ok(AbilityType::Wisdom),
|
||||||
|
"Charisma" => Ok(AbilityType::Charisma),
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ConditionType {
|
||||||
|
Blinded,
|
||||||
|
Charmed,
|
||||||
|
Deafened,
|
||||||
|
Exhaustion,
|
||||||
|
Frightened,
|
||||||
|
Grappled,
|
||||||
|
Incapacitated,
|
||||||
|
Invisible,
|
||||||
|
Paralyzed,
|
||||||
|
Petrified,
|
||||||
|
Poisoned,
|
||||||
|
Prone,
|
||||||
|
Restrained,
|
||||||
|
Stunned,
|
||||||
|
Unconscious
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionType {
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
ConditionType::Blinded => "Blinded".to_string(),
|
||||||
|
ConditionType::Charmed => "Charmed".to_string(),
|
||||||
|
ConditionType::Deafened => "Deafened".to_string(),
|
||||||
|
ConditionType::Exhaustion => "Exhaustion".to_string(),
|
||||||
|
ConditionType::Frightened => "Frightened".to_string(),
|
||||||
|
ConditionType::Grappled => "Grappled".to_string(),
|
||||||
|
ConditionType::Incapacitated => "Incapacitated".to_string(),
|
||||||
|
ConditionType::Invisible => "Invisible".to_string(),
|
||||||
|
ConditionType::Paralyzed => "Paralyzed".to_string(),
|
||||||
|
ConditionType::Petrified => "Petrified".to_string(),
|
||||||
|
ConditionType::Poisoned => "Poisoned".to_string(),
|
||||||
|
ConditionType::Prone => "Prone".to_string(),
|
||||||
|
ConditionType::Restrained => "Restrained".to_string(),
|
||||||
|
ConditionType::Stunned => "Stunned".to_string(),
|
||||||
|
ConditionType::Unconscious => "Unconscious".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ConditionType {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"Blinded" => Ok(ConditionType::Blinded),
|
||||||
|
"Charmed" => Ok(ConditionType::Charmed),
|
||||||
|
"Deafened" => Ok(ConditionType::Deafened),
|
||||||
|
"Exhaustion" => Ok(ConditionType::Exhaustion),
|
||||||
|
"Frightened" => Ok(ConditionType::Frightened),
|
||||||
|
"Grappled" => Ok(ConditionType::Grappled),
|
||||||
|
"Incapacitated" => Ok(ConditionType::Incapacitated),
|
||||||
|
"Invisible" => Ok(ConditionType::Invisible),
|
||||||
|
"Paralyzed" => Ok(ConditionType::Paralyzed),
|
||||||
|
"Petrified" => Ok(ConditionType::Petrified),
|
||||||
|
"Poisoned" => Ok(ConditionType::Poisoned),
|
||||||
|
"Prone" => Ok(ConditionType::Prone),
|
||||||
|
"Restrained" => Ok(ConditionType::Restrained),
|
||||||
|
"Stunned" => Ok(ConditionType::Stunned),
|
||||||
|
"Unconscious" => Ok(ConditionType::Unconscious),
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,16 +20,30 @@ diesel::table! {
|
|||||||
school -> Text,
|
school -> Text,
|
||||||
level -> Integer,
|
level -> Integer,
|
||||||
ritual -> Bool,
|
ritual -> Bool,
|
||||||
casting_time -> Text,
|
casting_time_amount -> Integer,
|
||||||
range -> Text,
|
casting_time_unit -> Text,
|
||||||
|
saving_throw -> Nullable<Array<Text>>,
|
||||||
|
attack_type -> Nullable<Text>,
|
||||||
|
damage_type -> Nullable<Text>,
|
||||||
|
conditions -> Nullable<Array<Text>>,
|
||||||
|
range_type -> Text,
|
||||||
|
range_amount -> Nullable<Integer>,
|
||||||
|
range_unit -> Nullable<Text>,
|
||||||
|
area_type -> Nullable<Text>,
|
||||||
|
area_amount -> Nullable<Integer>,
|
||||||
|
area_unit -> Nullable<Text>,
|
||||||
components_verbal -> Bool,
|
components_verbal -> Bool,
|
||||||
components_somatic -> Bool,
|
components_somatic -> Bool,
|
||||||
components_material -> Bool,
|
components_material -> Bool,
|
||||||
components_materials_needed -> Nullable<Text>,
|
components_materials_needed -> Nullable<Text>,
|
||||||
duration -> Text,
|
components_materials_cost -> Nullable<Integer>,
|
||||||
|
components_materials_consumed -> Nullable<Bool>,
|
||||||
|
duration_type -> Text,
|
||||||
|
duration_amount -> Nullable<Integer>,
|
||||||
|
duration_unit -> Nullable<Text>,
|
||||||
classes -> Array<Text>,
|
classes -> Array<Text>,
|
||||||
sources -> Array<Text>,
|
sources -> Array<Text>,
|
||||||
tags -> Array<Text>,
|
tags -> Array<Text>,
|
||||||
description -> Text
|
description -> Jsonb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,38 +6,40 @@ pub use routes::init_routes;
|
|||||||
|
|
||||||
pub fn load_data() {
|
pub fn load_data() {
|
||||||
let root_path = std::env::current_dir().unwrap();
|
let root_path = std::env::current_dir().unwrap();
|
||||||
let mut data_path = std::path::PathBuf::from(root_path);
|
let files = [
|
||||||
data_path.push("data/spells.json");
|
"cantrips.json", "level_1.json", "level_2.json", "level_3.json", "level_4.json", "level_5.json", "level_6.json", "level_7.json", "level_8.json", "level_9.json"
|
||||||
match data_path.to_str() {
|
];
|
||||||
Some(path) => {
|
let mut spells: Vec<Spell> = vec![];
|
||||||
log::debug!("Loading spells from {}", path);
|
for file in files {
|
||||||
match std::fs::read_to_string(data_path) {
|
let mut data_path = std::path::PathBuf::from(&root_path);
|
||||||
|
data_path.push(format!("data/spells/{}", file));
|
||||||
|
let path = data_path.to_str().unwrap();
|
||||||
|
match std::fs::read_to_string(path) {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
|
log::debug!("Loading spells from {}", path);
|
||||||
match serde_json::from_str::<serde_json::Value>(&data) {
|
match serde_json::from_str::<serde_json::Value>(&data) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
match serde_json::from_value::<Vec<Spell>>(json) {
|
match serde_json::from_value::<Vec<Spell>>(json) {
|
||||||
Ok(spells) => {
|
Ok(mut new_spells) => spells.append(&mut new_spells),
|
||||||
let count = QuerySpell::get_count().unwrap();
|
|
||||||
if count >= spells.len() as i64 {
|
|
||||||
log::warn!("Spell data is already loaded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for spell in spells {
|
|
||||||
match InsertSpell::insert(spell.into()) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(err) => log::error!("Failed to insert spell: {}", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => log::error!("Failed to parse spells data: {}", err)
|
Err(err) => log::error!("Failed to parse spells data: {}", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => log::error!("Failed to parse spells data to value: {}", err)
|
Err(err) => log::error!("Failed to parse spells data to value: {}", err)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Err(err) => log::error!("Failed to read spells data: {}", err)
|
Err(err) => log::error!("Failed to read from {}: {}", file, err)
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
None => log::error!("Failed to find spells data directory")
|
let count = QuerySpell::get_count().unwrap();
|
||||||
|
if count >= spells.len() as i64 {
|
||||||
|
log::warn!("Spell data is already loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for spell in spells {
|
||||||
|
let spell_name = spell.name.clone();
|
||||||
|
match InsertSpell::insert(spell.into()) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => log::error!("Failed to insert '{}' spell: {}", spell_name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use diesel::prelude::*;
|
use std::str::FromStr;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{db::schema::spells, error_handler::ServiceError};
|
use diesel::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize, ser::SerializeMap};
|
||||||
|
|
||||||
|
use crate::{db::{schema::spells, classes::AbilityType, conditions::ConditionType}, error_handler::ServiceError};
|
||||||
|
|
||||||
#[derive(Queryable, QueryableByName)]
|
#[derive(Queryable, QueryableByName)]
|
||||||
#[diesel(table_name = spells)]
|
#[diesel(table_name = spells)]
|
||||||
@@ -11,17 +13,31 @@ pub struct QuerySpell {
|
|||||||
pub school: String,
|
pub school: String,
|
||||||
pub level: i32,
|
pub level: i32,
|
||||||
pub ritual: bool,
|
pub ritual: bool,
|
||||||
pub casting_time: String,
|
pub casting_time_amount: i32,
|
||||||
pub range: String,
|
pub casting_time_unit: String,
|
||||||
|
pub saving_throw: Option<Vec<String>>,
|
||||||
|
pub attack_type: Option<String>,
|
||||||
|
pub damage_type: Option<String>,
|
||||||
|
pub conditions: Option<Vec<String>>,
|
||||||
|
pub range_type: String,
|
||||||
|
pub range_amount: Option<i32>,
|
||||||
|
pub range_unit: Option<String>,
|
||||||
|
pub area_type: Option<String>,
|
||||||
|
pub area_amount: Option<i32>,
|
||||||
|
pub area_unit: Option<String>,
|
||||||
pub components_verbal: bool,
|
pub components_verbal: bool,
|
||||||
pub components_somatic: bool,
|
pub components_somatic: bool,
|
||||||
pub components_material: bool,
|
pub components_material: bool,
|
||||||
pub components_materials_needed: Option<String>,
|
pub components_materials_needed: Option<String>,
|
||||||
pub duration: String,
|
pub components_materials_cost: Option<i32>,
|
||||||
|
pub components_materials_consumed: Option<bool>,
|
||||||
|
pub duration_type: String,
|
||||||
|
pub duration_amount: Option<i32>,
|
||||||
|
pub duration_unit: Option<String>,
|
||||||
pub classes: Vec<String>,
|
pub classes: Vec<String>,
|
||||||
pub sources: Vec<String>,
|
pub sources: Vec<String>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub description: String
|
pub description: serde_json::Value
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QuerySpell {
|
impl QuerySpell {
|
||||||
@@ -63,17 +79,31 @@ pub struct InsertSpell {
|
|||||||
pub school: String,
|
pub school: String,
|
||||||
pub level: i32,
|
pub level: i32,
|
||||||
pub ritual: bool,
|
pub ritual: bool,
|
||||||
pub casting_time: String,
|
pub casting_time_amount: i32,
|
||||||
pub range: String,
|
pub casting_time_unit: String,
|
||||||
|
pub saving_throw: Option<Vec<String>>,
|
||||||
|
pub attack_type: Option<String>,
|
||||||
|
pub damage_type: Option<String>,
|
||||||
|
pub conditions: Option<Vec<String>>,
|
||||||
|
pub range_type: String,
|
||||||
|
pub range_amount: Option<i32>,
|
||||||
|
pub range_unit: Option<String>,
|
||||||
|
pub area_type: Option<String>,
|
||||||
|
pub area_amount: Option<i32>,
|
||||||
|
pub area_unit: Option<String>,
|
||||||
pub components_verbal: bool,
|
pub components_verbal: bool,
|
||||||
pub components_somatic: bool,
|
pub components_somatic: bool,
|
||||||
pub components_material: bool,
|
pub components_material: bool,
|
||||||
pub components_materials_needed: Option<String>,
|
pub components_materials_needed: Option<String>,
|
||||||
pub duration: String,
|
pub components_materials_cost: Option<i32>,
|
||||||
|
pub components_materials_consumed: Option<bool>,
|
||||||
|
pub duration_type: String,
|
||||||
|
pub duration_amount: Option<i32>,
|
||||||
|
pub duration_unit: Option<String>,
|
||||||
pub classes: Vec<String>,
|
pub classes: Vec<String>,
|
||||||
pub sources: Vec<String>,
|
pub sources: Vec<String>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub description: String
|
pub description: serde_json::Value
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InsertSpell {
|
impl InsertSpell {
|
||||||
@@ -90,50 +120,541 @@ impl InsertSpell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Spell {
|
pub struct Spell {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub school: String,
|
pub school: SchoolType,
|
||||||
pub level: i32,
|
pub level: i32,
|
||||||
pub ritual: bool,
|
pub ritual: bool,
|
||||||
pub casting_time: String,
|
pub casting_time: CastingTime,
|
||||||
pub range: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub saving_throw: Option<Vec<AbilityType>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub attack_type: Option<SpellAttackType>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub damage_type: Option<SpellDamageType>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conditions: Option<Vec<ConditionType>>,
|
||||||
|
pub range: Range,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub area: Option<Area>,
|
||||||
pub components: Components,
|
pub components: Components,
|
||||||
pub duration: String,
|
pub duration: Duration,
|
||||||
pub classes: Vec<String>,
|
pub classes: Vec<String>,
|
||||||
pub sources: Vec<String>,
|
pub sources: Vec<Source>,
|
||||||
pub tags: Vec<String>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: String
|
pub tags: Option<Vec<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<Description>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[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<Self, Self::Err> {
|
||||||
|
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<Self, Self::Err> {
|
||||||
|
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<Self, Self::Err> {
|
||||||
|
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<Self, Self::Err> {
|
||||||
|
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<i32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub unit: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Area {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub area_type: AreaType,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub amount: Option<i32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub unit: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Self, Self::Err> {
|
||||||
|
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<i32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub unit: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DurationType {
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
DurationType::Concentration => "concentration".to_string(),
|
||||||
|
DurationType::Instantaneous => "instantaneous".to_string(),
|
||||||
|
DurationType::Timed => "timed".to_string(),
|
||||||
|
DurationType::UntilDispelled => "dispelled".to_string(),
|
||||||
|
DurationType::Special => "special".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DurationType {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"concentration" => Ok(DurationType::Concentration),
|
||||||
|
"instantaneous" => Ok(DurationType::Instantaneous),
|
||||||
|
"timed" => Ok(DurationType::Timed),
|
||||||
|
"dispelled" => Ok(DurationType::UntilDispelled),
|
||||||
|
"special" => Ok(DurationType::Special),
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Source {
|
||||||
|
pub source: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub page: Option<i32>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Description {
|
||||||
|
pub entries: Vec<Entry>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Entry {
|
||||||
|
pub entry_type: String,
|
||||||
|
pub items: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Entry {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 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 struct Components {
|
||||||
pub verbal: bool,
|
pub verbal: bool,
|
||||||
pub somatic: bool,
|
pub somatic: bool,
|
||||||
pub material: bool,
|
pub material: bool,
|
||||||
pub materials_needed: Option<String>
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub materials_needed: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub materials_cost: Option<i32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub materials_consumed: Option<bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<QuerySpell> for Spell {
|
impl From<QuerySpell> for Spell {
|
||||||
fn from(query: QuerySpell) -> Self {
|
fn from(query: QuerySpell) -> Self {
|
||||||
return Self {
|
return Self {
|
||||||
name: query.name,
|
name: query.name,
|
||||||
school: query.school,
|
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,
|
level: query.level,
|
||||||
ritual: query.ritual,
|
ritual: query.ritual,
|
||||||
casting_time: query.casting_time,
|
casting_time: CastingTime {
|
||||||
range: query.range,
|
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 {
|
components: Components {
|
||||||
verbal: query.components_verbal,
|
verbal: query.components_verbal,
|
||||||
somatic: query.components_somatic,
|
somatic: query.components_somatic,
|
||||||
material: query.components_material,
|
material: query.components_material,
|
||||||
materials_needed: query.components_materials_needed
|
materials_needed: query.components_materials_needed,
|
||||||
|
materials_cost: query.components_materials_cost,
|
||||||
|
materials_consumed: query.components_materials_consumed
|
||||||
|
},
|
||||||
|
duration: Duration {
|
||||||
|
duration_type: match DurationType::from_str(&query.duration_type) {
|
||||||
|
Ok(duration_type) => duration_type,
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("Failed to parse spell duration type: {}", query.duration_type);
|
||||||
|
DurationType::Special
|
||||||
|
}
|
||||||
|
},
|
||||||
|
amount: query.duration_amount,
|
||||||
|
unit: query.duration_unit
|
||||||
},
|
},
|
||||||
duration: query.duration,
|
|
||||||
classes: query.classes,
|
classes: query.classes,
|
||||||
sources: query.sources,
|
sources: query.sources.iter().map(|source| Source {
|
||||||
tags: query.tags,
|
source: source.to_string(),
|
||||||
description: query.description
|
page: None
|
||||||
|
}).collect(),
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,20 +663,67 @@ impl Into<InsertSpell> for Spell {
|
|||||||
fn into(self) -> InsertSpell {
|
fn into(self) -> InsertSpell {
|
||||||
return InsertSpell {
|
return InsertSpell {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
school: self.school,
|
school: self.school.to_string(),
|
||||||
level: self.level,
|
level: self.level,
|
||||||
ritual: self.ritual,
|
ritual: self.ritual,
|
||||||
casting_time: self.casting_time,
|
casting_time_amount: self.casting_time.amount,
|
||||||
range: self.range,
|
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_verbal: self.components.verbal,
|
||||||
components_somatic: self.components.somatic,
|
components_somatic: self.components.somatic,
|
||||||
components_material: self.components.material,
|
components_material: self.components.material,
|
||||||
components_materials_needed: self.components.materials_needed,
|
components_materials_needed: self.components.materials_needed,
|
||||||
duration: self.duration,
|
components_materials_cost: self.components.materials_cost,
|
||||||
|
components_materials_consumed: self.components.materials_consumed,
|
||||||
|
duration_type: self.duration.duration_type.to_string(),
|
||||||
|
duration_amount: self.duration.amount,
|
||||||
|
duration_unit: self.duration.unit,
|
||||||
classes: self.classes,
|
classes: self.classes,
|
||||||
sources: self.sources,
|
sources: self.sources.iter().map(|source| match source.page { Some(page) => format!("{} {}", source.source, page), None => source.source.to_string() }).collect(),
|
||||||
tags: self.tags,
|
tags: match self.tags {
|
||||||
description: self.description
|
Some(tags) => tags,
|
||||||
|
None => Vec::new()
|
||||||
|
},
|
||||||
|
description: match serde_json::to_value(self.description) {
|
||||||
|
Ok(description) => description,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to serialize spell description: {}", err);
|
||||||
|
serde_json::Value::Null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ use std::fmt;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ServiceError {
|
pub struct ServiceError {
|
||||||
pub error_status_code: u16,
|
pub status: u16,
|
||||||
pub error_message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceError {
|
impl ServiceError {
|
||||||
pub fn new(error_status_code: u16, error_message: String) -> ServiceError {
|
pub fn new(error_status_code: u16, error_message: String) -> ServiceError {
|
||||||
ServiceError {
|
ServiceError {
|
||||||
error_status_code,
|
status: error_status_code,
|
||||||
error_message,
|
message: error_message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ServiceError {
|
impl fmt::Display for ServiceError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str(self.error_message.as_str())
|
f.write_str(self.message.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,13 +42,13 @@ impl From<DieselError> for ServiceError {
|
|||||||
|
|
||||||
impl ResponseError for ServiceError {
|
impl ResponseError for ServiceError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
let status_code = match StatusCode::from_u16(self.error_status_code) {
|
let status_code = match StatusCode::from_u16(self.status) {
|
||||||
Ok(status_code) => status_code,
|
Ok(status_code) => status_code,
|
||||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
let error_message = match status_code.as_u16() < 500 {
|
let error_message = match status_code.as_u16() < 500 {
|
||||||
true => self.error_message.clone(),
|
true => self.message.clone(),
|
||||||
false => "Internal server error".to_string(),
|
false => "Internal server error".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -20,6 +20,8 @@ use serenity::http::Http;
|
|||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
use songbird::SerenityInit;
|
use songbird::SerenityInit;
|
||||||
|
|
||||||
|
use crate::commands::oai::GPTModel;
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod error_handler;
|
mod error_handler;
|
||||||
mod db;
|
mod db;
|
||||||
@@ -168,7 +170,15 @@ fn setup_discord_bot() {
|
|||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
info!("Loaded OpenAI token");
|
info!("Loaded OpenAI token");
|
||||||
Handler {
|
Handler {
|
||||||
oai: Some(commands::oai::OAI { client: reqwest::Client::new(), base_url: "https://api.openai.com/v1".to_string(), max_attempts: 5, token , max_context_questions: 15 })
|
oai: Some(commands::oai::OAI {
|
||||||
|
client: reqwest::Client::new(),
|
||||||
|
base_url: "https://api.openai.com/v1".to_string(),
|
||||||
|
max_attempts: 5,
|
||||||
|
token,
|
||||||
|
max_context_questions: 30,
|
||||||
|
max_tokens: 2048,
|
||||||
|
default_model: GPTModel::GPT35Turbo,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user