outfly/src/actor.rs

397 lines
12 KiB
Rust
Raw Normal View History

2024-03-17 22:49:50 +00:00
use bevy::prelude::*;
use crate::{nature, settings, actor, audio, hud};
const MIN_INTERACT_DISTANCE: f32 = 30.0;
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,
));
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,
handle_conversations,
handle_input,
));
app.add_event::<StartConversationEvent>();
2024-03-20 01:03:42 +00:00
app.add_event::<SendMessageEvent>();
2024-03-17 22:49:50 +00:00
}
}
#[derive(Event)]
pub struct StartConversationEvent {
2024-03-19 17:15:19 +00:00
pub talker: Talker,
}
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-17 22:49:50 +00:00
#[derive(Component)]
pub struct Actor {
pub hp: f32,
pub m: f32, // mass
pub v: Vec3, // velocity
2024-03-19 15:14:12 +00:00
pub angular_momentum: Quat,
pub inside_entity: u32,
2024-03-17 22:49:50 +00:00
}
impl Default for Actor {
fn default() -> Self {
Self {
hp: 100.0,
m: 100.0,
v: Vec3::ZERO,
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
}
}
}
#[derive(Component)] pub struct Player;
#[derive(Component)] pub struct PlayerDrivesThis;
#[derive(Component)] pub struct PlayerInConversation;
#[derive(Component)] pub struct InConversationWithPlayer;
#[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,
pub level: String,
2024-03-19 04:38:11 +00:00
pub reply: String,
pub goto: String,
pub choice: String,
}
#[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
}
#[derive(Component)]
2024-03-19 17:15:19 +00:00
#[derive(Clone)]
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-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-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,
}}}
#[derive(Component)]
pub struct Vehicle;
#[derive(Component)]
pub struct Engine {
pub thrust_forward: f32,
pub thrust_back: f32,
pub thrust_sideways: f32,
pub reaction_wheels: f32,
}
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-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,
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,
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>,
mut query: Query<(&mut LifeForm, &mut Suit)>,
) {
let d = time.delta_seconds();
for (mut lifeform, mut suit) in query.iter_mut() {
if lifeform.adrenaline_jolt.abs() > 1e-3 {
lifeform.adrenaline_jolt *= 0.99;
}
else {
lifeform.adrenaline_jolt = 0.0
}
lifeform.adrenaline = (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0);
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
}
}
pub fn handle_input(
mut commands: Commands,
keyboard_input: Res<ButtonInput<KeyCode>>,
settings: ResMut<settings::Settings>,
q_talker: Query<(&Talker, &Transform), Without<actor::Player>>,
mut player: Query<(&mut Actor, &mut Transform), With<actor::Player>>,
mut q_vehicles: Query<(Entity, &mut Visibility, &Transform), (With<actor::Vehicle>, Without<actor::Player>)>,
mut ew_conv: EventWriter<StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
)
{
let mindist = MIN_INTERACT_DISTANCE * MIN_INTERACT_DISTANCE;
if keyboard_input.just_pressed(settings.key_interact) {
if let Ok((_player_actor, player)) = player.get_single() {
for (talker, transform) in &q_talker {
if transform.translation.distance_squared(player.translation) <= mindist {
2024-03-19 17:15:19 +00:00
ew_conv.send(StartConversationEvent{talker: talker.clone()});
break;
}
}
}
}
else if keyboard_input.just_pressed(settings.key_vehicle) {
if let Ok((mut player_actor, mut player)) = player.get_single_mut() {
for (entity, mut visibility, vehicle_transform) in q_vehicles.iter_mut() {
if vehicle_transform.translation.distance_squared(player.translation) <= mindist {
player_actor.inside_entity = entity.index();
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
*player = *vehicle_transform;
*visibility = Visibility::Hidden;
commands.entity(entity).insert(PlayerDrivesThis);
break;
}
}
}
}
}
2024-03-19 04:38:11 +00:00
pub fn handle_new_conversations(
mut commands: Commands,
mut er_conv: EventReader<StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_conv: Query<&Chat>,
2024-03-19 05:01:17 +00:00
time: Res<Time>,
) {
let label = "INIT";
2024-03-19 17:15:19 +00:00
for event in er_conv.read() {
// 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();
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 {
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
});
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>,
mut q_conv: Query<(Entity, &mut Chat)>,
time: Res<Time>,
chat_branches: Query<&ChatBranch>,
mut log: ResMut<hud::Log>,
) {
let now = time.elapsed_seconds_f64();
for event in er_sendmsg.read() {
for (entity, mut chat) in &mut q_conv {
if chat.id != event.conv_id {
continue;
}
if event.conv_label == "EXIT" {
info!("Despawning chat.");
commands.entity(entity).despawn();
continue;
}
let branches: Vec<&ChatBranch> = chat_branches.iter()
.filter(|branch| branch.id == event.conv_id
&& branch.label == event.conv_label
&& branch.choice == event.text)
.collect();
if branches.len() != 1 {
error!("Expected 1 branch with ID '{}' and label '{}', but got {}! Aborting conversation.", chat.id, chat.label, branches.len());
continue;
}
let branch = branches[0];
// TODO despawn the choices
if !branch.reply.is_empty() {
match branch.level.as_str() {
"chat" => log.chat(branch.reply.clone(), branch.name.clone()),
"info" => log.info(branch.reply.clone()),
2024-03-21 04:32:39 +00:00
"warn" => log.warning(branch.reply.clone()),
_ => (),
}
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(),
});
}
}
}
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-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];
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-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-19 04:38:11 +00:00
}
}