split off chat logic from actor.rs into new chat.rs
This commit is contained in:
parent
d1defff3ad
commit
ca7d2facd9
291
src/actor.rs
291
src/actor.rs
|
@ -1,6 +1,6 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
use crate::{nature, settings, actor, audio, hud};
|
use crate::{actor, audio, chat, nature, settings};
|
||||||
|
|
||||||
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
||||||
const MIN_INTERACT_DISTANCE: f32 = 30.0;
|
const MIN_INTERACT_DISTANCE: f32 = 30.0;
|
||||||
|
@ -9,47 +9,20 @@ const NO_RIDE: u32 = 0;
|
||||||
pub struct ActorPlugin;
|
pub struct ActorPlugin;
|
||||||
impl Plugin for ActorPlugin {
|
impl Plugin for ActorPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<ChatBranch>();
|
|
||||||
app.add_systems(FixedUpdate, (
|
app.add_systems(FixedUpdate, (
|
||||||
update_physics_lifeforms,
|
update_physics_lifeforms,
|
||||||
));
|
));
|
||||||
app.add_systems(Update, (
|
app.add_systems(Update, (
|
||||||
handle_new_conversations,
|
|
||||||
handle_send_messages,
|
|
||||||
handle_conversations,
|
|
||||||
handle_input,
|
handle_input,
|
||||||
handle_chat_scripts,
|
|
||||||
handle_collisions,
|
handle_collisions,
|
||||||
));
|
));
|
||||||
app.add_systems(PostUpdate, (
|
app.add_systems(PostUpdate, (
|
||||||
handle_vehicle_enter_exit,
|
handle_vehicle_enter_exit,
|
||||||
));
|
));
|
||||||
app.add_event::<StartConversationEvent>();
|
|
||||||
app.add_event::<SendMessageEvent>();
|
|
||||||
app.add_event::<ChatScriptEvent>();
|
|
||||||
app.add_event::<VehicleEnterExitEvent>();
|
app.add_event::<VehicleEnterExitEvent>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(Event)]
|
#[derive(Event)]
|
||||||
pub struct VehicleEnterExitEvent {
|
pub struct VehicleEnterExitEvent {
|
||||||
vehicle: Entity,
|
vehicle: Entity,
|
||||||
|
@ -87,42 +60,6 @@ impl Default for Actor {
|
||||||
#[derive(Component)] pub struct ActorEnteringVehicle;
|
#[derive(Component)] pub struct ActorEnteringVehicle;
|
||||||
#[derive(Component)] pub struct ActorVehicleBeingEntered;
|
#[derive(Component)] pub struct ActorVehicleBeingEntered;
|
||||||
|
|
||||||
#[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(),
|
|
||||||
}}}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct LifeForm {
|
pub struct LifeForm {
|
||||||
pub is_alive: bool,
|
pub is_alive: bool,
|
||||||
|
@ -239,10 +176,10 @@ pub fn handle_input(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
settings: ResMut<settings::Settings>,
|
settings: ResMut<settings::Settings>,
|
||||||
q_talker: Query<(&Talker, &Transform), Without<actor::Player>>,
|
q_talker: Query<(&chat::Talker, &Transform), Without<actor::Player>>,
|
||||||
mut player: Query<(Entity, &mut Actor, &mut Transform), With<actor::Player>>,
|
mut player: Query<(Entity, &mut Actor, &mut Transform), With<actor::Player>>,
|
||||||
mut q_vehicles: Query<(Entity, &mut Visibility, &Transform), (With<actor::Vehicle>, Without<actor::Player>)>,
|
mut q_vehicles: Query<(Entity, &mut Visibility, &Transform), (With<actor::Vehicle>, Without<actor::Player>)>,
|
||||||
mut ew_conv: EventWriter<StartConversationEvent>,
|
mut ew_conv: EventWriter<chat::StartConversationEvent>,
|
||||||
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
|
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
|
||||||
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
|
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
|
||||||
) {
|
) {
|
||||||
|
@ -251,8 +188,9 @@ pub fn handle_input(
|
||||||
// Talking to people
|
// Talking to people
|
||||||
if let Ok((_player_entity, _player_actor, player)) = player.get_single() {
|
if let Ok((_player_entity, _player_actor, player)) = player.get_single() {
|
||||||
for (talker, transform) in &q_talker {
|
for (talker, transform) in &q_talker {
|
||||||
|
// TODO: replace Transform.translation with Position
|
||||||
if transform.translation.distance_squared(player.translation) <= mindist {
|
if transform.translation.distance_squared(player.translation) <= mindist {
|
||||||
ew_conv.send(StartConversationEvent{talker: talker.clone()});
|
ew_conv.send(chat::StartConversationEvent{talker: talker.clone()});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,225 +279,6 @@ pub fn handle_vehicle_enter_exit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, &mut Suit), Without<Player>>,
|
|
||||||
mut q_player: Query<(&mut Actor, &mut Suit), With<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);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_collisions(
|
fn handle_collisions(
|
||||||
mut collision_event_reader: EventReader<CollisionStarted>,
|
mut collision_event_reader: EventReader<CollisionStarted>,
|
||||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
|
|
292
src/chat.rs
Normal file
292
src/chat.rs
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ extern crate regex;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
use bevy::math::DVec3;
|
use bevy::math::DVec3;
|
||||||
use crate::{actor, nature, world};
|
use crate::{actor, chat, nature, world};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::f64::consts::PI as PI64;
|
use std::f64::consts::PI as PI64;
|
||||||
|
@ -137,8 +137,8 @@ impl ParserState {
|
||||||
self.text = default.text;
|
self.text = default.text;
|
||||||
self.is_choice = default.is_choice;
|
self.is_choice = default.is_choice;
|
||||||
}
|
}
|
||||||
fn as_chatbranch(&self) -> actor::ChatBranch {
|
fn as_chatbranch(&self) -> chat::ChatBranch {
|
||||||
return actor::ChatBranch {
|
return chat::ChatBranch {
|
||||||
id: self.chat.clone(),
|
id: self.chat.clone(),
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
label: self.label.clone(),
|
label: self.label.clone(),
|
||||||
|
@ -625,7 +625,7 @@ fn spawn_entities(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if !state.chat.is_empty() {
|
if !state.chat.is_empty() {
|
||||||
actor.insert(actor::Talker {
|
actor.insert(chat::Talker {
|
||||||
conv_id: state.chat.clone(),
|
conv_id: state.chat.clone(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
10
src/hud.rs
10
src/hud.rs
|
@ -1,4 +1,4 @@
|
||||||
use crate::{settings, actor, audio, nature};
|
use crate::{actor, audio, chat, nature, settings};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
|
@ -487,7 +487,7 @@ fn handle_input(
|
||||||
mut settings: ResMut<settings::Settings>,
|
mut settings: ResMut<settings::Settings>,
|
||||||
mut q_hud: Query<&mut Visibility, With<ToggleableHudElement>>,
|
mut q_hud: Query<&mut Visibility, With<ToggleableHudElement>>,
|
||||||
q_choices: Query<&ChoiceAvailable>,
|
q_choices: Query<&ChoiceAvailable>,
|
||||||
mut evwriter_sendmsg: EventWriter<actor::SendMessageEvent>,
|
mut evwriter_sendmsg: EventWriter<chat::SendMessageEvent>,
|
||||||
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
|
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
mut evwriter_togglemusic: EventWriter<audio::ToggleMusicEvent>,
|
mut evwriter_togglemusic: EventWriter<audio::ToggleMusicEvent>,
|
||||||
mut ambient_light: ResMut<AmbientLight>,
|
mut ambient_light: ResMut<AmbientLight>,
|
||||||
|
@ -518,7 +518,7 @@ fn handle_input(
|
||||||
for choice in &q_choices {
|
for choice in &q_choices {
|
||||||
if count == selected_choice {
|
if count == selected_choice {
|
||||||
evwriter_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
|
evwriter_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
|
||||||
evwriter_sendmsg.send(actor::SendMessageEvent {
|
evwriter_sendmsg.send(chat::SendMessageEvent {
|
||||||
conv_id: choice.conv_id.clone(),
|
conv_id: choice.conv_id.clone(),
|
||||||
conv_label: choice.conv_label.clone(),
|
conv_label: choice.conv_label.clone(),
|
||||||
text: choice.text.clone(),
|
text: choice.text.clone(),
|
||||||
|
@ -534,10 +534,10 @@ fn handle_input(
|
||||||
|
|
||||||
fn despawn_old_choices(
|
fn despawn_old_choices(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
q_conv: Query<&actor::Chat>,
|
q_conv: Query<&chat::Chat>,
|
||||||
q_choices: Query<(Entity, &ChoiceAvailable)>,
|
q_choices: Query<(Entity, &ChoiceAvailable)>,
|
||||||
) {
|
) {
|
||||||
let chats: Vec<&actor::Chat> = q_conv.iter().collect();
|
let chats: Vec<&chat::Chat> = q_conv.iter().collect();
|
||||||
'outer: for (entity, choice) in &q_choices {
|
'outer: for (entity, choice) in &q_choices {
|
||||||
// Let's see if this choice still has a chat in the appropriate state
|
// Let's see if this choice still has a chat in the appropriate state
|
||||||
for chat in &chats {
|
for chat in &chats {
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -1,11 +1,12 @@
|
||||||
|
mod actor;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod camera;
|
mod camera;
|
||||||
|
mod chat;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod world;
|
|
||||||
mod settings;
|
|
||||||
mod hud;
|
|
||||||
mod actor;
|
|
||||||
mod effects;
|
mod effects;
|
||||||
|
mod hud;
|
||||||
|
mod settings;
|
||||||
|
mod world;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod nature;
|
mod nature;
|
||||||
|
@ -50,13 +51,14 @@ impl Plugin for OutFlyPlugin {
|
||||||
DefaultPlugins,//.set(ImagePlugin::default_nearest()),
|
DefaultPlugins,//.set(ImagePlugin::default_nearest()),
|
||||||
FrameTimeDiagnosticsPlugin,
|
FrameTimeDiagnosticsPlugin,
|
||||||
|
|
||||||
world::WorldPlugin,
|
|
||||||
camera::CameraPlugin,
|
|
||||||
commands::CommandsPlugin,
|
|
||||||
hud::HudPlugin,
|
|
||||||
actor::ActorPlugin,
|
actor::ActorPlugin,
|
||||||
audio::AudioPlugin,
|
audio::AudioPlugin,
|
||||||
|
camera::CameraPlugin,
|
||||||
|
chat::ChatPlugin,
|
||||||
|
commands::CommandsPlugin,
|
||||||
effects::EffectsPlugin,
|
effects::EffectsPlugin,
|
||||||
|
hud::HudPlugin,
|
||||||
|
world::WorldPlugin,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue