fix conversation timings, seek past choices when dropping out of branches
This commit is contained in:
parent
39a74582fc
commit
0b22494751
113
src/chat.rs
113
src/chat.rs
|
@ -21,6 +21,7 @@ pub const TOKEN_GOTO: &str = "goto";
|
|||
pub const TOKEN_LABEL: &str = "label";
|
||||
pub const TOKEN_SCRIPT: &str = "script";
|
||||
pub const TOKEN_SOUND: &str = "sound";
|
||||
pub const TOKEN_NOWAIT: &str = "nowait";
|
||||
pub const TOKEN_GOTO_EXIT: &str = "EXIT";
|
||||
|
||||
pub const DEFAULT_SOUND: &str = "chat";
|
||||
|
@ -43,6 +44,12 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[
|
|||
TOKEN_LABEL,
|
||||
TOKEN_SCRIPT,
|
||||
TOKEN_SOUND,
|
||||
TOKEN_NOWAIT,
|
||||
];
|
||||
pub const SKIPPABLE_TOKENS: &[&str] = &[
|
||||
TOKEN_LABEL,
|
||||
TOKEN_GOTO,
|
||||
TOKEN_NOWAIT,
|
||||
];
|
||||
|
||||
pub struct ChatPlugin;
|
||||
|
@ -102,7 +109,7 @@ pub enum ChatEvent {
|
|||
DespawnAllChoices,
|
||||
DespawnAllChats,
|
||||
SpawnMessage(String, hud::LogLevel, String),
|
||||
SpawnChoice(String, usize, ChatPos),
|
||||
SpawnChoice(String, usize, ChatPos, bool),
|
||||
RunScript(String),
|
||||
Sleep(f64),
|
||||
//Script(String, String, String),
|
||||
|
@ -160,19 +167,25 @@ impl ChatDB {
|
|||
// Not acceptable:
|
||||
// - `"What's up?"`
|
||||
// - `{"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 mut result: Option<(String, Value, bool)> = None;
|
||||
let mut nowait = false;
|
||||
if let Some(Value::Mapping(map)) = yaml {
|
||||
for key in map.keys() {
|
||||
for (key, value) in map {
|
||||
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()) {
|
||||
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> {
|
||||
|
@ -226,16 +239,32 @@ impl ChatDB {
|
|||
chat.position[len - 1] += 1;
|
||||
|
||||
let mut popped = false;
|
||||
let mut seek_past_dialog_choices = false;
|
||||
while chat.position.len() > 0 {
|
||||
match self.at(chat.id, &chat.position) {
|
||||
None => {
|
||||
chat.position.pop();
|
||||
popped = true;
|
||||
seek_past_dialog_choices = true;
|
||||
if chat.position.len() > 0 {
|
||||
let index = chat.position.len() - 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(_) => {
|
||||
break;
|
||||
}
|
||||
|
@ -264,7 +293,7 @@ impl ChatDB {
|
|||
result = Some(Value::String(value_string.into()));
|
||||
}
|
||||
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()));
|
||||
next_pointer = Some(subconversation);
|
||||
}
|
||||
|
@ -289,23 +318,52 @@ impl ChatDB {
|
|||
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,
|
||||
chat: &mut Chat,
|
||||
event: &mut EventWriter<ChatEvent>,
|
||||
) -> bool {
|
||||
let current_item = self.at(chat.id, &chat.position);
|
||||
let mut add_choices = true;
|
||||
let mut processed_a_choice = false;
|
||||
match current_item {
|
||||
Some(Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(message.to_string(),
|
||||
hud::LogLevel::Chat, DEFAULT_SOUND.to_string()));
|
||||
}
|
||||
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()))) {
|
||||
add_choices = false;
|
||||
processed_a_choice = true;
|
||||
}
|
||||
let mut sound = "chat".to_string();
|
||||
|
||||
// We're going through the list of keys/values multiple times
|
||||
// to ensure that dependencies for certain commands are available
|
||||
|
@ -314,8 +372,7 @@ impl ChatDB {
|
|||
for (key, value) in &map {
|
||||
let key = key.as_str();
|
||||
match (key, value) {
|
||||
(Some(TOKEN_SET), _) => {}
|
||||
(Some(TOKEN_IF), _) => {}
|
||||
(Some(TOKEN_IF), _) => {} // TODO
|
||||
(Some(TOKEN_SOUND), Value::String(sound_name)) => {
|
||||
sound = sound_name.clone();
|
||||
}
|
||||
|
@ -339,6 +396,7 @@ impl ChatDB {
|
|||
event.send(ChatEvent::SpawnMessage(
|
||||
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:?}");
|
||||
}
|
||||
}
|
||||
return add_choices;
|
||||
return processed_a_choice;
|
||||
}
|
||||
|
||||
pub fn advance_chat(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) {
|
||||
event.send(ChatEvent::DespawnAllChoices);
|
||||
|
||||
// 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
|
||||
let mut finished_branch = self.advance_pointer(chat);
|
||||
self.advance_pointer(chat);
|
||||
|
||||
// Add choices, if available
|
||||
if add_choices {
|
||||
// Add the following choices, unless we ended up in the middle of a dialog
|
||||
// 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;
|
||||
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()) {
|
||||
if finished_branch {
|
||||
if reached_end_of_branch {
|
||||
break;
|
||||
}
|
||||
let mut goto: Vec<usize> = chat.position.clone();
|
||||
goto.push(0);
|
||||
event.send(ChatEvent::SpawnChoice(choice, key, goto));
|
||||
event.send(ChatEvent::SpawnChoice(choice, key, goto, nowait));
|
||||
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);
|
||||
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
||||
}
|
||||
ChatEvent::SpawnChoice(replytext, key, goto) => {
|
||||
ChatEvent::SpawnChoice(replytext, key, goto, nowait) => {
|
||||
commands.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
Choice {
|
||||
|
@ -521,8 +588,10 @@ pub fn handle_chat_events(
|
|||
goto: goto.clone(),
|
||||
}
|
||||
));
|
||||
if !nowait {
|
||||
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
||||
}
|
||||
}
|
||||
ChatEvent::RunScript(script) => {
|
||||
ew_chatscript.send(ChatScriptEvent(script.clone()));
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
- Nevermind. Thank you.:
|
||||
- goto: thx
|
||||
- Leave me alone!:
|
||||
- goto: END
|
||||
- goto: EXIT
|
||||
- Micros? What's that?:
|
||||
- Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body.
|
||||
- Ouch! Thank you so much.:
|
||||
|
@ -43,19 +43,22 @@
|
|||
- How are you feeling?
|
||||
- label: howru
|
||||
- I feel quite cozy, this space suit feels like a second skin.:
|
||||
- set: friends
|
||||
- Hah, it does, doesn't it?
|
||||
- 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.
|
||||
- Can I help you with anything else, maybe?
|
||||
- set: friends
|
||||
- I got this apocalyptic headache...:
|
||||
- set: friends
|
||||
- Heh, probably related to why you were passed out.
|
||||
- Go easy on yourself, I'm sure things will turn for the better.
|
||||
- Meanwhile, can I help you with anything?
|
||||
- set: friends
|
||||
- I... don't know, I'm pretty disoriented.:
|
||||
- Oh no. Do you need a lowdown on reality?
|
||||
- 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
|
||||
|
@ -99,9 +102,8 @@
|
|||
- goto: help
|
||||
- Well, anyway, need anything else?
|
||||
- goto: help
|
||||
- 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
|
||||
- I think I'm good for now:
|
||||
- goto: chocolate
|
||||
- Well, I hope you're ok.
|
||||
|
||||
|
||||
|
@ -116,7 +118,6 @@
|
|||
- label: pizzaplace
|
||||
- Oh and make sure to check out the pizza place!
|
||||
- Will do, bye!:
|
||||
- system: Disconnected.
|
||||
|
||||
|
||||
---
|
||||
|
@ -141,13 +142,15 @@
|
|||
- 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?
|
||||
- Now would you like today's special or not?
|
||||
- goto: offer
|
||||
- My head hurts, my suit leaks, I think I'm dying...:
|
||||
- Seriously? Let me have a look. Just press the 'Grant Access' button please.
|
||||
- "[GRANT ACCESS TO SPACESUIT WIFI]":
|
||||
- label: hack
|
||||
- warn: MALWARE 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
|
||||
- Yeah, suit's fucked, I'd look out for a repair shop
|
||||
- Anyway, wanna order today's special?
|
||||
|
@ -155,7 +158,7 @@
|
|||
- "[DENY ACCESS TO SPACESUIT WIFI]":
|
||||
- Oh come on, do you want my help or not?
|
||||
- "[GRANT ACCESS TO SPACESUIT WIFI]":
|
||||
- goto hack
|
||||
- goto: hack
|
||||
- "[DENY ACCESS TO SPACESUIT WIFI]":
|
||||
- Great, the first customer in ages, and they're brain damaged...
|
||||
- Fuck off!:
|
||||
|
|
Loading…
Reference in a new issue