outfly/src/chat.rs

293 lines
9.7 KiB
Rust
Raw Normal View History

use bevy::prelude::*;
use crate::{actor, audio, hud};
pub struct ChatPlugin;
impl Plugin for ChatPlugin {
fn build(&self, app: &mut App) {
app.register_type::<ChatBranch>();
app.add_systems(Update, (
handle_new_conversations,
handle_send_messages,
handle_conversations,
handle_chat_scripts,
));
app.add_event::<StartConversationEvent>();
app.add_event::<SendMessageEvent>();
app.add_event::<ChatScriptEvent>();
}
}
#[derive(Event)]
pub struct StartConversationEvent {
pub talker: Talker,
}
#[derive(Event)]
pub struct SendMessageEvent {
pub conv_id: String,
pub conv_label: String,
pub text: String,
}
#[derive(Event)]
pub struct ChatScriptEvent {
name: String,
param: String,
param2: String,
}
#[derive(Debug)]
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct ChatBranch {
pub id: String,
pub name: String,
pub label: String,
pub delay: f64,
pub sound: String,
pub level: String,
pub reply: String,
pub goto: String,
pub choice: String,
pub script: String,
pub script_parameter: String,
pub script_parameter2: String,
}
#[derive(Component)]
pub struct Chat {
pub id: String,
pub label: String,
pub timer: f64,
}
#[derive(Component)]
#[derive(Clone)]
pub struct Talker {
pub pronoun: String,
pub conv_id: String,
}
impl Default for Talker { fn default() -> Self { Self {
pronoun: "they/them".to_string(),
conv_id: "error".to_string(),
}}}
pub fn handle_new_conversations(
mut commands: Commands,
mut er_conv: EventReader<StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_conv: Query<&Chat>,
time: Res<Time>,
) {
let label = "INIT";
for event in er_conv.read() {
// check for existing chats with this id
let id = &event.talker.conv_id;
let chats: Vec<&Chat> = q_conv.iter().filter(|c| c.id == *id).collect();
if chats.len() > 0 {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
continue;
}
// no existing chats yet, let's create a new one
commands.spawn(Chat {
id: id.to_string(),
label: label.to_string(),
timer: time.elapsed_seconds_f64(),
});
break;
}
}
pub fn handle_send_messages(
mut commands: Commands,
mut er_sendmsg: EventReader<SendMessageEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_chatscript: EventWriter<ChatScriptEvent>,
mut q_conv: Query<(Entity, &mut Chat)>,
time: Res<Time>,
chat_branches: Query<&ChatBranch>,
mut log: ResMut<hud::Log>,
) {
let now = time.elapsed_seconds_f64();
for event in er_sendmsg.read() {
for (entity, mut chat) in &mut q_conv {
if chat.id != event.conv_id {
continue;
}
if event.conv_label == "EXIT" {
info!("Despawning chat.");
commands.entity(entity).despawn();
continue;
}
let branches: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == event.conv_id
&& branch.label == event.conv_label
&& branch.choice == event.text)
.collect();
if branches.len() != 1 {
error!("Expected 1 branch with ID '{}' and label '{}', but got {}! Aborting conversation.", chat.id, chat.label, branches.len());
continue;
}
let branch = branches[0];
// TODO despawn the choices
if !branch.reply.is_empty() {
match branch.level.as_str() {
"chat" => log.chat(branch.reply.clone(), branch.name.clone()),
"info" => log.info(branch.reply.clone()),
"warn" => log.warning(branch.reply.clone()),
_ => (),
}
}
chat.label = branch.goto.clone();
chat.timer = now + branch.delay;
if branch.sound != "" {
let sfx = audio::str2sfx(branch.sound.as_str());
ew_sfx.send(audio::PlaySfxEvent(sfx));
}
let choices: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == chat.id && branch.label == chat.label)
.collect();
for choice in choices {
if choice.choice.as_str() != hud::CHOICE_NONE {
commands.spawn(hud::ChoiceAvailable {
conv_id: choice.id.clone(),
conv_label: choice.label.clone(),
recipient: choice.name.clone(),
text: choice.choice.clone(),
});
}
}
if !branch.script.is_empty() {
ew_chatscript.send(ChatScriptEvent {
name: branch.script.clone(),
param: branch.script_parameter.clone(),
param2: branch.script_parameter2.clone(),
});
}
}
break; // let's only handle one of these per frame
}
}
pub fn handle_conversations(
mut commands: Commands,
mut log: ResMut<hud::Log>,
mut q_conv: Query<(Entity, &mut Chat)>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_chatscript: EventWriter<ChatScriptEvent>,
time: Res<Time>,
chat_branches: Query<&ChatBranch>, // TODO: use Table for faster iteration?
) {
let now = time.elapsed_seconds_f64();
for (entity, mut chat) in &mut q_conv {
if chat.label == "EXIT" {
info!("Despawning chat.");
commands.entity(entity).despawn();
continue;
}
if now < chat.timer {
continue;
}
let branches: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == chat.id
&& branch.label == chat.label
&& branch.choice == "")
.collect();
if branches.len() != 1 {
error!("Expected 1 branch with ID '{}' and label '{}', but got {}! Aborting conversation.", chat.id, chat.label, branches.len());
continue;
}
let branch = branches[0];
if !branch.reply.is_empty() {
match branch.level.as_str() {
"chat" => log.chat(branch.reply.clone(), branch.name.clone()),
"info" => log.info(branch.reply.clone()),
"warn" => log.warning(branch.reply.clone()),
_ => (),
}
}
if chat.label == "EXIT" {
// TODO: isn't this dead code?
continue;
}
chat.label = branch.goto.clone();
if branch.sound != "" {
let sfx = audio::str2sfx(branch.sound.as_str());
ew_sfx.send(audio::PlaySfxEvent(sfx));
}
chat.timer = now + branch.delay;
let choices: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == chat.id && branch.label == chat.label)
.collect();
for choice in choices {
if choice.choice.as_str() != hud::CHOICE_NONE {
commands.spawn(hud::ChoiceAvailable {
conv_id: choice.id.clone(),
conv_label: choice.label.clone(),
recipient: choice.name.clone(),
text: choice.choice.clone(),
});
}
}
if !branch.script.is_empty() {
ew_chatscript.send(ChatScriptEvent {
name: branch.script.clone(),
param: branch.script_parameter.clone(),
param2: branch.script_parameter2.clone(),
});
}
}
}
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), With<actor::Player>>,
) {
for script in er_chatscript.read() {
match script.name.as_str() {
"refilloxygen" => if let Ok(mut amount) = script.param.parse::<f32>() {
for (mut _actor, mut suit) in q_player.iter_mut() {
if script.param2.is_empty() {
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
}
else {
let mut found_other = false;
info!("param2={}", script.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 == script.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 `{}`", script.param2);
}
}
}
} else {
error!("Invalid parameter for command `{}`: `{}`", script.name, script.param);
}
_ => {}
}
}
}