Switch to one event output per command execution. Simplify coherence.
This commit is contained in:
parent
d23f09295e
commit
c6f10f7a61
|
@ -1,5 +1,5 @@
|
||||||
use crate::ai::convo::AiPrompt;
|
use crate::ai::convo::AiPrompt;
|
||||||
use crate::models::commands::{CommandEvent, EventConversionFailures, ParsedCommand};
|
use crate::models::commands::{CommandEvent, EventConversionFailure, ParsedCommand};
|
||||||
use crate::models::world::items::Item;
|
use crate::models::world::items::Item;
|
||||||
use crate::models::world::people::Person;
|
use crate::models::world::people::Person;
|
||||||
use crate::models::world::scenes::{Exit, Prop, Scene, Stage};
|
use crate::models::world::scenes::{Exit, Prop, Scene, Stage};
|
||||||
|
@ -75,8 +75,7 @@ impl<'a> From<&'a Exit> for ExitTableRow<'a> {
|
||||||
const COMMAND_EXECUTION_BNF: &'static str = r#"
|
const COMMAND_EXECUTION_BNF: &'static str = r#"
|
||||||
root ::= CommandExecution
|
root ::= CommandExecution
|
||||||
CommandEvent ::= "{" ws "\"eventName\":" ws string "," ws "\"appliesTo\":" ws string "," ws "\"parameter\":" ws string "}"
|
CommandEvent ::= "{" ws "\"eventName\":" ws string "," ws "\"appliesTo\":" ws string "," ws "\"parameter\":" ws string "}"
|
||||||
CommandEventlist ::= "[]" | "[" ws CommandEvent ("," ws CommandEvent)* "]"
|
CommandExecution ::= "{" ws "\"valid\":" ws boolean "," ws "\"reason\":" ws string "," ws "\"narration\":" ws string "," ws "\"event\":" ws CommandEvent "}"
|
||||||
CommandExecution ::= "{" ws "\"valid\":" ws boolean "," ws "\"reason\":" ws string "," ws "\"narration\":" ws string "," ws "\"events\":" ws CommandEventlist "}"
|
|
||||||
CommandExecutionlist ::= "[]" | "[" ws CommandExecution ("," ws CommandExecution)* "]"
|
CommandExecutionlist ::= "[]" | "[" ws CommandExecution ("," ws CommandExecution)* "]"
|
||||||
string ::= "\"" ([^"]*) "\""
|
string ::= "\"" ([^"]*) "\""
|
||||||
boolean ::= "true" | "false"
|
boolean ::= "true" | "false"
|
||||||
|
@ -113,16 +112,17 @@ The `events` field must be filled with entries if the command is valid. It is a
|
||||||
- `name`: The name of the event, which can be one of the ones detailed below.
|
- `name`: The name of the event, which can be one of the ones detailed below.
|
||||||
- `appliesTo`: The player, item, NPC, or other entity in the scene.
|
- `appliesTo`: The player, item, NPC, or other entity in the scene.
|
||||||
- The event applies only to one target.
|
- The event applies only to one target.
|
||||||
- The `appliesTo` field should be the `key` of the target. If no key was provided, use the target's name instead.
|
- The `appliesTo` field should be the `key` of the target. If no key was provided, use the target's name instead. The `key` is usualy a UUID.
|
||||||
- `parameter`: Optional parameter with a string value that will be parsed. Parameters allowed depend on the type of event, and are detailed below.
|
- `parameter`: Optional parameter with a string value that will be parsed. Parameters allowed depend on the type of event, and are detailed below.
|
||||||
|
|
||||||
The following events can be generated:
|
The following events can be generated:
|
||||||
- `change_scene`: The player's current scene is changed.
|
- `change_scene`: The player's current scene is changed.
|
||||||
- `appliesTo` must be set to `player`.
|
- `appliesTo` must be set to `player`.
|
||||||
- `parameter` must be the Scene Key of the new scene.
|
- `parameter` must be the Scene Key of the new scene. This is a UUID.
|
||||||
- `look_at_entity`: The player is looking at an entity--a person, prop, or item in the scene.
|
- `look_at_entity`: The player is looking at an entity--a person, prop, or item in the scene.
|
||||||
- `appliesTo` is the Scene Key of the current scene.
|
- `appliesTo` is the key of the person, prop, or item being looked at.
|
||||||
- `parameter` is the Entity Key of the entity being looked at.
|
- `appliesTo` must NOT be the **NAME** of the entity. It **MUST** be the UUID key.
|
||||||
|
- `parameter` is irrelevant for this event.
|
||||||
- `take_damage`: The target of the event takes an amount of damage.
|
- `take_damage`: The target of the event takes an amount of damage.
|
||||||
- `appliesTo` must be the target taking damage (player, NPC, item, prop, or other thing in the scene)
|
- `appliesTo` must be the target taking damage (player, NPC, item, prop, or other thing in the scene)
|
||||||
- `parameter` must be the amount of damage taken. This value must be a positive integer.
|
- `parameter` must be the amount of damage taken. This value must be a positive integer.
|
||||||
|
@ -145,6 +145,8 @@ The following events can be generated:
|
||||||
- `appliesTo` must be the target in the scene that the event would apply to, if it was a valid event.
|
- `appliesTo` must be the target in the scene that the event would apply to, if it was a valid event.
|
||||||
- `parameter` should be a value that theoretically makes sense, if this event was a valid event.
|
- `parameter` should be a value that theoretically makes sense, if this event was a valid event.
|
||||||
|
|
||||||
|
Make sure the `appliesTo` field and `parameter` field are UUIDs, if the event requires it.
|
||||||
|
|
||||||
Check that the events make sense and are generated correctly, given the original command.
|
Check that the events make sense and are generated correctly, given the original command.
|
||||||
|
|
||||||
The original command is the raw text entered by the player.
|
The original command is the raw text entered by the player.
|
||||||
|
@ -256,6 +258,6 @@ pub fn execution_prompt(original_cmd: &str, stage: &Stage, cmd: &ParsedCommand)
|
||||||
AiPrompt::new_with_grammar_and_size(&prompt, COMMAND_EXECUTION_BNF, 512)
|
AiPrompt::new_with_grammar_and_size(&prompt, COMMAND_EXECUTION_BNF, 512)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fix_prompt(scene: &Scene, failures: &EventConversionFailures) -> AiPrompt {
|
pub fn fix_prompt(scene: &Scene, failures: &EventConversionFailure) -> AiPrompt {
|
||||||
AiPrompt::new("")
|
AiPrompt::new("")
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use futures::{future, TryFutureExt};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
type CoherenceResult = Result<CommandEvent, EventCoherenceFailure>;
|
type CoherenceResult = Result<AiCommand, EventCoherenceFailure>;
|
||||||
|
|
||||||
pub struct CommandCoherence<'a> {
|
pub struct CommandCoherence<'a> {
|
||||||
logic: Rc<AiLogic>,
|
logic: Rc<AiLogic>,
|
||||||
|
@ -35,33 +35,20 @@ impl CommandCoherence<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fix_incoherent_events(
|
pub async fn fix_incoherent_event(
|
||||||
&self,
|
&self,
|
||||||
failures: Vec<EventCoherenceFailure>,
|
failure: EventCoherenceFailure,
|
||||||
) -> ExecutionConversionResult {
|
) -> ExecutionConversionResult {
|
||||||
let (successes, failures) = partition!(
|
|
||||||
stream::iter(failures.into_iter()).then(|failure| self.cohere_event(failure))
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO we need to use LLM on events that have failed non-LLM coherence.
|
// TODO we need to use LLM on events that have failed non-LLM coherence.
|
||||||
|
let coherent_event = self.cohere_event(failure).await?;
|
||||||
if successes.len() > 0 && failures.len() == 0 {
|
Ok(coherent_event)
|
||||||
ExecutionConversionResult::Success(AiCommand::from_events(successes))
|
|
||||||
} else if successes.len() > 0 && failures.len() > 0 {
|
|
||||||
ExecutionConversionResult::PartialSuccess(
|
|
||||||
AiCommand::from_events(successes),
|
|
||||||
failures.into(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ExecutionConversionResult::Failure(failures.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cohere_event(&self, failure: EventCoherenceFailure) -> CoherenceResult {
|
async fn cohere_event(&self, failure: EventCoherenceFailure) -> CoherenceResult {
|
||||||
let event_fix = async {
|
let event_fix = async {
|
||||||
match failure {
|
match failure {
|
||||||
EventCoherenceFailure::TargetDoesNotExist(event) => {
|
EventCoherenceFailure::TargetDoesNotExist(cmd) => {
|
||||||
self.fix_target_does_not_exist(event).await
|
self.fix_target_does_not_exist(cmd).await
|
||||||
}
|
}
|
||||||
EventCoherenceFailure::OtherError(event, _) => future::ok(event).await,
|
EventCoherenceFailure::OtherError(event, _) => future::ok(event).await,
|
||||||
}
|
}
|
||||||
|
@ -72,20 +59,22 @@ impl CommandCoherence<'_> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fix_target_does_not_exist(&self, mut event: CommandEvent) -> CoherenceResult {
|
async fn fix_target_does_not_exist(&self, mut cmd: AiCommand) -> CoherenceResult {
|
||||||
if let CommandEvent::LookAtEntity {
|
if cmd.event.is_none() {
|
||||||
ref mut entity_key,
|
return Ok(cmd);
|
||||||
ref mut scene_key,
|
}
|
||||||
} = event
|
|
||||||
{
|
let event: &mut CommandEvent = cmd.event.as_mut().unwrap();
|
||||||
let res = cohere_scene_and_entity(&self.db, &self.stage, entity_key, scene_key).await;
|
|
||||||
|
if let CommandEvent::LookAtEntity(ref mut entity_key) = event {
|
||||||
|
let res = cohere_scene_and_entity(&self.db, &self.stage, entity_key).await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => Ok(event),
|
Ok(_) => Ok(cmd),
|
||||||
Err(err) => Err(EventCoherenceFailure::OtherError(event, err.to_string())),
|
Err(err) => Err(EventCoherenceFailure::OtherError(cmd, err.to_string())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(event)
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,29 +85,11 @@ async fn cohere_scene_and_entity(
|
||||||
db: &Database,
|
db: &Database,
|
||||||
stage: &Stage,
|
stage: &Stage,
|
||||||
entity_key: &mut String,
|
entity_key: &mut String,
|
||||||
scene_key: &mut String,
|
|
||||||
) -> AnyhowResult<()> {
|
) -> AnyhowResult<()> {
|
||||||
// Normalize UUIDs, assuming that they are proper UUIDs.
|
// Normalize UUIDs, assuming that they are proper UUIDs.
|
||||||
normalize_keys(scene_key, entity_key);
|
normalize_keys(&mut [entity_key]);
|
||||||
|
|
||||||
// Sometimes scene key is actually the entity key, and the entity
|
let scene_key = &stage.key;
|
||||||
// key is blank.
|
|
||||||
if !scene_key.is_empty() && scene_key != &stage.key {
|
|
||||||
// Check if scene key is an entity
|
|
||||||
if db.entity_exists(&stage.key, &scene_key).await? {
|
|
||||||
entity_key.clear();
|
|
||||||
entity_key.push_str(&scene_key);
|
|
||||||
scene_key.clear();
|
|
||||||
scene_key.push_str(&stage.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If scene key isn't valid, override it from known-good
|
|
||||||
// information.
|
|
||||||
if !is_valid_scene_key(scene_key) {
|
|
||||||
scene_key.clear();
|
|
||||||
scene_key.push_str(&stage.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If entity key is not a valid UUID at this point, then we have
|
// If entity key is not a valid UUID at this point, then we have
|
||||||
// entered a weird situation.
|
// entered a weird situation.
|
||||||
|
@ -132,14 +103,11 @@ async fn cohere_scene_and_entity(
|
||||||
return Err(anyhow!("Scene key and entity key are the same"));
|
return Err(anyhow!("Scene key and entity key are the same"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is often likely that the scene key and entity key are reversed.
|
// Final result is if the entity actually exists or not now.
|
||||||
if db.entity_exists(&entity_key, &scene_key).await? {
|
db.entity_exists(entity_key).await.map(|_| ())
|
||||||
std::mem::swap(entity_key, scene_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
fn is_valid_scene_key(scene_key: &str) -> bool {
|
fn is_valid_scene_key(scene_key: &str) -> bool {
|
||||||
scene_key == root_scene_id() || Uuid::try_parse(&scene_key).is_ok()
|
scene_key == root_scene_id() || Uuid::try_parse(&scene_key).is_ok()
|
||||||
}
|
}
|
||||||
|
@ -157,15 +125,12 @@ pub fn strip_prefixes(value: String) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure entity keys are valid UUIDs, and fix them if possible.
|
/// Make sure entity keys are valid UUIDs, and fix them if possible.
|
||||||
fn normalize_keys(scene_key: &mut String, entity_key: &mut String) {
|
pub(super) fn normalize_keys(keys: &mut [&mut String]) {
|
||||||
if let Some(normalized) = normalize_uuid(&scene_key) {
|
for key in keys {
|
||||||
scene_key.clear();
|
if let Some(normalized) = normalize_uuid(&key) {
|
||||||
scene_key.push_str(&normalized);
|
key.clear();
|
||||||
|
key.push_str(&normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(normalized) = normalize_uuid(&entity_key) {
|
|
||||||
entity_key.clear();
|
|
||||||
entity_key.push_str(&normalized);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,125 +1,77 @@
|
||||||
use super::coherence::strip_prefixes;
|
use super::coherence::strip_prefixes;
|
||||||
use super::partition;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::Database,
|
db::Database,
|
||||||
models::commands::{
|
models::commands::{
|
||||||
AiCommand, CommandEvent, EventCoherenceFailure, EventConversionError,
|
AiCommand, CommandEvent, EventCoherenceFailure, EventParsingFailure,
|
||||||
EventConversionFailures, ExecutionConversionResult, RawCommandEvent, RawCommandExecution,
|
ExecutionConversionResult, Narrative, RawCommandEvent, RawCommandExecution,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::stream::{self, StreamExt, TryStreamExt};
|
|
||||||
use itertools::{Either, Itertools};
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use strum::VariantNames;
|
use strum::VariantNames;
|
||||||
|
|
||||||
type EventConversionResult = std::result::Result<CommandEvent, EventConversionError>;
|
type EventParsingResult = std::result::Result<CommandEvent, EventParsingFailure>;
|
||||||
|
|
||||||
impl CommandEvent {
|
impl CommandEvent {
|
||||||
pub fn new(raw_event: RawCommandEvent) -> EventConversionResult {
|
pub fn new(raw_event: RawCommandEvent) -> EventParsingResult {
|
||||||
let event_name = raw_event.event_name.as_str().to_lowercase();
|
let event_name = raw_event.event_name.as_str().to_lowercase();
|
||||||
|
|
||||||
if Self::VARIANTS.contains(&event_name.as_str()) {
|
if Self::VARIANTS.contains(&event_name.as_str()) {
|
||||||
deserialize_recognized_event(raw_event)
|
deserialize_recognized_event(raw_event)
|
||||||
} else {
|
} else {
|
||||||
Err(EventConversionError::UnrecognizedEvent(raw_event))
|
Err(EventParsingFailure::UnrecognizedEvent(raw_event))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<RawCommandEvent> for CommandEvent {
|
impl TryFrom<RawCommandEvent> for CommandEvent {
|
||||||
type Error = EventConversionError;
|
type Error = EventParsingFailure;
|
||||||
|
|
||||||
fn try_from(raw_event: RawCommandEvent) -> Result<Self, Self::Error> {
|
fn try_from(raw_event: RawCommandEvent) -> Result<Self, Self::Error> {
|
||||||
CommandEvent::new(raw_event)
|
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,
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
narration: raw.narration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn convert_raw_execution(
|
pub async fn convert_raw_execution(
|
||||||
mut raw_exec: RawCommandExecution,
|
mut raw_exec: RawCommandExecution,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
) -> ExecutionConversionResult {
|
) -> ExecutionConversionResult {
|
||||||
if !raw_exec.valid {
|
if !raw_exec.valid {
|
||||||
return ExecutionConversionResult::Success(AiCommand::from_raw_invalid(raw_exec));
|
return Ok(AiCommand::from_raw_invalid(raw_exec));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if raw_exec.event.is_none() {
|
||||||
|
return Ok(AiCommand::empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_event = raw_exec.event.unwrap();
|
||||||
|
|
||||||
let narrative = Narrative {
|
let narrative = Narrative {
|
||||||
valid: raw_exec.valid,
|
valid: raw_exec.valid,
|
||||||
reason: raw_exec.reason.take(),
|
reason: raw_exec.reason.take(),
|
||||||
narration: std::mem::take(&mut raw_exec.narration),
|
narration: std::mem::take(&mut raw_exec.narration),
|
||||||
};
|
};
|
||||||
|
|
||||||
let conversions: Vec<_> = raw_exec
|
let converted_event = CommandEvent::new(raw_event)?;
|
||||||
.events
|
let cmd = AiCommand::from_raw_success(narrative, converted_event);
|
||||||
.into_iter()
|
validate_event_coherence(db, cmd)
|
||||||
.map(|raw_event| CommandEvent::new(raw_event))
|
.await
|
||||||
.collect();
|
.map_err(|e| e.into())
|
||||||
|
|
||||||
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) = partition!(
|
|
||||||
stream::iter(converted.into_iter()).then(|event| validate_event_coherence(db, event))
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
fn deserialize_recognized_event(
|
||||||
raw_event: RawCommandEvent,
|
raw_event: RawCommandEvent,
|
||||||
) -> Result<CommandEvent, EventConversionError> {
|
) -> Result<CommandEvent, EventParsingFailure> {
|
||||||
let event_name = raw_event.event_name.as_str().to_lowercase();
|
let event_name = raw_event.event_name.as_str().to_lowercase();
|
||||||
let event_name = event_name.as_str();
|
let event_name = event_name.as_str();
|
||||||
|
|
||||||
match event_name {
|
match event_name {
|
||||||
// informational-related
|
// informational-related
|
||||||
"narration" => Ok(CommandEvent::Narration(raw_event.parameter)),
|
"narration" => Ok(CommandEvent::Narration(raw_event.parameter)),
|
||||||
"look_at_entity" => Ok(CommandEvent::LookAtEntity {
|
"look_at_entity" => Ok(CommandEvent::LookAtEntity(
|
||||||
entity_key: strip_prefixes(raw_event.parameter),
|
deserialize_and_normalize(raw_event),
|
||||||
scene_key: strip_prefixes(raw_event.applies_to),
|
)),
|
||||||
}),
|
|
||||||
|
|
||||||
// scene-related
|
// scene-related
|
||||||
"change_scene" => Ok(CommandEvent::ChangeScene {
|
"change_scene" => Ok(CommandEvent::ChangeScene {
|
||||||
|
@ -144,54 +96,77 @@ fn deserialize_recognized_event(
|
||||||
"take_damage" => deserialize_take_damage(raw_event),
|
"take_damage" => deserialize_take_damage(raw_event),
|
||||||
|
|
||||||
// unrecognized
|
// unrecognized
|
||||||
_ => Err(EventConversionError::UnrecognizedEvent(raw_event)),
|
_ => Err(EventParsingFailure::UnrecognizedEvent(raw_event)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize and normalize an expected UUID parameter.
|
||||||
|
fn deserialize_and_normalize(raw_event: RawCommandEvent) -> String {
|
||||||
|
let mut key = if !raw_event.applies_to.is_empty() {
|
||||||
|
raw_event.applies_to
|
||||||
|
} else {
|
||||||
|
raw_event.parameter
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut key = strip_prefixes(key);
|
||||||
|
super::coherence::normalize_keys(&mut [&mut key]);
|
||||||
|
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_single(raw_event: RawCommandEvent) -> String {
|
||||||
|
if !raw_event.applies_to.is_empty() {
|
||||||
|
raw_event.applies_to
|
||||||
|
} else {
|
||||||
|
raw_event.parameter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_take_damage(
|
fn deserialize_take_damage(
|
||||||
raw_event: RawCommandEvent,
|
raw_event: RawCommandEvent,
|
||||||
) -> Result<CommandEvent, EventConversionError> {
|
) -> Result<CommandEvent, EventParsingFailure> {
|
||||||
match raw_event.parameter.parse::<u32>() {
|
match raw_event.parameter.parse::<u32>() {
|
||||||
Ok(dmg) => Ok(CommandEvent::TakeDamage {
|
Ok(dmg) => Ok(CommandEvent::TakeDamage {
|
||||||
target: strip_prefixes(raw_event.applies_to),
|
target: strip_prefixes(raw_event.applies_to),
|
||||||
amount: dmg,
|
amount: dmg,
|
||||||
}),
|
}),
|
||||||
Err(_) => Err(EventConversionError::InvalidParameter(raw_event)),
|
Err(_) => Err(EventParsingFailure::InvalidParameter(raw_event)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn validate_event_coherence<'a>(
|
pub(super) async fn validate_event_coherence<'a>(
|
||||||
db: &Database,
|
db: &Database,
|
||||||
event: CommandEvent,
|
cmd: AiCommand,
|
||||||
) -> std::result::Result<CommandEvent, EventCoherenceFailure> {
|
) -> std::result::Result<AiCommand, EventCoherenceFailure> {
|
||||||
match event {
|
if cmd.event.is_none() {
|
||||||
CommandEvent::LookAtEntity {
|
return Ok(cmd);
|
||||||
ref entity_key,
|
}
|
||||||
ref scene_key,
|
|
||||||
} => match db.entity_exists(&scene_key, &entity_key).await {
|
match cmd.event.as_ref().unwrap() {
|
||||||
|
CommandEvent::LookAtEntity(ref entity_key) => match db.entity_exists(&entity_key).await {
|
||||||
Ok(exists) => match exists {
|
Ok(exists) => match exists {
|
||||||
true => Ok(event),
|
true => Ok(cmd),
|
||||||
false => Err(invalid_converted_event(event).unwrap()),
|
false => Err(invalid_converted_event(cmd).unwrap()),
|
||||||
},
|
},
|
||||||
Err(err) => Err(invalid_converted_event_because_err(event, err)),
|
Err(err) => Err(invalid_converted_event_because_err(cmd, err)),
|
||||||
},
|
},
|
||||||
CommandEvent::ChangeScene { ref scene_key } => match db.stage_exists(&scene_key).await {
|
CommandEvent::ChangeScene { ref scene_key } => match db.stage_exists(&scene_key).await {
|
||||||
Ok(exists) => match exists {
|
Ok(exists) => match exists {
|
||||||
true => Ok(event),
|
true => Ok(cmd),
|
||||||
false => Err(invalid_converted_event(event).unwrap()),
|
false => Err(invalid_converted_event(cmd).unwrap()),
|
||||||
},
|
},
|
||||||
Err(err) => Err(invalid_converted_event_because_err(event, err)),
|
Err(err) => Err(invalid_converted_event_because_err(cmd, err)),
|
||||||
},
|
},
|
||||||
_ => Ok(event),
|
_ => Ok(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The event was converted from the raw response properly, but the
|
/// The event was converted from the raw response properly, but the
|
||||||
/// information contained in the response is not valid.
|
/// information contained in the response is not valid.
|
||||||
fn invalid_converted_event(event: CommandEvent) -> Option<EventCoherenceFailure> {
|
fn invalid_converted_event(mut cmd: AiCommand) -> Option<EventCoherenceFailure> {
|
||||||
match event {
|
match cmd.event.as_mut().unwrap() {
|
||||||
CommandEvent::LookAtEntity { .. } => Some(EventCoherenceFailure::TargetDoesNotExist(event)),
|
CommandEvent::LookAtEntity { .. } => Some(EventCoherenceFailure::TargetDoesNotExist(cmd)),
|
||||||
CommandEvent::ChangeScene { .. } => Some(EventCoherenceFailure::TargetDoesNotExist(event)),
|
CommandEvent::ChangeScene { .. } => Some(EventCoherenceFailure::TargetDoesNotExist(cmd)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,8 +175,8 @@ fn invalid_converted_event(event: CommandEvent) -> Option<EventCoherenceFailure>
|
||||||
/// something went wrong with attempting to check the coherence of the
|
/// something went wrong with attempting to check the coherence of the
|
||||||
/// converted event.
|
/// converted event.
|
||||||
fn invalid_converted_event_because_err(
|
fn invalid_converted_event_because_err(
|
||||||
event: CommandEvent,
|
cmd: AiCommand,
|
||||||
err: anyhow::Error,
|
err: anyhow::Error,
|
||||||
) -> EventCoherenceFailure {
|
) -> EventCoherenceFailure {
|
||||||
EventCoherenceFailure::OtherError(event, format!("{}", err))
|
EventCoherenceFailure::OtherError(cmd, format!("{}", err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use crate::{
|
||||||
models::{
|
models::{
|
||||||
commands::{
|
commands::{
|
||||||
AiCommand, CommandEvent, CommandExecution, EventCoherenceFailure,
|
AiCommand, CommandEvent, CommandExecution, EventCoherenceFailure,
|
||||||
ExecutionConversionResult, ParsedCommand, ParsedCommands, RawCommandExecution,
|
EventConversionFailure, ExecutionConversionResult, ParsedCommand, ParsedCommands,
|
||||||
|
RawCommandExecution,
|
||||||
},
|
},
|
||||||
world::scenes::Stage,
|
world::scenes::Stage,
|
||||||
},
|
},
|
||||||
|
@ -119,33 +120,15 @@ impl CommandExecutor {
|
||||||
|
|
||||||
let converted = converter::convert_raw_execution(raw_exec, &self.db).await;
|
let converted = converter::convert_raw_execution(raw_exec, &self.db).await;
|
||||||
|
|
||||||
let execution: Result<AiCommand> = match converted {
|
let execution: AiCommand = match converted {
|
||||||
ExecutionConversionResult::Success(execution) => Ok(execution),
|
Ok(ai_command) => Ok(ai_command),
|
||||||
ExecutionConversionResult::PartialSuccess(mut execution, failures) => {
|
Err(failure) => {
|
||||||
// TODO also deal with conversion failures
|
// TODO also deal with conversion failures
|
||||||
// TODO deal with failures to fix incoherent events.
|
// TODO deal with failures to fix incoherent events.
|
||||||
// right now we just drop them.
|
// right now we just drop them.
|
||||||
let mut fixed_events = self
|
self.fix_incoherence(stage, failure).await
|
||||||
.fix_incoherence(stage, failures.coherence_failures)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.unwrap_or(vec![]);
|
|
||||||
|
|
||||||
execution.events.append(&mut fixed_events);
|
|
||||||
Ok(execution)
|
|
||||||
}
|
}
|
||||||
ExecutionConversionResult::Failure(failures) => {
|
}?;
|
||||||
// TODO also deal with conversion failures
|
|
||||||
// For a complete failure, we want to make sure all
|
|
||||||
// events become coherent.
|
|
||||||
Ok(AiCommand::from_events(
|
|
||||||
self.fix_incoherence(stage, failures.coherence_failures)
|
|
||||||
.await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let execution = execution?;
|
|
||||||
|
|
||||||
Ok(CommandExecution::AiCommand(execution))
|
Ok(CommandExecution::AiCommand(execution))
|
||||||
}
|
}
|
||||||
|
@ -153,18 +136,15 @@ impl CommandExecutor {
|
||||||
async fn fix_incoherence(
|
async fn fix_incoherence(
|
||||||
&self,
|
&self,
|
||||||
stage: &Stage,
|
stage: &Stage,
|
||||||
failures: Vec<EventCoherenceFailure>,
|
failure: EventConversionFailure,
|
||||||
) -> Result<Vec<CommandEvent>> {
|
) -> std::result::Result<AiCommand, EventConversionFailure> {
|
||||||
println!("Attempting to fix {} incoherent events", failures.len());
|
if let EventConversionFailure::CoherenceFailure(coherence_failure) = failure {
|
||||||
let fixer = coherence::CommandCoherence::new(&self.logic, &self.db, stage);
|
let fixer = coherence::CommandCoherence::new(&self.logic, &self.db, stage);
|
||||||
|
|
||||||
// TODO should do something w/ partial failures.
|
// TODO should do something w/ partial failures.
|
||||||
let events = match fixer.fix_incoherent_events(failures).await {
|
fixer.fix_incoherent_event(coherence_failure).await
|
||||||
ExecutionConversionResult::Success(AiCommand { events, .. }) => Ok(events),
|
} else {
|
||||||
ExecutionConversionResult::PartialSuccess(AiCommand { events, .. }, _) => Ok(events),
|
Err(failure)
|
||||||
ExecutionConversionResult::Failure(errs) => Err(errs),
|
}
|
||||||
}?;
|
|
||||||
|
|
||||||
Ok(events)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::models::commands::{CachedParsedCommand, ParsedCommand, ParsedCommands};
|
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, Entity};
|
use crate::models::{Content, ContentContainer, Entity, Insertable};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use arangors::document::options::InsertOptions;
|
use arangors::document::options::InsertOptions;
|
||||||
use arangors::graph::{EdgeDefinition, Graph};
|
use arangors::graph::{EdgeDefinition, Graph};
|
||||||
|
@ -384,11 +384,9 @@ impl Database {
|
||||||
Ok(stage_count > 0)
|
Ok(stage_count > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn entity_exists(&self, scene_key: &str, entity_key: &str) -> Result<bool> {
|
pub async fn entity_exists(&self, entity_key: &str) -> Result<bool> {
|
||||||
let mut vars = HashMap::new();
|
let mut vars = HashMap::new();
|
||||||
|
|
||||||
vars.insert("@scene_collection", SCENE_COLLECTION.into());
|
|
||||||
vars.insert("scene_key", to_json_value(scene_key).unwrap());
|
|
||||||
vars.insert("entity_key", to_json_value(entity_key).unwrap());
|
vars.insert("entity_key", to_json_value(entity_key).unwrap());
|
||||||
|
|
||||||
let db = self.db().await?;
|
let db = self.db().await?;
|
||||||
|
@ -400,9 +398,13 @@ impl Database {
|
||||||
Ok(entity_count > 0)
|
Ok(entity_count > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_entity(&self, scene_key: &str, entity_key: &str) -> Result<Option<Entity>> {
|
pub async fn load_entity_in_scene(
|
||||||
|
&self,
|
||||||
|
scene_key: &str,
|
||||||
|
entity_key: &str,
|
||||||
|
) -> Result<Option<Entity>> {
|
||||||
let aql = AqlQuery::builder()
|
let aql = AqlQuery::builder()
|
||||||
.query(queries::LOAD_ENTITY)
|
.query(queries::LOAD_ENTITY_IN_SCENE)
|
||||||
.bind_var("@scene_collection", SCENE_COLLECTION)
|
.bind_var("@scene_collection", SCENE_COLLECTION)
|
||||||
.bind_var("scene_key", to_json_value(scene_key)?)
|
.bind_var("scene_key", to_json_value(scene_key)?)
|
||||||
.bind_var("entity_key", to_json_value(entity_key)?)
|
.bind_var("entity_key", to_json_value(entity_key)?)
|
||||||
|
@ -412,6 +414,16 @@ impl Database {
|
||||||
Ok(take_first(results))
|
Ok(take_first(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn load_entity(&self, entity_key: &str) -> Result<Option<Entity>> {
|
||||||
|
let aql = AqlQuery::builder()
|
||||||
|
.query(queries::LOAD_ENTITY)
|
||||||
|
.bind_var("entity_key", to_json_value(entity_key)?)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let results = self.db().await?.aql_query(aql).await?;
|
||||||
|
Ok(take_first(results))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn cache_command(
|
pub async fn cache_command(
|
||||||
&self,
|
&self,
|
||||||
raw_cmd: &str,
|
raw_cmd: &str,
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub const LOAD_STAGE: &'static str = r#"
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
pub const LOAD_ENTITY: &'static str = r#"
|
pub const LOAD_ENTITY_IN_SCENE: &'static str = r#"
|
||||||
LET entities = (
|
LET entities = (
|
||||||
FOR scene IN @@scene_collection
|
FOR scene IN @@scene_collection
|
||||||
FILTER scene._key == @scene_key
|
FILTER scene._key == @scene_key
|
||||||
|
@ -43,6 +43,23 @@ FOR ent in entities
|
||||||
RETURN ent
|
RETURN ent
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
pub const LOAD_ENTITY: &'static str = r#"
|
||||||
|
LET entities = (
|
||||||
|
LET people = (FOR person in people
|
||||||
|
FILTER person._key == @entity_key
|
||||||
|
RETURN MERGE({ "type": "Person"}, person))
|
||||||
|
|
||||||
|
LET items = (FOR item in items
|
||||||
|
FILTER item._key == @entity_key
|
||||||
|
RETURN MERGE({ "type": "Item" }, item))
|
||||||
|
|
||||||
|
RETURN FIRST(APPEND(people, items)))
|
||||||
|
|
||||||
|
FOR ent in entities
|
||||||
|
FILTER ent != null
|
||||||
|
RETURN ent
|
||||||
|
"#;
|
||||||
|
|
||||||
pub const UPSERT_SCENE: &'static str = r#"
|
pub const UPSERT_SCENE: &'static str = r#"
|
||||||
UPSERT { _key: @scene_key }
|
UPSERT { _key: @scene_key }
|
||||||
INSERT <SCENE_JSON>
|
INSERT <SCENE_JSON>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::io::display;
|
use crate::io::display;
|
||||||
use crate::models::commands::{
|
use crate::models::commands::{
|
||||||
AiCommand, BuiltinCommand, CommandExecution, ExecutionConversionResult, EventConversionFailures,
|
AiCommand, BuiltinCommand, CommandExecution, ExecutionConversionResult, EventConversionFailure,
|
||||||
};
|
};
|
||||||
use crate::state::GameState;
|
use crate::state::GameState;
|
||||||
use crate::{commands::CommandExecutor, db::Database};
|
use crate::{commands::CommandExecutor, db::Database};
|
||||||
|
@ -42,8 +42,7 @@ impl GameLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
display!("\n\n{}\n\n", execution.narration);
|
display!("\n\n{}\n\n", execution.narration);
|
||||||
|
if let Some(event) = execution.event {
|
||||||
for event in execution.events {
|
|
||||||
self.state.update(event).await?;
|
self.state.update(event).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,8 @@ pub struct RawCommandExecution {
|
||||||
pub valid: bool,
|
pub valid: bool,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
pub narration: String,
|
pub narration: String,
|
||||||
pub events: Vec<RawCommandEvent>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub event: Option<RawCommandEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawCommandExecution {
|
impl RawCommandExecution {
|
||||||
|
@ -69,7 +70,7 @@ impl RawCommandExecution {
|
||||||
valid: true,
|
valid: true,
|
||||||
reason: None,
|
reason: None,
|
||||||
narration: "".to_string(),
|
narration: "".to_string(),
|
||||||
events: vec![],
|
event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,10 +89,7 @@ pub struct RawCommandEvent {
|
||||||
pub enum CommandEvent {
|
pub enum CommandEvent {
|
||||||
// Informational
|
// Informational
|
||||||
Narration(String),
|
Narration(String),
|
||||||
LookAtEntity {
|
LookAtEntity(String),
|
||||||
entity_key: String,
|
|
||||||
scene_key: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Movement-related
|
// Movement-related
|
||||||
ChangeScene {
|
ChangeScene {
|
||||||
|
@ -137,6 +135,14 @@ pub enum CommandExecution {
|
||||||
AiCommand(AiCommand),
|
AiCommand(AiCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple struct to hold the narrative parts of the
|
||||||
|
/// RawCommandExecution to minimize clones.
|
||||||
|
pub struct Narrative {
|
||||||
|
pub valid: bool,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub narration: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// An "AI Command" is a command execution generated by the LLM and
|
/// An "AI Command" is a command execution generated by the LLM and
|
||||||
/// run through coherence validation/fixing, and (assuming it is
|
/// run through coherence validation/fixing, and (assuming it is
|
||||||
/// valid) contains a series of events to apply to the game state.
|
/// valid) contains a series of events to apply to the game state.
|
||||||
|
@ -145,16 +151,31 @@ 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 event: Option<CommandEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AiCommand {
|
impl AiCommand {
|
||||||
|
fn from_narrative_and_event(narrative: Narrative, event: CommandEvent) -> AiCommand {
|
||||||
|
AiCommand {
|
||||||
|
event: Some(event),
|
||||||
|
valid: narrative.valid,
|
||||||
|
reason: match &narrative.reason {
|
||||||
|
Some(reason) if !narrative.valid && reason.is_empty() => {
|
||||||
|
Some("invalid for unknown reason".to_string())
|
||||||
|
}
|
||||||
|
Some(_) if !narrative.valid => narrative.reason,
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
narration: narrative.narration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn empty() -> AiCommand {
|
pub fn empty() -> AiCommand {
|
||||||
AiCommand {
|
AiCommand {
|
||||||
valid: true,
|
valid: true,
|
||||||
reason: None,
|
reason: None,
|
||||||
narration: "".to_string(),
|
narration: "".to_string(),
|
||||||
events: vec![],
|
event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,68 +184,48 @@ impl AiCommand {
|
||||||
valid: raw.valid,
|
valid: raw.valid,
|
||||||
reason: raw.reason,
|
reason: raw.reason,
|
||||||
narration: "".to_string(),
|
narration: "".to_string(),
|
||||||
events: vec![],
|
event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_events(events: Vec<CommandEvent>) -> AiCommand {
|
pub fn from_raw_success(narrative: Narrative, event: CommandEvent) -> AiCommand {
|
||||||
|
Self::from_narrative_and_event(narrative, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_event(event: CommandEvent) -> AiCommand {
|
||||||
AiCommand {
|
AiCommand {
|
||||||
valid: true,
|
valid: true,
|
||||||
reason: None,
|
reason: None,
|
||||||
narration: "".to_string(),
|
narration: "".to_string(),
|
||||||
events,
|
event: Some(event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
pub type ExecutionConversionResult = std::result::Result<AiCommand, EventConversionFailure>;
|
||||||
pub enum ExecutionConversionResult {
|
|
||||||
Success(AiCommand),
|
#[derive(Error, Clone, Debug)]
|
||||||
PartialSuccess(AiCommand, EventConversionFailures),
|
pub enum EventConversionFailure {
|
||||||
Failure(EventConversionFailures),
|
#[error("parsing failure: {0:?}")]
|
||||||
|
ParsingFailure(EventParsingFailure),
|
||||||
|
#[error("coherence failure: {0:?}")]
|
||||||
|
CoherenceFailure(EventCoherenceFailure),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ExecutionConversionResult {
|
impl From<EventCoherenceFailure> for EventConversionFailure {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn from(value: EventCoherenceFailure) -> Self {
|
||||||
write!(f, "{:?}", self)
|
EventConversionFailure::CoherenceFailure(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EventParsingFailure> for EventConversionFailure {
|
||||||
|
fn from(value: EventParsingFailure) -> Self {
|
||||||
|
EventConversionFailure::ParsingFailure(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Clone, Debug)]
|
#[derive(Error, Clone, Debug)]
|
||||||
pub struct EventConversionFailures {
|
pub enum EventParsingFailure {
|
||||||
pub conversion_failures: Vec<EventConversionError>,
|
|
||||||
pub coherence_failures: Vec<EventCoherenceFailure>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventConversionFailures {
|
|
||||||
pub fn from_failures(
|
|
||||||
conversion_failures: Vec<EventConversionError>,
|
|
||||||
coherence_failures: Vec<EventCoherenceFailure>,
|
|
||||||
) -> EventConversionFailures {
|
|
||||||
EventConversionFailures {
|
|
||||||
conversion_failures,
|
|
||||||
coherence_failures,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<EventCoherenceFailure>> for EventConversionFailures {
|
|
||||||
fn from(value: Vec<EventCoherenceFailure>) -> Self {
|
|
||||||
EventConversionFailures {
|
|
||||||
coherence_failures: value,
|
|
||||||
conversion_failures: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for EventConversionFailures {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Clone, Debug)]
|
|
||||||
pub enum EventConversionError {
|
|
||||||
#[error("invalid parameter for {0:?}")]
|
#[error("invalid parameter for {0:?}")]
|
||||||
InvalidParameter(RawCommandEvent),
|
InvalidParameter(RawCommandEvent),
|
||||||
|
|
||||||
|
@ -235,24 +236,18 @@ pub enum EventConversionError {
|
||||||
#[derive(Error, Clone, Debug)]
|
#[derive(Error, Clone, Debug)]
|
||||||
pub enum EventCoherenceFailure {
|
pub enum EventCoherenceFailure {
|
||||||
#[error("target of command does not exist")]
|
#[error("target of command does not exist")]
|
||||||
TargetDoesNotExist(CommandEvent),
|
TargetDoesNotExist(AiCommand),
|
||||||
|
|
||||||
#[error("uncategorized coherence failure: {1}")]
|
#[error("uncategorized coherence failure: {1}")]
|
||||||
OtherError(CommandEvent, String),
|
OtherError(AiCommand, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventCoherenceFailure {
|
impl EventCoherenceFailure {
|
||||||
/// Consume self to extract the CommandEvent wrapped in this enum.
|
/// Consume self to extract the CommandEvent wrapped in this enum.
|
||||||
pub fn as_event(self) -> CommandEvent {
|
pub fn as_event(self) -> Option<CommandEvent> {
|
||||||
match self {
|
match self {
|
||||||
EventCoherenceFailure::OtherError(event, _) => event,
|
EventCoherenceFailure::OtherError(cmd, _) => cmd.event,
|
||||||
Self::TargetDoesNotExist(event) => event,
|
Self::TargetDoesNotExist(cmd) => cmd.event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EventCoherenceFailure> for CommandEvent {
|
|
||||||
fn from(value: EventCoherenceFailure) -> Self {
|
|
||||||
value.as_event()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl GameState {
|
||||||
match event {
|
match event {
|
||||||
CommandEvent::ChangeScene { scene_key } => self.change_scene(&scene_key).await?,
|
CommandEvent::ChangeScene { scene_key } => self.change_scene(&scene_key).await?,
|
||||||
CommandEvent::Narration(narration) => println!("\n\n{}\n\n", narration),
|
CommandEvent::Narration(narration) => println!("\n\n{}\n\n", narration),
|
||||||
CommandEvent::LookAtEntity { ref entity_key, .. } => self.look_at(entity_key).await?,
|
CommandEvent::LookAtEntity(ref entity_key) => self.look_at(entity_key).await?,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ impl GameState {
|
||||||
async fn look_at(&mut self, entity_key: &str) -> Result<()> {
|
async fn look_at(&mut self, entity_key: &str) -> Result<()> {
|
||||||
let maybe_entity = self
|
let maybe_entity = self
|
||||||
.db
|
.db
|
||||||
.load_entity(&self.current_scene.key, entity_key)
|
.load_entity_in_scene(&self.current_scene.key, entity_key)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(entity) = maybe_entity {
|
if let Some(entity) = maybe_entity {
|
||||||
|
@ -76,6 +76,8 @@ impl GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
display!("\n");
|
display!("\n");
|
||||||
|
} else {
|
||||||
|
display!("You don't see that thing or person here.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue