implement conversation system

This commit is contained in:
yuni 2024-03-19 05:38:11 +01:00
parent 60370ad583
commit bac0b59733
4 changed files with 174 additions and 6 deletions

View file

@ -0,0 +1,44 @@
(
resources: {},
entities: {
4294967296: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "INIT",
delay: 0.0,
sound: "ping",
reply: "Requesting permission to communicate...",
goto: "requested",
),
},
),
4294967297: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "requested",
delay: 1.0,
sound: "chat",
reply: "Oh hey there, didn't even notice you! Was playing some VR game! What's up?",
goto: "reply1",
),
},
),
4294967298: (
components: {
"outfly::actor::ChatBranch": (
id: "hialien",
name: "Icarus",
label: "reply1",
delay: 3.0,
sound: "chat",
reply: "Not so chatty, huh? That's ok. See you around.",
goto: "EXIT",
),
},
),
},
)

View file

@ -2,12 +2,17 @@ use bevy::prelude::*;
use crate::{nature, settings, actor, audio, hud}; use crate::{nature, settings, actor, audio, hud};
const MIN_INTERACT_DISTANCE: f32 = 30.0; const MIN_INTERACT_DISTANCE: f32 = 30.0;
const ASSET_CONVERSATIONS: &str = "scenes/conversations.scn.ron";
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.add_systems(Startup, setup);
app.register_type::<ChatBranch>();
app.register_type::<ChatChoice>();
app.add_systems(FixedUpdate, update); app.add_systems(FixedUpdate, update);
app.add_systems(Update, ( app.add_systems(Update, (
handle_new_conversations,
handle_conversations, handle_conversations,
handle_input, handle_input,
)); ));
@ -42,6 +47,34 @@ impl Default for Actor {
#[derive(Component)] pub struct PlayerInConversation; #[derive(Component)] pub struct PlayerInConversation;
#[derive(Component)] pub struct InConversationWithPlayer; #[derive(Component)] pub struct InConversationWithPlayer;
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct ChatBranch {
pub id: String,
pub name: String,
pub label: String,
pub delay: f32,
pub sound: String,
pub reply: String,
pub goto: String,
}
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct ChatChoice {
pub id: String,
pub label: String,
pub choice: String,
pub goto: String,
}
#[derive(Component)]
pub struct Chat {
pub id: String,
pub label: String,
pub timer: f32,
}
#[derive(Component)] #[derive(Component)]
pub struct Talker { pub struct Talker {
pub conv: String, pub conv: String,
@ -75,6 +108,37 @@ const SUIT_SIMPLE: Suit = Suit {
oxygen_max: nature::OXY_D, oxygen_max: nature::OXY_D,
}; };
pub fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
commands.spawn(DynamicSceneBundle {
scene: asset_server.load(ASSET_CONVERSATIONS),
..default()
});
}
//#[allow(dead_code)]
//pub fn serialize(
// world: &mut World,
//) {
// let mut scene_world = World::new();
// scene_world.spawn(ChatBranch {
// id: "hialien".to_string(),
// name: "Icarus".to_string(),
// label: "INTERACT".to_string(),
// delay: 0.0,
// reply: "Requesting permission to communicate...".to_string(),
// goto: "requested".to_string(),
// });
// let type_registry = world.resource::<AppTypeRegistry>().clone();
// scene_world.insert_resource(type_registry);
// let type_registry = world.resource::<AppTypeRegistry>();
// let scene = DynamicScene::from_world(&scene_world);
// let serialized_scene = scene.serialize_ron(type_registry).unwrap();
// info!("{}", serialized_scene);
//}
pub fn update( pub fn update(
time: Res<Time>, time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut Suit)>, mut query: Query<(&mut LifeForm, &mut Suit)>,
@ -107,7 +171,7 @@ pub fn handle_input(
for (talker, transform) in &query { for (talker, transform) in &query {
if transform.translation.distance_squared(player.translation) <= mindist { if transform.translation.distance_squared(player.translation) <= mindist {
ew_conv.send(StartConversationEvent{conv: talker.conv.clone()}); ew_conv.send(StartConversationEvent{conv: talker.conv.clone()});
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::IncomingChatMessage)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
break; break;
} }
} }
@ -115,16 +179,58 @@ pub fn handle_input(
} }
} }
pub fn handle_conversations( pub fn handle_new_conversations(
mut commands: Commands,
mut er_conv: EventReader<StartConversationEvent>, mut er_conv: EventReader<StartConversationEvent>,
mut log: ResMut<hud::Log>, mut log: ResMut<hud::Log>,
) { ) {
for my_event in er_conv.read() { for _my_event in er_conv.read() {
log.chat(my_event.conv.clone(), "Alien".to_string()); log.info("Establishing connection with Alien".to_string());
commands.spawn(Chat {
id: "hialien".to_string(),
label: "INIT".to_string(),
timer: 0.0,
});
//log.chat(my_event.conv.clone(), "Alien".to_string());
break; break;
} }
} }
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>,
chat_branches: Query<&ChatBranch>, // TODO: use Table for faster iteration?
//chat_choices: Query<&ChatChoice>,
) {
for (entity, mut chat) in &mut q_conv {
if chat.label == "EXIT" {
debug!("Despawning chat.");
commands.entity(entity).despawn();
continue;
}
let branches: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == chat.id && branch.label == chat.label)
.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];
log.chat(branch.reply.clone(), branch.name.clone());
if chat.label == "EXIT" {
continue;
}
chat.label = branch.goto.clone();
if branch.sound != "" {
let sfx = audio::str2sfx(branch.sound.as_str());
ew_sfx.send(audio::PlaySfxEvent(sfx));
}
}
}
//pub enum SuitSystemHandler { //pub enum SuitSystemHandler {
// Heat, // Heat,

View file

@ -4,7 +4,8 @@ use crate::settings;
const ASSET_CLICK: &str = "sounds/click-button-140881-crop.ogg"; const ASSET_CLICK: &str = "sounds/click-button-140881-crop.ogg";
const ASSET_SWITCH: &str = "sounds/typosonic-typing-192811-crop.ogg"; const ASSET_SWITCH: &str = "sounds/typosonic-typing-192811-crop.ogg";
const ASSET_INCOMING_MESSAGE: &str = "tmp/multi-pop-2-188167.mp3.ogg"; const ASSET_INCOMING_MESSAGE: &str = "tmp/beep-6-96243.ogg";
const ASSET_PING: &str = "tmp/glitch-sound-fx-pack-04-118236.ogg";
const ASSET_RADIO: &str = "tmp/LP - Girls Go Wild (Official Music Video)-M7XRN0oHGIM.ogg"; const ASSET_RADIO: &str = "tmp/LP - Girls Go Wild (Official Music Video)-M7XRN0oHGIM.ogg";
const ASSET_BGM: &str = "tmp/FTL - Faster Than Light (2012) OST - 12 - Void (Explore)-edQw2yYXQJM.ogg"; const ASSET_BGM: &str = "tmp/FTL - Faster Than Light (2012) OST - 12 - Void (Explore)-edQw2yYXQJM.ogg";
@ -24,6 +25,8 @@ pub enum Sfx {
IncomingChatMessage, IncomingChatMessage,
Click, Click,
Switch, Switch,
Ping,
None,
} }
#[derive(Event)] pub struct PlaySfxEvent(pub Sfx); #[derive(Event)] pub struct PlaySfxEvent(pub Sfx);
@ -36,6 +39,7 @@ pub enum Sfx {
#[derive(Resource)] pub struct SoundClick(Handle<AudioSource>); #[derive(Resource)] pub struct SoundClick(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundSwitch(Handle<AudioSource>); #[derive(Resource)] pub struct SoundSwitch(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundIncomingMessage(Handle<AudioSource>); #[derive(Resource)] pub struct SoundIncomingMessage(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundPing(Handle<AudioSource>);
pub fn setup( pub fn setup(
mut commands: Commands, mut commands: Commands,
@ -86,6 +90,7 @@ pub fn setup(
commands.insert_resource(SoundClick(asset_server.load(ASSET_CLICK))); commands.insert_resource(SoundClick(asset_server.load(ASSET_CLICK)));
commands.insert_resource(SoundSwitch(asset_server.load(ASSET_SWITCH))); commands.insert_resource(SoundSwitch(asset_server.load(ASSET_SWITCH)));
commands.insert_resource(SoundIncomingMessage(asset_server.load(ASSET_INCOMING_MESSAGE))); commands.insert_resource(SoundIncomingMessage(asset_server.load(ASSET_INCOMING_MESSAGE)));
commands.insert_resource(SoundPing(asset_server.load(ASSET_PING)));
} }
pub fn toggle_bgm( pub fn toggle_bgm(
@ -113,6 +118,7 @@ pub fn play_sfx(
sound_click: Res<SoundClick>, sound_click: Res<SoundClick>,
sound_switch: Res<SoundSwitch>, sound_switch: Res<SoundSwitch>,
sound_incoming_message: Res<SoundIncomingMessage>, sound_incoming_message: Res<SoundIncomingMessage>,
sound_ping: Res<SoundPing>,
) { ) {
if settings.mute_sfx && !events_sfx.is_empty() { if settings.mute_sfx && !events_sfx.is_empty() {
events_sfx.clear(); events_sfx.clear();
@ -123,12 +129,24 @@ pub fn play_sfx(
Sfx::Switch => sound_switch.0.clone(), Sfx::Switch => sound_switch.0.clone(),
Sfx::Click => sound_click.0.clone(), Sfx::Click => sound_click.0.clone(),
Sfx::IncomingChatMessage => sound_incoming_message.0.clone(), Sfx::IncomingChatMessage => sound_incoming_message.0.clone(),
Sfx::Ping => sound_ping.0.clone(),
Sfx::None => sound_ping.0.clone(),
}, },
settings: PlaybackSettings::DESPAWN, settings: PlaybackSettings::DESPAWN,
}); });
} }
} }
pub fn str2sfx(sfx_label: &str) -> Sfx {
return match sfx_label {
"switch" => Sfx::Switch,
"click" => Sfx::Click,
"chat" => Sfx::IncomingChatMessage,
"ping" => Sfx::Ping,
_ => Sfx::None,
};
}
pub fn update_music( pub fn update_music(
mut events: EventReader<ToggleMusicEvent>, mut events: EventReader<ToggleMusicEvent>,
bgm_controller: Query<&AudioSink, With<ComponentBGM>>, bgm_controller: Query<&AudioSink, With<ComponentBGM>>,

View file

@ -7,7 +7,7 @@ use std::time::SystemTime;
const HUD_REFRESH_TIME: f32 = 0.5; const HUD_REFRESH_TIME: f32 = 0.5;
const FONT: &str = "tmp/fonts/NotoSansSC-Thin.ttf"; const FONT: &str = "tmp/fonts/NotoSansSC-Thin.ttf";
const LOG_MAX: usize = 5; const LOG_MAX: usize = 20;
const LOG_MAX_TIME_S: u64 = 20; const LOG_MAX_TIME_S: u64 = 20;
pub struct HudPlugin; pub struct HudPlugin;