Compare commits
8 Commits
d2ed246b8b
...
ce89b61c19
Author | SHA1 | Date |
---|---|---|
projectmoon | ce89b61c19 | |
projectmoon | c4d0397fb5 | |
projectmoon | b82b87345e | |
projectmoon | 82001b99b7 | |
projectmoon | 001e15e594 | |
projectmoon | 773f3a1a47 | |
projectmoon | bdf5b4da08 | |
projectmoon | 336a4231a0 |
|
@ -11,7 +11,7 @@ use crate::models::{
|
||||||
Content, ContentContainer, ContentRelation,
|
Content, ContentContainer, ContentRelation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::generator::AiClient;
|
use super::generator::AiGenerator;
|
||||||
|
|
||||||
const DIRECTIONS: [&str; 15] = [
|
const DIRECTIONS: [&str; 15] = [
|
||||||
"north",
|
"north",
|
||||||
|
@ -80,11 +80,11 @@ fn is_duplicate_recorded(failures: &[CoherenceFailure], exit: &Exit) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct AiCoherence {
|
pub(super) struct AiCoherence {
|
||||||
generator: Rc<AiClient>,
|
generator: Rc<AiGenerator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AiCoherence {
|
impl AiCoherence {
|
||||||
pub fn new(generator: Rc<AiClient>) -> AiCoherence {
|
pub fn new(generator: Rc<AiGenerator>) -> AiCoherence {
|
||||||
AiCoherence { generator }
|
AiCoherence { generator }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::prompts::{execution_prompts, parsing_prompts, world_prompts};
|
||||||
|
|
||||||
use crate::kobold_api::Client as KoboldClient;
|
use crate::kobold_api::Client as KoboldClient;
|
||||||
use crate::models::coherence::{CoherenceFailure, SceneFix};
|
use crate::models::coherence::{CoherenceFailure, SceneFix};
|
||||||
use crate::models::commands::{Command, Commands, RawCommandExecution, VerbsResponse};
|
use crate::models::commands::{ParsedCommand, ParsedCommands, RawCommandExecution, VerbsResponse};
|
||||||
use crate::models::world::raw::{
|
use crate::models::world::raw::{
|
||||||
ExitSeed, ItemDetails, ItemSeed, PersonDetails, PersonSeed, SceneSeed,
|
ExitSeed, ItemDetails, ItemSeed, PersonDetails, PersonSeed, SceneSeed,
|
||||||
};
|
};
|
||||||
|
@ -26,16 +26,16 @@ fn find_exit_position(exits: &[Exit], exit_to_find: &Exit) -> Result<usize> {
|
||||||
/// information via the LLM and doing basic coherence on it. Things
|
/// information via the LLM and doing basic coherence on it. Things
|
||||||
/// like ID creation, data management, and advanced coherence are done
|
/// like ID creation, data management, and advanced coherence are done
|
||||||
/// at a higher level.
|
/// at a higher level.
|
||||||
pub struct AiClient {
|
pub struct AiGenerator {
|
||||||
parsing_convo: AiConversation,
|
parsing_convo: AiConversation,
|
||||||
world_creation_convo: AiConversation,
|
world_creation_convo: AiConversation,
|
||||||
person_creation_convo: AiConversation,
|
person_creation_convo: AiConversation,
|
||||||
execution_convo: AiConversation,
|
execution_convo: AiConversation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AiClient {
|
impl AiGenerator {
|
||||||
pub fn new(client: Rc<KoboldClient>) -> AiClient {
|
pub fn new(client: Rc<KoboldClient>) -> AiGenerator {
|
||||||
AiClient {
|
AiGenerator {
|
||||||
parsing_convo: AiConversation::new(client.clone()),
|
parsing_convo: AiConversation::new(client.clone()),
|
||||||
world_creation_convo: AiConversation::new(client.clone()),
|
world_creation_convo: AiConversation::new(client.clone()),
|
||||||
person_creation_convo: AiConversation::new(client.clone()),
|
person_creation_convo: AiConversation::new(client.clone()),
|
||||||
|
@ -56,7 +56,7 @@ impl AiClient {
|
||||||
self.person_creation_convo.reset();
|
self.person_creation_convo.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse(&self, cmd: &str) -> Result<Commands> {
|
pub async fn parse(&self, cmd: &str) -> Result<ParsedCommands> {
|
||||||
// If convo so far is empty, add the instruction header,
|
// If convo so far is empty, add the instruction header,
|
||||||
// otherwise only append to existing convo.
|
// otherwise only append to existing convo.
|
||||||
let prompt = match self.parsing_convo.is_empty() {
|
let prompt = match self.parsing_convo.is_empty() {
|
||||||
|
@ -64,7 +64,7 @@ impl AiClient {
|
||||||
false => parsing_prompts::continuation_prompt(&cmd),
|
false => parsing_prompts::continuation_prompt(&cmd),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cmds: Commands = self.parsing_convo.execute(&prompt).await?;
|
let mut cmds: ParsedCommands = self.parsing_convo.execute(&prompt).await?;
|
||||||
let verbs = self.find_verbs(cmd).await?;
|
let verbs = self.find_verbs(cmd).await?;
|
||||||
self.check_coherence(&verbs, &mut cmds).await?;
|
self.check_coherence(&verbs, &mut cmds).await?;
|
||||||
Ok(cmds)
|
Ok(cmds)
|
||||||
|
@ -83,13 +83,13 @@ impl AiClient {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_coherence(&self, verbs: &[String], commands: &mut Commands) -> Result<()> {
|
async fn check_coherence(&self, verbs: &[String], commands: &mut ParsedCommands) -> Result<()> {
|
||||||
// let coherence_prompt = parsing_prompts::coherence_prompt();
|
// let coherence_prompt = parsing_prompts::coherence_prompt();
|
||||||
// let mut commands: Commands = self.parsing_convo.execute(&coherence_prompt).await?;
|
// let mut commands: Commands = self.parsing_convo.execute(&coherence_prompt).await?;
|
||||||
|
|
||||||
// Non-LLM coherence checks: remove empty commands, remove
|
// Non-LLM coherence checks: remove empty commands, remove
|
||||||
// non-verbs, etc.
|
// non-verbs, etc.
|
||||||
let filtered_commands: Vec<Command> = commands
|
let filtered_commands: Vec<ParsedCommand> = commands
|
||||||
.clone()
|
.clone()
|
||||||
.commands
|
.commands
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -102,7 +102,7 @@ impl AiClient {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_raw(&self, stage: &Stage, cmd: &Command) -> Result<RawCommandExecution> {
|
pub async fn execute_raw(&self, stage: &Stage, cmd: &ParsedCommand) -> Result<RawCommandExecution> {
|
||||||
let prompt = execution_prompts::execution_prompt(stage, &cmd);
|
let prompt = execution_prompts::execution_prompt(stage, &cmd);
|
||||||
let raw_exec: RawCommandExecution = self.execution_convo.execute(&prompt).await?;
|
let raw_exec: RawCommandExecution = self.execution_convo.execute(&prompt).await?;
|
||||||
Ok(raw_exec)
|
Ok(raw_exec)
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
use crate::db::Database;
|
||||||
|
use crate::kobold_api::Client as KoboldClient;
|
||||||
|
use crate::models::commands::{
|
||||||
|
AiCommand, ParsedCommands, ExecutionConversionResult, RawCommandExecution,
|
||||||
|
};
|
||||||
|
use crate::models::world::items::{Category, Item, Rarity};
|
||||||
|
use crate::models::world::people::{Gender, Person, Sex};
|
||||||
|
use crate::models::world::raw::{ItemSeed, PersonSeed, SceneSeed};
|
||||||
|
use crate::models::world::scenes::{Exit, Scene, SceneStub, Stage};
|
||||||
|
use crate::models::{new_uuid_string, Content, ContentContainer, ContentRelation};
|
||||||
|
use crate::commands::converter as command_converter;
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::coherence::AiCoherence;
|
||||||
|
use super::generator::AiGenerator;
|
||||||
|
|
||||||
|
/// Highest-level AI/LLM construct, which returns fully converted game
|
||||||
|
/// objects to us. Basically, call the mid-level `client` to create
|
||||||
|
/// seed objects, then call the mid level client again to detail the
|
||||||
|
/// entities from their seeds. Then, stick a DB ID on them and put
|
||||||
|
/// them in the database(?).
|
||||||
|
pub struct AiLogic {
|
||||||
|
generator: Rc<AiGenerator>,
|
||||||
|
coherence: AiCoherence,
|
||||||
|
db: Rc<Database>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AiLogic {
|
||||||
|
pub fn new(api_client: Rc<KoboldClient>, db: &Rc<Database>) -> AiLogic {
|
||||||
|
let generator = Rc::new(AiGenerator::new(api_client));
|
||||||
|
let coherence = AiCoherence::new(generator.clone());
|
||||||
|
|
||||||
|
AiLogic {
|
||||||
|
generator,
|
||||||
|
coherence,
|
||||||
|
db: db.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(
|
||||||
|
&self,
|
||||||
|
stage: &Stage,
|
||||||
|
cmd: &str,
|
||||||
|
) -> Result<(ParsedCommands, RawCommandExecution)> {
|
||||||
|
let parsed_cmd = self.generator.parse(cmd).await?;
|
||||||
|
let execution = self.execute_parsed(stage, &parsed_cmd).await?;
|
||||||
|
Ok((parsed_cmd, execution))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_parsed(
|
||||||
|
&self,
|
||||||
|
stage: &Stage,
|
||||||
|
parsed_cmd: &ParsedCommands,
|
||||||
|
) -> Result<RawCommandExecution> {
|
||||||
|
//TODO handle multiple commands in list
|
||||||
|
if parsed_cmd.commands.is_empty() {
|
||||||
|
return Ok(RawCommandExecution::empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = &parsed_cmd.commands[0];
|
||||||
|
let raw_exec: RawCommandExecution = self.generator.execute_raw(stage, cmd).await?;
|
||||||
|
|
||||||
|
// Coherence check:
|
||||||
|
// Set aside any events that are not in the enum
|
||||||
|
// Set aside anything with correct event, but wrong parameters.
|
||||||
|
// Ask LLM to fix them, if possible
|
||||||
|
//TODO make a aiclient::fix_execution
|
||||||
|
|
||||||
|
self.generator.reset_commands();
|
||||||
|
Ok(raw_exec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_person(&self, scene: &SceneSeed, seed: &PersonSeed) -> Result<Person> {
|
||||||
|
self.generator.reset_person_creation();
|
||||||
|
let details = self.generator.create_person_details(scene, seed).await?;
|
||||||
|
|
||||||
|
let gender = match details.gender.to_lowercase().as_ref() {
|
||||||
|
"male" | "man" | "boy" | "transmasc" => Gender::Male,
|
||||||
|
"female" | "woman" | "girl" | "transfem" => Gender::Female,
|
||||||
|
"nonbinary" => Gender::NonBinary,
|
||||||
|
// fall back to using sex
|
||||||
|
_ => match details.sex.to_lowercase().as_ref() {
|
||||||
|
"male" | "man" | "boy" | "transmasc" => Gender::Male,
|
||||||
|
"female" | "woman" | "girl" | "transfem" => Gender::Female,
|
||||||
|
_ => Gender::NonBinary, // TODO 1/3 chance!
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let sex = match details.sex.to_lowercase().as_ref() {
|
||||||
|
"male" | "man" | "boy" | "transfem" => Sex::Male,
|
||||||
|
"female" | "woman" | "girl" | "transmasc" => Sex::Female,
|
||||||
|
_ => match gender {
|
||||||
|
Gender::Male => Sex::Male,
|
||||||
|
Gender::Female => Sex::Male,
|
||||||
|
_ => Sex::Male, // TODO 50/50 chance!
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.generator.reset_person_creation();
|
||||||
|
|
||||||
|
Ok(Person {
|
||||||
|
_key: Some(new_uuid_string()),
|
||||||
|
name: seed.name.to_string(),
|
||||||
|
description: details.description,
|
||||||
|
age: details.age,
|
||||||
|
residence: details.residence,
|
||||||
|
current_activity: details.current_activity,
|
||||||
|
occupation: seed.occupation.to_string(),
|
||||||
|
race: seed.race.clone(),
|
||||||
|
sex,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_item(&self, scene: &SceneSeed, seed: &ItemSeed) -> Result<Item> {
|
||||||
|
let details = self.generator.create_item_details(scene, seed).await?;
|
||||||
|
|
||||||
|
// TODO these have to be sent to the AI
|
||||||
|
let category = Category::Other;
|
||||||
|
let rarity = Rarity::Common;
|
||||||
|
|
||||||
|
Ok(Item {
|
||||||
|
_key: Some(new_uuid_string()),
|
||||||
|
name: seed.name.to_string(),
|
||||||
|
description: details.description,
|
||||||
|
attributes: details.attributes,
|
||||||
|
secret_attributes: details.secret_attributes,
|
||||||
|
category,
|
||||||
|
rarity,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_scene_with_id(
|
||||||
|
&self,
|
||||||
|
scene_type: &str,
|
||||||
|
fantasticalness: &str,
|
||||||
|
scene_id: &str,
|
||||||
|
) -> Result<ContentContainer> {
|
||||||
|
let mut content = self.create_scene(scene_type, fantasticalness).await?;
|
||||||
|
let scene = content.owner.as_scene_mut();
|
||||||
|
scene._key = Some(scene_id.to_string());
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_scene_from_stub(
|
||||||
|
&self,
|
||||||
|
stub: SceneStub,
|
||||||
|
connected_scene: &Scene,
|
||||||
|
) -> Result<ContentContainer> {
|
||||||
|
self.generator.reset_world_creation();
|
||||||
|
|
||||||
|
let seed = self
|
||||||
|
.generator
|
||||||
|
.create_scene_seed_from_stub(&stub, connected_scene)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// There are two coherence steps: the first fixes up exit
|
||||||
|
// directions and stuff, while the second is the normal scene
|
||||||
|
// coherence (that can invoke the LLM).
|
||||||
|
let mut content = self.fill_in_scene_from_stub(seed, stub).await?;
|
||||||
|
self.coherence
|
||||||
|
.make_scene_from_stub_coherent(&mut content, connected_scene);
|
||||||
|
self.coherence.make_scene_coherent(&mut content).await?;
|
||||||
|
|
||||||
|
self.generator.reset_world_creation();
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_scene(
|
||||||
|
&self,
|
||||||
|
scene_type: &str,
|
||||||
|
fantasticalness: &str,
|
||||||
|
) -> Result<ContentContainer> {
|
||||||
|
self.generator.reset_world_creation();
|
||||||
|
|
||||||
|
let scene_seed = self
|
||||||
|
.generator
|
||||||
|
.create_scene_seed(scene_type, fantasticalness)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut content = self.fill_in_scene(scene_seed).await?;
|
||||||
|
self.coherence.make_scene_coherent(&mut content).await?;
|
||||||
|
|
||||||
|
self.generator.reset_world_creation();
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fill_in_scene_from_stub(
|
||||||
|
&self,
|
||||||
|
seed: SceneSeed,
|
||||||
|
stub: SceneStub,
|
||||||
|
) -> Result<ContentContainer> {
|
||||||
|
let mut content = self.fill_in_scene(seed).await?;
|
||||||
|
let new_scene = content.owner.as_scene_mut();
|
||||||
|
new_scene._id = stub._id;
|
||||||
|
new_scene._key = stub._key;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fill_in_scene(&self, mut scene_seed: SceneSeed) -> Result<ContentContainer> {
|
||||||
|
let mut content_in_scene = vec![];
|
||||||
|
|
||||||
|
// People in scene
|
||||||
|
let mut people = vec![];
|
||||||
|
for person_seed in scene_seed.people.as_slice() {
|
||||||
|
let person = self.create_person(&scene_seed, person_seed).await?;
|
||||||
|
people.push(ContentRelation::person(person));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items in scene
|
||||||
|
let mut items = vec![];
|
||||||
|
for item_seed in scene_seed.items.as_slice() {
|
||||||
|
let item = self.create_item(&scene_seed, item_seed).await?;
|
||||||
|
items.push(ContentRelation::item(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO items on people, which will require 'recursive' ContentContainers.
|
||||||
|
|
||||||
|
let exits: Vec<_> = scene_seed
|
||||||
|
.exits
|
||||||
|
.drain(0..)
|
||||||
|
.map(|seed| Exit::from(seed))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut stubs: Vec<_> = exits
|
||||||
|
.iter()
|
||||||
|
.map(|exit| ContentRelation::scene_stub(SceneStub::from(exit)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut scene = Scene {
|
||||||
|
_key: Some(new_uuid_string()),
|
||||||
|
name: scene_seed.name,
|
||||||
|
region: scene_seed.region,
|
||||||
|
description: scene_seed.description,
|
||||||
|
props: scene_seed.props.drain(0..).map_into().collect(),
|
||||||
|
is_stub: false,
|
||||||
|
exits,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
content_in_scene.append(&mut people);
|
||||||
|
content_in_scene.append(&mut items);
|
||||||
|
content_in_scene.append(&mut stubs);
|
||||||
|
|
||||||
|
Ok(ContentContainer {
|
||||||
|
owner: Content::Scene(scene),
|
||||||
|
contained: content_in_scene,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
270
src/ai/mod.rs
270
src/ai/mod.rs
|
@ -1,271 +1,5 @@
|
||||||
use crate::db::Database;
|
pub(self) mod coherence;
|
||||||
use crate::kobold_api::Client as KoboldClient;
|
|
||||||
use crate::models::commands::{
|
|
||||||
CommandExecution, Commands, ExecutionConversionResult, RawCommandExecution,
|
|
||||||
};
|
|
||||||
use crate::models::world::items::{Category, Item, Rarity};
|
|
||||||
use crate::models::world::people::{Gender, Person, Sex};
|
|
||||||
use crate::models::world::raw::{ItemSeed, PersonSeed, SceneSeed};
|
|
||||||
use crate::models::world::scenes::{Exit, Scene, SceneStub, Stage};
|
|
||||||
use crate::models::{new_uuid_string, Content, ContentContainer, ContentRelation};
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
mod coherence;
|
|
||||||
pub mod convo;
|
pub mod convo;
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
pub mod prompts;
|
pub mod prompts;
|
||||||
|
pub mod logic;
|
||||||
use convo::AiPrompt;
|
|
||||||
use generator::AiClient;
|
|
||||||
|
|
||||||
use self::coherence::AiCoherence;
|
|
||||||
|
|
||||||
/// Highest-level AI/LLM construct, which returns fully converted game
|
|
||||||
/// objects to us. Basically, call the mid-level `client` to create
|
|
||||||
/// seed objects, then call the mid level client again to detail the
|
|
||||||
/// entities from their seeds. Then, stick a DB ID on them and put
|
|
||||||
/// them in the database(?).
|
|
||||||
pub struct AiLogic {
|
|
||||||
generator: Rc<AiClient>,
|
|
||||||
coherence: AiCoherence,
|
|
||||||
db: Rc<Database>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AiLogic {
|
|
||||||
pub fn new(api_client: Rc<KoboldClient>, db: &Rc<Database>) -> AiLogic {
|
|
||||||
let generator = Rc::new(AiClient::new(api_client));
|
|
||||||
let coherence = AiCoherence::new(generator.clone());
|
|
||||||
|
|
||||||
AiLogic {
|
|
||||||
generator,
|
|
||||||
coherence,
|
|
||||||
db: db.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute(
|
|
||||||
&mut self,
|
|
||||||
stage: &Stage,
|
|
||||||
cmd: &str,
|
|
||||||
) -> Result<(Commands, CommandExecution)> {
|
|
||||||
let parsed_cmd = self.generator.parse(cmd).await?;
|
|
||||||
let execution = self.execute_parsed(stage, &parsed_cmd).await?;
|
|
||||||
Ok((parsed_cmd, execution))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute_parsed(
|
|
||||||
&mut self,
|
|
||||||
stage: &Stage,
|
|
||||||
parsed_cmd: &Commands,
|
|
||||||
) -> Result<CommandExecution> {
|
|
||||||
//TODO handle multiple commands in list
|
|
||||||
if parsed_cmd.commands.is_empty() {
|
|
||||||
return Ok(CommandExecution::empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
let cmd = &parsed_cmd.commands[0];
|
|
||||||
let raw_exec: RawCommandExecution = self.generator.execute_raw(stage, cmd).await?;
|
|
||||||
|
|
||||||
// Coherence check:
|
|
||||||
// Set aside any events that are not in the enum
|
|
||||||
// Set aside anything with correct event, but wrong parameters.
|
|
||||||
// Ask LLM to fix them, if possible
|
|
||||||
//TODO make a aiclient::fix_execution
|
|
||||||
let converted = crate::commands::convert_raw_execution(raw_exec, &self.db).await;
|
|
||||||
|
|
||||||
self.generator.reset_commands();
|
|
||||||
|
|
||||||
//TODO handle the errored events aside from yeeting them out
|
|
||||||
match converted {
|
|
||||||
ExecutionConversionResult::Success(execution) => Ok(execution),
|
|
||||||
ExecutionConversionResult::PartialSuccess(execution, _) => Ok(execution),
|
|
||||||
ExecutionConversionResult::Failure(failures) => {
|
|
||||||
bail!("unhandled command execution failure: {:?}", failures)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_person(&mut self, scene: &SceneSeed, seed: &PersonSeed) -> Result<Person> {
|
|
||||||
self.generator.reset_person_creation();
|
|
||||||
let details = self.generator.create_person_details(scene, seed).await?;
|
|
||||||
|
|
||||||
let gender = match details.gender.to_lowercase().as_ref() {
|
|
||||||
"male" | "man" | "boy" | "transmasc" => Gender::Male,
|
|
||||||
"female" | "woman" | "girl" | "transfem" => Gender::Female,
|
|
||||||
"nonbinary" => Gender::NonBinary,
|
|
||||||
// fall back to using sex
|
|
||||||
_ => match details.sex.to_lowercase().as_ref() {
|
|
||||||
"male" | "man" | "boy" | "transmasc" => Gender::Male,
|
|
||||||
"female" | "woman" | "girl" | "transfem" => Gender::Female,
|
|
||||||
_ => Gender::NonBinary, // TODO 1/3 chance!
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let sex = match details.sex.to_lowercase().as_ref() {
|
|
||||||
"male" | "man" | "boy" | "transfem" => Sex::Male,
|
|
||||||
"female" | "woman" | "girl" | "transmasc" => Sex::Female,
|
|
||||||
_ => match gender {
|
|
||||||
Gender::Male => Sex::Male,
|
|
||||||
Gender::Female => Sex::Male,
|
|
||||||
_ => Sex::Male, // TODO 50/50 chance!
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
self.generator.reset_person_creation();
|
|
||||||
|
|
||||||
Ok(Person {
|
|
||||||
_key: Some(new_uuid_string()),
|
|
||||||
name: seed.name.to_string(),
|
|
||||||
description: details.description,
|
|
||||||
age: details.age,
|
|
||||||
residence: details.residence,
|
|
||||||
current_activity: details.current_activity,
|
|
||||||
occupation: seed.occupation.to_string(),
|
|
||||||
race: seed.race.clone(),
|
|
||||||
sex,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_item(&mut self, scene: &SceneSeed, seed: &ItemSeed) -> Result<Item> {
|
|
||||||
let details = self.generator.create_item_details(scene, seed).await?;
|
|
||||||
|
|
||||||
// TODO these have to be sent to the AI
|
|
||||||
let category = Category::Other;
|
|
||||||
let rarity = Rarity::Common;
|
|
||||||
|
|
||||||
Ok(Item {
|
|
||||||
_key: Some(new_uuid_string()),
|
|
||||||
name: seed.name.to_string(),
|
|
||||||
description: details.description,
|
|
||||||
attributes: details.attributes,
|
|
||||||
secret_attributes: details.secret_attributes,
|
|
||||||
category,
|
|
||||||
rarity,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_scene_with_id(
|
|
||||||
&mut self,
|
|
||||||
scene_type: &str,
|
|
||||||
fantasticalness: &str,
|
|
||||||
scene_id: &str,
|
|
||||||
) -> Result<ContentContainer> {
|
|
||||||
let mut content = self.create_scene(scene_type, fantasticalness).await?;
|
|
||||||
let scene = content.owner.as_scene_mut();
|
|
||||||
scene._key = Some(scene_id.to_string());
|
|
||||||
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_scene_from_stub(
|
|
||||||
&mut self,
|
|
||||||
stub: SceneStub,
|
|
||||||
connected_scene: &Scene,
|
|
||||||
) -> Result<ContentContainer> {
|
|
||||||
self.generator.reset_world_creation();
|
|
||||||
|
|
||||||
let seed = self
|
|
||||||
.generator
|
|
||||||
.create_scene_seed_from_stub(&stub, connected_scene)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// There are two coherence steps: the first fixes up exit
|
|
||||||
// directions and stuff, while the second is the normal scene
|
|
||||||
// coherence (that can invoke the LLM).
|
|
||||||
let mut content = self.fill_in_scene_from_stub(seed, stub).await?;
|
|
||||||
self.coherence
|
|
||||||
.make_scene_from_stub_coherent(&mut content, connected_scene);
|
|
||||||
self.coherence.make_scene_coherent(&mut content).await?;
|
|
||||||
|
|
||||||
self.generator.reset_world_creation();
|
|
||||||
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_scene(
|
|
||||||
&mut self,
|
|
||||||
scene_type: &str,
|
|
||||||
fantasticalness: &str,
|
|
||||||
) -> Result<ContentContainer> {
|
|
||||||
self.generator.reset_world_creation();
|
|
||||||
|
|
||||||
let scene_seed = self
|
|
||||||
.generator
|
|
||||||
.create_scene_seed(scene_type, fantasticalness)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut content = self.fill_in_scene(scene_seed).await?;
|
|
||||||
self.coherence.make_scene_coherent(&mut content).await?;
|
|
||||||
|
|
||||||
self.generator.reset_world_creation();
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fill_in_scene_from_stub(
|
|
||||||
&mut self,
|
|
||||||
seed: SceneSeed,
|
|
||||||
stub: SceneStub,
|
|
||||||
) -> Result<ContentContainer> {
|
|
||||||
let mut content = self.fill_in_scene(seed).await?;
|
|
||||||
let new_scene = content.owner.as_scene_mut();
|
|
||||||
new_scene._id = stub._id;
|
|
||||||
new_scene._key = stub._key;
|
|
||||||
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fill_in_scene(&mut self, mut scene_seed: SceneSeed) -> Result<ContentContainer> {
|
|
||||||
let mut content_in_scene = vec![];
|
|
||||||
|
|
||||||
// People in scene
|
|
||||||
let mut people = vec![];
|
|
||||||
for person_seed in scene_seed.people.as_slice() {
|
|
||||||
let person = self.create_person(&scene_seed, person_seed).await?;
|
|
||||||
people.push(ContentRelation::person(person));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items in scene
|
|
||||||
let mut items = vec![];
|
|
||||||
for item_seed in scene_seed.items.as_slice() {
|
|
||||||
let item = self.create_item(&scene_seed, item_seed).await?;
|
|
||||||
items.push(ContentRelation::item(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO items on people, which will require 'recursive' ContentContainers.
|
|
||||||
|
|
||||||
let exits: Vec<_> = scene_seed
|
|
||||||
.exits
|
|
||||||
.drain(0..)
|
|
||||||
.map(|seed| Exit::from(seed))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut stubs: Vec<_> = exits
|
|
||||||
.iter()
|
|
||||||
.map(|exit| ContentRelation::scene_stub(SceneStub::from(exit)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut scene = Scene {
|
|
||||||
_key: Some(new_uuid_string()),
|
|
||||||
name: scene_seed.name,
|
|
||||||
region: scene_seed.region,
|
|
||||||
description: scene_seed.description,
|
|
||||||
props: scene_seed.props.drain(0..).map_into().collect(),
|
|
||||||
is_stub: false,
|
|
||||||
exits,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
content_in_scene.append(&mut people);
|
|
||||||
content_in_scene.append(&mut items);
|
|
||||||
content_in_scene.append(&mut stubs);
|
|
||||||
|
|
||||||
Ok(ContentContainer {
|
|
||||||
owner: Content::Scene(scene),
|
|
||||||
contained: content_in_scene,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::ai::AiPrompt;
|
use crate::ai::convo::AiPrompt;
|
||||||
use crate::models::commands::{Command, CommandEvent, EventConversionFailures};
|
use crate::models::commands::{ParsedCommand, CommandEvent, EventConversionFailures};
|
||||||
use crate::models::world::scenes::{Scene, Stage};
|
use crate::models::world::scenes::{Scene, Stage};
|
||||||
use strum::VariantNames;
|
use strum::VariantNames;
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ fn stage_info(stage: &Stage) -> String {
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execution_prompt(stage: &Stage, cmd: &Command) -> AiPrompt {
|
pub fn execution_prompt(stage: &Stage, cmd: &ParsedCommand) -> AiPrompt {
|
||||||
let scene_info = stage_info(&stage);
|
let scene_info = stage_info(&stage);
|
||||||
|
|
||||||
let prompt = COMMAND_EXECUTION_PROMPT
|
let prompt = COMMAND_EXECUTION_PROMPT
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::ai::AiPrompt;
|
use crate::ai::convo::AiPrompt;
|
||||||
|
|
||||||
pub const COMMAND_BNF: &str = r#"
|
pub const COMMAND_BNF: &str = r#"
|
||||||
root ::= Commands
|
root ::= Commands
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ai::AiPrompt,
|
ai::convo::AiPrompt,
|
||||||
models::world::{
|
models::world::{
|
||||||
raw::{PersonSeed, SceneSeed},
|
raw::{PersonSeed, SceneSeed},
|
||||||
scenes::{Exit, Scene, SceneStub},
|
scenes::{Exit, Scene, SceneStub},
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
use crate::models::commands::{BuiltinCommand, AiCommand};
|
||||||
|
use crate::models::world::scenes::Stage;
|
||||||
|
|
||||||
|
pub fn check_builtin_command(stage: &Stage, cmd: &str) -> Option<BuiltinCommand> {
|
||||||
|
match cmd {
|
||||||
|
"look" => look_command(stage),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn look_command(_stage: &Stage) -> Option<BuiltinCommand> {
|
||||||
|
Some(BuiltinCommand::Look)
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
use crate::{
|
||||||
|
db::Database,
|
||||||
|
models::commands::{
|
||||||
|
CommandEvent, AiCommand, EventCoherenceFailure, EventConversionError,
|
||||||
|
EventConversionFailures, ExecutionConversionResult, RawCommandEvent, RawCommandExecution,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use futures::stream::{self, StreamExt, TryStreamExt};
|
||||||
|
use itertools::{Either, Itertools};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use strum::VariantNames;
|
||||||
|
|
||||||
|
type EventConversionResult = std::result::Result<CommandEvent, EventConversionError>;
|
||||||
|
|
||||||
|
impl CommandEvent {
|
||||||
|
pub fn new(raw_event: RawCommandEvent) -> EventConversionResult {
|
||||||
|
let event_name = raw_event.event_name.as_str().to_lowercase();
|
||||||
|
|
||||||
|
if Self::VARIANTS.contains(&event_name.as_str()) {
|
||||||
|
deserialize_recognized_event(raw_event)
|
||||||
|
} else {
|
||||||
|
Err(EventConversionError::UnrecognizedEvent(raw_event))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<RawCommandEvent> for CommandEvent {
|
||||||
|
type Error = EventConversionError;
|
||||||
|
|
||||||
|
fn try_from(raw_event: RawCommandEvent) -> Result<Self, Self::Error> {
|
||||||
|
CommandEvent::new(raw_event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal struct to hold the narrative parts of the
|
||||||
|
/// RawCommandExecution to minimize clones.
|
||||||
|
struct Narrative {
|
||||||
|
valid: bool,
|
||||||
|
reason: Option<String>,
|
||||||
|
narration: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_raw_success(raw: Narrative, events: Vec<CommandEvent>) -> AiCommand {
|
||||||
|
AiCommand {
|
||||||
|
events,
|
||||||
|
valid: raw.valid,
|
||||||
|
reason: match &raw.reason {
|
||||||
|
Some(reason) if !raw.valid && reason.is_empty() => {
|
||||||
|
Some("invalid for unknown reason".to_string())
|
||||||
|
}
|
||||||
|
Some(_) if !raw.valid => raw.reason.clone(),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
narration: raw.narration.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn convert_raw_execution(
|
||||||
|
mut raw_exec: RawCommandExecution,
|
||||||
|
db: &Database,
|
||||||
|
) -> ExecutionConversionResult {
|
||||||
|
if !raw_exec.valid {
|
||||||
|
return ExecutionConversionResult::Success(AiCommand::from_raw_invalid(raw_exec));
|
||||||
|
}
|
||||||
|
|
||||||
|
let narrative = Narrative {
|
||||||
|
valid: raw_exec.valid,
|
||||||
|
reason: raw_exec.reason.take(),
|
||||||
|
narration: std::mem::take(&mut raw_exec.narration),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conversions: Vec<_> = raw_exec
|
||||||
|
.events
|
||||||
|
.into_iter()
|
||||||
|
.map(|raw_event| CommandEvent::new(raw_event))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (converted, conversion_failures): (Vec<_>, Vec<_>) =
|
||||||
|
conversions.into_iter().partition_map(|res| match res {
|
||||||
|
Ok(converted_event) => Either::Left(converted_event),
|
||||||
|
Err(err) => Either::Right(err),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Coherence validation of converted events.
|
||||||
|
let (successes, incoherent_events): (Vec<_>, Vec<_>) = stream::iter(converted.into_iter())
|
||||||
|
.then(|event| validate_event_coherence(db, event))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.partition_map(|res| match res {
|
||||||
|
Ok(event) => Either::Left(event),
|
||||||
|
Err(err) => Either::Right(err),
|
||||||
|
});
|
||||||
|
|
||||||
|
let failure_len = conversion_failures.len() + incoherent_events.len();
|
||||||
|
|
||||||
|
if successes.len() > 0 && failure_len == 0 {
|
||||||
|
ExecutionConversionResult::Success(from_raw_success(narrative, successes))
|
||||||
|
} else if successes.len() > 0 && failure_len > 0 {
|
||||||
|
let converted_execution = from_raw_success(narrative, successes);
|
||||||
|
let failures =
|
||||||
|
EventConversionFailures::from_failures(conversion_failures, incoherent_events);
|
||||||
|
ExecutionConversionResult::PartialSuccess(converted_execution, failures)
|
||||||
|
} else {
|
||||||
|
ExecutionConversionResult::Failure(EventConversionFailures::from_failures(
|
||||||
|
conversion_failures,
|
||||||
|
incoherent_events,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_recognized_event(
|
||||||
|
raw_event: RawCommandEvent,
|
||||||
|
) -> Result<CommandEvent, EventConversionError> {
|
||||||
|
let event_name = raw_event.event_name.as_str().to_lowercase();
|
||||||
|
let event_name = event_name.as_str();
|
||||||
|
|
||||||
|
match event_name {
|
||||||
|
// scene-related
|
||||||
|
"change_scene" => Ok(CommandEvent::ChangeScene {
|
||||||
|
scene_key: raw_event
|
||||||
|
.parameter
|
||||||
|
.strip_prefix("scenes/") // Mini coherence check
|
||||||
|
.map(String::from)
|
||||||
|
.unwrap_or(raw_event.parameter)
|
||||||
|
.clone(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// bodily position-related
|
||||||
|
"stand" => Ok(CommandEvent::Stand {
|
||||||
|
target: raw_event.applies_to,
|
||||||
|
}),
|
||||||
|
"sit" => Ok(CommandEvent::Sit {
|
||||||
|
target: raw_event.applies_to,
|
||||||
|
}),
|
||||||
|
"prone" => Ok(CommandEvent::Prone {
|
||||||
|
target: raw_event.applies_to,
|
||||||
|
}),
|
||||||
|
"crouch" => Ok(CommandEvent::Crouch {
|
||||||
|
target: raw_event.applies_to,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// combat-related
|
||||||
|
"take_damage" => deserialize_take_damage(raw_event),
|
||||||
|
|
||||||
|
// miscellaneous
|
||||||
|
"narration" => Ok(CommandEvent::Narration(raw_event.parameter)),
|
||||||
|
|
||||||
|
// unrecognized
|
||||||
|
_ => Err(EventConversionError::UnrecognizedEvent(raw_event)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_take_damage(
|
||||||
|
raw_event: RawCommandEvent,
|
||||||
|
) -> Result<CommandEvent, EventConversionError> {
|
||||||
|
match raw_event.parameter.parse::<u32>() {
|
||||||
|
Ok(dmg) => Ok(CommandEvent::TakeDamage {
|
||||||
|
target: raw_event.applies_to,
|
||||||
|
amount: dmg,
|
||||||
|
}),
|
||||||
|
Err(_) => Err(EventConversionError::InvalidParameter(raw_event)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate_event_coherence<'a>(
|
||||||
|
db: &Database,
|
||||||
|
event: CommandEvent,
|
||||||
|
) -> std::result::Result<CommandEvent, EventCoherenceFailure> {
|
||||||
|
match event {
|
||||||
|
CommandEvent::ChangeScene { ref scene_key } => match db.stage_exists(&scene_key).await {
|
||||||
|
Ok(exists) => match exists {
|
||||||
|
true => Ok(event),
|
||||||
|
false => Err(invalid_converted_event(event).unwrap()),
|
||||||
|
},
|
||||||
|
Err(err) => Err(invalid_converted_event_because_err(event, err)),
|
||||||
|
},
|
||||||
|
_ => Ok(event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The event was converted from the raw response properly, but the
|
||||||
|
/// information contained in the response is not valid.
|
||||||
|
fn invalid_converted_event(event: CommandEvent) -> Option<EventCoherenceFailure> {
|
||||||
|
match event {
|
||||||
|
CommandEvent::ChangeScene { .. } => Some(EventCoherenceFailure::TargetDoesNotExist(event)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The event was converted from the raw response properly, but
|
||||||
|
/// something went wrong with attempting to check the coherence of the
|
||||||
|
/// converted event.
|
||||||
|
fn invalid_converted_event_because_err(
|
||||||
|
event: CommandEvent,
|
||||||
|
err: anyhow::Error,
|
||||||
|
) -> EventCoherenceFailure {
|
||||||
|
EventCoherenceFailure::OtherError(event, format!("{}", err))
|
||||||
|
}
|
|
@ -1,197 +1,115 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
ai::logic::AiLogic,
|
||||||
db::Database,
|
db::Database,
|
||||||
models::commands::{
|
models::{
|
||||||
CommandEvent, CommandExecution, EventCoherenceFailure, EventConversionError,
|
commands::{
|
||||||
EventConversionFailures, ExecutionConversionResult, RawCommandEvent, RawCommandExecution,
|
AiCommand, BuiltinCommand, CommandExecution, ExecutionConversionResult, ParsedCommand,
|
||||||
|
ParsedCommands, RawCommandExecution,
|
||||||
|
},
|
||||||
|
world::scenes::Stage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use futures::stream::{self, StreamExt, TryStreamExt};
|
use std::rc::Rc;
|
||||||
use itertools::{Either, Itertools};
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use strum::VariantNames;
|
pub mod builtins;
|
||||||
|
pub mod converter;
|
||||||
|
|
||||||
type EventConversionResult = std::result::Result<CommandEvent, EventConversionError>;
|
fn directional_command(direction: &str) -> ParsedCommand {
|
||||||
type RefEventConversionResult<'a> = std::result::Result<&'a CommandEvent, EventConversionError>;
|
ParsedCommand {
|
||||||
|
verb: "go".to_string(),
|
||||||
impl CommandEvent {
|
target: direction.to_string(),
|
||||||
pub fn new(raw_event: RawCommandEvent) -> EventConversionResult {
|
location: "direction".to_string(),
|
||||||
let event_name = raw_event.event_name.as_str().to_lowercase();
|
using: "".to_string(),
|
||||||
|
|
||||||
if Self::VARIANTS.contains(&event_name.as_str()) {
|
|
||||||
deserialize_recognized_event(raw_event)
|
|
||||||
} else {
|
|
||||||
Err(EventConversionError::UnrecognizedEvent(raw_event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<RawCommandEvent> for CommandEvent {
|
|
||||||
type Error = EventConversionError;
|
|
||||||
|
|
||||||
fn try_from(raw_event: RawCommandEvent) -> Result<Self, Self::Error> {
|
|
||||||
CommandEvent::new(raw_event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal struct to hold the narrative parts of the
|
/// Translate certain common commands to commands better understood by
|
||||||
/// RawCommandExecution to minimize clones.
|
/// the LLM.
|
||||||
struct Narrative {
|
fn translate(cmd: &str) -> Option<ParsedCommands> {
|
||||||
valid: bool,
|
let cmd = match cmd {
|
||||||
reason: Option<String>,
|
"n" => Some(directional_command("north")),
|
||||||
narration: String,
|
"s" => Some(directional_command("south")),
|
||||||
}
|
"e" => Some(directional_command("east")),
|
||||||
|
"w" => Some(directional_command("west")),
|
||||||
fn from_raw_success(raw: Narrative, events: Vec<CommandEvent>) -> CommandExecution {
|
"nw" => Some(directional_command("northwest")),
|
||||||
CommandExecution {
|
"ne" => Some(directional_command("northeast")),
|
||||||
events,
|
"sw" => Some(directional_command("southwest")),
|
||||||
valid: raw.valid,
|
"se" => Some(directional_command("southeast")),
|
||||||
reason: match &raw.reason {
|
"up" => Some(directional_command("up")),
|
||||||
Some(reason) if !raw.valid && reason.is_empty() => {
|
"down" => Some(directional_command("down")),
|
||||||
Some("invalid for unknown reason".to_string())
|
"in" => Some(directional_command("in")),
|
||||||
}
|
"out" => Some(directional_command("out")),
|
||||||
Some(_) if !raw.valid => raw.reason.clone(),
|
"back" => Some(directional_command("back")),
|
||||||
|
"from" => Some(directional_command("from")),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
|
||||||
narration: raw.narration.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn convert_raw_execution(
|
|
||||||
mut raw_exec: RawCommandExecution,
|
|
||||||
db: &Database,
|
|
||||||
) -> ExecutionConversionResult {
|
|
||||||
if !raw_exec.valid {
|
|
||||||
return ExecutionConversionResult::Success(CommandExecution::from_raw_invalid(raw_exec));
|
|
||||||
}
|
|
||||||
|
|
||||||
let narrative = Narrative {
|
|
||||||
valid: raw_exec.valid,
|
|
||||||
reason: raw_exec.reason.take(),
|
|
||||||
narration: std::mem::take(&mut raw_exec.narration),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let conversions: Vec<_> = raw_exec
|
cmd.map(|c| ParsedCommands {
|
||||||
.events
|
commands: vec![c],
|
||||||
.into_iter()
|
count: 1,
|
||||||
.map(|raw_event| CommandEvent::new(raw_event))
|
})
|
||||||
.collect();
|
}
|
||||||
|
|
||||||
let (converted, conversion_failures): (Vec<_>, Vec<_>) =
|
pub struct CommandExecutor {
|
||||||
conversions.into_iter().partition_map(|res| match res {
|
logic: Rc<AiLogic>,
|
||||||
Ok(converted_event) => Either::Left(converted_event),
|
db: Rc<Database>,
|
||||||
Err(err) => Either::Right(err),
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Coherence check of converted events.
|
impl CommandExecutor {
|
||||||
let (successes, incoherent_events): (Vec<_>, Vec<_>) = stream::iter(converted.into_iter())
|
pub fn new(logic: Rc<AiLogic>, db: Rc<Database>) -> CommandExecutor {
|
||||||
.then(|event| check_event_coherence(db, event))
|
CommandExecutor { logic, db }
|
||||||
.collect::<Vec<_>>()
|
}
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.partition_map(|res| match res {
|
|
||||||
Ok(event) => Either::Left(event),
|
|
||||||
Err(err) => Either::Right(err),
|
|
||||||
});
|
|
||||||
|
|
||||||
let failure_len = conversion_failures.len() + incoherent_events.len();
|
async fn check_translation_and_cache(
|
||||||
|
&self,
|
||||||
|
stage: &Stage,
|
||||||
|
cmd: &str,
|
||||||
|
) -> Result<Option<ParsedCommands>> {
|
||||||
|
let maybe_commands = match translate(cmd) {
|
||||||
|
Some(translated_cmds) => Some(translated_cmds),
|
||||||
|
None => self
|
||||||
|
.db
|
||||||
|
.load_cached_command(cmd, &stage.scene)
|
||||||
|
.await?
|
||||||
|
.map(|c| c.commands),
|
||||||
|
};
|
||||||
|
|
||||||
if successes.len() > 0 && failure_len == 0 {
|
Ok(maybe_commands)
|
||||||
ExecutionConversionResult::Success(from_raw_success(narrative, successes))
|
}
|
||||||
} else if successes.len() > 0 && failure_len > 0 {
|
|
||||||
let converted_execution = from_raw_success(narrative, successes);
|
pub async fn execute(&self, stage: &Stage, cmd: &str) -> Result<CommandExecution> {
|
||||||
let failures =
|
CommandExecution::AiCommand(AiCommand::empty());
|
||||||
EventConversionFailures::from_failures(conversion_failures, incoherent_events);
|
|
||||||
ExecutionConversionResult::PartialSuccess(converted_execution, failures)
|
if let Some(builtin) = builtins::check_builtin_command(stage, cmd) {
|
||||||
|
return Ok(CommandExecution::Builtin(builtin));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pre_parsed = self.check_translation_and_cache(stage, cmd).await?;
|
||||||
|
let raw_exec: RawCommandExecution = if let Some(pre_parsed_cmds) = pre_parsed {
|
||||||
|
self.logic.execute_parsed(stage, &pre_parsed_cmds).await?
|
||||||
} else {
|
} else {
|
||||||
ExecutionConversionResult::Failure(EventConversionFailures::from_failures(
|
let (cmds_to_cache, execution) = self.logic.execute(stage, cmd).await?;
|
||||||
conversion_failures,
|
|
||||||
incoherent_events,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_recognized_event(
|
self.db
|
||||||
raw_event: RawCommandEvent,
|
.cache_command(cmd, &stage.scene, &cmds_to_cache)
|
||||||
) -> Result<CommandEvent, EventConversionError> {
|
.await?;
|
||||||
let event_name = raw_event.event_name.as_str().to_lowercase();
|
|
||||||
let event_name = event_name.as_str();
|
|
||||||
|
|
||||||
match event_name {
|
execution
|
||||||
// scene-related
|
};
|
||||||
"change_scene" => Ok(CommandEvent::ChangeScene {
|
|
||||||
scene_key: raw_event
|
|
||||||
.parameter
|
|
||||||
.strip_prefix("scenes/")
|
|
||||||
.map(String::from)
|
|
||||||
.unwrap_or(raw_event.parameter)
|
|
||||||
.clone(),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// bodily position-related
|
let converted = converter::convert_raw_execution(raw_exec, &self.db).await;
|
||||||
"stand" => Ok(CommandEvent::Stand {
|
|
||||||
target: raw_event.applies_to,
|
|
||||||
}),
|
|
||||||
"sit" => Ok(CommandEvent::Sit {
|
|
||||||
target: raw_event.applies_to,
|
|
||||||
}),
|
|
||||||
"prone" => Ok(CommandEvent::Prone {
|
|
||||||
target: raw_event.applies_to,
|
|
||||||
}),
|
|
||||||
"crouch" => Ok(CommandEvent::Crouch {
|
|
||||||
target: raw_event.applies_to,
|
|
||||||
}),
|
|
||||||
|
|
||||||
// combat-related
|
//TODO handle the errored events aside from getting rid of them
|
||||||
"take_damage" => deserialize_take_damage(raw_event),
|
let execution: AiCommand = match converted {
|
||||||
|
ExecutionConversionResult::Success(execution) => Ok(execution),
|
||||||
|
ExecutionConversionResult::PartialSuccess(execution, _) => Ok(execution),
|
||||||
|
ExecutionConversionResult::Failure(failures) => Err(anyhow!(
|
||||||
|
"unhandled command execution failure: {:?}",
|
||||||
|
failures
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
|
||||||
// miscellaneous
|
Ok(CommandExecution::AiCommand(execution))
|
||||||
"narration" => Ok(CommandEvent::Narration(raw_event.parameter)),
|
|
||||||
|
|
||||||
// unrecognized
|
|
||||||
_ => Err(EventConversionError::UnrecognizedEvent(raw_event)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_take_damage(
|
|
||||||
raw_event: RawCommandEvent,
|
|
||||||
) -> Result<CommandEvent, EventConversionError> {
|
|
||||||
match raw_event.parameter.parse::<u32>() {
|
|
||||||
Ok(dmg) => Ok(CommandEvent::TakeDamage {
|
|
||||||
target: raw_event.applies_to,
|
|
||||||
amount: dmg,
|
|
||||||
}),
|
|
||||||
Err(_) => Err(EventConversionError::InvalidParameter(raw_event)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_event_coherence<'a>(
|
|
||||||
db: &Database,
|
|
||||||
event: CommandEvent,
|
|
||||||
) -> std::result::Result<CommandEvent, EventCoherenceFailure> {
|
|
||||||
match event {
|
|
||||||
CommandEvent::ChangeScene { ref scene_key } => match db.stage_exists(&scene_key).await {
|
|
||||||
Ok(exists) => match exists {
|
|
||||||
true => Ok(event),
|
|
||||||
false => Err(invalid_converted_event(event).unwrap()),
|
|
||||||
},
|
|
||||||
Err(err) => Err(invalid_converted_event_because_err(event, err)),
|
|
||||||
},
|
|
||||||
_ => Ok(event),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_converted_event(event: CommandEvent) -> Option<EventCoherenceFailure> {
|
|
||||||
match event {
|
|
||||||
CommandEvent::ChangeScene { .. } => Some(EventCoherenceFailure::TargetDoesNotExist(event)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_converted_event_because_err(
|
|
||||||
event: CommandEvent,
|
|
||||||
err: anyhow::Error,
|
|
||||||
) -> EventCoherenceFailure {
|
|
||||||
EventCoherenceFailure::OtherError(event, format!("{}", err))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::models::commands::{CachedCommand, Command, Commands};
|
use crate::models::commands::{CachedParsedCommand, ParsedCommand, ParsedCommands};
|
||||||
use crate::models::world::scenes::{Scene, Stage, StageOrStub};
|
use crate::models::world::scenes::{Scene, Stage, StageOrStub};
|
||||||
use crate::models::{Content, ContentContainer, Insertable};
|
use crate::models::{Content, ContentContainer, Insertable};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -388,10 +388,10 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
raw_cmd: &str,
|
raw_cmd: &str,
|
||||||
scene: &Scene,
|
scene: &Scene,
|
||||||
parsed_cmds: &Commands,
|
parsed_cmds: &ParsedCommands,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let collection = self.collection(CMD_COLLECTION).await?;
|
let collection = self.collection(CMD_COLLECTION).await?;
|
||||||
let doc = CachedCommand {
|
let doc = CachedParsedCommand {
|
||||||
raw: raw_cmd.to_string(),
|
raw: raw_cmd.to_string(),
|
||||||
scene_key: scene._key.as_ref().cloned().expect("scene is missing key"),
|
scene_key: scene._key.as_ref().cloned().expect("scene is missing key"),
|
||||||
commands: parsed_cmds.clone(),
|
commands: parsed_cmds.clone(),
|
||||||
|
@ -405,7 +405,7 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
raw_cmd: &str,
|
raw_cmd: &str,
|
||||||
scene: &Scene,
|
scene: &Scene,
|
||||||
) -> Result<Option<CachedCommand>> {
|
) -> Result<Option<CachedParsedCommand>> {
|
||||||
let scene_key = scene._key.as_deref();
|
let scene_key = scene._key.as_deref();
|
||||||
let aql = AqlQuery::builder()
|
let aql = AqlQuery::builder()
|
||||||
.query(queries::LOAD_CACHED_COMMAND)
|
.query(queries::LOAD_CACHED_COMMAND)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::db::Database;
|
|
||||||
use crate::io::display;
|
use crate::io::display;
|
||||||
use crate::models::commands::CommandExecution;
|
use crate::models::commands::{AiCommand, BuiltinCommand, CommandExecution};
|
||||||
use crate::state::GameState;
|
use crate::state::GameState;
|
||||||
|
use crate::{commands::CommandExecutor, db::Database};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use reedline::{DefaultPrompt, Reedline, Signal};
|
use reedline::{DefaultPrompt, Reedline, Signal};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct GameLoop {
|
pub struct GameLoop {
|
||||||
|
executor: CommandExecutor,
|
||||||
state: GameState,
|
state: GameState,
|
||||||
db: Rc<Database>,
|
db: Rc<Database>,
|
||||||
editor: Reedline,
|
editor: Reedline,
|
||||||
|
@ -14,16 +15,21 @@ pub struct GameLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameLoop {
|
impl GameLoop {
|
||||||
pub fn new(state: GameState, db: Rc<Database>) -> GameLoop {
|
pub fn new(state: GameState, db: &Rc<Database>) -> GameLoop {
|
||||||
|
let executor_db = db.clone();
|
||||||
|
let loop_db = db.clone();
|
||||||
|
let executor_logic = state.logic.clone();
|
||||||
|
|
||||||
GameLoop {
|
GameLoop {
|
||||||
state,
|
state,
|
||||||
db,
|
db: loop_db,
|
||||||
|
executor: CommandExecutor::new(executor_logic, executor_db),
|
||||||
editor: Reedline::create(),
|
editor: Reedline::create(),
|
||||||
prompt: DefaultPrompt::default(),
|
prompt: DefaultPrompt::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_execution(&mut self, execution: CommandExecution) -> Result<()> {
|
async fn handle_ai_command(&mut self, execution: AiCommand) -> Result<()> {
|
||||||
if !execution.valid {
|
if !execution.valid {
|
||||||
display!(
|
display!(
|
||||||
"You can't do that: {}",
|
"You can't do that: {}",
|
||||||
|
@ -42,32 +48,29 @@ impl GameLoop {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_command(&mut self, cmd: &str) -> Result<CommandExecution> {
|
// TODO this will probably eventually be moved to its own file.
|
||||||
let stage = &self.state.current_scene;
|
async fn handle_builtin(&mut self, builtin: BuiltinCommand) -> Result<()> {
|
||||||
|
match builtin {
|
||||||
let cached_command = self.db.load_cached_command(cmd, &stage.scene).await?;
|
BuiltinCommand::Look => display!("{}", self.state.current_scene),
|
||||||
|
|
||||||
let execution = if let Some(cached) = cached_command {
|
|
||||||
self.state
|
|
||||||
.logic
|
|
||||||
.execute_parsed(stage, &cached.commands)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
let (cmds_to_cache, execution) = self.state.logic.execute(stage, cmd).await?;
|
|
||||||
|
|
||||||
self.db
|
|
||||||
.cache_command(cmd, &stage.scene, &cmds_to_cache)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
execution
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(execution)
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_execution(&mut self, execution: CommandExecution) -> Result<()> {
|
||||||
|
match execution {
|
||||||
|
CommandExecution::Builtin(builtin) => self.handle_builtin(builtin).await?,
|
||||||
|
CommandExecution::AiCommand(exec) => self.handle_ai_command(exec).await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_input(&mut self, cmd: &str) -> Result<()> {
|
async fn handle_input(&mut self, cmd: &str) -> Result<()> {
|
||||||
if !cmd.is_empty() {
|
if !cmd.is_empty() {
|
||||||
let execution = self.execute_command(cmd).await?;
|
//let execution = self.execute_command(cmd).await?;
|
||||||
|
let mut stage = &self.state.current_scene;
|
||||||
|
let execution = self.executor.execute(&mut stage, cmd).await?;
|
||||||
self.handle_execution(execution).await?;
|
self.handle_execution(execution).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,11 @@ pub(crate) fn display_text<S : AsRef<str>>(text: S) {
|
||||||
|
|
||||||
macro_rules! display {
|
macro_rules! display {
|
||||||
($text:expr) => {
|
($text:expr) => {
|
||||||
crate::io::display_text($text);
|
crate::io::display_text($text)
|
||||||
};
|
};
|
||||||
|
|
||||||
($fmt:expr, $text:expr) => {
|
($fmt:expr, $text:expr) => {
|
||||||
crate::io::display_text(format!($fmt, $text));
|
crate::io::display_text(format!($fmt, $text))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use game_loop::GameLoop;
|
use game_loop::GameLoop;
|
||||||
|
use ai::logic::AiLogic;
|
||||||
use models::world::scenes::{root_scene_id, Stage};
|
use models::world::scenes::{root_scene_id, Stage};
|
||||||
use state::GameState;
|
use state::GameState;
|
||||||
use std::{io::stdout, rc::Rc, time::Duration};
|
use std::{io::stdout, rc::Rc, time::Duration};
|
||||||
|
@ -101,7 +102,7 @@ async fn main() -> Result<()> {
|
||||||
base_client,
|
base_client,
|
||||||
));
|
));
|
||||||
let db = Rc::new(Database::new(conn, "test_world").await?);
|
let db = Rc::new(Database::new(conn, "test_world").await?);
|
||||||
let logic = ai::AiLogic::new(client, &db);
|
let logic = Rc::new(AiLogic::new(client, &db));
|
||||||
|
|
||||||
let mut state = GameState {
|
let mut state = GameState {
|
||||||
logic,
|
logic,
|
||||||
|
@ -113,7 +114,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
load_root_scene(&db, &mut state).await?;
|
load_root_scene(&db, &mut state).await?;
|
||||||
|
|
||||||
let mut game_loop = GameLoop::new(state, db.clone());
|
let mut game_loop = GameLoop::new(state, &db);
|
||||||
game_loop.run_loop().await?;
|
game_loop.run_loop().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,22 +2,24 @@ use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumString, EnumVariantNames};
|
use strum::{EnumString, EnumVariantNames};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::world::scenes::Stage;
|
||||||
|
|
||||||
/// Stored in the database to bypass AI 'parsing' when possible.
|
/// Stored in the database to bypass AI 'parsing' when possible.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CachedCommand {
|
pub struct CachedParsedCommand {
|
||||||
pub raw: String,
|
pub raw: String,
|
||||||
pub scene_key: String,
|
pub scene_key: String,
|
||||||
pub commands: Commands,
|
pub commands: ParsedCommands,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Commands {
|
pub struct ParsedCommands {
|
||||||
pub commands: Vec<Command>,
|
pub commands: Vec<ParsedCommand>,
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Command {
|
pub struct ParsedCommand {
|
||||||
pub verb: String,
|
pub verb: String,
|
||||||
pub target: String,
|
pub target: String,
|
||||||
pub location: String,
|
pub location: String,
|
||||||
|
@ -49,6 +51,17 @@ pub struct RawCommandExecution {
|
||||||
pub events: Vec<RawCommandEvent>,
|
pub events: Vec<RawCommandEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawCommandExecution {
|
||||||
|
pub fn empty() -> RawCommandExecution {
|
||||||
|
RawCommandExecution {
|
||||||
|
valid: true,
|
||||||
|
reason: None,
|
||||||
|
narration: "".to_string(),
|
||||||
|
events: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RawCommandEvent {
|
pub struct RawCommandEvent {
|
||||||
|
@ -87,17 +100,34 @@ pub enum CommandEvent {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A builtin command has more immediate access to necessary
|
||||||
|
/// information, so we can be a bit more loose with what we give it. A
|
||||||
|
/// builtin command is only created directly via checking for builtin
|
||||||
|
/// commands. These commands may have little or no parameters, as they
|
||||||
|
/// are meant for simple, direct commands like looking, movement, etc.
|
||||||
|
pub enum BuiltinCommand {
|
||||||
|
Look,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CommandExecution {
|
||||||
|
Builtin(BuiltinCommand),
|
||||||
|
AiCommand(AiCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An "AI Command" is a command execution generated by the LLM and
|
||||||
|
/// run through coherence validation/fixing, and (assuming it is
|
||||||
|
/// valid) contains a series of events to apply to the game state.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CommandExecution {
|
pub struct AiCommand {
|
||||||
pub valid: bool,
|
pub valid: bool,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
pub narration: String,
|
pub narration: String,
|
||||||
pub events: Vec<CommandEvent>,
|
pub events: Vec<CommandEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandExecution {
|
impl AiCommand {
|
||||||
pub fn empty() -> CommandExecution {
|
pub fn empty() -> AiCommand {
|
||||||
CommandExecution {
|
AiCommand {
|
||||||
valid: true,
|
valid: true,
|
||||||
reason: None,
|
reason: None,
|
||||||
narration: "".to_string(),
|
narration: "".to_string(),
|
||||||
|
@ -105,8 +135,8 @@ impl CommandExecution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_raw_invalid(raw: RawCommandExecution) -> CommandExecution {
|
pub fn from_raw_invalid(raw: RawCommandExecution) -> AiCommand {
|
||||||
CommandExecution {
|
AiCommand {
|
||||||
valid: raw.valid,
|
valid: raw.valid,
|
||||||
reason: raw.reason,
|
reason: raw.reason,
|
||||||
narration: "".to_string(),
|
narration: "".to_string(),
|
||||||
|
@ -117,8 +147,8 @@ impl CommandExecution {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ExecutionConversionResult {
|
pub enum ExecutionConversionResult {
|
||||||
Success(CommandExecution),
|
Success(AiCommand),
|
||||||
PartialSuccess(CommandExecution, EventConversionFailures),
|
PartialSuccess(AiCommand, EventConversionFailures),
|
||||||
Failure(EventConversionFailures),
|
Failure(EventConversionFailures),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::models::Insertable;
|
use crate::models::Insertable;
|
||||||
use crate::{
|
use crate::{
|
||||||
ai::AiLogic,
|
ai::logic::AiLogic,
|
||||||
db::Database,
|
db::Database,
|
||||||
models::{
|
models::{
|
||||||
commands::CommandEvent,
|
commands::CommandEvent,
|
||||||
|
@ -13,7 +13,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub start_prompt: String,
|
pub start_prompt: String,
|
||||||
pub logic: AiLogic,
|
pub logic: Rc<AiLogic>,
|
||||||
pub db: Rc<Database>,
|
pub db: Rc<Database>,
|
||||||
pub current_scene: Stage,
|
pub current_scene: Stage,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue