fix conversation timings, seek past choices when dropping out of branches

This commit is contained in:
yuni 2024-04-14 01:15:38 +02:00
parent 39a74582fc
commit 0b22494751
2 changed files with 105 additions and 33 deletions

View file

@ -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()));
}

View file

@ -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!: