2024-03-17 22:49:50 +00:00
|
|
|
use bevy::prelude::*;
|
2024-03-19 00:24:27 +00:00
|
|
|
use crate::{nature, settings, actor, audio, hud};
|
|
|
|
|
|
|
|
const MIN_INTERACT_DISTANCE: f32 = 30.0;
|
2024-03-28 12:26:14 +00:00
|
|
|
const NO_RIDE: u32 = 0;
|
2024-03-17 22:49:50 +00:00
|
|
|
|
|
|
|
pub struct ActorPlugin;
|
|
|
|
impl Plugin for ActorPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
2024-03-19 04:38:11 +00:00
|
|
|
app.register_type::<ChatBranch>();
|
2024-03-19 15:14:12 +00:00
|
|
|
app.add_systems(FixedUpdate, (
|
|
|
|
update_physics_lifeforms,
|
|
|
|
update_physics_actors,
|
|
|
|
));
|
2024-03-19 00:24:27 +00:00
|
|
|
app.add_systems(Update, (
|
2024-03-19 04:38:11 +00:00
|
|
|
handle_new_conversations,
|
2024-03-20 01:03:42 +00:00
|
|
|
handle_send_messages,
|
2024-03-19 00:24:27 +00:00
|
|
|
handle_conversations,
|
|
|
|
handle_input,
|
2024-03-23 19:13:37 +00:00
|
|
|
handle_chat_scripts,
|
2024-03-28 15:02:36 +00:00
|
|
|
handle_vehicle_enter_exit,
|
2024-03-19 00:24:27 +00:00
|
|
|
));
|
|
|
|
app.add_event::<StartConversationEvent>();
|
2024-03-20 01:03:42 +00:00
|
|
|
app.add_event::<SendMessageEvent>();
|
2024-03-23 19:13:37 +00:00
|
|
|
app.add_event::<ChatScriptEvent>();
|
2024-03-28 15:02:36 +00:00
|
|
|
app.add_event::<VehicleEnterExitEvent>();
|
2024-03-17 22:49:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 00:24:27 +00:00
|
|
|
#[derive(Event)]
|
|
|
|
pub struct StartConversationEvent {
|
2024-03-19 17:15:19 +00:00
|
|
|
pub talker: Talker,
|
2024-03-19 00:24:27 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 01:03:42 +00:00
|
|
|
#[derive(Event)]
|
|
|
|
pub struct SendMessageEvent {
|
|
|
|
pub conv_id: String,
|
|
|
|
pub conv_label: String,
|
|
|
|
pub text: String,
|
|
|
|
}
|
|
|
|
|
2024-03-23 19:13:37 +00:00
|
|
|
#[derive(Event)]
|
|
|
|
pub struct ChatScriptEvent {
|
|
|
|
name: String,
|
|
|
|
param: String,
|
2024-03-23 20:26:56 +00:00
|
|
|
param2: String,
|
2024-03-23 19:13:37 +00:00
|
|
|
}
|
|
|
|
|
2024-03-28 15:02:36 +00:00
|
|
|
#[derive(Event)]
|
|
|
|
pub struct VehicleEnterExitEvent {
|
|
|
|
vehicle: Entity,
|
|
|
|
driver: Entity,
|
|
|
|
is_entering: bool,
|
|
|
|
is_player: bool
|
|
|
|
}
|
|
|
|
|
2024-03-17 22:49:50 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct Actor {
|
2024-03-23 20:26:56 +00:00
|
|
|
pub id: String,
|
2024-03-17 22:49:50 +00:00
|
|
|
pub hp: f32,
|
|
|
|
pub m: f32, // mass
|
|
|
|
pub v: Vec3, // velocity
|
2024-03-19 15:14:12 +00:00
|
|
|
pub angular_momentum: Quat,
|
2024-03-28 12:26:14 +00:00
|
|
|
pub inside_entity: u32,
|
2024-03-17 22:49:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Actor {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2024-03-23 20:26:56 +00:00
|
|
|
id: "".to_string(),
|
2024-03-17 22:49:50 +00:00
|
|
|
hp: 100.0,
|
|
|
|
m: 100.0,
|
|
|
|
v: Vec3::ZERO,
|
2024-03-28 12:26:14 +00:00
|
|
|
inside_entity: NO_RIDE,
|
2024-03-19 15:14:12 +00:00
|
|
|
angular_momentum: Quat::from_euler(EulerRot::XYZ, 0.001, 0.01, 0.003),
|
2024-03-17 22:49:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 00:24:27 +00:00
|
|
|
#[derive(Component)] pub struct Player;
|
2024-03-28 12:26:14 +00:00
|
|
|
#[derive(Component)] pub struct PlayerDrivesThis;
|
2024-03-19 00:24:27 +00:00
|
|
|
#[derive(Component)] pub struct PlayerInConversation;
|
|
|
|
#[derive(Component)] pub struct InConversationWithPlayer;
|
2024-03-28 15:02:36 +00:00
|
|
|
#[derive(Component)] pub struct ActorEnteringVehicle;
|
|
|
|
#[derive(Component)] pub struct ActorVehicleBeingEntered;
|
2024-03-19 00:24:27 +00:00
|
|
|
|
2024-03-20 04:52:02 +00:00
|
|
|
#[derive(Debug)]
|
2024-03-19 04:38:11 +00:00
|
|
|
#[derive(Component, Reflect, Default)]
|
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct ChatBranch {
|
|
|
|
pub id: String,
|
|
|
|
pub name: String,
|
|
|
|
pub label: String,
|
2024-03-19 05:01:17 +00:00
|
|
|
pub delay: f64,
|
2024-03-19 04:38:11 +00:00
|
|
|
pub sound: String,
|
2024-03-19 22:44:00 +00:00
|
|
|
pub level: String,
|
2024-03-19 04:38:11 +00:00
|
|
|
pub reply: String,
|
|
|
|
pub goto: String,
|
|
|
|
pub choice: String,
|
2024-03-23 19:13:37 +00:00
|
|
|
pub script: String,
|
|
|
|
pub script_parameter: String,
|
2024-03-23 19:49:48 +00:00
|
|
|
pub script_parameter2: String,
|
2024-03-19 04:38:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
pub struct Chat {
|
|
|
|
pub id: String,
|
|
|
|
pub label: String,
|
2024-03-19 05:01:17 +00:00
|
|
|
pub timer: f64,
|
2024-03-19 04:38:11 +00:00
|
|
|
}
|
|
|
|
|
2024-03-19 00:24:27 +00:00
|
|
|
#[derive(Component)]
|
2024-03-19 17:15:19 +00:00
|
|
|
#[derive(Clone)]
|
2024-03-19 00:24:27 +00:00
|
|
|
pub struct Talker {
|
2024-03-20 20:03:22 +00:00
|
|
|
pub pronoun: String,
|
2024-03-19 17:15:19 +00:00
|
|
|
pub conv_id: String,
|
2024-03-19 00:24:27 +00:00
|
|
|
}
|
2024-03-20 20:03:22 +00:00
|
|
|
impl Default for Talker { fn default() -> Self { Self {
|
|
|
|
pronoun: "they/them".to_string(),
|
|
|
|
conv_id: "error".to_string(),
|
|
|
|
}}}
|
2024-03-19 00:24:27 +00:00
|
|
|
|
2024-03-17 22:49:50 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct LifeForm {
|
2024-03-20 17:37:42 +00:00
|
|
|
pub is_alive: bool,
|
2024-03-17 22:49:50 +00:00
|
|
|
pub adrenaline: f32,
|
|
|
|
pub adrenaline_baseline: f32,
|
|
|
|
pub adrenaline_jolt: f32,
|
|
|
|
}
|
|
|
|
impl Default for LifeForm { fn default() -> Self { Self {
|
2024-03-20 17:37:42 +00:00
|
|
|
is_alive: true,
|
2024-03-17 22:49:50 +00:00
|
|
|
adrenaline: 0.3,
|
|
|
|
adrenaline_baseline: 0.3,
|
|
|
|
adrenaline_jolt: 0.0,
|
|
|
|
}}}
|
|
|
|
|
2024-03-28 12:26:14 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct Vehicle;
|
|
|
|
|
2024-03-28 13:10:10 +00:00
|
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
|
|
pub enum EngineType {
|
|
|
|
Monopropellant,
|
|
|
|
Rocket,
|
|
|
|
//Ion,
|
|
|
|
}
|
|
|
|
|
2024-03-28 12:26:14 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct Engine {
|
|
|
|
pub thrust_forward: f32,
|
|
|
|
pub thrust_back: f32,
|
|
|
|
pub thrust_sideways: f32,
|
|
|
|
pub reaction_wheels: f32,
|
2024-03-28 13:10:10 +00:00
|
|
|
pub engine_type: EngineType,
|
2024-03-28 12:26:14 +00:00
|
|
|
}
|
|
|
|
impl Default for Engine {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
thrust_forward: 1.0,
|
|
|
|
thrust_back: 1.0,
|
|
|
|
thrust_sideways: 1.0,
|
|
|
|
reaction_wheels: 1.0,
|
2024-03-28 13:10:10 +00:00
|
|
|
engine_type: EngineType::Monopropellant,
|
2024-03-28 12:26:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-17 22:49:50 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct Suit {
|
|
|
|
pub oxygen: f32,
|
|
|
|
pub power: f32,
|
|
|
|
pub oxygen_max: f32,
|
|
|
|
pub power_max: f32,
|
2024-03-20 17:37:10 +00:00
|
|
|
pub integrity: f32, // [0.0 - 1.0]
|
2024-03-17 22:49:50 +00:00
|
|
|
}
|
|
|
|
impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } }
|
|
|
|
|
|
|
|
const SUIT_SIMPLE: Suit = Suit {
|
|
|
|
power: 1e5,
|
|
|
|
power_max: 1e5,
|
2024-03-18 22:53:52 +00:00
|
|
|
oxygen: nature::OXY_D,
|
|
|
|
oxygen_max: nature::OXY_D,
|
2024-03-20 17:37:10 +00:00
|
|
|
integrity: 1e5,
|
2024-03-17 22:49:50 +00:00
|
|
|
};
|
|
|
|
|
2024-03-19 15:14:12 +00:00
|
|
|
pub fn update_physics_actors(
|
|
|
|
time: Res<Time>,
|
|
|
|
mut q_actors: Query<(&mut Actor, &mut Transform)>,
|
|
|
|
) {
|
2024-03-19 15:38:08 +00:00
|
|
|
let d = time.delta_seconds();
|
2024-03-19 15:14:12 +00:00
|
|
|
for (actor, mut transform) in q_actors.iter_mut() {
|
|
|
|
transform.rotate(actor.angular_momentum);
|
2024-03-19 15:38:08 +00:00
|
|
|
// TODO: animate only a step based on time between update:
|
|
|
|
//transform.rotate(actor.angular_momentum.slerp(Quat::IDENTITY, d)); // not working
|
|
|
|
transform.translation += d * actor.v;
|
2024-03-19 15:14:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_physics_lifeforms(
|
2024-03-17 22:49:50 +00:00
|
|
|
time: Res<Time>,
|
2024-03-28 22:13:59 +00:00
|
|
|
mut query: Query<(&mut LifeForm, &mut Suit, &Actor)>,
|
2024-03-17 22:49:50 +00:00
|
|
|
) {
|
|
|
|
let d = time.delta_seconds();
|
2024-03-28 22:13:59 +00:00
|
|
|
for (mut lifeform, mut suit, actor) in query.iter_mut() {
|
2024-03-17 22:49:50 +00:00
|
|
|
if lifeform.adrenaline_jolt.abs() > 1e-3 {
|
|
|
|
lifeform.adrenaline_jolt *= 0.99;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
lifeform.adrenaline_jolt = 0.0
|
|
|
|
}
|
2024-03-28 22:13:59 +00:00
|
|
|
let speed = actor.v.length();
|
|
|
|
if speed > 1000.0 {
|
|
|
|
lifeform.adrenaline += 0.001;
|
|
|
|
}
|
2024-03-17 22:49:50 +00:00
|
|
|
lifeform.adrenaline = (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0);
|
2024-03-20 17:37:10 +00:00
|
|
|
|
|
|
|
let mut oxygen_drain = nature::OXY_S;
|
|
|
|
let integr_threshold = 0.5;
|
|
|
|
if suit.integrity < integr_threshold {
|
|
|
|
// The oxygen drain from suit integrity scales with (2 - 2x)^4,
|
|
|
|
// which is a function with ~16 at x=0 and 0 at x=1.
|
|
|
|
// Furthermore, x is divided by the integrity threshold (e.g. 0.5)
|
|
|
|
// to squeeze the function horizontally, and change the range for
|
|
|
|
// the x parameter from [0-1] to [0-integritythreshold]
|
|
|
|
//
|
|
|
|
// 16 |.
|
|
|
|
// |.
|
|
|
|
// |'.
|
|
|
|
// | '.
|
|
|
|
// | '..
|
|
|
|
// |______''....
|
|
|
|
// x=0 x=1
|
|
|
|
let drain_scaling = (2.0 - 2.0 * suit.integrity / integr_threshold).powf(4.0);
|
|
|
|
oxygen_drain += suit.oxygen * 0.01 * drain_scaling;
|
|
|
|
}
|
|
|
|
suit.oxygen = (suit.oxygen - oxygen_drain*d).clamp(0.0, suit.oxygen_max);
|
2024-03-17 22:49:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 00:24:27 +00:00
|
|
|
pub fn handle_input(
|
2024-03-28 12:26:14 +00:00
|
|
|
mut commands: Commands,
|
2024-03-19 00:24:27 +00:00
|
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
|
|
settings: ResMut<settings::Settings>,
|
2024-03-28 12:26:14 +00:00
|
|
|
q_talker: Query<(&Talker, &Transform), Without<actor::Player>>,
|
2024-03-28 15:02:36 +00:00
|
|
|
mut player: Query<(Entity, &mut Actor, &mut Transform), With<actor::Player>>,
|
2024-03-28 12:26:14 +00:00
|
|
|
mut q_vehicles: Query<(Entity, &mut Visibility, &Transform), (With<actor::Vehicle>, Without<actor::Player>)>,
|
2024-03-19 00:24:27 +00:00
|
|
|
mut ew_conv: EventWriter<StartConversationEvent>,
|
2024-03-28 15:02:36 +00:00
|
|
|
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
|
|
|
|
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
|
|
|
|
) {
|
2024-03-28 12:26:14 +00:00
|
|
|
let mindist = MIN_INTERACT_DISTANCE * MIN_INTERACT_DISTANCE;
|
2024-03-19 00:24:27 +00:00
|
|
|
if keyboard_input.just_pressed(settings.key_interact) {
|
2024-03-28 15:02:36 +00:00
|
|
|
if let Ok((_player_entity, _player_actor, player)) = player.get_single() {
|
2024-03-28 12:26:14 +00:00
|
|
|
for (talker, transform) in &q_talker {
|
2024-03-19 00:24:27 +00:00
|
|
|
if transform.translation.distance_squared(player.translation) <= mindist {
|
2024-03-19 17:15:19 +00:00
|
|
|
ew_conv.send(StartConversationEvent{talker: talker.clone()});
|
2024-03-19 00:24:27 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-28 12:26:14 +00:00
|
|
|
else if keyboard_input.just_pressed(settings.key_vehicle) {
|
2024-03-28 15:02:36 +00:00
|
|
|
// Entering Vehicles
|
|
|
|
if q_player_drives.is_empty() {
|
|
|
|
if let Ok((player_entity, _player_actor, player)) = player.get_single_mut() {
|
|
|
|
for (vehicle_entity, _visibility, vehicle_transform) in q_vehicles.iter_mut() {
|
|
|
|
if vehicle_transform.translation.distance_squared(player.translation) <= mindist {
|
|
|
|
commands.entity(vehicle_entity).insert(ActorVehicleBeingEntered);
|
|
|
|
commands.entity(player_entity).insert(ActorEnteringVehicle);
|
|
|
|
ew_vehicle.send(VehicleEnterExitEvent{
|
|
|
|
vehicle: vehicle_entity,
|
|
|
|
driver: player_entity,
|
|
|
|
is_entering: q_player_drives.is_empty(),
|
|
|
|
is_player: true,
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Exiting Vehicles
|
|
|
|
else {
|
|
|
|
if let Ok((player_entity, _player_actor, _player)) = player.get_single_mut() {
|
|
|
|
for vehicle_entity in &q_player_drives {
|
|
|
|
commands.entity(vehicle_entity).insert(ActorVehicleBeingEntered);
|
|
|
|
commands.entity(player_entity).insert(ActorEnteringVehicle);
|
|
|
|
ew_vehicle.send(VehicleEnterExitEvent{
|
|
|
|
vehicle: vehicle_entity,
|
|
|
|
driver: player_entity,
|
|
|
|
is_entering: false,
|
|
|
|
is_player: true,
|
|
|
|
});
|
2024-03-28 12:26:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-19 00:24:27 +00:00
|
|
|
}
|
|
|
|
|
2024-03-28 15:02:36 +00:00
|
|
|
pub fn handle_vehicle_enter_exit(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
|
|
|
|
mut q_drivers: Query<(Entity, &mut Actor, &mut Visibility, &mut Transform), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
|
|
|
|
mut q_vehicles: Query<(Entity, &mut Actor, &mut Visibility, &mut Transform), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
|
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
|
|
) {
|
|
|
|
for event in er_vehicle.read() {
|
|
|
|
for (driver, mut driver_actor, mut driver_vis, mut driver_trans) in q_drivers.iter_mut() {
|
|
|
|
if driver == event.driver {
|
|
|
|
for (vehicle, mut vehicle_actor, mut vehicle_vis, mut vehicle_trans) in q_vehicles.iter_mut() {
|
|
|
|
if vehicle == event.vehicle {
|
|
|
|
if event.is_entering {
|
|
|
|
// Entering Vehicle
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
|
|
|
|
*driver_vis = Visibility::Hidden;
|
|
|
|
*driver_trans = vehicle_trans.clone();
|
|
|
|
if event.is_player {
|
|
|
|
//player_actor.inside_entity = entity.index();
|
|
|
|
*vehicle_vis = Visibility::Hidden;
|
|
|
|
driver_actor.v = vehicle_actor.v;
|
|
|
|
commands.entity(vehicle).insert(PlayerDrivesThis);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Exiting Vehicle
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
|
|
|
|
*driver_vis = Visibility::Inherited;
|
|
|
|
if event.is_player {
|
|
|
|
*vehicle_vis = Visibility::Inherited;
|
|
|
|
vehicle_actor.v = driver_actor.v;
|
|
|
|
vehicle_actor.angular_momentum = driver_actor.angular_momentum;
|
|
|
|
*vehicle_trans = driver_trans.clone();
|
|
|
|
commands.entity(vehicle).remove::<PlayerDrivesThis>();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*driver_trans = vehicle_trans.clone();
|
|
|
|
*driver_trans = vehicle_trans.clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 04:38:11 +00:00
|
|
|
pub fn handle_new_conversations(
|
|
|
|
mut commands: Commands,
|
2024-03-19 00:24:27 +00:00
|
|
|
mut er_conv: EventReader<StartConversationEvent>,
|
2024-03-19 05:07:20 +00:00
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
|
|
|
q_conv: Query<&Chat>,
|
2024-03-19 05:01:17 +00:00
|
|
|
time: Res<Time>,
|
2024-03-19 00:24:27 +00:00
|
|
|
) {
|
2024-03-19 05:07:20 +00:00
|
|
|
let label = "INIT";
|
2024-03-19 17:15:19 +00:00
|
|
|
for event in er_conv.read() {
|
2024-03-19 05:07:20 +00:00
|
|
|
// check for existing chats with this id
|
2024-03-19 17:15:19 +00:00
|
|
|
let id = &event.talker.conv_id;
|
|
|
|
let chats: Vec<&Chat> = q_conv.iter().filter(|c| c.id == *id).collect();
|
2024-03-19 05:07:20 +00:00
|
|
|
if chats.len() > 0 {
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// no existing chats yet, let's create a new one
|
2024-03-19 04:38:11 +00:00
|
|
|
commands.spawn(Chat {
|
2024-03-19 05:07:20 +00:00
|
|
|
id: id.to_string(),
|
|
|
|
label: label.to_string(),
|
2024-03-19 05:01:17 +00:00
|
|
|
timer: time.elapsed_seconds_f64(),
|
2024-03-19 04:38:11 +00:00
|
|
|
});
|
2024-03-19 00:24:27 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-20 01:03:42 +00:00
|
|
|
pub fn handle_send_messages(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut er_sendmsg: EventReader<SendMessageEvent>,
|
|
|
|
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
2024-03-23 19:13:37 +00:00
|
|
|
mut ew_chatscript: EventWriter<ChatScriptEvent>,
|
2024-03-20 01:03:42 +00:00
|
|
|
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
|
|
|
|
|
2024-03-20 04:52:02 +00:00
|
|
|
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()),
|
2024-03-21 04:32:39 +00:00
|
|
|
"warn" => log.warning(branch.reply.clone()),
|
2024-03-20 04:52:02 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
2024-03-20 01:03:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2024-03-23 19:13:37 +00:00
|
|
|
if !branch.script.is_empty() {
|
|
|
|
ew_chatscript.send(ChatScriptEvent {
|
|
|
|
name: branch.script.clone(),
|
|
|
|
param: branch.script_parameter.clone(),
|
2024-03-23 20:26:56 +00:00
|
|
|
param2: branch.script_parameter2.clone(),
|
2024-03-23 19:13:37 +00:00
|
|
|
});
|
|
|
|
}
|
2024-03-20 01:03:42 +00:00
|
|
|
}
|
|
|
|
break; // let's only handle one of these per frame
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 04:38:11 +00:00
|
|
|
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>,
|
2024-03-23 19:13:37 +00:00
|
|
|
mut ew_chatscript: EventWriter<ChatScriptEvent>,
|
2024-03-19 05:01:17 +00:00
|
|
|
time: Res<Time>,
|
2024-03-19 04:38:11 +00:00
|
|
|
chat_branches: Query<&ChatBranch>, // TODO: use Table for faster iteration?
|
|
|
|
) {
|
2024-03-19 05:01:17 +00:00
|
|
|
let now = time.elapsed_seconds_f64();
|
2024-03-19 04:38:11 +00:00
|
|
|
for (entity, mut chat) in &mut q_conv {
|
|
|
|
if chat.label == "EXIT" {
|
2024-03-20 01:03:42 +00:00
|
|
|
info!("Despawning chat.");
|
2024-03-19 04:38:11 +00:00
|
|
|
commands.entity(entity).despawn();
|
|
|
|
continue;
|
|
|
|
}
|
2024-03-19 05:01:17 +00:00
|
|
|
if now < chat.timer {
|
|
|
|
continue;
|
|
|
|
}
|
2024-03-19 04:38:11 +00:00
|
|
|
|
|
|
|
let branches: Vec<&ChatBranch> = chat_branches.iter()
|
2024-03-20 01:03:42 +00:00
|
|
|
.filter(|branch| branch.id == chat.id
|
|
|
|
&& branch.label == chat.label
|
|
|
|
&& branch.choice == "")
|
2024-03-19 04:38:11 +00:00
|
|
|
.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];
|
2024-03-19 22:44:00 +00:00
|
|
|
|
2024-03-20 04:52:02 +00:00
|
|
|
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()),
|
2024-03-23 19:13:37 +00:00
|
|
|
"warn" => log.warning(branch.reply.clone()),
|
2024-03-20 04:52:02 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
2024-03-19 22:44:00 +00:00
|
|
|
}
|
|
|
|
|
2024-03-19 04:38:11 +00:00
|
|
|
if chat.label == "EXIT" {
|
2024-03-20 01:03:42 +00:00
|
|
|
// TODO: isn't this dead code?
|
2024-03-19 04:38:11 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
chat.label = branch.goto.clone();
|
|
|
|
if branch.sound != "" {
|
|
|
|
let sfx = audio::str2sfx(branch.sound.as_str());
|
|
|
|
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
|
|
|
}
|
2024-03-19 05:01:17 +00:00
|
|
|
chat.timer = now + branch.delay;
|
2024-03-20 01:03:42 +00:00
|
|
|
|
|
|
|
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(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2024-03-23 19:13:37 +00:00
|
|
|
|
|
|
|
if !branch.script.is_empty() {
|
|
|
|
ew_chatscript.send(ChatScriptEvent {
|
|
|
|
name: branch.script.clone(),
|
|
|
|
param: branch.script_parameter.clone(),
|
2024-03-23 20:26:56 +00:00
|
|
|
param2: branch.script_parameter2.clone(),
|
2024-03-23 19:13:37 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_chat_scripts(
|
|
|
|
mut er_chatscript: EventReader<ChatScriptEvent>,
|
2024-03-23 20:26:56 +00:00
|
|
|
mut q_actor: Query<(&mut Actor, &mut Suit), Without<Player>>,
|
2024-03-23 19:13:37 +00:00
|
|
|
mut q_player: Query<(&mut Actor, &mut Suit), With<Player>>,
|
|
|
|
) {
|
|
|
|
for script in er_chatscript.read() {
|
|
|
|
match script.name.as_str() {
|
2024-03-23 20:26:56 +00:00
|
|
|
"refilloxygen" => if let Ok(mut amount) = script.param.parse::<f32>() {
|
2024-03-23 19:13:37 +00:00
|
|
|
for (mut _actor, mut suit) in q_player.iter_mut() {
|
2024-03-23 20:26:56 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2024-03-23 19:13:37 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error!("Invalid parameter for command `{}`: `{}`", script.name, script.param);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2024-03-19 04:38:11 +00:00
|
|
|
}
|
|
|
|
}
|