2024-04-14 12:55:00 +00:00
|
|
|
use crate::{actor, audio, effects, hud, var, world};
|
2024-04-04 11:33:54 +00:00
|
|
|
use bevy::prelude::*;
|
2024-04-13 20:10:13 +00:00
|
|
|
use bevy::math::DVec3;
|
|
|
|
use bevy_xpbd_3d::prelude::*;
|
2024-04-13 15:17:24 +00:00
|
|
|
use serde_yaml::Value;
|
|
|
|
use serde::Deserialize;
|
2024-04-14 01:21:48 +00:00
|
|
|
use std::collections::HashMap;
|
2024-04-04 11:33:54 +00:00
|
|
|
|
2024-04-12 19:34:55 +00:00
|
|
|
pub const CHATS: &[&str] = &[
|
|
|
|
include_str!("chats/serenity.yaml"),
|
|
|
|
include_str!("chats/startrans.yaml"),
|
|
|
|
];
|
|
|
|
|
2024-04-12 21:03:46 +00:00
|
|
|
pub const TOKEN_CHAT: &str = "chat";
|
2024-04-13 19:45:05 +00:00
|
|
|
pub const TOKEN_MSG: &str = "msg";
|
2024-04-13 10:24:56 +00:00
|
|
|
pub const TOKEN_SYSTEM: &str = "system";
|
|
|
|
pub const TOKEN_WARN: &str = "warn";
|
2024-04-13 21:03:41 +00:00
|
|
|
pub const TOKEN_SLEEP: &str = "sleep";
|
2024-04-13 10:24:56 +00:00
|
|
|
pub const TOKEN_SET: &str = "set";
|
|
|
|
pub const TOKEN_IF: &str = "if";
|
|
|
|
pub const TOKEN_GOTO: &str = "goto";
|
|
|
|
pub const TOKEN_LABEL: &str = "label";
|
|
|
|
pub const TOKEN_SCRIPT: &str = "script";
|
2024-04-13 21:21:53 +00:00
|
|
|
pub const TOKEN_SOUND: &str = "sound";
|
2024-04-13 23:15:38 +00:00
|
|
|
pub const TOKEN_NOWAIT: &str = "nowait";
|
2024-04-14 01:21:48 +00:00
|
|
|
|
|
|
|
pub const TOKEN_INCLUDE: &str = "include";
|
2024-04-13 20:43:41 +00:00
|
|
|
pub const TOKEN_GOTO_EXIT: &str = "EXIT";
|
2024-04-13 13:34:35 +00:00
|
|
|
|
2024-04-13 21:35:59 +00:00
|
|
|
pub const DEFAULT_SOUND: &str = "chat";
|
2024-04-13 20:43:41 +00:00
|
|
|
pub const MAX_BRANCH_DEPTH: usize = 64;
|
2024-04-13 13:34:35 +00:00
|
|
|
|
2024-04-14 12:55:00 +00:00
|
|
|
pub const CHOICE_TIMER: f64 = 40.0 * var::DEFAULT_CHAT_SPEED as f64;
|
2024-04-12 23:34:18 +00:00
|
|
|
pub const LETTERS_PER_SECOND: f32 = 17.0;
|
2024-04-14 12:55:00 +00:00
|
|
|
pub const TALKER_SPEED_FACTOR: f32 = var::DEFAULT_CHAT_SPEED / LETTERS_PER_SECOND;
|
2024-04-12 23:21:38 +00:00
|
|
|
pub const CHAT_SPEED_MIN_LEN: f32 = 40.0;
|
2024-04-12 21:03:46 +00:00
|
|
|
|
2024-04-13 10:24:56 +00:00
|
|
|
pub const NON_CHOICE_TOKENS: &[&str] = &[
|
|
|
|
TOKEN_CHAT,
|
2024-04-13 19:45:05 +00:00
|
|
|
TOKEN_MSG,
|
2024-04-13 10:24:56 +00:00
|
|
|
TOKEN_SYSTEM,
|
|
|
|
TOKEN_WARN,
|
2024-04-13 21:03:41 +00:00
|
|
|
TOKEN_SLEEP,
|
2024-04-13 10:24:56 +00:00
|
|
|
TOKEN_SET,
|
|
|
|
TOKEN_IF,
|
|
|
|
TOKEN_GOTO,
|
|
|
|
TOKEN_LABEL,
|
|
|
|
TOKEN_SCRIPT,
|
2024-04-13 21:21:53 +00:00
|
|
|
TOKEN_SOUND,
|
2024-04-13 23:15:38 +00:00
|
|
|
TOKEN_NOWAIT,
|
|
|
|
];
|
|
|
|
pub const SKIPPABLE_TOKENS: &[&str] = &[
|
2024-04-14 02:14:04 +00:00
|
|
|
TOKEN_CHAT,
|
2024-04-13 23:15:38 +00:00
|
|
|
TOKEN_LABEL,
|
|
|
|
TOKEN_GOTO,
|
|
|
|
TOKEN_NOWAIT,
|
2024-04-13 10:24:56 +00:00
|
|
|
];
|
|
|
|
|
2024-04-04 11:33:54 +00:00
|
|
|
pub struct ChatPlugin;
|
|
|
|
impl Plugin for ChatPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
2024-04-12 19:26:23 +00:00
|
|
|
app.add_systems(Startup, load_chats);
|
2024-04-12 21:03:46 +00:00
|
|
|
app.add_systems(Update, (
|
2024-04-13 16:57:23 +00:00
|
|
|
handle_reply_keys.before(handle_chat_timer),
|
|
|
|
handle_chat_timer.before(handle_chat_events),
|
2024-04-12 23:21:38 +00:00
|
|
|
handle_new_conversations.before(handle_chat_events),
|
2024-04-13 20:10:13 +00:00
|
|
|
handle_chat_events.before(handle_chat_scripts),
|
|
|
|
handle_chat_scripts,
|
2024-04-12 21:03:46 +00:00
|
|
|
));
|
2024-04-04 11:33:54 +00:00
|
|
|
app.add_event::<StartConversationEvent>();
|
2024-04-12 22:05:42 +00:00
|
|
|
app.add_event::<ChatEvent>();
|
2024-04-13 20:10:13 +00:00
|
|
|
app.add_event::<ChatScriptEvent>();
|
2024-04-12 19:34:55 +00:00
|
|
|
app.insert_resource(ChatDB(Vec::new()));
|
2024-04-04 11:33:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-13 16:57:23 +00:00
|
|
|
type ChatPos = Vec<usize>;
|
|
|
|
|
2024-04-12 21:28:15 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct Chat {
|
2024-04-14 14:20:51 +00:00
|
|
|
pub internal_id: usize,
|
2024-04-13 16:57:23 +00:00
|
|
|
pub position: ChatPos,
|
2024-04-12 21:28:15 +00:00
|
|
|
pub timer: f64,
|
2024-04-12 23:21:38 +00:00
|
|
|
pub talker: Talker,
|
2024-04-12 21:28:15 +00:00
|
|
|
}
|
|
|
|
|
2024-04-12 22:05:42 +00:00
|
|
|
#[derive(Component)]
|
2024-04-13 13:26:45 +00:00
|
|
|
pub struct Choice {
|
|
|
|
pub text: String,
|
|
|
|
pub key: usize,
|
2024-04-13 16:57:23 +00:00
|
|
|
pub goto: ChatPos,
|
2024-04-13 13:26:45 +00:00
|
|
|
}
|
2024-04-12 22:05:42 +00:00
|
|
|
|
2024-04-13 20:10:13 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Talker {
|
2024-04-14 14:20:51 +00:00
|
|
|
pub chat_name: String,
|
|
|
|
pub actor_id: String,
|
2024-04-13 20:10:13 +00:00
|
|
|
pub name: Option<String>,
|
|
|
|
pub pronoun: Option<String>,
|
|
|
|
pub talking_speed: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Event)]
|
|
|
|
pub struct StartConversationEvent {
|
|
|
|
pub talker: Talker,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Event)]
|
|
|
|
pub struct ChatScriptEvent(String);
|
|
|
|
|
|
|
|
#[derive(Event)]
|
|
|
|
pub enum ChatEvent {
|
|
|
|
DespawnAllChoices,
|
|
|
|
DespawnAllChats,
|
2024-04-13 21:35:59 +00:00
|
|
|
SpawnMessage(String, hud::LogLevel, String),
|
2024-04-13 23:15:38 +00:00
|
|
|
SpawnChoice(String, usize, ChatPos, bool),
|
2024-04-13 20:10:13 +00:00
|
|
|
RunScript(String),
|
2024-04-14 12:57:49 +00:00
|
|
|
SleepSeconds(f64),
|
2024-04-14 14:20:51 +00:00
|
|
|
SetVariable(String),
|
2024-04-13 20:10:13 +00:00
|
|
|
}
|
|
|
|
|
2024-04-12 22:11:32 +00:00
|
|
|
// This is the only place where any YAML interaction should be happening.
|
2024-04-12 21:03:46 +00:00
|
|
|
#[derive(Resource)]
|
2024-04-13 15:17:24 +00:00
|
|
|
pub struct ChatDB(Vec<Value>);
|
2024-04-12 21:03:46 +00:00
|
|
|
impl ChatDB {
|
2024-04-12 22:11:32 +00:00
|
|
|
pub fn load_from_str(&mut self, yaml_string: &str) -> Result<(), ()> {
|
2024-04-13 15:17:24 +00:00
|
|
|
let mut count = 0;
|
|
|
|
for document in serde_yaml::Deserializer::from_str(yaml_string) {
|
|
|
|
match Value::deserialize(document) {
|
|
|
|
Ok(yaml_data) => {
|
|
|
|
if let Value::Sequence(yaml_sequence) = yaml_data {
|
|
|
|
self.0.push(Value::Sequence(yaml_sequence));
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
error!("Could not load YAML: {:?}", yaml_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(error) => {
|
|
|
|
dbg!(error);
|
|
|
|
return Err(());
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 22:11:32 +00:00
|
|
|
}
|
2024-04-13 15:17:24 +00:00
|
|
|
info!("Loaded {count} conversations");
|
|
|
|
return Ok(());
|
2024-04-12 22:11:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-14 01:21:48 +00:00
|
|
|
pub fn preprocess_includes(&mut self) {
|
|
|
|
let mut include_db: HashMap<String, Vec<Value>> = HashMap::new();
|
|
|
|
for sequence in &self.0 {
|
|
|
|
if let Some(vector) = sequence.as_sequence() {
|
|
|
|
if let Some(first_item) = vector.get(0) {
|
|
|
|
if let Some(map) = first_item.as_mapping() {
|
|
|
|
for (key, value) in map {
|
|
|
|
if let (Some(key), Some(value)) = (key.as_str(), value.as_str()) {
|
|
|
|
if key == TOKEN_CHAT {
|
|
|
|
include_db.insert(value.to_string(), vector.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for mut sequence in self.0.iter_mut() {
|
|
|
|
ChatDB::preprocess_includes_recursively(&mut sequence, &include_db);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn preprocess_includes_recursively(sequence: &mut Value, include_db: &HashMap<String, Vec<Value>>) {
|
|
|
|
let mut changes: Vec<(usize, String)> = Vec::new();
|
|
|
|
if let Some(vector) = sequence.as_sequence_mut() {
|
|
|
|
for (index, item) in vector.iter_mut().enumerate() {
|
|
|
|
match item {
|
|
|
|
Value::Mapping(map) => {
|
|
|
|
for (key, value) in map.iter_mut() {
|
|
|
|
if let (Some(key), Some(value)) = (key.as_str(), value.as_str()) {
|
|
|
|
if key == TOKEN_INCLUDE {
|
|
|
|
changes.push((index, value.to_string()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if value.is_sequence() {
|
|
|
|
ChatDB::preprocess_includes_recursively(value, include_db);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (index, label) in changes {
|
2024-04-14 01:37:33 +00:00
|
|
|
if index < vector.len() {
|
|
|
|
vector.remove(index);
|
2024-04-14 13:37:23 +00:00
|
|
|
if let Some(chat) = include_db.get(&label) {
|
|
|
|
vector.splice(index..index, chat.iter().cloned());
|
|
|
|
}
|
2024-04-14 01:37:33 +00:00
|
|
|
}
|
2024-04-14 01:21:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-12 22:39:21 +00:00
|
|
|
pub fn get_chat_by_id(&self, id: &String) -> Result<usize, String> {
|
|
|
|
let mut found: Option<usize> = None;
|
2024-04-12 21:03:46 +00:00
|
|
|
for (index, object_yaml) in self.0.iter().enumerate() {
|
2024-04-12 21:18:07 +00:00
|
|
|
if let Some(chat_id) = object_yaml[0][TOKEN_CHAT].as_str() {
|
|
|
|
if chat_id == id {
|
|
|
|
if found.is_some() {
|
|
|
|
return Err("Found multiple chats with the same id!".to_string());
|
|
|
|
}
|
2024-04-12 22:39:21 +00:00
|
|
|
found = Some(index);
|
2024-04-12 21:18:07 +00:00
|
|
|
}
|
2024-04-12 21:03:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(result) = found {
|
|
|
|
return Ok(result);
|
|
|
|
}
|
|
|
|
return Err(format!("No chat with the conversation ID `{id}` was found."));
|
|
|
|
}
|
2024-04-12 22:11:32 +00:00
|
|
|
|
2024-04-13 18:23:38 +00:00
|
|
|
// For a given Value, check whether it's a Value::Mapping and whether it
|
|
|
|
// contains a choice. Mappings that will be detected as choices:
|
|
|
|
// - `{"What's up?": [...]}`
|
|
|
|
// - `{"What's up?": [...], "if": "value > 3"}`
|
|
|
|
// Not acceptable:
|
|
|
|
// - `"What's up?"`
|
|
|
|
// - `{"goto": "foo"}`
|
2024-04-13 23:15:38 +00:00
|
|
|
// Returns (choice text, sub-conversation branch, nowait flag)
|
|
|
|
fn search_choice(&self, yaml: Option<&Value>) -> Option<(String, Value, bool)> {
|
2024-04-13 10:24:56 +00:00
|
|
|
let non_choice_tokens = NON_CHOICE_TOKENS.to_vec();
|
2024-04-13 23:15:38 +00:00
|
|
|
let mut result: Option<(String, Value, bool)> = None;
|
|
|
|
let mut nowait = false;
|
2024-04-13 19:07:51 +00:00
|
|
|
if let Some(Value::Mapping(map)) = yaml {
|
2024-04-13 23:15:38 +00:00
|
|
|
for (key, value) in map {
|
2024-04-13 15:17:24 +00:00
|
|
|
if let Value::String(key) = key {
|
2024-04-13 23:15:38 +00:00
|
|
|
if key == TOKEN_NOWAIT && value.as_bool() == Some(true) {
|
|
|
|
nowait = true;
|
|
|
|
}
|
2024-04-13 10:24:56 +00:00
|
|
|
if non_choice_tokens.contains(&key.as_str()) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-04-13 23:15:38 +00:00
|
|
|
result = Some((key.into(), map[key].clone(), nowait));
|
2024-04-13 10:24:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-13 23:15:38 +00:00
|
|
|
return result;
|
2024-04-13 10:24:56 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 20:43:41 +00:00
|
|
|
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> {
|
|
|
|
if pos.len() > MAX_BRANCH_DEPTH {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
if let Some(vector) = sequence.as_sequence() {
|
|
|
|
for (index, item) in vector.iter().enumerate() {
|
|
|
|
match item {
|
|
|
|
Value::String(_) => {}
|
|
|
|
Value::Mapping(map) => {
|
|
|
|
for (key, value) in map {
|
|
|
|
if let Some(key) = key.as_str() {
|
|
|
|
if key == TOKEN_LABEL {
|
|
|
|
if value == label {
|
|
|
|
pos.push(index);
|
|
|
|
return Some(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if value.is_sequence() {
|
|
|
|
pos.push(index);
|
|
|
|
if let Some(result) = self.search_label_recursively(
|
|
|
|
value, label, pos.clone()) {
|
|
|
|
return Some(result)
|
|
|
|
}
|
|
|
|
pos.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn search_label(&self, chat_id: usize, label: &String) -> Option<ChatPos> {
|
|
|
|
if label == TOKEN_GOTO_EXIT {
|
|
|
|
return Some(vec![]);
|
|
|
|
}
|
|
|
|
return self.search_label_recursively(&self.0[chat_id], label, vec![]);
|
|
|
|
}
|
|
|
|
|
2024-04-13 18:59:55 +00:00
|
|
|
// returns true if we reached the end of a branch and possibly popped the position stack
|
2024-04-13 14:03:15 +00:00
|
|
|
fn advance_pointer(&self, chat: &mut Chat) -> bool {
|
2024-04-13 18:23:38 +00:00
|
|
|
let len = chat.position.len();
|
|
|
|
if len == 0 {
|
2024-04-13 18:59:55 +00:00
|
|
|
return true; // out of bounds
|
2024-04-13 18:23:38 +00:00
|
|
|
}
|
|
|
|
chat.position[len - 1] += 1;
|
2024-04-13 15:53:28 +00:00
|
|
|
|
2024-04-13 18:59:55 +00:00
|
|
|
let mut popped = false;
|
2024-04-13 23:15:38 +00:00
|
|
|
let mut seek_past_dialog_choices = false;
|
2024-04-13 15:53:28 +00:00
|
|
|
while chat.position.len() > 0 {
|
2024-04-14 14:20:51 +00:00
|
|
|
match self.at(chat.internal_id, &chat.position) {
|
2024-04-13 16:57:23 +00:00
|
|
|
None => {
|
|
|
|
chat.position.pop();
|
2024-04-13 18:59:55 +00:00
|
|
|
popped = true;
|
2024-04-13 23:15:38 +00:00
|
|
|
seek_past_dialog_choices = true;
|
2024-04-13 18:23:38 +00:00
|
|
|
if chat.position.len() > 0 {
|
|
|
|
let index = chat.position.len() - 1;
|
|
|
|
chat.position[index] += 1;
|
|
|
|
}
|
2024-04-13 16:57:23 +00:00
|
|
|
},
|
2024-04-13 23:15:38 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2024-04-13 16:57:23 +00:00
|
|
|
Some(_) => {
|
|
|
|
break;
|
|
|
|
}
|
2024-04-13 15:53:28 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-13 18:23:38 +00:00
|
|
|
if chat.position.len() == 0 {
|
2024-04-13 18:59:55 +00:00
|
|
|
return true; // out of bounds
|
2024-04-13 18:23:38 +00:00
|
|
|
}
|
2024-04-13 18:59:55 +00:00
|
|
|
return popped;
|
2024-04-13 14:03:15 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 18:23:38 +00:00
|
|
|
// Returns the Value at the given ID/position, as-is.
|
|
|
|
// If it's a choice, it returns the entire {"choice text": [...]} mapping.
|
|
|
|
fn at(&self, id: usize, position: &Vec<usize>) -> Option<Value> {
|
2024-04-13 15:53:28 +00:00
|
|
|
if position.len() == 0 {
|
|
|
|
return None;
|
2024-04-13 15:17:24 +00:00
|
|
|
}
|
2024-04-13 18:23:38 +00:00
|
|
|
let mut result: Option<Value> = None;
|
|
|
|
let mut pointer: Option<Value> = Some(self.0[id].clone());
|
|
|
|
let mut next_pointer: Option<Value> = None;
|
2024-04-13 15:53:28 +00:00
|
|
|
for index in position {
|
2024-04-13 18:23:38 +00:00
|
|
|
if let Some(Value::Sequence(seq)) = &pointer {
|
|
|
|
let value = seq.get(*index);
|
|
|
|
match value {
|
|
|
|
Some(Value::String(value_string)) => {
|
|
|
|
result = Some(Value::String(value_string.into()));
|
|
|
|
}
|
|
|
|
Some(Value::Mapping(mapping)) => {
|
2024-04-13 23:15:38 +00:00
|
|
|
if let Some((_choicetext, subconversation, _)) = self.search_choice(value) {
|
2024-04-13 18:23:38 +00:00
|
|
|
result = Some(Value::Mapping(mapping.clone()));
|
|
|
|
next_pointer = Some(subconversation);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
result = Some(Value::Mapping(mapping.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
2024-04-13 18:59:55 +00:00
|
|
|
return None; // Out of bounds.
|
2024-04-13 18:23:38 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
error!("Could not handle YAML value {value:?}");
|
|
|
|
return None;
|
|
|
|
}
|
2024-04-13 16:57:23 +00:00
|
|
|
}
|
2024-04-13 18:23:38 +00:00
|
|
|
pointer = next_pointer;
|
|
|
|
next_pointer = None;
|
2024-04-13 15:53:28 +00:00
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
2024-04-13 18:23:38 +00:00
|
|
|
return result;
|
2024-04-13 14:03:15 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 23:15:38 +00:00
|
|
|
// 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 {
|
2024-04-14 14:20:51 +00:00
|
|
|
let current_item = self.at(chat.internal_id, &chat.position);
|
2024-04-13 23:15:38 +00:00
|
|
|
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(
|
2024-04-13 19:07:51 +00:00
|
|
|
&self,
|
|
|
|
chat: &mut Chat,
|
|
|
|
event: &mut EventWriter<ChatEvent>,
|
|
|
|
) -> bool {
|
2024-04-14 14:20:51 +00:00
|
|
|
let current_item = self.at(chat.internal_id, &chat.position);
|
2024-04-13 23:15:38 +00:00
|
|
|
let mut processed_a_choice = false;
|
2024-04-13 18:59:55 +00:00
|
|
|
match current_item {
|
|
|
|
Some(Value::String(message)) => {
|
2024-04-13 21:35:59 +00:00
|
|
|
event.send(ChatEvent::SpawnMessage(message.to_string(),
|
|
|
|
hud::LogLevel::Chat, DEFAULT_SOUND.to_string()));
|
2024-04-13 18:59:55 +00:00
|
|
|
}
|
2024-04-13 19:45:05 +00:00
|
|
|
Some(Value::Mapping(map)) => {
|
2024-04-13 23:15:38 +00:00
|
|
|
let mut sound = DEFAULT_SOUND.to_string();
|
|
|
|
|
|
|
|
// Is this a dialog choice?
|
2024-04-13 19:45:05 +00:00
|
|
|
if let Some(_) = self.search_choice(Some(&Value::Mapping(map.clone()))) {
|
2024-04-13 23:15:38 +00:00
|
|
|
processed_a_choice = true;
|
2024-04-13 18:59:55 +00:00
|
|
|
}
|
2024-04-13 21:35:59 +00:00
|
|
|
|
|
|
|
// We're going through the list of keys/values multiple times
|
|
|
|
// to ensure that dependencies for certain commands are available
|
|
|
|
|
|
|
|
// First pass
|
|
|
|
for (key, value) in &map {
|
|
|
|
let key = key.as_str();
|
|
|
|
match (key, value) {
|
2024-04-13 23:15:38 +00:00
|
|
|
(Some(TOKEN_IF), _) => {} // TODO
|
2024-04-13 21:35:59 +00:00
|
|
|
(Some(TOKEN_SOUND), Value::String(sound_name)) => {
|
|
|
|
sound = sound_name.clone();
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Second pass
|
|
|
|
for (key, value) in &map {
|
2024-04-13 19:45:05 +00:00
|
|
|
let key = key.as_str();
|
|
|
|
match (key, value) {
|
|
|
|
(Some(TOKEN_MSG), Value::String(message)) => {
|
|
|
|
event.send(ChatEvent::SpawnMessage(
|
2024-04-13 21:35:59 +00:00
|
|
|
message.to_string(), hud::LogLevel::Chat, sound.clone()));
|
2024-04-13 19:45:05 +00:00
|
|
|
}
|
|
|
|
(Some(TOKEN_SYSTEM), Value::String(message)) => {
|
|
|
|
event.send(ChatEvent::SpawnMessage(
|
2024-04-13 21:35:59 +00:00
|
|
|
message.to_string(), hud::LogLevel::Info, sound.clone()));
|
2024-04-13 19:45:05 +00:00
|
|
|
}
|
|
|
|
(Some(TOKEN_WARN), Value::String(message)) => {
|
|
|
|
event.send(ChatEvent::SpawnMessage(
|
2024-04-13 21:35:59 +00:00
|
|
|
message.to_string(), hud::LogLevel::Warning, sound.clone()));
|
|
|
|
}
|
2024-04-14 14:20:51 +00:00
|
|
|
(Some(TOKEN_SET), Value::String(instructions)) => {
|
|
|
|
event.send(ChatEvent::SetVariable(instructions.to_string()));
|
|
|
|
}
|
2024-04-13 21:35:59 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Third pass
|
|
|
|
for (key, value) in &map {
|
|
|
|
let key = key.as_str();
|
|
|
|
match (key, value) {
|
|
|
|
(Some(TOKEN_SLEEP), Value::Number(time)) => {
|
|
|
|
if let Some(time_f64) = time.as_f64() {
|
2024-04-14 12:57:49 +00:00
|
|
|
event.send(ChatEvent::SleepSeconds(time_f64));
|
2024-04-13 21:35:59 +00:00
|
|
|
}
|
2024-04-13 19:45:05 +00:00
|
|
|
}
|
2024-04-13 20:43:41 +00:00
|
|
|
(Some(TOKEN_GOTO), Value::String(label)) => {
|
2024-04-14 14:20:51 +00:00
|
|
|
match self.search_label(chat.internal_id, &label) {
|
2024-04-13 20:43:41 +00:00
|
|
|
Some(pos) => {
|
|
|
|
chat.position = pos;
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
error!("Could not find goto label {label}!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-13 20:10:13 +00:00
|
|
|
(Some(TOKEN_SCRIPT), Value::String(script)) => {
|
2024-04-13 21:35:59 +00:00
|
|
|
event.send(ChatEvent::RunScript(script.clone()));
|
2024-04-13 19:45:05 +00:00
|
|
|
}
|
2024-04-13 21:35:59 +00:00
|
|
|
_ => {}
|
2024-04-13 19:45:05 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-13 18:59:55 +00:00
|
|
|
}
|
|
|
|
None => {
|
2024-04-13 19:07:51 +00:00
|
|
|
if chat.position.len() == 0 {
|
2024-04-13 21:35:59 +00:00
|
|
|
event.send(ChatEvent::SpawnMessage("Disconnected.".to_string(),
|
|
|
|
hud::LogLevel::Info, DEFAULT_SOUND.to_string()));
|
2024-04-13 19:07:51 +00:00
|
|
|
event.send(ChatEvent::DespawnAllChats);
|
|
|
|
}
|
2024-04-13 18:59:55 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
error!("Can't handle YAML value {current_item:?}");
|
|
|
|
}
|
2024-04-12 22:39:21 +00:00
|
|
|
}
|
2024-04-13 23:15:38 +00:00
|
|
|
return processed_a_choice;
|
2024-04-13 19:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn advance_chat(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) {
|
|
|
|
event.send(ChatEvent::DespawnAllChoices);
|
|
|
|
|
|
|
|
// Handle this entry in the chat list
|
2024-04-13 23:15:38 +00:00
|
|
|
let processed_a_choice: bool = self.process_yaml_entry(chat, event);
|
2024-04-13 10:24:56 +00:00
|
|
|
|
2024-04-13 18:59:55 +00:00
|
|
|
// Move on to next entry
|
2024-04-13 23:15:38 +00:00
|
|
|
self.advance_pointer(chat);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
2024-04-13 18:59:55 +00:00
|
|
|
|
2024-04-13 23:15:38 +00:00
|
|
|
// Spawn choices until we reach a non-choice item or the end of the branch
|
2024-04-13 18:59:55 +00:00
|
|
|
let mut key: usize = 0;
|
2024-04-13 23:15:38 +00:00
|
|
|
let mut reached_end_of_branch = false;
|
|
|
|
while let Some((choice, _, nowait)) =
|
2024-04-14 14:20:51 +00:00
|
|
|
self.search_choice(self.at(chat.internal_id, &chat.position).as_ref()) {
|
2024-04-13 23:15:38 +00:00
|
|
|
if reached_end_of_branch {
|
2024-04-13 18:59:55 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
let mut goto: Vec<usize> = chat.position.clone();
|
2024-04-13 16:57:23 +00:00
|
|
|
goto.push(0);
|
2024-04-13 23:15:38 +00:00
|
|
|
event.send(ChatEvent::SpawnChoice(choice, key, goto, nowait));
|
2024-04-13 13:26:45 +00:00
|
|
|
key += 1;
|
2024-04-13 23:15:38 +00:00
|
|
|
reached_end_of_branch = self.advance_pointer(chat);
|
2024-04-13 10:24:56 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-12 22:05:42 +00:00
|
|
|
}
|
2024-04-12 21:03:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-12 19:34:55 +00:00
|
|
|
pub fn load_chats(mut chatdb: ResMut<ChatDB>) {
|
|
|
|
for chat_yaml in CHATS {
|
2024-04-12 22:11:32 +00:00
|
|
|
if chatdb.load_from_str(chat_yaml).is_err() {
|
2024-04-12 19:34:55 +00:00
|
|
|
error!("Could not load chat definitions. Validate files in `src/chats/` path.");
|
|
|
|
}
|
2024-04-12 19:26:23 +00:00
|
|
|
}
|
2024-04-14 01:21:48 +00:00
|
|
|
chatdb.preprocess_includes();
|
2024-04-12 19:26:23 +00:00
|
|
|
}
|
2024-04-12 21:03:46 +00:00
|
|
|
|
|
|
|
pub fn handle_new_conversations(
|
2024-04-12 21:28:15 +00:00
|
|
|
mut commands: Commands,
|
2024-04-12 21:03:46 +00:00
|
|
|
mut er_conv: EventReader<StartConversationEvent>,
|
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
2024-04-12 22:05:42 +00:00
|
|
|
mut ew_chatevent: EventWriter<ChatEvent>,
|
2024-04-12 21:03:46 +00:00
|
|
|
chatdb: Res<ChatDB>,
|
2024-04-12 21:28:15 +00:00
|
|
|
q_chats: Query<&Chat>,
|
|
|
|
time: Res<Time>,
|
2024-04-12 21:03:46 +00:00
|
|
|
) {
|
|
|
|
for event in er_conv.read() {
|
2024-04-12 22:05:42 +00:00
|
|
|
if !q_chats.is_empty() {
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
|
|
|
|
return;
|
|
|
|
}
|
2024-04-14 14:20:51 +00:00
|
|
|
match (*chatdb).get_chat_by_id(&event.talker.chat_name) {
|
2024-04-12 21:03:46 +00:00
|
|
|
Ok(chat_id) => {
|
2024-04-12 22:05:42 +00:00
|
|
|
let mut chat = Chat {
|
2024-04-14 14:20:51 +00:00
|
|
|
internal_id: chat_id,
|
2024-04-13 14:03:15 +00:00
|
|
|
position: vec![0],
|
2024-04-12 22:05:42 +00:00
|
|
|
timer: time.elapsed_seconds_f64(),
|
2024-04-12 23:21:38 +00:00
|
|
|
talker: event.talker.clone(),
|
2024-04-12 22:05:42 +00:00
|
|
|
};
|
|
|
|
chatdb.advance_chat(&mut chat, &mut ew_chatevent);
|
2024-04-12 21:28:15 +00:00
|
|
|
commands.spawn((
|
2024-04-12 22:05:42 +00:00
|
|
|
chat,
|
2024-04-12 21:28:15 +00:00
|
|
|
world::DespawnOnPlayerDeath,
|
|
|
|
));
|
2024-04-12 21:03:46 +00:00
|
|
|
}
|
|
|
|
Err(error) => {
|
|
|
|
error!("Error while looking for chat ID: {error}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 22:05:42 +00:00
|
|
|
|
2024-04-12 23:21:38 +00:00
|
|
|
pub fn handle_chat_timer(
|
|
|
|
time: Res<Time>,
|
|
|
|
chatdb: Res<ChatDB>,
|
|
|
|
mut q_chats: Query<&mut Chat>,
|
|
|
|
mut ew_chatevent: EventWriter<ChatEvent>,
|
|
|
|
) {
|
|
|
|
let now = time.elapsed_seconds_f64();
|
|
|
|
for mut chat in &mut q_chats {
|
|
|
|
if now >= chat.timer {
|
|
|
|
chatdb.advance_chat(&mut chat, &mut ew_chatevent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-12 22:05:42 +00:00
|
|
|
pub fn handle_chat_events(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut er_chatevent: EventReader<ChatEvent>,
|
2024-04-13 20:10:13 +00:00
|
|
|
mut ew_chatscript: EventWriter<ChatScriptEvent>,
|
2024-04-13 21:21:53 +00:00
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
2024-04-12 22:39:21 +00:00
|
|
|
mut log: ResMut<hud::Log>,
|
2024-04-14 14:20:51 +00:00
|
|
|
mut vars: ResMut<var::GameVars>,
|
2024-04-12 22:05:42 +00:00
|
|
|
q_choices: Query<Entity, With<Choice>>,
|
2024-04-12 23:21:38 +00:00
|
|
|
mut q_chats: Query<(Entity, &mut Chat)>,
|
|
|
|
time: Res<Time>,
|
2024-04-14 12:55:00 +00:00
|
|
|
settings: Res<var::Settings>,
|
2024-04-12 22:05:42 +00:00
|
|
|
) {
|
|
|
|
for event in er_chatevent.read() {
|
2024-04-12 23:21:38 +00:00
|
|
|
let now = time.elapsed_seconds_f64();
|
|
|
|
let chat_maybe = q_chats.get_single_mut();
|
|
|
|
if chat_maybe.is_err() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let (chat_entity, mut chat) = chat_maybe.unwrap();
|
|
|
|
|
2024-04-12 22:05:42 +00:00
|
|
|
match event {
|
|
|
|
ChatEvent::DespawnAllChoices => {
|
|
|
|
for entity in &q_choices {
|
|
|
|
commands.entity(entity).despawn();
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 23:21:38 +00:00
|
|
|
ChatEvent::DespawnAllChats => {
|
|
|
|
commands.entity(chat_entity).despawn();
|
|
|
|
}
|
2024-04-13 21:35:59 +00:00
|
|
|
ChatEvent::SpawnMessage(message, level, sound) => {
|
2024-04-13 19:45:05 +00:00
|
|
|
match level {
|
|
|
|
hud::LogLevel::Chat => {
|
2024-04-13 21:21:53 +00:00
|
|
|
log.chat(message.into(), chat.talker.name.clone().unwrap_or("".to_string()));
|
2024-04-13 19:45:05 +00:00
|
|
|
}
|
|
|
|
hud::LogLevel::Info | hud::LogLevel::Notice => {
|
|
|
|
log.info(message.into());
|
|
|
|
}
|
|
|
|
hud::LogLevel::Warning => {
|
|
|
|
log.warning(message.into());
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 23:34:18 +00:00
|
|
|
chat.timer = now + ((message.len() as f32).max(CHAT_SPEED_MIN_LEN) * TALKER_SPEED_FACTOR * chat.talker.talking_speed / settings.chat_speed) as f64;
|
2024-04-13 21:21:53 +00:00
|
|
|
|
2024-04-13 21:35:59 +00:00
|
|
|
let sfx = audio::str2sfx(sound);
|
2024-04-13 21:21:53 +00:00
|
|
|
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
2024-04-12 22:39:21 +00:00
|
|
|
}
|
2024-04-13 23:15:38 +00:00
|
|
|
ChatEvent::SpawnChoice(replytext, key, goto, nowait) => {
|
2024-04-13 13:26:45 +00:00
|
|
|
commands.spawn((
|
|
|
|
world::DespawnOnPlayerDeath,
|
|
|
|
Choice {
|
|
|
|
text: replytext.into(),
|
|
|
|
key: *key,
|
2024-04-13 16:57:23 +00:00
|
|
|
goto: goto.clone(),
|
2024-04-13 13:26:45 +00:00
|
|
|
}
|
|
|
|
));
|
2024-04-13 23:15:38 +00:00
|
|
|
if !nowait {
|
|
|
|
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
|
|
|
}
|
2024-04-13 13:26:45 +00:00
|
|
|
}
|
2024-04-13 20:10:13 +00:00
|
|
|
ChatEvent::RunScript(script) => {
|
|
|
|
ew_chatscript.send(ChatScriptEvent(script.clone()));
|
|
|
|
}
|
2024-04-14 12:57:49 +00:00
|
|
|
ChatEvent::SleepSeconds(sleep_duration) => {
|
2024-04-13 21:03:41 +00:00
|
|
|
chat.timer = now + sleep_duration;
|
|
|
|
}
|
2024-04-14 14:20:51 +00:00
|
|
|
ChatEvent::SetVariable(string) => {
|
|
|
|
if let Some((key, value)) = string.split_once(" ") {
|
|
|
|
vars.set_in_scope(&chat.talker.actor_id, key, value.into());
|
|
|
|
} else {
|
|
|
|
vars.set_in_scope(&chat.talker.actor_id, string, "".into());
|
|
|
|
}
|
|
|
|
}
|
2024-04-12 22:05:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-13 13:44:23 +00:00
|
|
|
|
|
|
|
fn handle_reply_keys(
|
|
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
2024-04-14 12:55:00 +00:00
|
|
|
settings: ResMut<var::Settings>,
|
2024-04-13 13:44:23 +00:00
|
|
|
q_choices: Query<&Choice>,
|
2024-04-13 16:57:23 +00:00
|
|
|
mut q_chats: Query<&mut Chat>,
|
2024-04-13 13:44:23 +00:00
|
|
|
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
|
2024-04-13 16:57:23 +00:00
|
|
|
time: Res<Time>,
|
2024-04-13 13:44:23 +00:00
|
|
|
) {
|
|
|
|
let mut selected_choice: usize = 0;
|
|
|
|
'outer: for key in settings.get_reply_keys() {
|
|
|
|
if keyboard_input.just_pressed(key) {
|
|
|
|
for choice in &q_choices {
|
|
|
|
if choice.key == selected_choice {
|
2024-04-13 16:57:23 +00:00
|
|
|
if let Ok(mut chat) = q_chats.get_single_mut() {
|
|
|
|
evwriter_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
|
|
|
|
chat.timer = time.elapsed_seconds_f64();
|
|
|
|
chat.position = choice.goto.clone();
|
|
|
|
}
|
2024-04-13 13:44:23 +00:00
|
|
|
break 'outer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
selected_choice += 1;
|
|
|
|
}
|
|
|
|
}
|
2024-04-13 20:10:13 +00:00
|
|
|
|
|
|
|
pub fn handle_chat_scripts(
|
|
|
|
mut er_chatscript: EventReader<ChatScriptEvent>,
|
|
|
|
mut q_actor: Query<(&mut actor::Actor, &mut actor::Suit), Without<actor::Player>>,
|
|
|
|
mut q_player: Query<(&mut actor::Actor, &mut actor::Suit, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
|
|
|
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
|
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
|
|
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
|
|
|
|
) {
|
|
|
|
for script in er_chatscript.read() {
|
|
|
|
// Parse the script string
|
|
|
|
let mut parts = script.0.split_whitespace();
|
|
|
|
let name = parts.next();
|
|
|
|
if name.is_none() {
|
|
|
|
error!("ChatScriptEvent should not contain a script name and its parameters, got empty String");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let name = name.unwrap();
|
|
|
|
let param1 = parts.next().unwrap_or("");
|
|
|
|
let param2 = parts.next().unwrap_or("");
|
|
|
|
|
|
|
|
// Process the script
|
|
|
|
match name {
|
|
|
|
"refilloxygen" => if let Ok(mut amount) = param1.to_string().parse::<f32>() {
|
|
|
|
for (_, mut suit, _) in q_player.iter_mut() {
|
|
|
|
if param2.is_empty() {
|
|
|
|
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
let mut found_other = false;
|
|
|
|
info!("param2={}", param2);
|
|
|
|
for (other_actor, mut other_suit) in q_actor.iter_mut() {
|
|
|
|
if !other_actor.id.is_empty() {
|
|
|
|
info!("ID={}", other_actor.id);
|
|
|
|
}
|
|
|
|
if other_actor.id == param2 {
|
|
|
|
found_other = true;
|
|
|
|
amount = amount
|
|
|
|
.clamp(0.0, other_suit.oxygen)
|
|
|
|
.clamp(0.0, suit.oxygen_max - suit.oxygen);
|
|
|
|
other_suit.oxygen = other_suit.oxygen - amount;
|
|
|
|
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found_other {
|
|
|
|
error!("Script error: could not find actor with ID `{}`", param2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error!("Invalid parameter for command `{}`: `{}`", name, param1);
|
|
|
|
}
|
|
|
|
"cryotrip" => {
|
|
|
|
if param1.is_empty() {
|
|
|
|
error!("Chat script cryotrip needs a parameter");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
|
|
|
|
if param1 == "oscillation".to_string() {
|
2024-04-14 02:13:51 +00:00
|
|
|
*pos = Position(DVec3::new(-184968e3, 149410e3, -134273e3));
|
2024-04-13 20:10:13 +00:00
|
|
|
v.0 = DVec3::ZERO;
|
|
|
|
}
|
|
|
|
else if param1 == "metisprime".to_string() {
|
|
|
|
*pos = Position(DVec3::new(27643e3, -47e3, -124434e3));
|
|
|
|
v.0 = DVec3::ZERO;
|
|
|
|
}
|
|
|
|
else if param1 == "serenity".to_string() {
|
|
|
|
*pos = Position(DVec3::new(-121095e3, 582e3, -190816e3));
|
|
|
|
v.0 = DVec3::ZERO;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
error!("Invalid destination for cryotrip chat script: '{}'", param1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Ok((_, mut suit, mut gforce)) = q_player.get_single_mut() {
|
|
|
|
suit.oxygen = suit.oxygen_max;
|
|
|
|
gforce.ignore_gforce_seconds = 1.0;
|
|
|
|
}
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
|
|
|
ew_effect.send(effects::SpawnEffectEvent {
|
|
|
|
class: effects::Effects::FadeIn(Color::CYAN),
|
|
|
|
duration: 1.0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"cryofadeout" => {
|
|
|
|
ew_effect.send(effects::SpawnEffectEvent {
|
|
|
|
class: effects::Effects::FadeOut(Color::CYAN),
|
|
|
|
duration: 5.1,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
error!("Error, undefined chat script {name}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|