outfly/src/chat.rs

204 lines
6 KiB
Rust
Raw Normal View History

2024-04-12 19:26:23 +00:00
extern crate yaml_rust;
use bevy::prelude::*;
2024-04-12 21:03:46 +00:00
use yaml_rust::{Yaml, YamlLoader};
use crate::{audio, hud, world};
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";
pub const TALKER_SPEED_FACTOR: f32 = 1.0 / 17.0;
pub const CHAT_SPEED_MIN_LEN: f32 = 40.0;
pub const NAME_FALLBACK: &str = "Unknown";
2024-04-12 21:03:46 +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, (
handle_new_conversations.before(handle_chat_events),
handle_chat_events,
handle_chat_timer.before(handle_chat_events),
2024-04-12 21:03:46 +00:00
));
app.add_event::<StartConversationEvent>();
app.add_event::<ChatEvent>();
2024-04-12 19:34:55 +00:00
app.insert_resource(ChatDB(Vec::new()));
}
}
#[derive(Component)]
pub struct Chat {
pub id: usize,
pub position: usize,
pub timer: f64,
pub talker: Talker,
}
#[derive(Component)]
pub struct Choice;
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)]
pub struct ChatDB(Vec<Yaml>);
impl ChatDB {
2024-04-12 22:11:32 +00:00
pub fn load_from_str(&mut self, yaml_string: &str) -> Result<(), ()> {
if let Ok(mut yaml_data) = YamlLoader::load_from_str(yaml_string) {
self.0.append(&mut yaml_data);
return Ok(());
}
return Err(());
}
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());
}
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
pub fn advance_chat(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) {
event.send(ChatEvent::DespawnAllChoices);
let conv = &self.0[chat.id].as_vec();
if conv.is_none() {
return;
}
let conv = conv.unwrap();
chat.position += 1;
if chat.position >= conv.len() {
event.send(ChatEvent::DisplayMessage("Disconnected.".to_string()));
event.send(ChatEvent::DespawnAllChats);
return;
}
if let Some(message) = conv[chat.position].as_str() {
event.send(ChatEvent::DisplayMessage(message.to_string()));
}
}
2024-04-12 21:03:46 +00:00
}
#[derive(Component)]
#[derive(Clone)]
pub struct Talker {
pub conv_id: String,
2024-04-12 21:03:46 +00:00
pub name: Option<String>,
pub pronoun: Option<String>,
pub talking_speed: f32,
}
#[derive(Event)]
pub struct StartConversationEvent {
pub talker: Talker,
}
2024-04-12 19:26:23 +00:00
#[derive(Event)]
pub enum ChatEvent {
DespawnAllChoices,
DespawnAllChats,
DisplayMessage(String),
//SpawnChoice(String),
//Script(String, String, String),
}
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-12 21:03:46 +00:00
pub fn handle_new_conversations(
mut commands: Commands,
2024-04-12 21:03:46 +00:00
mut er_conv: EventReader<StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_chatevent: EventWriter<ChatEvent>,
2024-04-12 21:03:46 +00:00
chatdb: Res<ChatDB>,
q_chats: Query<&Chat>,
time: Res<Time>,
2024-04-12 21:03:46 +00:00
) {
for event in er_conv.read() {
if !q_chats.is_empty() {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
return;
}
2024-04-12 21:03:46 +00:00
match (*chatdb).get_chat_by_id(&event.talker.conv_id) {
Ok(chat_id) => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
let mut chat = Chat {
id: chat_id,
position: 0,
timer: time.elapsed_seconds_f64(),
talker: event.talker.clone(),
};
chatdb.advance_chat(&mut chat, &mut ew_chatevent);
commands.spawn((
chat,
world::DespawnOnPlayerDeath,
));
2024-04-12 21:03:46 +00:00
}
Err(error) => {
error!("Error while looking for chat ID: {error}");
}
}
}
}
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);
}
}
}
pub fn handle_chat_events(
mut commands: Commands,
mut er_chatevent: EventReader<ChatEvent>,
mut log: ResMut<hud::Log>,
q_choices: Query<Entity, With<Choice>>,
mut q_chats: Query<(Entity, &mut Chat)>,
time: Res<Time>,
) {
for event in er_chatevent.read() {
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();
match event {
ChatEvent::DespawnAllChoices => {
for entity in &q_choices {
commands.entity(entity).despawn();
}
}
ChatEvent::DespawnAllChats => {
commands.entity(chat_entity).despawn();
}
ChatEvent::DisplayMessage(message) => {
log.chat(message.into(), chat.talker.name.clone().unwrap_or(NAME_FALLBACK.to_string()));
chat.timer = now + ((message.len() as f32).max(CHAT_SPEED_MIN_LEN) * TALKER_SPEED_FACTOR * chat.talker.talking_speed) as f64;
}
}
}
}