rip out existing chat implementation
(this was SO satisfying)
This commit is contained in:
parent
5ca31fda65
commit
56385b257d
12
src/actor.rs
12
src/actor.rs
|
@ -2,7 +2,7 @@ use bevy::prelude::*;
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
use bevy::scene::SceneInstance;
|
use bevy::scene::SceneInstance;
|
||||||
use bevy::math::DVec3;
|
use bevy::math::DVec3;
|
||||||
use crate::{actor, audio, camera, chat2, commands, effects, hud, nature, settings, world};
|
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, settings, world};
|
||||||
|
|
||||||
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
||||||
const MAX_TRANSMISSION_DISTANCE: f32 = 60.0;
|
const MAX_TRANSMISSION_DISTANCE: f32 = 60.0;
|
||||||
|
@ -235,11 +235,11 @@ pub fn handle_input(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
mut settings: ResMut<settings::Settings>,
|
mut settings: ResMut<settings::Settings>,
|
||||||
q_talker: Query<(&chat2::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
|
q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
|
||||||
player: Query<Entity, With<actor::Player>>,
|
player: Query<Entity, With<actor::Player>>,
|
||||||
q_camera: Query<&Transform, With<Camera>>,
|
q_camera: Query<&Transform, With<Camera>>,
|
||||||
q_vehicles: Query<(Entity, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>,
|
q_vehicles: Query<(Entity, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>,
|
||||||
mut ew_conv: EventWriter<chat2::StartConversationEvent>,
|
mut ew_conv: EventWriter<chat::StartConversationEvent>,
|
||||||
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
|
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
|
||||||
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
|
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
|
||||||
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
|
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
|
||||||
|
@ -252,14 +252,14 @@ pub fn handle_input(
|
||||||
|
|
||||||
if keyboard_input.just_pressed(settings.key_interact) {
|
if keyboard_input.just_pressed(settings.key_interact) {
|
||||||
// Talking to people
|
// Talking to people
|
||||||
let objects: Vec<(chat2::Talker, &Transform)> = q_talker
|
let objects: Vec<(chat::Talker, &Transform)> = q_talker
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(talker, transform)| (talker.clone(), transform))
|
.map(|(talker, transform)| (talker.clone(), transform))
|
||||||
.collect();
|
.collect();
|
||||||
// TODO: replace Transform.translation with Position
|
// TODO: replace Transform.translation with Position
|
||||||
if let (Some(talker), dist) = camera::find_closest_target::<chat2::Talker>(objects, camtrans) {
|
if let (Some(talker), dist) = camera::find_closest_target::<chat::Talker>(objects, camtrans) {
|
||||||
if dist <= MAX_TRANSMISSION_DISTANCE {
|
if dist <= MAX_TRANSMISSION_DISTANCE {
|
||||||
ew_conv.send(chat2::StartConversationEvent{talker: talker.clone()});
|
ew_conv.send(chat::StartConversationEvent{talker: talker.clone()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Entering Vehicles
|
// Entering Vehicles
|
||||||
|
|
390
src/chat.rs
390
src/chat.rs
|
@ -1,79 +1,12 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_xpbd_3d::prelude::*;
|
|
||||||
use bevy::math::DVec3;
|
|
||||||
use crate::{actor, audio, hud, settings, world, effects};
|
|
||||||
|
|
||||||
pub struct ChatPlugin;
|
pub struct ChatPlugin;
|
||||||
impl Plugin for ChatPlugin {
|
impl Plugin for ChatPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<ChatBranch>();
|
|
||||||
app.add_systems(Update, (
|
|
||||||
handle_new_conversations,
|
|
||||||
handle_reply_keys,
|
|
||||||
handle_send_messages,
|
|
||||||
handle_conversations,
|
|
||||||
handle_chat_scripts,
|
|
||||||
));
|
|
||||||
app.add_systems(PostUpdate, despawn_old_choices);
|
|
||||||
app.add_event::<StartConversationEvent>();
|
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)]
|
|
||||||
pub struct ChoiceAvailable {
|
|
||||||
pub conv_id: String,
|
|
||||||
pub conv_label: String,
|
|
||||||
pub recipient: String,
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Talker {
|
pub struct Talker {
|
||||||
|
@ -82,325 +15,10 @@ pub struct Talker {
|
||||||
}
|
}
|
||||||
impl Default for Talker { fn default() -> Self { Self {
|
impl Default for Talker { fn default() -> Self { Self {
|
||||||
pronoun: "they/them".to_string(),
|
pronoun: "they/them".to_string(),
|
||||||
conv_id: "error".to_string(),
|
conv_id: "undefined".to_string(),
|
||||||
}}}
|
}}}
|
||||||
|
|
||||||
pub fn handle_new_conversations(
|
#[derive(Event)]
|
||||||
mut commands: Commands,
|
pub struct StartConversationEvent {
|
||||||
mut er_conv: EventReader<StartConversationEvent>,
|
pub talker: Talker,
|
||||||
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(),
|
|
||||||
},
|
|
||||||
world::DespawnOnPlayerDeath,
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_reply_keys(
|
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
||||||
settings: ResMut<settings::Settings>,
|
|
||||||
q_choices: Query<&ChoiceAvailable>,
|
|
||||||
mut evwriter_sendmsg: EventWriter<SendMessageEvent>,
|
|
||||||
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
||||||
) {
|
|
||||||
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(SendMessageEvent {
|
|
||||||
conv_id: choice.conv_id.clone(),
|
|
||||||
conv_label: choice.conv_label.clone(),
|
|
||||||
text: choice.text.clone(),
|
|
||||||
});
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selected_choice += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 '{}', label '{}', choice '{}', but got {}! Aborting conversation.", event.conv_id, event.conv_label, event.text, 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((
|
|
||||||
ChoiceAvailable {
|
|
||||||
conv_id: choice.id.clone(),
|
|
||||||
conv_label: choice.label.clone(),
|
|
||||||
recipient: choice.name.clone(),
|
|
||||||
text: choice.choice.clone(),
|
|
||||||
},
|
|
||||||
world::DespawnOnPlayerDeath,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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((
|
|
||||||
ChoiceAvailable {
|
|
||||||
conv_id: choice.id.clone(),
|
|
||||||
conv_label: choice.label.clone(),
|
|
||||||
recipient: choice.name.clone(),
|
|
||||||
text: choice.choice.clone(),
|
|
||||||
},
|
|
||||||
world::DespawnOnPlayerDeath,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
|
||||||
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
|
|
||||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
||||||
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
|
|
||||||
) {
|
|
||||||
for script in er_chatscript.read() {
|
|
||||||
match script.name.as_str() {
|
|
||||||
"refilloxygen" => if let Ok(mut amount) = script.param.parse::<f32>() {
|
|
||||||
for (_, 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);
|
|
||||||
}
|
|
||||||
"cryotrip" => {
|
|
||||||
if script.param.is_empty() {
|
|
||||||
error!("Chat script cryotrip needs a parameter");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
|
|
||||||
if script.param == "oscillation".to_string() {
|
|
||||||
*pos = Position(DVec3::new(147e6, 165e6, 336e6));
|
|
||||||
v.0 = DVec3::ZERO;
|
|
||||||
}
|
|
||||||
else if script.param == "metisprime".to_string() {
|
|
||||||
*pos = Position(DVec3::new(27643e3, -47e3, -124434e3));
|
|
||||||
v.0 = DVec3::ZERO;
|
|
||||||
}
|
|
||||||
else if script.param == "serenity".to_string() {
|
|
||||||
*pos = Position(DVec3::new(-121095e3, 582e3, -190816e3));
|
|
||||||
v.0 = DVec3::ZERO;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
error!("Invalid destination for cryotrip chat script: '{}'", script.param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Ok((_, mut suit, mut gforce)) = q_player.get_single_mut() {
|
|
||||||
suit.oxygen = suit.oxygen_max;
|
|
||||||
gforce.ignore_gforce_seconds = 1.0;
|
|
||||||
}
|
|
||||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
|
||||||
ew_effect.send(effects::SpawnEffectEvent {
|
|
||||||
class: effects::Effects::FadeIn(Color::CYAN),
|
|
||||||
duration: 1.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"cryofadeout" => {
|
|
||||||
ew_effect.send(effects::SpawnEffectEvent {
|
|
||||||
class: effects::Effects::FadeOut(Color::CYAN),
|
|
||||||
duration: 5.1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let script_name = &script.name;
|
|
||||||
error!("Error, undefined chat script {script_name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn despawn_old_choices(
|
|
||||||
mut commands: Commands,
|
|
||||||
q_conv: Query<&Chat>,
|
|
||||||
q_choices: Query<(Entity, &ChoiceAvailable)>,
|
|
||||||
) {
|
|
||||||
let chats: Vec<&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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
406
src/chat_old.rs
Normal file
406
src/chat_old.rs
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_xpbd_3d::prelude::*;
|
||||||
|
use bevy::math::DVec3;
|
||||||
|
use crate::{actor, audio, hud, settings, world, effects};
|
||||||
|
|
||||||
|
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_reply_keys,
|
||||||
|
handle_send_messages,
|
||||||
|
handle_conversations,
|
||||||
|
handle_chat_scripts,
|
||||||
|
));
|
||||||
|
app.add_systems(PostUpdate, despawn_old_choices);
|
||||||
|
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)]
|
||||||
|
pub struct ChoiceAvailable {
|
||||||
|
pub conv_id: String,
|
||||||
|
pub conv_label: String,
|
||||||
|
pub recipient: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[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(),
|
||||||
|
},
|
||||||
|
world::DespawnOnPlayerDeath,
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_reply_keys(
|
||||||
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
settings: ResMut<settings::Settings>,
|
||||||
|
q_choices: Query<&ChoiceAvailable>,
|
||||||
|
mut evwriter_sendmsg: EventWriter<SendMessageEvent>,
|
||||||
|
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
|
) {
|
||||||
|
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(SendMessageEvent {
|
||||||
|
conv_id: choice.conv_id.clone(),
|
||||||
|
conv_label: choice.conv_label.clone(),
|
||||||
|
text: choice.text.clone(),
|
||||||
|
});
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selected_choice += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 '{}', label '{}', choice '{}', but got {}! Aborting conversation.", event.conv_id, event.conv_label, event.text, 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((
|
||||||
|
ChoiceAvailable {
|
||||||
|
conv_id: choice.id.clone(),
|
||||||
|
conv_label: choice.label.clone(),
|
||||||
|
recipient: choice.name.clone(),
|
||||||
|
text: choice.choice.clone(),
|
||||||
|
},
|
||||||
|
world::DespawnOnPlayerDeath,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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((
|
||||||
|
ChoiceAvailable {
|
||||||
|
conv_id: choice.id.clone(),
|
||||||
|
conv_label: choice.label.clone(),
|
||||||
|
recipient: choice.name.clone(),
|
||||||
|
text: choice.choice.clone(),
|
||||||
|
},
|
||||||
|
world::DespawnOnPlayerDeath,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
||||||
|
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
|
||||||
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||||
|
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
|
||||||
|
) {
|
||||||
|
for script in er_chatscript.read() {
|
||||||
|
match script.name.as_str() {
|
||||||
|
"refilloxygen" => if let Ok(mut amount) = script.param.parse::<f32>() {
|
||||||
|
for (_, 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);
|
||||||
|
}
|
||||||
|
"cryotrip" => {
|
||||||
|
if script.param.is_empty() {
|
||||||
|
error!("Chat script cryotrip needs a parameter");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
|
||||||
|
if script.param == "oscillation".to_string() {
|
||||||
|
*pos = Position(DVec3::new(147e6, 165e6, 336e6));
|
||||||
|
v.0 = DVec3::ZERO;
|
||||||
|
}
|
||||||
|
else if script.param == "metisprime".to_string() {
|
||||||
|
*pos = Position(DVec3::new(27643e3, -47e3, -124434e3));
|
||||||
|
v.0 = DVec3::ZERO;
|
||||||
|
}
|
||||||
|
else if script.param == "serenity".to_string() {
|
||||||
|
*pos = Position(DVec3::new(-121095e3, 582e3, -190816e3));
|
||||||
|
v.0 = DVec3::ZERO;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error!("Invalid destination for cryotrip chat script: '{}'", script.param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok((_, mut suit, mut gforce)) = q_player.get_single_mut() {
|
||||||
|
suit.oxygen = suit.oxygen_max;
|
||||||
|
gforce.ignore_gforce_seconds = 1.0;
|
||||||
|
}
|
||||||
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
||||||
|
ew_effect.send(effects::SpawnEffectEvent {
|
||||||
|
class: effects::Effects::FadeIn(Color::CYAN),
|
||||||
|
duration: 1.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"cryofadeout" => {
|
||||||
|
ew_effect.send(effects::SpawnEffectEvent {
|
||||||
|
class: effects::Effects::FadeOut(Color::CYAN),
|
||||||
|
duration: 5.1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let script_name = &script.name;
|
||||||
|
error!("Error, undefined chat script {script_name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn despawn_old_choices(
|
||||||
|
mut commands: Commands,
|
||||||
|
q_conv: Query<&Chat>,
|
||||||
|
q_choices: Query<(Entity, &ChoiceAvailable)>,
|
||||||
|
) {
|
||||||
|
let chats: Vec<&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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,14 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
- chat: Rudy
|
||||||
|
- system: "Error: No response"
|
||||||
|
- system: Lifeform in cryostasis detected
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
- chat: ClippyTransSerenity
|
- chat: ClippyTransSerenity
|
||||||
- Welcome at StarTrans Cargo Services! You have reached Serenity Station.
|
- Welcome at StarTrans Cargo Services! You have reached Serenity Station.
|
||||||
- "Ready for a trip? Available bus stops: Oscillation Station, Metis Prime Station"
|
- "Ready for a trip? Available bus stops: Oscillation Station, Metis Prime Station"
|
||||||
|
|
|
@ -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, chat2, hud, nature, world};
|
use crate::{actor, chat, hud, 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;
|
||||||
|
@ -22,7 +22,6 @@ impl Plugin for CommandsPlugin {
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
enum DefClass {
|
enum DefClass {
|
||||||
Actor,
|
Actor,
|
||||||
Chat,
|
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,18 +68,18 @@ struct ParserState {
|
||||||
light_color: Option<Color>,
|
light_color: Option<Color>,
|
||||||
ar_model: Option<String>,
|
ar_model: Option<String>,
|
||||||
|
|
||||||
// Chat fields
|
// // Chat fields
|
||||||
delay: f64,
|
// delay: f64,
|
||||||
text: String,
|
// text: String,
|
||||||
level: String,
|
// level: String,
|
||||||
label: String,
|
// label: String,
|
||||||
goto: String,
|
// goto: String,
|
||||||
is_choice: bool,
|
// is_choice: bool,
|
||||||
stores_item: bool,
|
// stores_item: bool,
|
||||||
script: String,
|
// script: String,
|
||||||
script_parameter: String,
|
// script_parameter: String,
|
||||||
script_parameter2: String,
|
// script_parameter2: String,
|
||||||
sound: Option<String>,
|
// sound: Option<String>,
|
||||||
}
|
}
|
||||||
impl Default for ParserState {
|
impl Default for ParserState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -125,34 +124,34 @@ impl Default for ParserState {
|
||||||
light_color: None,
|
light_color: None,
|
||||||
ar_model: None,
|
ar_model: None,
|
||||||
|
|
||||||
delay: 0.0,
|
// delay: 0.0,
|
||||||
text: "".to_string(),
|
// text: "".to_string(),
|
||||||
level: "chat".to_string(),
|
// level: "chat".to_string(),
|
||||||
label: "".to_string(),
|
// label: "".to_string(),
|
||||||
goto: "".to_string(),
|
// goto: "".to_string(),
|
||||||
is_choice: false,
|
// is_choice: false,
|
||||||
stores_item: false,
|
// stores_item: false,
|
||||||
script: "".to_string(),
|
// script: "".to_string(),
|
||||||
script_parameter: "".to_string(),
|
// script_parameter: "".to_string(),
|
||||||
script_parameter2: "".to_string(),
|
// script_parameter2: "".to_string(),
|
||||||
sound: Some("chat".to_string()),
|
// sound: Some("chat".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ParserState {
|
impl ParserState {
|
||||||
fn reset_message(&mut self) {
|
// fn reset_message(&mut self) {
|
||||||
let default = ParserState::default();
|
// let default = ParserState::default();
|
||||||
self.label = default.label;
|
// self.label = default.label;
|
||||||
self.delay = default.delay;
|
// self.delay = default.delay;
|
||||||
self.goto = default.goto;
|
// self.goto = default.goto;
|
||||||
self.level = default.level;
|
// self.level = default.level;
|
||||||
self.text = default.text;
|
// self.text = default.text;
|
||||||
self.is_choice = default.is_choice;
|
// self.is_choice = default.is_choice;
|
||||||
self.script = default.script;
|
// self.script = default.script;
|
||||||
self.script_parameter = default.script_parameter;
|
// self.script_parameter = default.script_parameter;
|
||||||
self.script_parameter2 = default.script_parameter2;
|
// self.script_parameter2 = default.script_parameter2;
|
||||||
self.sound = default.sound;
|
// self.sound = default.sound;
|
||||||
}
|
// }
|
||||||
// fn as_chatbranch(&self) -> chat::ChatBranch {
|
// fn as_chatbranch(&self) -> chat::ChatBranch {
|
||||||
// return chat::ChatBranch {
|
// return chat::ChatBranch {
|
||||||
// id: self.chat.clone(),
|
// id: self.chat.clone(),
|
||||||
|
@ -217,6 +216,11 @@ pub fn load_defs(
|
||||||
}
|
}
|
||||||
|
|
||||||
match parts.as_slice() {
|
match parts.as_slice() {
|
||||||
|
["name", name] => {
|
||||||
|
debug!("Registering name: {}", name);
|
||||||
|
state.name = Some(name.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
// Parsing actors
|
// Parsing actors
|
||||||
["actor", x, y, z, model] => {
|
["actor", x, y, z, model] => {
|
||||||
ew_spawn.send(SpawnEvent(state));
|
ew_spawn.send(SpawnEvent(state));
|
||||||
|
@ -475,10 +479,6 @@ pub fn load_defs(
|
||||||
// state.class = DefClass::Chat;
|
// state.class = DefClass::Chat;
|
||||||
// state.chat = chat_name.to_string();
|
// state.chat = chat_name.to_string();
|
||||||
// }
|
// }
|
||||||
// ["name", name] => {
|
|
||||||
// debug!("Registering name: {}", name);
|
|
||||||
// state.name = Some(name.to_string());
|
|
||||||
// }
|
|
||||||
// ["msg", sleep, text] => {
|
// ["msg", sleep, text] => {
|
||||||
// debug!("Registering message (sleep={}): {}", sleep, text);
|
// debug!("Registering message (sleep={}): {}", sleep, text);
|
||||||
// ew_spawn.send(SpawnEvent(state.clone()));
|
// ew_spawn.send(SpawnEvent(state.clone()));
|
||||||
|
@ -568,9 +568,9 @@ pub fn load_defs(
|
||||||
// ["sound", "none"] => {
|
// ["sound", "none"] => {
|
||||||
// state.sound = None;
|
// state.sound = None;
|
||||||
// }
|
// }
|
||||||
// _ => {
|
_ => {
|
||||||
// error!("No match for [{}]", parts.join(","));
|
error!("No match for [{}]", parts.join(","));
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ew_spawn.send(SpawnEvent(state));
|
ew_spawn.send(SpawnEvent(state));
|
||||||
|
@ -691,7 +691,7 @@ fn spawn_entities(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if !state.chat.is_empty() {
|
if !state.chat.is_empty() {
|
||||||
actor.insert(chat2::Talker {
|
actor.insert(chat::Talker {
|
||||||
conv_id: state.chat.clone(),
|
conv_id: state.chat.clone(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
188
src/defs.txt
188
src/defs.txt
|
@ -180,9 +180,6 @@ actor -3300 10 0 pizzeria
|
||||||
rotationy -0.5
|
rotationy -0.5
|
||||||
scale 3
|
scale 3
|
||||||
chatid pizzaclippy
|
chatid pizzaclippy
|
||||||
chat pizzaclippy
|
|
||||||
name "Clippy™ Convenience Companion"
|
|
||||||
msg 4 INIT EXIT "At your service!"
|
|
||||||
|
|
||||||
actor -35 0 0 suit
|
actor -35 0 0 suit
|
||||||
relativeto pizzeria
|
relativeto pizzeria
|
||||||
|
@ -198,50 +195,6 @@ actor -3300 10 0 pizzeria
|
||||||
rotationy 1
|
rotationy 1
|
||||||
angularmomentum 0 0 0
|
angularmomentum 0 0 0
|
||||||
pronoun he
|
pronoun he
|
||||||
chat pizzeria
|
|
||||||
name "Space Pizza™"
|
|
||||||
msg 4 INIT cool "Welcome to Space Pizza™, best pizza around the rings!"
|
|
||||||
msg 4 cool order "Great to see a customer, we don't get many lately"
|
|
||||||
msg 50 order special "Would you like to order today's special?"
|
|
||||||
choice 3 special whatsthespecial "What's the special?"
|
|
||||||
msg 4 whatsthespecial pineapple "Suspicious Spacefunghi"
|
|
||||||
msg 4 pineapple smoothie "With free pineapple imitation"
|
|
||||||
msg 7 smoothie tube "Our pizza smoothies are freshly blended every day"
|
|
||||||
choice 3 tube wtftube "Wait... pizza smoothie?"
|
|
||||||
msg 6 wtftube anyway "Huh? Of course, smoothie! How else do you want to get that pizza down your spacesuit feeding tube?"
|
|
||||||
msg 6 anyway pass "An emulsion of deliciousness!"
|
|
||||||
msg 30 tube pass "Deliciousness for your spacesuit feeding tube!"
|
|
||||||
choice 3 pass yourloss "I think I'll pass..."
|
|
||||||
msg 3 yourloss end "Your loss, mate"
|
|
||||||
msg 3 pass prank "Hey? Are you still there?"
|
|
||||||
choice 3 special carved "Wh... what's a pizzeria doing here?"
|
|
||||||
msg 6 carved work "Hah, beautiful, right? I carved it out this asteroid myself!"
|
|
||||||
msg 7 work nowwannaorder "You know how much work it was to neutralize the rotation of the asteroid, so my valued customers don't bang against the walls while drinking my pizza?"
|
|
||||||
msg 20 nowwannaorder special "Now would you like today's special or not?"
|
|
||||||
choice 3 special ohno "My head hurts, my suit leaks, I think I'm dying..."
|
|
||||||
msg 20 ohno pressok "Seriously? Let me have a look. Just press the 'Grant Access' button please."
|
|
||||||
choice 3 pressok virus "[GRANT ACCESS TO SPACESUIT WIFI]"
|
|
||||||
msg 3 virus bitcoin "MALWARE DETECTED"
|
|
||||||
lvl warn
|
|
||||||
msg 6 bitcoin wtf "BITCOIN MINER DETECTED"
|
|
||||||
lvl warn
|
|
||||||
choice 5 wtf justchecking "Hey, what are you doing with me?"
|
|
||||||
msg 6 justchecking suitfucked "Just checking your systems, hang on tight"
|
|
||||||
msg 5 suitfucked wtfanyway "Yeah, suit's fucked, I'd look out for a repair shop"
|
|
||||||
msg 5 wtf wtfanyway "Yeah, suit's fucked, I'd look out for a repair shop"
|
|
||||||
msg 5 wtfanyway special "Anyway, wanna order today's special?"
|
|
||||||
choice 3 pressok deny "[DENY ACCESS TO SPACESUIT WIFI]"
|
|
||||||
msg 5 deny pressok2 "Oh come on, do you want my help or not?"
|
|
||||||
choice 3 pressok2 virus "[GRANT ACCESS TO SPACESUIT WIFI]"
|
|
||||||
choice 3 pressok2 deny2 "[DENY ACCESS TO SPACESUIT WIFI]"
|
|
||||||
choice 3 pressok2 deny2 "Fuck off!"
|
|
||||||
msg 2 deny2 end "Great, the first customer in ages, and they're brain damaged..."
|
|
||||||
msg 3 pressok2 prank "Hey? Are you still there?"
|
|
||||||
msg 3 pressok prank "Hey? Are you still there?"
|
|
||||||
msg 3 special prank "Hey? Are you still there?"
|
|
||||||
msg 4 prank end "This a prank?"
|
|
||||||
msg 0 end EXIT "Disconnected."
|
|
||||||
lvl info
|
|
||||||
|
|
||||||
actor 60 -15 -40 suit
|
actor 60 -15 -40 suit
|
||||||
relativeto player
|
relativeto player
|
||||||
|
@ -257,90 +210,6 @@ actor 60 -15 -40 suit
|
||||||
wants maxrotation 0.5
|
wants maxrotation 0.5
|
||||||
wants maxvelocity 0
|
wants maxvelocity 0
|
||||||
pronoun it
|
pronoun it
|
||||||
chat hi_icarus
|
|
||||||
name Icarus
|
|
||||||
msg 4 INIT hi1 "Oh hey, you're awake!"
|
|
||||||
msg 6 hi1 hi2 "I found you drifting out cold, and thought, I better watch over you."
|
|
||||||
msg 40 hi2 hireply "Took us here behind that moonlet, to shield you from the micros."
|
|
||||||
choice 1 hireply thx "Thank you!"
|
|
||||||
msg 6 thx feeling "No worries. Folks are stretched thin around this corner, we gotta watch out for each other."
|
|
||||||
msg 40 feeling howru "How are you feeling?"
|
|
||||||
choice 1 hireply didntask "Eh... I didn't ask for this."
|
|
||||||
msg 6 didntask problem "Sure, 'cause you were unconscious. I just did what felt right. Is there a problem?"
|
|
||||||
choice 1 problem thx "Nevermind. Thank you."
|
|
||||||
choice 1 problem end "Leave me alone!"
|
|
||||||
choice 1 hireply micros "Micros? What's that?"
|
|
||||||
msg 40 micros microsexplained "Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body."
|
|
||||||
choice 1 microsexplained thx "Ouch! Thank you so much."
|
|
||||||
choice 1 microsexplained didntask "Whatever... I didn't ask for this."
|
|
||||||
msg 40 microsexplained howru "How are you feeling?"
|
|
||||||
msg 40 hireply howru "How are you feeling?"
|
|
||||||
choice 1 howru good1 "I feel quite cozy, this space suit feels like a second skin."
|
|
||||||
msg 4 good1 good2 "Hah, it does, doesn't it?"
|
|
||||||
msg 4 good2 good3 "But take care, your suit seems to be leaking. I'd patch it up if I were you."
|
|
||||||
msg 4 good3 good4 "I'm all out of SuitPatch™ SuperGlue™ right now, otherwise i'd share."
|
|
||||||
msg 40 good4 canihelp "Can I help you with anything else, maybe?"
|
|
||||||
choice 1 howru headache1 "I got this apocalyptic headache..."
|
|
||||||
msg 4 headache1 headache2 "Heh, probably related to why you were passed out."
|
|
||||||
msg 4 headache2 headache3 "Go easy on yourself, I'm sure things will turn for the better."
|
|
||||||
msg 40 headache3 canihelp "Meanwhile, can I help you with anything?"
|
|
||||||
choice 1 howru disoriented "I... don't know, I'm pretty disoriented."
|
|
||||||
msg 40 disoriented canihelp "Oh no. Do you need a lowdown on reality?"
|
|
||||||
choice 1 canihelp where1 "Where are we?"
|
|
||||||
msg 4 where1 where2 "This is space, my friend."
|
|
||||||
msg 4 where2 where3 "That massive crescent over there, that's Juptiter."
|
|
||||||
msg 4 where3 where4 "We're about 150,000km away from it, on the very outside of it's rings."
|
|
||||||
msg 4 where4 where5 "This area is called the Thebe gossamer ring."
|
|
||||||
msg 4 where5 where6 "The moon Thebe is actually pretty close right now, flinging all those micros at us."
|
|
||||||
choice 4 where6 where6micros "Micros? What's that?"
|
|
||||||
msg 4 where6micros canihelp "Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body."
|
|
||||||
msg 40 where6 canihelp "Anything else?"
|
|
||||||
choice 1 canihelp year1 "What year is this?"
|
|
||||||
msg 4 year1 year2 "Oh, is your Augmented Reality deactivated?"
|
|
||||||
msg 40 year2 canihelp "Push the TAB button, your space suit's AR will show you the date and time."
|
|
||||||
choice 1 canihelp why1 "Why am I here?"
|
|
||||||
msg 4 why1 why2 "That's a very philosophical question."
|
|
||||||
msg 4 why2 why3 "I don't know."
|
|
||||||
msg 40 why3 canihelp "It's probably related to the choices you made so far."
|
|
||||||
choice 1 canihelp whatnow1 "What should I do?"
|
|
||||||
msg 4 whatnow1 whatnow2 "Ah, that's the beauty of life."
|
|
||||||
msg 4 whatnow2 whatnow3 "You can just do whatever you want."
|
|
||||||
msg 6 whatnow3 whatnow4 "So long as you have the means, and respect people's boundaries."
|
|
||||||
msg 4 whatnow4 whatnow5 "I'm here mostly for the view and the peace."
|
|
||||||
msg 4 whatnow5 whatnow6 "Just look at Jupiter, it's mesmerizing, isn't it?"
|
|
||||||
msg 7 whatnow6 whatnow7 "So far away from everything, nobody expects anything from you."
|
|
||||||
msg 6 whatnow7 whatnow8 "If you want, you can take my sports racing capsule MeteorAceGT™ for a ride. It's right over there."
|
|
||||||
msg 8 whatnow8 whatnow9 "It rides like a punch in the face, don't hurt yourself, ok?"
|
|
||||||
choice 1 whatnow9 whatnow9tookind "You're too kind!"
|
|
||||||
msg 4 whatnow9tookind whatnow10 "Ah, don't mention it!"
|
|
||||||
msg 40 whatnow10 canihelp "There's also a half-decent pizza restaurant over there, look for the neon sign."
|
|
||||||
msg 40 whatnow9 canihelp "There's also a half-decent pizza restaurant over there, look for the neon sign."
|
|
||||||
choice 1 canihelp money1 "Do you have some money for me?"
|
|
||||||
msg 40 money1 money2 "Huh? What is money?"
|
|
||||||
choice 1 money2 money2currency "Currency? Flat round things that you can exchange for goods and services?"
|
|
||||||
msg 4 money2currency money2currency2 "Uhm... are you talking about pizza?"
|
|
||||||
msg 4 money2currency2 money2currency3 "I don't have any pizza with me right now."
|
|
||||||
msg 4 money2currency3 canihelp "But there's a pizza place right over there, look for the neon sign."
|
|
||||||
msg 40 money2 canihelp "Well, anyway, need anything else?"
|
|
||||||
choice 1 canihelp chocolate "I think I'm good for now"
|
|
||||||
msg 4 canihelp chocolate "Well, anyway."
|
|
||||||
choice 1 howru alone "I just want to be alone right now"
|
|
||||||
msg 4 alone end_pizza "Oh, sure. Ping me if you need anything. I'll go back to playing my VR game."
|
|
||||||
msg 4 howru chocolate "Well, I hope you're ok."
|
|
||||||
msg 40 chocolate wantchocolate "I got some left-over instant hot chocolate, would you like some?"
|
|
||||||
choice 1 wantchocolate yeschocolate "Oh yes! Please!"
|
|
||||||
msg 4 yeschocolate chocolatehere "Here you go, my friend!"
|
|
||||||
msg 4 chocolatehere pizza "Received 1x ChuggaChug™ Instant Hot Chocolate"
|
|
||||||
lvl info
|
|
||||||
msg 4 wantchocolate pizza "I guess not. Well. I think I'll go back to playing my VR game. Ping me if you need anything."
|
|
||||||
msg 10 pizza end_pizza "Oh and make sure to check out the pizza place!"
|
|
||||||
choice 1 end_pizza end2 "Will do, bye!"
|
|
||||||
msg 0 end2 EXIT "Disconnected."
|
|
||||||
lvl info
|
|
||||||
msg 0 end_pizza EXIT "Disconnected."
|
|
||||||
lvl info
|
|
||||||
msg 0 end EXIT "Disconnected."
|
|
||||||
lvl info
|
|
||||||
|
|
||||||
actor -300 0 40 suit
|
actor -300 0 40 suit
|
||||||
relativeto player
|
relativeto player
|
||||||
|
@ -350,16 +219,6 @@ actor -300 0 40 suit
|
||||||
oxygen 0.08
|
oxygen 0.08
|
||||||
scale 2
|
scale 2
|
||||||
collider capsule 1 0.5
|
collider capsule 1 0.5
|
||||||
chat drifter
|
|
||||||
name "Drifter"
|
|
||||||
msg 2 INIT dead "Error: No response"
|
|
||||||
lvl info
|
|
||||||
msg 15 dead outcold "No life signs detected"
|
|
||||||
lvl info
|
|
||||||
choice 0 outcold EXIT "Damn, it's gotta be moldy in that suit. How long has it been drifting?"
|
|
||||||
choice 0 outcold EXIT "Harvest some oxygen"
|
|
||||||
script refilloxygen 1 drifter
|
|
||||||
msg 0 outcold EXIT ""
|
|
||||||
|
|
||||||
actor 100 -18000 2000 "orb_busstop"
|
actor 100 -18000 2000 "orb_busstop"
|
||||||
relativeto player
|
relativeto player
|
||||||
|
@ -380,41 +239,6 @@ actor 100 -18000 2000 "orb_busstop"
|
||||||
rotationy -0.5
|
rotationy -0.5
|
||||||
scale 3
|
scale 3
|
||||||
chatid "busstopclippy"
|
chatid "busstopclippy"
|
||||||
chat "busstopclippy"
|
|
||||||
name "StarTrans Clippy™"
|
|
||||||
msg 2 INIT question "Welcome at StarTrans Cargo Services!"
|
|
||||||
msg 2 question wait "Ready for a trip? Available bus stops: Oscillation Station, Metis Prime Station"
|
|
||||||
msg 40 wait answer ""
|
|
||||||
sound none
|
|
||||||
choice 2 answer how1 "Bus stop?! How does this work?"
|
|
||||||
msg 6 how1 how2 "StarTrans Cargo Services is the most convenient way to travel the vast distances of space."
|
|
||||||
msg 6 how2 how3 "Just activate your suit's built-in cryostasis. A StarTrans carrier will pick you up and you will wake up at your destination in the blink of an eye."
|
|
||||||
msg 40 how3 answer "Of course we will supply you with free oxygen and ensure your safety."
|
|
||||||
choice 2 answer vehicle "Can I take a spacecraft with me?"
|
|
||||||
msg 40 vehicle answer "Absolutely."
|
|
||||||
choice 1 answer stopA1 "Take me to Oscillation Station, please."
|
|
||||||
msg 5 stopA1 stopA2 "StarTrans wishes you a pleasant journey."
|
|
||||||
script cryofadeout
|
|
||||||
msg 0 stopA2 EXIT ""
|
|
||||||
script cryotrip oscillation
|
|
||||||
sound none
|
|
||||||
choice 1 answer stopB1 "Take me to Metis Prime Station, please."
|
|
||||||
msg 5 stopB1 stopB2 "StarTrans wishes you a pleasant journey."
|
|
||||||
script cryofadeout
|
|
||||||
msg 0 stopB2 EXIT ""
|
|
||||||
script cryotrip metisprime
|
|
||||||
choice 1 answer stopC1 "Take me to Serenity Station, please."
|
|
||||||
msg 5 stopC1 stopC2 "StarTrans wishes you a pleasant journey."
|
|
||||||
script cryofadeout
|
|
||||||
msg 0 stopC2 EXIT ""
|
|
||||||
script cryotrip serenity
|
|
||||||
choice 1 answer oxy "Can you please fill up my oxygen tank without taking me anywhere?"
|
|
||||||
msg 2 oxy EXIT "Acceptable."
|
|
||||||
script refilloxygen 1000
|
|
||||||
sound none
|
|
||||||
choice 2 answer bye "No, thank you."
|
|
||||||
msg 2 bye EXIT "Feel free to come back any time."
|
|
||||||
msg 0 answer EXIT "Connection terminated."
|
|
||||||
actor 40 10 40 "orb_busstop"
|
actor 40 10 40 "orb_busstop"
|
||||||
name "Light Orb"
|
name "Light Orb"
|
||||||
relativeto busstopclippy
|
relativeto busstopclippy
|
||||||
|
@ -440,12 +264,6 @@ actor 100 -18000 2000 "orb_busstop"
|
||||||
scale 2
|
scale 2
|
||||||
collider capsule 1 0.5
|
collider capsule 1 0.5
|
||||||
chatid "busstop1clippynpc1"
|
chatid "busstop1clippynpc1"
|
||||||
chat "busstop1clippynpc1"
|
|
||||||
name "Rudy"
|
|
||||||
msg 3 INIT cryo "Error: No response"
|
|
||||||
lvl info
|
|
||||||
msg 0 cryo EXIT "Lifeform in cryostasis detected."
|
|
||||||
lvl info
|
|
||||||
|
|
||||||
actor 147002e3 165001e3 336e6 "orb_busstop"
|
actor 147002e3 165001e3 336e6 "orb_busstop"
|
||||||
relativeto jupiter
|
relativeto jupiter
|
||||||
|
@ -466,7 +284,6 @@ actor 147002e3 165001e3 336e6 "orb_busstop"
|
||||||
rotationy -0.5
|
rotationy -0.5
|
||||||
scale 3
|
scale 3
|
||||||
chatid "busstopclippy"
|
chatid "busstopclippy"
|
||||||
chat "busstopclippy"
|
|
||||||
actor 40 10 40 "orb_busstop"
|
actor 40 10 40 "orb_busstop"
|
||||||
name "Light Orb"
|
name "Light Orb"
|
||||||
relativeto busstopclippy2
|
relativeto busstopclippy2
|
||||||
|
@ -503,7 +320,6 @@ actor 27643e3 -44e3 -124434e3 "orb_busstop"
|
||||||
rotationy -0.5
|
rotationy -0.5
|
||||||
scale 3
|
scale 3
|
||||||
chatid "busstopclippy"
|
chatid "busstopclippy"
|
||||||
chat "busstopclippy"
|
|
||||||
actor 40 10 40 "orb_busstop"
|
actor 40 10 40 "orb_busstop"
|
||||||
name "Light Orb"
|
name "Light Orb"
|
||||||
relativeto busstopclippy3
|
relativeto busstopclippy3
|
||||||
|
@ -520,7 +336,3 @@ actor 27643e3 -44e3 -124434e3 "orb_busstop"
|
||||||
name "Light Orb"
|
name "Light Orb"
|
||||||
relativeto busstopclippy3
|
relativeto busstopclippy3
|
||||||
light "47FF00" 1000000
|
light "47FF00" 1000000
|
||||||
|
|
||||||
chat error
|
|
||||||
name ERROR
|
|
||||||
msg 0 INIT EXIT "Unspecified conversation ID"
|
|
||||||
|
|
|
@ -413,7 +413,7 @@ fn update_hud(
|
||||||
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<&chat::ChoiceAvailable>,
|
//q_choices: Query<&chat::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>,
|
||||||
q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>,
|
q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
mod actor;
|
mod actor;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod camera;
|
mod camera;
|
||||||
mod chat2;
|
mod chat;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod effects;
|
mod effects;
|
||||||
mod hud;
|
mod hud;
|
||||||
|
@ -55,7 +55,7 @@ impl Plugin for OutFlyPlugin {
|
||||||
actor::ActorPlugin,
|
actor::ActorPlugin,
|
||||||
audio::AudioPlugin,
|
audio::AudioPlugin,
|
||||||
camera::CameraPlugin,
|
camera::CameraPlugin,
|
||||||
chat2::ChatPlugin,
|
chat::ChatPlugin,
|
||||||
commands::CommandsPlugin,
|
commands::CommandsPlugin,
|
||||||
effects::EffectsPlugin,
|
effects::EffectsPlugin,
|
||||||
hud::HudPlugin,
|
hud::HudPlugin,
|
||||||
|
|
Loading…
Reference in a new issue