fix conversation timings, seek past choices when dropping out of branches
This commit is contained in:
parent
39a74582fc
commit
0b22494751
115
src/chat.rs
115
src/chat.rs
|
@ -21,6 +21,7 @@ pub const TOKEN_GOTO: &str = "goto";
|
||||||
pub const TOKEN_LABEL: &str = "label";
|
pub const TOKEN_LABEL: &str = "label";
|
||||||
pub const TOKEN_SCRIPT: &str = "script";
|
pub const TOKEN_SCRIPT: &str = "script";
|
||||||
pub const TOKEN_SOUND: &str = "sound";
|
pub const TOKEN_SOUND: &str = "sound";
|
||||||
|
pub const TOKEN_NOWAIT: &str = "nowait";
|
||||||
pub const TOKEN_GOTO_EXIT: &str = "EXIT";
|
pub const TOKEN_GOTO_EXIT: &str = "EXIT";
|
||||||
|
|
||||||
pub const DEFAULT_SOUND: &str = "chat";
|
pub const DEFAULT_SOUND: &str = "chat";
|
||||||
|
@ -43,6 +44,12 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[
|
||||||
TOKEN_LABEL,
|
TOKEN_LABEL,
|
||||||
TOKEN_SCRIPT,
|
TOKEN_SCRIPT,
|
||||||
TOKEN_SOUND,
|
TOKEN_SOUND,
|
||||||
|
TOKEN_NOWAIT,
|
||||||
|
];
|
||||||
|
pub const SKIPPABLE_TOKENS: &[&str] = &[
|
||||||
|
TOKEN_LABEL,
|
||||||
|
TOKEN_GOTO,
|
||||||
|
TOKEN_NOWAIT,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub struct ChatPlugin;
|
pub struct ChatPlugin;
|
||||||
|
@ -102,7 +109,7 @@ pub enum ChatEvent {
|
||||||
DespawnAllChoices,
|
DespawnAllChoices,
|
||||||
DespawnAllChats,
|
DespawnAllChats,
|
||||||
SpawnMessage(String, hud::LogLevel, String),
|
SpawnMessage(String, hud::LogLevel, String),
|
||||||
SpawnChoice(String, usize, ChatPos),
|
SpawnChoice(String, usize, ChatPos, bool),
|
||||||
RunScript(String),
|
RunScript(String),
|
||||||
Sleep(f64),
|
Sleep(f64),
|
||||||
//Script(String, String, String),
|
//Script(String, String, String),
|
||||||
|
@ -160,19 +167,25 @@ impl ChatDB {
|
||||||
// Not acceptable:
|
// Not acceptable:
|
||||||
// - `"What's up?"`
|
// - `"What's up?"`
|
||||||
// - `{"goto": "foo"}`
|
// - `{"goto": "foo"}`
|
||||||
fn search_choice(&self, yaml: Option<&Value>) -> Option<(String, Value)> {
|
// Returns (choice text, sub-conversation branch, nowait flag)
|
||||||
|
fn search_choice(&self, yaml: Option<&Value>) -> Option<(String, Value, bool)> {
|
||||||
let non_choice_tokens = NON_CHOICE_TOKENS.to_vec();
|
let non_choice_tokens = NON_CHOICE_TOKENS.to_vec();
|
||||||
|
let mut result: Option<(String, Value, bool)> = None;
|
||||||
|
let mut nowait = false;
|
||||||
if let Some(Value::Mapping(map)) = yaml {
|
if let Some(Value::Mapping(map)) = yaml {
|
||||||
for key in map.keys() {
|
for (key, value) in map {
|
||||||
if let Value::String(key) = key {
|
if let Value::String(key) = key {
|
||||||
|
if key == TOKEN_NOWAIT && value.as_bool() == Some(true) {
|
||||||
|
nowait = true;
|
||||||
|
}
|
||||||
if non_choice_tokens.contains(&key.as_str()) {
|
if non_choice_tokens.contains(&key.as_str()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return Some((key.into(), map[key].clone()));
|
result = Some((key.into(), map[key].clone(), nowait));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return None;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> {
|
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> {
|
||||||
|
@ -226,16 +239,32 @@ impl ChatDB {
|
||||||
chat.position[len - 1] += 1;
|
chat.position[len - 1] += 1;
|
||||||
|
|
||||||
let mut popped = false;
|
let mut popped = false;
|
||||||
|
let mut seek_past_dialog_choices = false;
|
||||||
while chat.position.len() > 0 {
|
while chat.position.len() > 0 {
|
||||||
match self.at(chat.id, &chat.position) {
|
match self.at(chat.id, &chat.position) {
|
||||||
None => {
|
None => {
|
||||||
chat.position.pop();
|
chat.position.pop();
|
||||||
popped = true;
|
popped = true;
|
||||||
|
seek_past_dialog_choices = true;
|
||||||
if chat.position.len() > 0 {
|
if chat.position.len() > 0 {
|
||||||
let index = chat.position.len() - 1;
|
let index = chat.position.len() - 1;
|
||||||
chat.position[index] += 1;
|
chat.position[index] += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Some(Value::Mapping(map)) => {
|
||||||
|
if seek_past_dialog_choices && self.search_choice(Some(&Value::Mapping(map))).is_some() {
|
||||||
|
// we just dropped out of a branch and ended up in a dialog
|
||||||
|
// choice. let's seek past all the choices until we find
|
||||||
|
// the next non-dialog-choice item.
|
||||||
|
if chat.position.len() > 0 {
|
||||||
|
let index = chat.position.len() - 1;
|
||||||
|
chat.position[index] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -264,7 +293,7 @@ impl ChatDB {
|
||||||
result = Some(Value::String(value_string.into()));
|
result = Some(Value::String(value_string.into()));
|
||||||
}
|
}
|
||||||
Some(Value::Mapping(mapping)) => {
|
Some(Value::Mapping(mapping)) => {
|
||||||
if let Some((_choicetext, subconversation)) = self.search_choice(value) {
|
if let Some((_choicetext, subconversation, _)) = self.search_choice(value) {
|
||||||
result = Some(Value::Mapping(mapping.clone()));
|
result = Some(Value::Mapping(mapping.clone()));
|
||||||
next_pointer = Some(subconversation);
|
next_pointer = Some(subconversation);
|
||||||
}
|
}
|
||||||
|
@ -289,23 +318,52 @@ impl ChatDB {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_yaml_entry(
|
// Determines whether the item at the current position is "skippable".
|
||||||
|
// This means that we should process it right away to get to the correct
|
||||||
|
// position for finding the choices of a message.
|
||||||
|
// This includes flow control tokens like "goto", no-op tokens like "label",
|
||||||
|
// but not something with a side effect like "script", and especially not "if".
|
||||||
|
fn is_skippable(&self, chat: &mut Chat) -> bool {
|
||||||
|
let current_item = self.at(chat.id, &chat.position);
|
||||||
|
if current_item.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(map) = current_item.unwrap().as_mapping() {
|
||||||
|
for key in map.keys() {
|
||||||
|
if let Some(key) = key.as_str() {
|
||||||
|
if !SKIPPABLE_TOKENS.contains(&key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// It's a mapping that contains ONLY keys in SKIPPABLE_TOKENS.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_yaml_entry(
|
||||||
&self,
|
&self,
|
||||||
chat: &mut Chat,
|
chat: &mut Chat,
|
||||||
event: &mut EventWriter<ChatEvent>,
|
event: &mut EventWriter<ChatEvent>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let current_item = self.at(chat.id, &chat.position);
|
let current_item = self.at(chat.id, &chat.position);
|
||||||
let mut add_choices = true;
|
let mut processed_a_choice = false;
|
||||||
match current_item {
|
match current_item {
|
||||||
Some(Value::String(message)) => {
|
Some(Value::String(message)) => {
|
||||||
event.send(ChatEvent::SpawnMessage(message.to_string(),
|
event.send(ChatEvent::SpawnMessage(message.to_string(),
|
||||||
hud::LogLevel::Chat, DEFAULT_SOUND.to_string()));
|
hud::LogLevel::Chat, DEFAULT_SOUND.to_string()));
|
||||||
}
|
}
|
||||||
Some(Value::Mapping(map)) => {
|
Some(Value::Mapping(map)) => {
|
||||||
|
let mut sound = DEFAULT_SOUND.to_string();
|
||||||
|
|
||||||
|
// Is this a dialog choice?
|
||||||
if let Some(_) = self.search_choice(Some(&Value::Mapping(map.clone()))) {
|
if let Some(_) = self.search_choice(Some(&Value::Mapping(map.clone()))) {
|
||||||
add_choices = false;
|
processed_a_choice = true;
|
||||||
}
|
}
|
||||||
let mut sound = "chat".to_string();
|
|
||||||
|
|
||||||
// We're going through the list of keys/values multiple times
|
// We're going through the list of keys/values multiple times
|
||||||
// to ensure that dependencies for certain commands are available
|
// to ensure that dependencies for certain commands are available
|
||||||
|
@ -314,8 +372,7 @@ impl ChatDB {
|
||||||
for (key, value) in &map {
|
for (key, value) in &map {
|
||||||
let key = key.as_str();
|
let key = key.as_str();
|
||||||
match (key, value) {
|
match (key, value) {
|
||||||
(Some(TOKEN_SET), _) => {}
|
(Some(TOKEN_IF), _) => {} // TODO
|
||||||
(Some(TOKEN_IF), _) => {}
|
|
||||||
(Some(TOKEN_SOUND), Value::String(sound_name)) => {
|
(Some(TOKEN_SOUND), Value::String(sound_name)) => {
|
||||||
sound = sound_name.clone();
|
sound = sound_name.clone();
|
||||||
}
|
}
|
||||||
|
@ -339,6 +396,7 @@ impl ChatDB {
|
||||||
event.send(ChatEvent::SpawnMessage(
|
event.send(ChatEvent::SpawnMessage(
|
||||||
message.to_string(), hud::LogLevel::Warning, sound.clone()));
|
message.to_string(), hud::LogLevel::Warning, sound.clone()));
|
||||||
}
|
}
|
||||||
|
(Some(TOKEN_SET), _) => {} // TODO
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,31 +438,40 @@ impl ChatDB {
|
||||||
error!("Can't handle YAML value {current_item:?}");
|
error!("Can't handle YAML value {current_item:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return add_choices;
|
return processed_a_choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_chat(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) {
|
pub fn advance_chat(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) {
|
||||||
event.send(ChatEvent::DespawnAllChoices);
|
event.send(ChatEvent::DespawnAllChoices);
|
||||||
|
|
||||||
// Handle this entry in the chat list
|
// Handle this entry in the chat list
|
||||||
let add_choices = self.process_yaml_entry(chat, event);
|
let processed_a_choice: bool = self.process_yaml_entry(chat, event);
|
||||||
|
|
||||||
// Move on to next entry
|
// Move on to next entry
|
||||||
let mut finished_branch = self.advance_pointer(chat);
|
self.advance_pointer(chat);
|
||||||
|
|
||||||
// Add choices, if available
|
// Add the following choices, unless we ended up in the middle of a dialog
|
||||||
if add_choices {
|
// choice list, and should just skip through to the next non-dialog-choice item
|
||||||
|
if !processed_a_choice {
|
||||||
|
// Skip/process some entries right away, to be able to fetch the correct choices
|
||||||
|
while self.is_skippable(chat) {
|
||||||
|
self.process_yaml_entry(chat, event);
|
||||||
|
self.advance_pointer(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn choices until we reach a non-choice item or the end of the branch
|
||||||
let mut key: usize = 0;
|
let mut key: usize = 0;
|
||||||
while let Some((choice, _)) =
|
let mut reached_end_of_branch = false;
|
||||||
|
while let Some((choice, _, nowait)) =
|
||||||
self.search_choice(self.at(chat.id, &chat.position).as_ref()) {
|
self.search_choice(self.at(chat.id, &chat.position).as_ref()) {
|
||||||
if finished_branch {
|
if reached_end_of_branch {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let mut goto: Vec<usize> = chat.position.clone();
|
let mut goto: Vec<usize> = chat.position.clone();
|
||||||
goto.push(0);
|
goto.push(0);
|
||||||
event.send(ChatEvent::SpawnChoice(choice, key, goto));
|
event.send(ChatEvent::SpawnChoice(choice, key, goto, nowait));
|
||||||
key += 1;
|
key += 1;
|
||||||
finished_branch = self.advance_pointer(chat);
|
reached_end_of_branch = self.advance_pointer(chat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -512,7 +579,7 @@ pub fn handle_chat_events(
|
||||||
let sfx = audio::str2sfx(sound);
|
let sfx = audio::str2sfx(sound);
|
||||||
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
||||||
}
|
}
|
||||||
ChatEvent::SpawnChoice(replytext, key, goto) => {
|
ChatEvent::SpawnChoice(replytext, key, goto, nowait) => {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
world::DespawnOnPlayerDeath,
|
world::DespawnOnPlayerDeath,
|
||||||
Choice {
|
Choice {
|
||||||
|
@ -521,7 +588,9 @@ pub fn handle_chat_events(
|
||||||
goto: goto.clone(),
|
goto: goto.clone(),
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
if !nowait {
|
||||||
|
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ChatEvent::RunScript(script) => {
|
ChatEvent::RunScript(script) => {
|
||||||
ew_chatscript.send(ChatScriptEvent(script.clone()));
|
ew_chatscript.send(ChatScriptEvent(script.clone()));
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
- Nevermind. Thank you.:
|
- Nevermind. Thank you.:
|
||||||
- goto: thx
|
- goto: thx
|
||||||
- Leave me alone!:
|
- Leave me alone!:
|
||||||
- goto: END
|
- goto: EXIT
|
||||||
- Micros? What's that?:
|
- Micros? What's that?:
|
||||||
- Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body.
|
- Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body.
|
||||||
- Ouch! Thank you so much.:
|
- Ouch! Thank you so much.:
|
||||||
|
@ -43,19 +43,22 @@
|
||||||
- How are you feeling?
|
- How are you feeling?
|
||||||
- label: howru
|
- label: howru
|
||||||
- I feel quite cozy, this space suit feels like a second skin.:
|
- I feel quite cozy, this space suit feels like a second skin.:
|
||||||
|
- set: friends
|
||||||
- Hah, it does, doesn't it?
|
- Hah, it does, doesn't it?
|
||||||
- But take care, your suit seems to be leaking. I'd patch it up if I were you.
|
- But take care, your suit seems to be leaking. I'd patch it up if I were you.
|
||||||
- I'm all out of SuitPatch™ SuperGlue™ right now, otherwise i'd share.
|
- I'm all out of SuitPatch™ SuperGlue™ right now, otherwise i'd share.
|
||||||
- Can I help you with anything else, maybe?
|
- Can I help you with anything else, maybe?
|
||||||
- set: friends
|
|
||||||
- I got this apocalyptic headache...:
|
- I got this apocalyptic headache...:
|
||||||
|
- set: friends
|
||||||
- Heh, probably related to why you were passed out.
|
- Heh, probably related to why you were passed out.
|
||||||
- Go easy on yourself, I'm sure things will turn for the better.
|
- Go easy on yourself, I'm sure things will turn for the better.
|
||||||
- Meanwhile, can I help you with anything?
|
- Meanwhile, can I help you with anything?
|
||||||
- set: friends
|
|
||||||
- I... don't know, I'm pretty disoriented.:
|
- I... don't know, I'm pretty disoriented.:
|
||||||
- Oh no. Do you need a lowdown on reality?
|
|
||||||
- set: friends
|
- set: friends
|
||||||
|
- Oh no. Do you need a lowdown on reality?
|
||||||
|
- I just want to be alone right now:
|
||||||
|
- Oh, sure. Ping me if you need anything. I'll go back to playing my VR game.
|
||||||
|
- goto: EXIT
|
||||||
|
|
||||||
|
|
||||||
- label: help
|
- label: help
|
||||||
|
@ -99,9 +102,8 @@
|
||||||
- goto: help
|
- goto: help
|
||||||
- Well, anyway, need anything else?
|
- Well, anyway, need anything else?
|
||||||
- goto: help
|
- goto: help
|
||||||
- I just want to be alone right now:
|
- I think I'm good for now:
|
||||||
- Oh, sure. Ping me if you need anything. I'll go back to playing my VR game.
|
- goto: chocolate
|
||||||
- goto: EXIT
|
|
||||||
- Well, I hope you're ok.
|
- Well, I hope you're ok.
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,7 +118,6 @@
|
||||||
- label: pizzaplace
|
- label: pizzaplace
|
||||||
- Oh and make sure to check out the pizza place!
|
- Oh and make sure to check out the pizza place!
|
||||||
- Will do, bye!:
|
- Will do, bye!:
|
||||||
- system: Disconnected.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -141,13 +142,15 @@
|
||||||
- Hah, beautiful, right? I carved it out this asteroid myself!
|
- Hah, beautiful, right? I carved it out this asteroid myself!
|
||||||
- You know how much work it was to neutralize the rotation of the asteroid, so my valued customers don't bang against the walls while drinking my pizza?
|
- You know how much work it was to neutralize the rotation of the asteroid, so my valued customers don't bang against the walls while drinking my pizza?
|
||||||
- Now would you like today's special or not?
|
- Now would you like today's special or not?
|
||||||
|
- goto: offer
|
||||||
- My head hurts, my suit leaks, I think I'm dying...:
|
- My head hurts, my suit leaks, I think I'm dying...:
|
||||||
- Seriously? Let me have a look. Just press the 'Grant Access' button please.
|
- Seriously? Let me have a look. Just press the 'Grant Access' button please.
|
||||||
- "[GRANT ACCESS TO SPACESUIT WIFI]":
|
- "[GRANT ACCESS TO SPACESUIT WIFI]":
|
||||||
- label: hack
|
- label: hack
|
||||||
- warn: MALWARE DETECTED
|
- warn: MALWARE DETECTED
|
||||||
- warn: BITCOIN MINER DETECTED
|
- warn: BITCOIN MINER DETECTED
|
||||||
- Hey, what are you doing with me?:
|
- nowait: true
|
||||||
|
Hey, what are you doing with me?:
|
||||||
- Just checking your systems, hang on tight
|
- Just checking your systems, hang on tight
|
||||||
- Yeah, suit's fucked, I'd look out for a repair shop
|
- Yeah, suit's fucked, I'd look out for a repair shop
|
||||||
- Anyway, wanna order today's special?
|
- Anyway, wanna order today's special?
|
||||||
|
@ -155,7 +158,7 @@
|
||||||
- "[DENY ACCESS TO SPACESUIT WIFI]":
|
- "[DENY ACCESS TO SPACESUIT WIFI]":
|
||||||
- Oh come on, do you want my help or not?
|
- Oh come on, do you want my help or not?
|
||||||
- "[GRANT ACCESS TO SPACESUIT WIFI]":
|
- "[GRANT ACCESS TO SPACESUIT WIFI]":
|
||||||
- goto hack
|
- goto: hack
|
||||||
- "[DENY ACCESS TO SPACESUIT WIFI]":
|
- "[DENY ACCESS TO SPACESUIT WIFI]":
|
||||||
- Great, the first customer in ages, and they're brain damaged...
|
- Great, the first customer in ages, and they're brain damaged...
|
||||||
- Fuck off!:
|
- Fuck off!:
|
||||||
|
|
Loading…
Reference in a new issue