outfly/src/actor.rs

297 lines
11 KiB
Rust

use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
use crate::{actor, audio, chat, nature, settings};
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MIN_INTERACT_DISTANCE: f32 = 30.0;
const NO_RIDE: u32 = 0;
pub struct ActorPlugin;
impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) {
app.add_systems(FixedUpdate, (
update_physics_lifeforms,
));
app.add_systems(Update, (
handle_input,
handle_collisions,
));
app.add_systems(PostUpdate, (
handle_vehicle_enter_exit,
));
app.add_event::<VehicleEnterExitEvent>();
}
}
#[derive(Event)]
pub struct VehicleEnterExitEvent {
vehicle: Entity,
driver: Entity,
is_entering: bool,
is_player: bool
}
#[derive(Component)]
pub struct Actor {
pub id: String,
pub hp: f32,
pub m: f32, // mass
pub inside_entity: u32,
pub camdistance: f32,
}
impl Default for Actor {
fn default() -> Self {
Self {
id: "".to_string(),
hp: 100.0,
m: 100.0,
inside_entity: NO_RIDE,
camdistance: 15.0,
}
}
}
#[derive(Component)] pub struct Player; // Attached to the suit of the player
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct PlayerInConversation;
#[derive(Component)] pub struct InConversationWithPlayer;
#[derive(Component)] pub struct ActorEnteringVehicle;
#[derive(Component)] pub struct ActorVehicleBeingEntered;
#[derive(Component)]
pub struct LifeForm {
pub is_alive: bool,
pub adrenaline: f32,
pub adrenaline_baseline: f32,
pub adrenaline_jolt: f32,
}
impl Default for LifeForm { fn default() -> Self { Self {
is_alive: true,
adrenaline: 0.3,
adrenaline_baseline: 0.3,
adrenaline_jolt: 0.0,
}}}
#[derive(Component)]
pub struct Vehicle {
stored_drivers_collider: Option<Collider>,
}
impl Default for Vehicle { fn default() -> Self { Self {
stored_drivers_collider: None,
}}}
#[derive(Copy, Clone, PartialEq)]
pub enum EngineType {
Monopropellant,
Rocket,
Ion,
}
#[derive(Component)]
pub struct Engine {
pub thrust_forward: f32,
pub thrust_back: f32,
pub thrust_sideways: f32,
pub reaction_wheels: f32,
pub engine_type: EngineType,
pub warmup_seconds: f32,
pub current_warmup: 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,
engine_type: EngineType::Monopropellant,
warmup_seconds: 1.5,
current_warmup: 0.0,
}
}
}
#[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]
}
impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } }
const SUIT_SIMPLE: Suit = Suit {
power: 1e5,
power_max: 1e5,
oxygen: nature::OXY_D,
oxygen_max: nature::OXY_D,
integrity: 1.0,
};
pub fn update_physics_lifeforms(
time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut Suit, &LinearVelocity)>,
) {
let d = time.delta_seconds();
for (mut lifeform, mut suit, velocity) in query.iter_mut() {
if lifeform.adrenaline_jolt.abs() > 1e-3 {
lifeform.adrenaline_jolt *= 0.99;
}
else {
lifeform.adrenaline_jolt = 0.0
}
let speed = velocity.length();
if speed > 1000.0 {
lifeform.adrenaline += 0.001;
}
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);
}
}
pub fn handle_input(
mut commands: Commands,
keyboard_input: Res<ButtonInput<KeyCode>>,
settings: ResMut<settings::Settings>,
q_talker: Query<(&chat::Talker, &Transform), Without<actor::Player>>,
mut player: Query<(Entity, &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<chat::StartConversationEvent>,
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
) {
let mindist = MIN_INTERACT_DISTANCE * MIN_INTERACT_DISTANCE;
if keyboard_input.just_pressed(settings.key_interact) {
// Talking to people
if let Ok((_player_entity, _player_actor, player)) = player.get_single() {
for (talker, transform) in &q_talker {
// TODO: replace Transform.translation with Position
if transform.translation.distance_squared(player.translation) <= mindist {
ew_conv.send(chat::StartConversationEvent{talker: talker.clone()});
break;
}
}
}
// 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;
}
}
}
}
}
else if keyboard_input.just_pressed(settings.key_vehicle) {
// Exiting Vehicles
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,
});
break;
}
}
}
}
pub fn handle_vehicle_enter_exit(
mut commands: Commands,
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
for event in er_vehicle.read() {
for (driver, mut driver_vis, driver_collider) in q_drivers.iter_mut() {
if driver == event.driver {
for (vehicle, mut vehicle_component, mut vehicle_vis) in q_vehicles.iter_mut() {
if !event.is_player {
continue;
}
if vehicle == event.vehicle {
if event.is_entering {
// Entering Vehicle
if let Some(collider) = driver_collider {
vehicle_component.stored_drivers_collider = Some(collider.clone());
}
commands.entity(driver).remove::<RigidBody>();
*driver_vis = Visibility::Hidden; //seems to have no effect...
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
commands.entity(driver).remove::<PlayerCamera>();
commands.entity(driver).remove::<Collider>();
commands.entity(vehicle).insert(PlayerCamera);
commands.entity(vehicle).insert(PlayerDrivesThis);
}
else {
// Exiting Vehicle
if let Some(collider) = &vehicle_component.stored_drivers_collider {
commands.entity(driver).insert(collider.clone());
}
commands.entity(driver).insert(RigidBody::Dynamic);
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
commands.entity(vehicle).remove::<PlayerCamera>();
commands.entity(driver).insert(PlayerCamera);
commands.entity(vehicle).remove::<PlayerDrivesThis>();
*vehicle_vis = Visibility::Visible;
}
}
}
}
}
}
}
fn handle_collisions(
mut collision_event_reader: EventReader<CollisionStarted>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_player: Query<Entity, With<PlayerCamera>>,
mut q_player_lifeform: Query<&mut LifeForm, With<Player>>,
) {
if let (Ok(player), Ok(mut lifeform)) = (q_player.get_single(), q_player_lifeform.get_single_mut()) {
for CollisionStarted(entity1, entity2) in collision_event_reader.read() {
if *entity1 == player || *entity2 == player {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
lifeform.adrenaline_jolt += 0.1;
}
}
}
}