implement sending chat responses

This commit is contained in:
yuni 2024-03-20 02:03:42 +01:00
parent 4a71cba57e
commit 204c5c160c
4 changed files with 310 additions and 23 deletions

View file

@ -11,6 +11,7 @@
sound: "ping", sound: "ping",
level: "info", level: "info",
reply: "Requesting permission to communicate...", reply: "Requesting permission to communicate...",
choice: "",
goto: "requested", goto: "requested",
), ),
}, },
@ -25,6 +26,7 @@
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Oh hey there!", reply: "Oh hey there!",
choice: "",
goto: "sup", goto: "sup",
), ),
}, },
@ -39,6 +41,7 @@
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Didn't even notice you! Was playing some VR game! What's up?", reply: "Didn't even notice you! Was playing some VR game! What's up?",
choice: "",
goto: "reply1", goto: "reply1",
), ),
}, },
@ -49,10 +52,11 @@
id: "hialien", id: "hialien",
name: "Icarus", name: "Icarus",
label: "reply1", label: "reply1",
delay: 3.5, delay: 5.5,
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Not so chatty, huh? That's ok. See you around.", reply: "Are you sure? Your suit is sending a distress call. But whatever you say, have fun!",
choice: "I'm good, how are you?",
goto: "pizza", goto: "pizza",
), ),
}, },
@ -62,16 +66,77 @@
"outfly::actor::ChatBranch": ( "outfly::actor::ChatBranch": (
id: "hialien", id: "hialien",
name: "Icarus", name: "Icarus",
label: "pizza", label: "reply1",
delay: 1.5, delay: 5.5,
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Make sure to check out the Pizza place.", reply: "Yeah I can imagine, looks like your suit is leaking. Take care!",
goto: "disco", choice: "Uhm... where am I? I don't feel so good.",
goto: "pizza",
), ),
}, },
), ),
4294967301: ( 4294967301: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "reply1",
delay: 3.5,
sound: "chat",
level: "chat",
reply: "Not so chatty, huh? That's ok. See you around.",
choice: "",
goto: "pizza",
),
},
),
4294967302: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "pizza",
delay: 2.5,
sound: "chat",
level: "chat",
reply: "Make sure to check out the Pizza place.",
choice: "",
goto: "disco_interactive",
),
},
),
4294967303: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "disco_interactive",
delay: 0.0,
sound: "ping",
level: "info",
reply: "Disconnected.",
choice: "",
goto: "EXIT",
),
},
),
4294967304: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "disco_interactive",
delay: 1.5,
sound: "ping",
level: "chat",
reply: "Bye!",
choice: "See ya~",
goto: "disco",
),
},
),
4294967305: (
components: { components: {
"outfly::actor::ChatBranch": ( "outfly::actor::ChatBranch": (
id: "hialien", id: "hialien",
@ -81,10 +146,30 @@
sound: "ping", sound: "ping",
level: "info", level: "info",
reply: "Disconnected.", reply: "Disconnected.",
choice: "",
goto: "EXIT", goto: "EXIT",
), ),
}, },
), ),
4294967306: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "sup",
delay: 8.0,
sound: "chat",
level: "chat",
reply: "Didn't even notice you! Was playing some VR game! What's up?",
choice: "Uhm... hi",
goto: "reply1",
),
},
),
4294967400: ( 4294967400: (
components: { components: {
"outfly::actor::ChatBranch": ( "outfly::actor::ChatBranch": (
@ -95,6 +180,7 @@
sound: "ping", sound: "ping",
level: "chat", level: "chat",
reply: "Requesting permission to communicate...", reply: "Requesting permission to communicate...",
choice: "",
goto: "requested", goto: "requested",
), ),
}, },
@ -109,6 +195,7 @@
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Welcome to Space Pizza™, best pizza all across the Jovian rings!", reply: "Welcome to Space Pizza™, best pizza all across the Jovian rings!",
choice: "",
goto: "ask", goto: "ask",
), ),
}, },
@ -123,6 +210,7 @@
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Would you like to order today's special Miracle Spacefungi? Freshly blended pizza smoothie ready for your space suit feeding tube!", reply: "Would you like to order today's special Miracle Spacefungi? Freshly blended pizza smoothie ready for your space suit feeding tube!",
choice: "",
goto: "hello?", goto: "hello?",
), ),
}, },
@ -137,11 +225,13 @@
sound: "chat", sound: "chat",
level: "chat", level: "chat",
reply: "Hello? Are you still there?", reply: "Hello? Are you still there?",
choice: "",
choice_delay: 0.0,
goto: "disco", goto: "disco",
), ),
}, },
), ),
4294967304: ( 4294967404: (
components: { components: {
"outfly::actor::ChatBranch": ( "outfly::actor::ChatBranch": (
id: "pizzeria", id: "pizzeria",
@ -151,6 +241,7 @@
sound: "ping", sound: "ping",
level: "info", level: "info",
reply: "Disconnected.", reply: "Disconnected.",
choice: "",
goto: "EXIT", goto: "EXIT",
), ),
}, },

View file

@ -9,17 +9,18 @@ impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.register_type::<ChatBranch>(); app.register_type::<ChatBranch>();
app.register_type::<ChatChoice>();
app.add_systems(FixedUpdate, ( app.add_systems(FixedUpdate, (
update_physics_lifeforms, update_physics_lifeforms,
update_physics_actors, update_physics_actors,
)); ));
app.add_systems(Update, ( app.add_systems(Update, (
handle_new_conversations, handle_new_conversations,
handle_send_messages,
handle_conversations, handle_conversations,
handle_input, handle_input,
)); ));
app.add_event::<StartConversationEvent>(); app.add_event::<StartConversationEvent>();
app.add_event::<SendMessageEvent>();
} }
} }
@ -28,6 +29,13 @@ pub struct StartConversationEvent {
pub talker: Talker, pub talker: Talker,
} }
#[derive(Event)]
pub struct SendMessageEvent {
pub conv_id: String,
pub conv_label: String,
pub text: String,
}
#[derive(Component)] #[derive(Component)]
pub struct Actor { pub struct Actor {
pub hp: f32, pub hp: f32,
@ -62,15 +70,7 @@ pub struct ChatBranch {
pub level: String, pub level: String,
pub reply: String, pub reply: String,
pub goto: String, pub goto: String,
}
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct ChatChoice {
pub id: String,
pub label: String,
pub choice: String, pub choice: String,
pub goto: String,
} }
#[derive(Component)] #[derive(Component)]
@ -202,6 +202,70 @@ pub fn handle_new_conversations(
} }
} }
pub fn handle_send_messages(
mut commands: Commands,
mut er_sendmsg: EventReader<SendMessageEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
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
match branch.level.as_str() {
"chat" => log.chat(branch.reply.clone(), branch.name.clone()),
"info" => log.info(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(),
});
}
}
}
break; // let's only handle one of these per frame
}
}
pub fn handle_conversations( pub fn handle_conversations(
mut commands: Commands, mut commands: Commands,
mut log: ResMut<hud::Log>, mut log: ResMut<hud::Log>,
@ -209,12 +273,11 @@ pub fn handle_conversations(
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
time: Res<Time>, time: Res<Time>,
chat_branches: Query<&ChatBranch>, // TODO: use Table for faster iteration? chat_branches: Query<&ChatBranch>, // TODO: use Table for faster iteration?
//chat_choices: Query<&ChatChoice>,
) { ) {
let now = time.elapsed_seconds_f64(); let now = time.elapsed_seconds_f64();
for (entity, mut chat) in &mut q_conv { for (entity, mut chat) in &mut q_conv {
if chat.label == "EXIT" { if chat.label == "EXIT" {
debug!("Despawning chat."); info!("Despawning chat.");
commands.entity(entity).despawn(); commands.entity(entity).despawn();
continue; continue;
} }
@ -223,7 +286,9 @@ pub fn handle_conversations(
} }
let branches: Vec<&ChatBranch> = chat_branches.iter() let branches: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == chat.id && branch.label == chat.label) .filter(|branch| branch.id == chat.id
&& branch.label == chat.label
&& branch.choice == "")
.collect(); .collect();
if branches.len() != 1 { if branches.len() != 1 {
error!("Expected 1 branch with ID '{}' and label '{}', but got {}! Aborting conversation.", chat.id, chat.label, branches.len()); error!("Expected 1 branch with ID '{}' and label '{}', but got {}! Aborting conversation.", chat.id, chat.label, branches.len());
@ -238,6 +303,7 @@ pub fn handle_conversations(
} }
if chat.label == "EXIT" { if chat.label == "EXIT" {
// TODO: isn't this dead code?
continue; continue;
} }
chat.label = branch.goto.clone(); chat.label = branch.goto.clone();
@ -246,5 +312,19 @@ pub fn handle_conversations(
ew_sfx.send(audio::PlaySfxEvent(sfx)); ew_sfx.send(audio::PlaySfxEvent(sfx));
} }
chat.timer = now + branch.delay; 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(),
});
}
}
} }
} }

View file

@ -5,16 +5,18 @@ use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::time::SystemTime; use std::time::SystemTime;
const HUD_REFRESH_TIME: f32 = 0.5; pub const HUD_REFRESH_TIME: f32 = 0.5;
const FONT: &str = "external/NotoSansSC-Thin.ttf"; pub const FONT: &str = "external/NotoSansSC-Thin.ttf";
const LOG_MAX: usize = 20; pub const LOG_MAX: usize = 20;
const LOG_MAX_TIME_S: u64 = 20; pub const LOG_MAX_TIME_S: u64 = 20;
pub const CHOICE_NONE: &str = "";
pub struct HudPlugin; pub struct HudPlugin;
impl Plugin for HudPlugin { impl Plugin for HudPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, (update, handle_input)); app.add_systems(Update, (update, handle_input));
app.add_systems(PostUpdate, despawn_old_choices);
app.insert_resource(Log { app.insert_resource(Log {
logs: VecDeque::with_capacity(LOG_MAX), logs: VecDeque::with_capacity(LOG_MAX),
needs_rerendering: true, needs_rerendering: true,
@ -30,6 +32,14 @@ impl Plugin for HudPlugin {
#[derive(Resource)] #[derive(Resource)]
struct FPSUpdateTimer(Timer); struct FPSUpdateTimer(Timer);
#[derive(Component)]
pub struct ChoiceAvailable {
pub conv_id: String,
pub conv_label: String,
pub recipient: String,
pub text: String,
}
pub enum LogLevel { pub enum LogLevel {
Warning, Warning,
//Error, //Error,
@ -235,6 +245,24 @@ fn setup(
..default() ..default()
} }
), ),
TextSection::new(
"\n",
TextStyle {
font: asset_server.load(FONT),
font_size: settings.font_size_hud,
color: Color::WHITE,
..default()
}
),
TextSection::new(
"<Choices>",
TextStyle {
font: asset_server.load(FONT),
font_size: settings.font_size_hud,
color: Color::WHITE,
..default()
}
),
]).with_style(Style { ]).with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
bottom: Val::VMin(0.0), bottom: Val::VMin(0.0),
@ -271,6 +299,7 @@ fn update(
player: Query<(&actor::Suit, &actor::LifeForm), With<actor::Player>>, player: Query<(&actor::Suit, &actor::LifeForm), With<actor::Player>>,
mut timer: ResMut<FPSUpdateTimer>, mut timer: ResMut<FPSUpdateTimer>,
mut query: Query<&mut Text, With<GaugesText>>, mut query: Query<&mut Text, With<GaugesText>>,
q_choices: Query<&ChoiceAvailable>,
mut query_chat: Query<&mut Text, (With<ChatText>, Without<GaugesText>)>, mut query_chat: Query<&mut Text, (With<ChatText>, Without<GaugesText>)>,
query_all_actors: Query<&actor::Actor>, query_all_actors: Query<&actor::Actor>,
settings: Res<settings::Settings>, settings: Res<settings::Settings>,
@ -300,6 +329,17 @@ fn update(
} }
if let Ok(mut chat) = query_chat.get_single_mut() { if let Ok(mut chat) = query_chat.get_single_mut() {
// Choices
let mut choices: Vec<String> = Vec::new();
let mut count = 1;
for choice in &q_choices {
choices.push(format!("[{}. @{}: {}]",
count, choice.recipient, choice.text));
count += 1;
}
chat.sections[2].value = choices.join("\n");
// Chat Log and System Log
let logfilter = if settings.hud_active { let logfilter = if settings.hud_active {
|_msg: &&Message| { true } |_msg: &&Message| { true }
} else { } else {
@ -324,8 +364,10 @@ fn handle_input(
mut settings: ResMut<settings::Settings>, mut settings: ResMut<settings::Settings>,
mut query: Query<&mut Visibility, With<GaugesText>>, mut query: Query<&mut Visibility, With<GaugesText>>,
mut query_bloomsettings: Query<&mut BloomSettings>, mut query_bloomsettings: Query<&mut BloomSettings>,
mut evwriter_sendmsg: EventWriter<actor::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>,
q_choices: Query<&ChoiceAvailable>,
) { ) {
if keyboard_input.just_pressed(settings.key_togglehud) { if keyboard_input.just_pressed(settings.key_togglehud) {
if let Ok(mut vis) = query.get_single_mut() { if let Ok(mut vis) = query.get_single_mut() {
@ -345,4 +387,43 @@ fn handle_input(
} }
} }
} }
let mut selected_choice = 1;
'outer: for key in settings.get_reply_keys() {
if keyboard_input.just_pressed(key) {
let mut count = 1;
for choice in &q_choices {
if count == selected_choice {
evwriter_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
evwriter_sendmsg.send(actor::SendMessageEvent {
conv_id: choice.conv_id.clone(),
conv_label: choice.conv_label.clone(),
text: choice.text.clone(),
});
break 'outer;
}
count += 1;
}
}
selected_choice += 1;
}
}
fn despawn_old_choices(
mut commands: Commands,
q_conv: Query<&actor::Chat>,
q_choices: Query<(Entity, &ChoiceAvailable)>,
) {
let chats: Vec<&actor::Chat> = q_conv.iter().collect();
'outer: for (entity, choice) in &q_choices {
// Let's see if this choice still has a chat in the appropriate state
for chat in &chats {
if choice.conv_id == chat.id && choice.conv_label == chat.label {
continue 'outer;
}
}
// Despawn the choice, since no matching chat was found
commands.entity(entity).despawn();
}
} }

View file

@ -22,6 +22,16 @@ pub struct Settings {
pub key_run: KeyCode, pub key_run: KeyCode,
pub key_stop: KeyCode, pub key_stop: KeyCode,
pub key_interact: KeyCode, pub key_interact: KeyCode,
pub key_reply1: KeyCode,
pub key_reply2: KeyCode,
pub key_reply3: KeyCode,
pub key_reply4: KeyCode,
pub key_reply5: KeyCode,
pub key_reply6: KeyCode,
pub key_reply7: KeyCode,
pub key_reply8: KeyCode,
pub key_reply9: KeyCode,
pub key_reply10: KeyCode,
} }
impl Default for Settings { impl Default for Settings {
@ -47,6 +57,16 @@ impl Default for Settings {
key_run: KeyCode::KeyR, key_run: KeyCode::KeyR,
key_stop: KeyCode::Space, key_stop: KeyCode::Space,
key_interact: KeyCode::KeyE, key_interact: KeyCode::KeyE,
key_reply1: KeyCode::Digit1,
key_reply2: KeyCode::Digit2,
key_reply3: KeyCode::Digit3,
key_reply4: KeyCode::Digit4,
key_reply5: KeyCode::Digit5,
key_reply6: KeyCode::Digit6,
key_reply7: KeyCode::Digit7,
key_reply8: KeyCode::Digit8,
key_reply9: KeyCode::Digit9,
key_reply10: KeyCode::Digit0,
} }
} }
} }
@ -56,4 +76,19 @@ impl Settings {
println!("Resetting settings!"); println!("Resetting settings!");
*self = Self::default(); *self = Self::default();
} }
pub fn get_reply_keys(&self) -> [KeyCode; 10] {
return [
self.key_reply1,
self.key_reply2,
self.key_reply3,
self.key_reply4,
self.key_reply5,
self.key_reply6,
self.key_reply7,
self.key_reply8,
self.key_reply9,
self.key_reply10,
];
}
} }