apply cargo fmt

This commit is contained in:
yuni 2024-05-22 04:57:20 +02:00
parent b0ac508d91
commit 87199f41db
15 changed files with 1646 additions and 1259 deletions

View file

@ -14,9 +14,9 @@
// //
// This module should never handle any visual aspects directly. // This module should never handle any visual aspects directly.
use crate::prelude::*;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use crate::prelude::*;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0; pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0; const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
@ -25,23 +25,30 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0;
pub struct ActorPlugin; pub struct ActorPlugin;
impl Plugin for ActorPlugin { impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(FixedUpdate, ( app.add_systems(
FixedUpdate,
(
update_physics_lifeforms, update_physics_lifeforms,
update_power, update_power,
handle_wants_maxrotation, handle_wants_maxrotation,
handle_wants_maxvelocity, handle_wants_maxvelocity,
)); ),
app.add_systems(PostUpdate, handle_gforce );
app.add_systems(
PostUpdate,
handle_gforce
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
.after(sync::position_to_transform)); .after(sync::position_to_transform),
app.add_systems(Update, ( );
app.add_systems(
Update,
(
handle_input.run_if(in_control), handle_input.run_if(in_control),
handle_collisions, handle_collisions,
handle_damage, handle_damage,
)); ),
app.add_systems(PostUpdate, ( );
handle_vehicle_enter_exit, app.add_systems(PostUpdate, (handle_vehicle_enter_exit,));
));
app.add_event::<VehicleEnterExitEvent>(); app.add_event::<VehicleEnterExitEvent>();
} }
} }
@ -66,7 +73,7 @@ pub struct VehicleEnterExitEvent {
driver: Entity, driver: Entity,
name: Option<String>, name: Option<String>,
is_entering: bool, is_entering: bool,
is_player: bool is_player: bool,
} }
#[derive(Component)] #[derive(Component)]
@ -112,26 +119,41 @@ pub struct ExperiencesGForce {
pub last_linear_velocity: DVec3, pub last_linear_velocity: DVec3,
pub ignore_gforce_seconds: f32, pub ignore_gforce_seconds: f32,
} }
impl Default for ExperiencesGForce { fn default() -> Self { Self { impl Default for ExperiencesGForce {
fn default() -> Self {
Self {
gforce: 0.0, gforce: 0.0,
damage_threshold: 100.0, damage_threshold: 100.0,
visual_effect_threshold: 20.0, visual_effect_threshold: 20.0,
visual_effect: 0.0, visual_effect: 0.0,
last_linear_velocity: DVec3::splat(0.0), last_linear_velocity: DVec3::splat(0.0),
ignore_gforce_seconds: 0.0, ignore_gforce_seconds: 0.0,
}}} }
}
}
#[derive(Component)] pub struct Player; // Attached to the suit of the player #[derive(Component)]
#[derive(Component)] pub struct PlayerCollider; // Attached to the collider of the suit of the player pub struct Player; // Attached to the suit of the player
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle #[derive(Component)]
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view pub struct PlayerCollider; // Attached to the collider of the suit of the player
#[derive(Component)] pub struct JustNowEnteredVehicle; #[derive(Component)]
#[derive(Component)] pub struct ActorEnteringVehicle; pub struct PlayerDrivesThis; // Attached to the entered vehicle
#[derive(Component)] pub struct ActorVehicleBeingEntered; #[derive(Component)]
#[derive(Component)] pub struct PlayersFlashLight; pub struct PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct WantsMaxRotation(pub f64); #[derive(Component)]
#[derive(Component)] pub struct WantsMaxVelocity(pub f64); pub struct JustNowEnteredVehicle;
#[derive(Component)] pub struct Identifier(pub String); #[derive(Component)]
pub struct ActorEnteringVehicle;
#[derive(Component)]
pub struct ActorVehicleBeingEntered;
#[derive(Component)]
pub struct PlayersFlashLight;
#[derive(Component)]
pub struct WantsMaxRotation(pub f64);
#[derive(Component)]
pub struct WantsMaxVelocity(pub f64);
#[derive(Component)]
pub struct Identifier(pub String);
#[derive(Component)] #[derive(Component)]
pub struct LifeForm { pub struct LifeForm {
@ -140,20 +162,28 @@ pub struct LifeForm {
pub adrenaline_baseline: f32, pub adrenaline_baseline: f32,
pub adrenaline_jolt: f32, pub adrenaline_jolt: f32,
} }
impl Default for LifeForm { fn default() -> Self { Self { impl Default for LifeForm {
fn default() -> Self {
Self {
is_alive: true, is_alive: true,
adrenaline: 0.3, adrenaline: 0.3,
adrenaline_baseline: 0.3, adrenaline_baseline: 0.3,
adrenaline_jolt: 0.0, adrenaline_jolt: 0.0,
}}} }
}
}
#[derive(Component)] #[derive(Component)]
pub struct Vehicle { pub struct Vehicle {
stored_drivers_collider: Option<Collider>, stored_drivers_collider: Option<Collider>,
} }
impl Default for Vehicle { fn default() -> Self { Self { impl Default for Vehicle {
fn default() -> Self {
Self {
stored_drivers_collider: None, stored_drivers_collider: None,
}}} }
}
}
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum EngineType { pub enum EngineType {
@ -192,7 +222,11 @@ pub struct Suit {
pub oxygen_max: f32, pub oxygen_max: f32,
pub integrity: f32, // [0.0 - 1.0] pub integrity: f32, // [0.0 - 1.0]
} }
impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } } impl Default for Suit {
fn default() -> Self {
SUIT_SIMPLE
}
}
const SUIT_SIMPLE: Suit = Suit { const SUIT_SIMPLE: Suit = Suit {
oxygen: nature::OXY_D, oxygen: nature::OXY_D,
@ -236,8 +270,7 @@ pub fn update_power(
} }
} }
} }
battery.power = (battery.power + battery.reactor * d) battery.power = (battery.power + battery.reactor * d).clamp(0.0, battery.capacity);
.clamp(0.0, battery.capacity);
} }
} }
@ -249,15 +282,15 @@ pub fn update_physics_lifeforms(
for (mut lifeform, mut hp, mut suit, velocity) in query.iter_mut() { for (mut lifeform, mut hp, mut suit, velocity) in query.iter_mut() {
if lifeform.adrenaline_jolt.abs() > 1e-3 { if lifeform.adrenaline_jolt.abs() > 1e-3 {
lifeform.adrenaline_jolt *= 0.99; lifeform.adrenaline_jolt *= 0.99;
} } else {
else {
lifeform.adrenaline_jolt = 0.0 lifeform.adrenaline_jolt = 0.0
} }
let speed = velocity.length(); let speed = velocity.length();
if speed > 1000.0 { if speed > 1000.0 {
lifeform.adrenaline += 0.001; lifeform.adrenaline += 0.001;
} }
lifeform.adrenaline = (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.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 mut oxygen_drain = nature::OXY_S;
let integr_threshold = 0.5; let integr_threshold = 0.5;
@ -294,7 +327,14 @@ pub fn handle_input(
player: Query<Entity, With<actor::Player>>, player: Query<Entity, With<actor::Player>>,
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>, mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
q_vehicles: Query<(Entity, &Actor, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>, q_vehicles: Query<
(Entity, &Actor, &Transform),
(
With<actor::Vehicle>,
Without<actor::Player>,
Without<Camera>,
),
>,
mut ew_conv: EventWriter<chat::StartConversationEvent>, mut ew_conv: EventWriter<chat::StartConversationEvent>,
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>, mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
@ -313,9 +353,12 @@ pub fn handle_input(
.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::<chat::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(chat::StartConversationEvent{talker: talker.clone()}); ew_conv.send(chat::StartConversationEvent {
talker: talker.clone(),
});
} }
} }
// Entering Vehicles // Entering Vehicles
@ -340,11 +383,12 @@ pub fn handle_input(
} }
} }
} }
} } else if keyboard_input.just_pressed(settings.key_vehicle) {
else if keyboard_input.just_pressed(settings.key_vehicle) {
// Exiting Vehicles // Exiting Vehicles
for vehicle_entity in &q_player_drives { for vehicle_entity in &q_player_drives {
commands.entity(vehicle_entity).insert(ActorVehicleBeingEntered); commands
.entity(vehicle_entity)
.insert(ActorVehicleBeingEntered);
commands.entity(player_entity).insert(ActorEnteringVehicle); commands.entity(player_entity).insert(ActorEnteringVehicle);
ew_vehicle.send(VehicleEnterExitEvent { ew_vehicle.send(VehicleEnterExitEvent {
vehicle: vehicle_entity, vehicle: vehicle_entity,
@ -355,8 +399,7 @@ pub fn handle_input(
}); });
break; break;
} }
} } else if keyboard_input.just_pressed(settings.key_flashlight) {
else if keyboard_input.just_pressed(settings.key_flashlight) {
for mut flashlight_vis in &mut q_flashlight { for mut flashlight_vis in &mut q_flashlight {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
if *flashlight_vis == Visibility::Hidden { if *flashlight_vis == Visibility::Hidden {
@ -367,8 +410,7 @@ pub fn handle_input(
settings.flashlight_active = false; settings.flashlight_active = false;
} }
} }
} } else if keyboard_input.just_pressed(settings.key_cruise_control) {
else if keyboard_input.just_pressed(settings.key_cruise_control) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
settings.cruise_control_active ^= true; settings.cruise_control_active ^= true;
} }
@ -379,9 +421,28 @@ pub fn handle_vehicle_enter_exit(
mut settings: ResMut<Settings>, mut settings: ResMut<Settings>,
mut er_vehicle: EventReader<VehicleEnterExitEvent>, mut er_vehicle: EventReader<VehicleEnterExitEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>, mut ew_achievement: EventWriter<game::AchievementEvent>,
mut q_playerflashlight: Query<&mut Visibility, (With<PlayersFlashLight>, Without<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>, mut q_playerflashlight: Query<
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>, &mut Visibility,
mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>, (
With<PlayersFlashLight>,
Without<ActorVehicleBeingEntered>,
Without<ActorEnteringVehicle>,
),
>,
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>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) { ) {
for event in er_vehicle.read() { for event in er_vehicle.read() {
@ -410,11 +471,11 @@ pub fn handle_vehicle_enter_exit(
settings.flashlight_active = false; settings.flashlight_active = false;
} }
if let Some(vehicle_name) = &event.name { if let Some(vehicle_name) = &event.name {
ew_achievement.send(game::AchievementEvent ew_achievement.send(game::AchievementEvent::RideVehicle(
::RideVehicle(vehicle_name.clone())); vehicle_name.clone(),
));
} }
} } else {
else {
// Exiting Vehicle // Exiting Vehicle
if let Some(collider) = &vehicle_component.stored_drivers_collider { if let Some(collider) = &vehicle_component.stored_drivers_collider {
commands.entity(driver).insert(collider.clone()); commands.entity(driver).insert(collider.clone());
@ -439,7 +500,9 @@ fn handle_collisions(
q_player: Query<Entity, With<PlayerCollider>>, q_player: Query<Entity, With<PlayerCollider>>,
mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With<Player>>, mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With<Player>>,
) { ) {
if let (Ok(player), Ok((mut lifeform, mut suit))) = (q_player.get_single(), q_player_lifeform.get_single_mut()) { if let (Ok(player), Ok((mut lifeform, mut suit))) =
(q_player.get_single(), q_player_lifeform.get_single_mut())
{
for CollisionStarted(entity1, entity2) in collision_event_reader.read() { for CollisionStarted(entity1, entity2) in collision_event_reader.read() {
if *entity1 == player || *entity2 == player { if *entity1 == player || *entity2 == player {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
@ -461,9 +524,9 @@ fn handle_wants_maxrotation(
if total > maxrot.0 { if total > maxrot.0 {
v_ang.0 = DVec3::splat(0.0); v_ang.0 = DVec3::splat(0.0);
} }
} } else {
else { let angular_slowdown: f64 =
let angular_slowdown: f64 = (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64; (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64;
v_ang.0 *= angular_slowdown; v_ang.0 *= angular_slowdown;
} }
} }
@ -485,7 +548,8 @@ fn handle_wants_maxvelocity(
} }
// TODO: respect engine parameters for different thrusts for different directions // TODO: respect engine parameters for different thrusts for different directions
let avg_thrust = (engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0; let avg_thrust =
(engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0;
let acceleration = (avg_thrust * dt) as f64 * -v.0; let acceleration = (avg_thrust * dt) as f64 * -v.0;
v.0 += acceleration; v.0 += acceleration;
if v.0.length() + EPSILON < acceleration.length() { if v.0.length() + EPSILON < acceleration.length() {
@ -507,8 +571,7 @@ fn handle_damage(
if hp.current <= 0.0 { if hp.current <= 0.0 {
ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype)); ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype));
} }
} } else {
else {
hp.current -= hp.damage; hp.current -= hp.damage;
} }
hp.damage = 0.0; hp.damage = 0.0;
@ -535,12 +598,12 @@ fn handle_gforce(
if gforce.visual_effect > 0.0001 { if gforce.visual_effect > 0.0001 {
gforce.visual_effect *= 0.984; gforce.visual_effect *= 0.984;
} } else if gforce.visual_effect > 0.0 {
else if gforce.visual_effect > 0.0 {
gforce.visual_effect = 0.0; gforce.visual_effect = 0.0;
} }
if gforce.gforce > gforce.visual_effect_threshold { if gforce.gforce > gforce.visual_effect_threshold {
gforce.visual_effect += (gforce.gforce - gforce.visual_effect_threshold).powf(2.0) / 300000.0 gforce.visual_effect +=
(gforce.gforce - gforce.visual_effect_threshold).powf(2.0) / 300000.0
} }
} }
} }

View file

@ -10,16 +10,19 @@
// //
// This module manages sound effects and music. // This module manages sound effects and music.
use bevy::prelude::*;
use bevy::audio::{PlaybackMode, Volume};
use crate::prelude::*; use crate::prelude::*;
use bevy::audio::{PlaybackMode, Volume};
use bevy::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
pub struct AudioPlugin; pub struct AudioPlugin;
impl Plugin for AudioPlugin { impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>())); app.add_systems(
Update,
respawn_sinks.run_if(on_event::<RespawnSinksEvent>()),
);
app.add_systems(Update, play_zoom_sfx); app.add_systems(Update, play_zoom_sfx);
app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>())); app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>()));
app.add_systems(PostUpdate, play_sfx); app.add_systems(PostUpdate, play_sfx);
@ -28,27 +31,47 @@ impl Plugin for AudioPlugin {
app.add_event::<PauseAllSfxEvent>(); app.add_event::<PauseAllSfxEvent>();
app.add_event::<ToggleMusicEvent>(); app.add_event::<ToggleMusicEvent>();
app.add_event::<RespawnSinksEvent>(); app.add_event::<RespawnSinksEvent>();
app.insert_resource(ZoomTimer( app.insert_resource(ZoomTimer(Timer::from_seconds(0.09, TimerMode::Repeating)));
Timer::from_seconds(0.09, TimerMode::Repeating)));
} }
} }
#[derive(Resource)] pub struct ZoomTimer(Timer); #[derive(Resource)]
pub struct ZoomTimer(Timer);
const PATHS: &[(SfxType, Sfx, &str)] = &[ const PATHS: &[(SfxType, Sfx, &str)] = &[
(SfxType::BGM, Sfx::BGM, "music/Aleksey Chistilin - Cinematic Cello.ogg"), (
(SfxType::LoopSfx, Sfx::ElectricMotor, "sounds/electricmotor.ogg"), SfxType::BGM,
Sfx::BGM,
"music/Aleksey Chistilin - Cinematic Cello.ogg",
),
(
SfxType::LoopSfx,
Sfx::ElectricMotor,
"sounds/electricmotor.ogg",
),
(SfxType::LoopSfx, Sfx::Ion, "sounds/ion.ogg"), (SfxType::LoopSfx, Sfx::Ion, "sounds/ion.ogg"),
(SfxType::LoopSfx, Sfx::Rocket, "sounds/rocket.ogg"), (SfxType::LoopSfx, Sfx::Rocket, "sounds/rocket.ogg"),
(SfxType::LoopSfx, Sfx::Thruster, "sounds/thruster.ogg"), (SfxType::LoopSfx, Sfx::Thruster, "sounds/thruster.ogg"),
(SfxType::OneOff, Sfx::Achieve, "sounds/achieve.ogg"), (SfxType::OneOff, Sfx::Achieve, "sounds/achieve.ogg"),
(SfxType::OneOff, Sfx::Click, "sounds/click-button-140881-crop.ogg"), (
SfxType::OneOff,
Sfx::Click,
"sounds/click-button-140881-crop.ogg",
),
(SfxType::OneOff, Sfx::Connect, "sounds/connect.ogg"), (SfxType::OneOff, Sfx::Connect, "sounds/connect.ogg"),
(SfxType::OneOff, Sfx::Crash, "sounds/crash.ogg"), (SfxType::OneOff, Sfx::Crash, "sounds/crash.ogg"),
(SfxType::OneOff, Sfx::EnterVehicle, "sounds/bikestart.ogg"), (SfxType::OneOff, Sfx::EnterVehicle, "sounds/bikestart.ogg"),
(SfxType::OneOff, Sfx::IncomingChatMessage, "sounds/connect.ogg"), (
SfxType::OneOff,
Sfx::IncomingChatMessage,
"sounds/connect.ogg",
),
(SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"), (SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"),
(SfxType::OneOff, Sfx::Switch, "sounds/typosonic-typing-192811-crop.ogg"), (
SfxType::OneOff,
Sfx::Switch,
"sounds/typosonic-typing-192811-crop.ogg",
),
(SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"), (SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"),
(SfxType::OneOff, Sfx::Woosh, "sounds/woosh.ogg"), (SfxType::OneOff, Sfx::Woosh, "sounds/woosh.ogg"),
(SfxType::OneOff, Sfx::Zoom, "sounds/zoom.ogg"), (SfxType::OneOff, Sfx::Zoom, "sounds/zoom.ogg"),
@ -96,11 +119,16 @@ pub enum SfxType {
OneOff, OneOff,
} }
#[derive(Event)] pub struct PlaySfxEvent(pub Sfx); #[derive(Event)]
#[derive(Event)] pub struct PauseAllSfxEvent; pub struct PlaySfxEvent(pub Sfx);
#[derive(Event)] pub struct RespawnSinksEvent; #[derive(Event)]
#[derive(Event)] pub struct ToggleMusicEvent(); pub struct PauseAllSfxEvent;
#[derive(Resource)] pub struct Sounds(HashMap<Sfx, Handle<AudioSource>>); #[derive(Event)]
pub struct RespawnSinksEvent;
#[derive(Event)]
pub struct ToggleMusicEvent();
#[derive(Resource)]
pub struct Sounds(HashMap<Sfx, Handle<AudioSource>>);
pub fn setup( pub fn setup(
mut commands: Commands, mut commands: Commands,
@ -140,7 +168,7 @@ pub fn respawn_sinks(
}, },
}, },
)); ));
}, }
SfxType::LoopSfx => { SfxType::LoopSfx => {
commands.spawn(( commands.spawn((
*sfx, *sfx,
@ -154,8 +182,8 @@ pub fn respawn_sinks(
}, },
}, },
)); ));
}, }
SfxType::OneOff => () SfxType::OneOff => (),
} }
} }
} }
@ -192,8 +220,7 @@ pub fn update_music(
} }
if settings.mute_music { if settings.mute_music {
bgm_sink.pause(); bgm_sink.pause();
} } else {
else {
bgm_sink.play(); bgm_sink.play();
} }
} }
@ -211,7 +238,9 @@ pub fn play_zoom_sfx(
if !timer.0.tick(time.delta()).just_finished() { if !timer.0.tick(time.delta()).just_finished() {
return; return;
} }
if settings.map_active && (*last_zoom_level - mapcam.target_zoom_level).abs() > mapcam.target_zoom_level * 0.2 { if settings.map_active
&& (*last_zoom_level - mapcam.target_zoom_level).abs() > mapcam.target_zoom_level * 0.2
{
if *last_zoom_level != 0.0 && mapcam.target_zoom_level != camera::INITIAL_ZOOM_LEVEL { if *last_zoom_level != 0.0 && mapcam.target_zoom_level != camera::INITIAL_ZOOM_LEVEL {
ew_sfx.send(PlaySfxEvent(Sfx::Zoom)); ew_sfx.send(PlaySfxEvent(Sfx::Zoom));
} }
@ -219,9 +248,7 @@ pub fn play_zoom_sfx(
} }
} }
pub fn pause_all( pub fn pause_all(q_audiosinks: Query<&AudioSink, With<Sfx>>) {
q_audiosinks: Query<&AudioSink, With<Sfx>>,
) {
for sink in &q_audiosinks { for sink in &q_audiosinks {
sink.pause(); sink.pause();
} }

View file

@ -12,16 +12,16 @@
// movement-related keyboard input, and provides some camera- // movement-related keyboard input, and provides some camera-
// related computation functions. // related computation functions.
use bevy::prelude::*; use crate::prelude::*;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::window::PrimaryWindow;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings}; use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}; use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap};
use bevy::prelude::*;
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*; use bevy::window::PrimaryWindow;
use bevy_xpbd_3d::plugins::sync; use bevy_xpbd_3d::plugins::sync;
use crate::prelude::*; use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
pub const INITIAL_ZOOM_LEVEL: f64 = 10.0; pub const INITIAL_ZOOM_LEVEL: f64 = 10.0;
@ -34,13 +34,19 @@ impl Plugin for CameraPlugin {
app.add_systems(Update, handle_input.run_if(in_control)); app.add_systems(Update, handle_input.run_if(in_control));
app.add_systems(Update, update_map_only_object_visibility.run_if(alive)); app.add_systems(Update, update_map_only_object_visibility.run_if(alive));
app.add_systems(Update, manage_player_actor.after(handle_input)); app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player app.add_systems(
PostUpdate,
sync_camera_to_player
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
.after(apply_input_to_player) .after(apply_input_to_player)
.before(TransformSystem::TransformPropagate)); .before(TransformSystem::TransformPropagate),
app.add_systems(PostUpdate, update_mapcam_center );
app.add_systems(
PostUpdate,
update_mapcam_center
.before(sync::position_to_transform) .before(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform)); .in_set(sync::SyncSet::PositionToTransform),
);
app.add_systems(Update, update_map_camera.run_if(in_control)); app.add_systems(Update, update_map_camera.run_if(in_control));
app.add_systems(Update, update_fov.run_if(alive)); app.add_systems(Update, update_fov.run_if(alive));
app.add_systems(PreUpdate, apply_input_to_player); app.add_systems(PreUpdate, apply_input_to_player);
@ -53,9 +59,12 @@ impl Plugin for CameraPlugin {
transform_to_position: false, transform_to_position: false,
}); });
// 2. Add own position->transform sync function // 2. Add own position->transform sync function
app.add_systems(PostUpdate, position_to_transform app.add_systems(
PostUpdate,
position_to_transform
.after(sync::position_to_transform) .after(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform)); .in_set(sync::SyncSet::PositionToTransform),
);
} }
} }
@ -93,10 +102,7 @@ impl Default for MapCam {
} }
} }
pub fn setup_camera( pub fn setup_camera(mut commands: Commands, settings: Res<var::Settings>) {
mut commands: Commands,
settings: Res<var::Settings>,
) {
// Add player // Add player
commands.spawn(( commands.spawn((
Camera3dBundle { Camera3dBundle {
@ -128,7 +134,8 @@ pub fn setup_camera(
minimum_distance: 0.1, minimum_distance: 0.1,
maximum_distance: 5000.0, maximum_distance: 5000.0,
..default() ..default()
}.into(), }
.into(),
..default() ..default()
}); });
@ -153,10 +160,10 @@ pub fn sync_camera_to_player(
// Translation // Translation
if settings.third_person { if settings.third_person {
camera_transform.translation = player_transform.translation + rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0)); camera_transform.translation = player_transform.translation
+ rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
camera_transform.rotation = rotation * Quat::from_euler(EulerRot::XYZ, -0.02, 0.0, 0.0); camera_transform.rotation = rotation * Quat::from_euler(EulerRot::XYZ, -0.02, 0.0, 0.0);
} } else {
else {
camera_transform.translation = player_transform.translation; camera_transform.translation = player_transform.translation;
camera_transform.rotation = rotation; camera_transform.rotation = rotation;
} }
@ -167,7 +174,14 @@ pub fn update_map_camera(
mut mapcam: ResMut<MapCam>, mut mapcam: ResMut<MapCam>,
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>, mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>, q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
q_target: Query<(Entity, &Transform), (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>, q_target: Query<
(Entity, &Transform),
(
With<hud::IsTargeted>,
Without<Camera>,
Without<actor::PlayerCamera>,
),
>,
q_target_changed: Query<(), Changed<hud::IsTargeted>>, q_target_changed: Query<(), Changed<hud::IsTargeted>>,
mut mouse_events: EventReader<MouseMotion>, mut mouse_events: EventReader<MouseMotion>,
mut er_mousewheel: EventReader<MouseWheel>, mut er_mousewheel: EventReader<MouseWheel>,
@ -195,7 +209,9 @@ pub fn update_map_camera(
// at the extreme values and the orientation will flicker back/forth. // at the extreme values and the orientation will flicker back/forth.
let min_zoom: f64 = target_trans.scale.x as f64 * 2.0; let min_zoom: f64 = target_trans.scale.x as f64 * 2.0;
let max_zoom: f64 = 17e18; // at this point, camera starts glitching let max_zoom: f64 = 17e18; // at this point, camera starts glitching
mapcam.pitch = (mapcam.pitch + mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64).clamp(-PI / 2.0 + EPSILON, PI / 2.0 - EPSILON); mapcam.pitch = (mapcam.pitch
+ mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64)
.clamp(-PI / 2.0 + EPSILON, PI / 2.0 - EPSILON);
mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64; mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64;
// Reset movement offset if target changes // Reset movement offset if target changes
@ -226,7 +242,11 @@ pub fn update_map_camera(
// Update zoom level // Update zoom level
if !mapcam.initialized { if !mapcam.initialized {
let factor: f64 = if target_trans == player_trans { 7.0 } else { 1.0 }; let factor: f64 = if target_trans == player_trans {
7.0
} else {
1.0
};
mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor; mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor;
mapcam.zoom_level *= target_trans.scale.x as f64 * factor; mapcam.zoom_level *= target_trans.scale.x as f64 * factor;
mapcam.initialized = true; mapcam.initialized = true;
@ -241,12 +261,16 @@ pub fn update_map_camera(
for wheel_event in er_mousewheel.read() { for wheel_event in er_mousewheel.read() {
change_zoom -= wheel_event.y as f64 * 3.0; change_zoom -= wheel_event.y as f64 * 3.0;
} }
mapcam.target_zoom_level = (mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom); mapcam.target_zoom_level =
(mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom);
let zoom_speed = 0.05; // should be between 0.0001 (slow) and 1.0 (instant) let zoom_speed = 0.05; // should be between 0.0001 (slow) and 1.0 (instant)
mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level + (1.0 - zoom_speed) * mapcam.zoom_level).clamp(min_zoom, max_zoom); mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level
+ (1.0 - zoom_speed) * mapcam.zoom_level)
.clamp(min_zoom, max_zoom);
// Update point of view // Update point of view
let pov_rotation = DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64); let pov_rotation =
DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
let point_of_view = pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0)); let point_of_view = pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0));
// Update movement offset // Update movement offset
@ -285,8 +309,7 @@ pub fn update_fov(
mut settings: ResMut<var::Settings>, mut settings: ResMut<var::Settings>,
mut q_camera: Query<&mut Projection, With<Camera>>, mut q_camera: Query<&mut Projection, With<Camera>>,
) { ) {
if let (Ok(gforce), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut()) if let (Ok(gforce), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut()) {
{
let fov: f32; let fov: f32;
if mouse_input.pressed(settings.key_zoom) { if mouse_input.pressed(settings.key_zoom) {
fov = settings.zoom_fov.to_radians(); fov = settings.zoom_fov.to_radians();
@ -294,12 +317,16 @@ pub fn update_fov(
settings.is_zooming = true; settings.is_zooming = true;
} }
} else { } else {
fov = (gforce.visual_effect.clamp(0.0, 1.0) * settings.fov_highspeed + settings.fov).to_radians(); fov = (gforce.visual_effect.clamp(0.0, 1.0) * settings.fov_highspeed + settings.fov)
.to_radians();
if settings.is_zooming { if settings.is_zooming {
settings.is_zooming = false; settings.is_zooming = false;
} }
}; };
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() }); *projection = Projection::Perspective(PerspectiveProjection {
fov: fov,
..default()
});
} }
} }
@ -325,18 +352,40 @@ fn manage_player_actor(
mut commands: Commands, mut commands: Commands,
settings: Res<var::Settings>, settings: Res<var::Settings>,
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>, mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
mut q_hiddenplayer: Query<(Entity, &mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity, Option<&mut actor::ExperiencesGForce>, Option<&actor::JustNowEnteredVehicle>), (With<actor::Player>, Without<actor::PlayerCamera>)>, mut q_hiddenplayer: Query<
q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With<actor::PlayerDrivesThis>, Without<actor::Player>)>, (
Entity,
&mut Visibility,
&mut Position,
&mut Rotation,
&mut LinearVelocity,
&mut AngularVelocity,
Option<&mut actor::ExperiencesGForce>,
Option<&actor::JustNowEnteredVehicle>,
),
(With<actor::Player>, Without<actor::PlayerCamera>),
>,
q_ride: Query<
(
&Transform,
&Position,
&Rotation,
&LinearVelocity,
&AngularVelocity,
),
(With<actor::PlayerDrivesThis>, Without<actor::Player>),
>,
) { ) {
for mut vis in &mut q_playercam { for mut vis in &mut q_playercam {
if settings.third_person || settings.map_active { if settings.third_person || settings.map_active {
*vis = Visibility::Inherited; *vis = Visibility::Inherited;
} } else {
else {
*vis = Visibility::Hidden; *vis = Visibility::Hidden;
} }
} }
for (entity, mut vis, mut pos, mut rot, mut v, mut angv, mut gforce, entering) in &mut q_hiddenplayer { for (entity, mut vis, mut pos, mut rot, mut v, mut angv, mut gforce, entering) in
&mut q_hiddenplayer
{
// If we are riding a vehicle, place the player at the position where // If we are riding a vehicle, place the player at the position where
// it would be after exiting the vehicle. // it would be after exiting the vehicle.
// I would rather place it in the center of the vehicle, but at the time // I would rather place it in the center of the vehicle, but at the time
@ -344,7 +393,8 @@ fn manage_player_actor(
// exiting the vehicle, so I'm doing it here instead, as a workaround. // exiting the vehicle, so I'm doing it here instead, as a workaround.
*vis = Visibility::Hidden; *vis = Visibility::Hidden;
if let Ok((ride_trans, ride_pos, ride_rot, ride_v, ride_angv)) = q_ride.get_single() { if let Ok((ride_trans, ride_pos, ride_rot, ride_v, ride_angv)) = q_ride.get_single() {
pos.0 = ride_pos.0 + DVec3::from(ride_trans.rotation * Vec3::new(0.0, 0.0, ride_trans.scale.z * 2.0)); pos.0 = ride_pos.0
+ DVec3::from(ride_trans.rotation * Vec3::new(0.0, 0.0, ride_trans.scale.z * 2.0));
rot.0 = ride_rot.0 * DQuat::from_array([-1.0, 0.0, 0.0, 0.0]); rot.0 = ride_rot.0 * DQuat::from_array([-1.0, 0.0, 0.0, 0.0]);
*v = ride_v.clone(); *v = ride_v.clone();
*angv = ride_angv.clone(); *angv = ride_angv.clone();
@ -353,7 +403,9 @@ fn manage_player_actor(
// vehicles at high relative speed, even though they probably should. // vehicles at high relative speed, even though they probably should.
if let (Some(gforce), Some(_)) = (&mut gforce, entering) { if let (Some(gforce), Some(_)) = (&mut gforce, entering) {
gforce.last_linear_velocity = v.0; gforce.last_linear_velocity = v.0;
commands.entity(entity).remove::<actor::JustNowEnteredVehicle>(); commands
.entity(entity)
.remove::<actor::JustNowEnteredVehicle>();
} }
} }
} }
@ -368,14 +420,17 @@ pub fn apply_input_to_player(
key_input: Res<ButtonInput<KeyCode>>, key_input: Res<ButtonInput<KeyCode>>,
q_audiosinks: Query<(&audio::Sfx, &AudioSink)>, q_audiosinks: Query<(&audio::Sfx, &AudioSink)>,
q_target: Query<&LinearVelocity, (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>, q_target: Query<&LinearVelocity, (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
mut q_playercam: Query<( mut q_playercam: Query<
(
&Transform, &Transform,
&mut actor::Engine, &mut actor::Engine,
&mut AngularVelocity, &mut AngularVelocity,
&mut LinearVelocity, &mut LinearVelocity,
&mut ExternalTorque, &mut ExternalTorque,
Option<&actor::PlayerDrivesThis>, Option<&actor::PlayerDrivesThis>,
), (With<actor::PlayerCamera>, Without<Camera>)>, ),
(With<actor::PlayerCamera>, Without<Camera>),
>,
) { ) {
if settings.map_active || !settings.in_control() { if settings.map_active || !settings.in_control() {
return; return;
@ -390,8 +445,7 @@ pub fn apply_input_to_player(
focused = window.focused; focused = window.focused;
win_res_x = window.resolution.width(); win_res_x = window.resolution.width();
win_res_y = window.resolution.height(); win_res_y = window.resolution.height();
} } else {
else {
win_res_x = 1920.0; win_res_x = 1920.0;
win_res_y = 1050.0; win_res_y = 1050.0;
} }
@ -402,7 +456,9 @@ pub fn apply_input_to_player(
DVec3::splat(0.0) DVec3::splat(0.0)
}; };
if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) = q_playercam.get_single_mut() { if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) =
q_playercam.get_single_mut()
{
// Handle key input // Handle key input
if focused { if focused {
if key_input.pressed(settings.key_forward) || settings.cruise_control_active { if key_input.pressed(settings.key_forward) || settings.cruise_control_active {
@ -426,7 +482,10 @@ pub fn apply_input_to_player(
if key_input.pressed(settings.key_stop) { if key_input.pressed(settings.key_stop) {
let stop_direction = (target_v - v.0).normalize(); let stop_direction = (target_v - v.0).normalize();
if stop_direction.length_squared() > 0.3 { if stop_direction.length_squared() > 0.3 {
axis_input += 1.0 * DVec3::from(player_transform.rotation.inverse() * stop_direction.as_vec3()); axis_input += 1.0
* DVec3::from(
player_transform.rotation.inverse() * stop_direction.as_vec3(),
);
} }
} }
} else { } else {
@ -442,7 +501,8 @@ pub fn apply_input_to_player(
axis_input = axis_input.clamp(DVec3::splat(-1.0), DVec3::splat(1.0)); axis_input = axis_input.clamp(DVec3::splat(-1.0), DVec3::splat(1.0));
// Apply movement update // Apply movement update
let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 { let forward_factor = engine.current_warmup
* (if axis_input.z > 0.0 {
engine.thrust_forward engine.thrust_forward
} else { } else {
engine.thrust_back engine.thrust_back
@ -452,8 +512,10 @@ pub fn apply_input_to_player(
let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64); let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64);
if axis_input.length_squared() > 0.003 { if axis_input.length_squared() > 0.003 {
let acceleration_global: DVec3 = DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3()); let acceleration_global: DVec3 =
let mut acceleration_total: DVec3 = (actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global; DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
let mut acceleration_total: DVec3 =
(actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global;
let threshold = 1e-5; let threshold = 1e-5;
if key_input.pressed(settings.key_stop) { if key_input.pressed(settings.key_stop) {
// Decelerate (or match velocity to target_v) // Decelerate (or match velocity to target_v)
@ -461,8 +523,7 @@ pub fn apply_input_to_player(
for i in 0..3 { for i in 0..3 {
if dv[i].abs() < threshold { if dv[i].abs() < threshold {
v[i] = target_v[i]; v[i] = target_v[i];
} } else if dv[i].signum() != (dv[i] + acceleration_total[i]).signum() {
else if dv[i].signum() != (dv[i] + acceleration_total[i]).signum() {
// Almost stopped, but we overshot v=0 // Almost stopped, but we overshot v=0
v[i] = target_v[i]; v[i] = target_v[i];
acceleration_total[i] = 0.0; acceleration_total[i] = 0.0;
@ -471,18 +532,23 @@ pub fn apply_input_to_player(
} }
// TODO: handle mass // TODO: handle mass
v.0 += acceleration_total; v.0 += acceleration_total;
engine.current_warmup = (engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0); engine.current_warmup =
(engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
play_thruster_sound = true; play_thruster_sound = true;
} } else {
else { engine.current_warmup =
engine.current_warmup = (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0); (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
} }
// Handle mouse input and mouse-like key bindings // Handle mouse input and mouse-like key bindings
let mut play_reactionwheel_sound = false; let mut play_reactionwheel_sound = false;
let mut mouse_delta = Vec2::ZERO; let mut mouse_delta = Vec2::ZERO;
let mut pitch_yaw_rot = Vec3::ZERO; let mut pitch_yaw_rot = Vec3::ZERO;
let sensitivity_factor = if settings.is_zooming { settings.zoom_sensitivity_factor } else { 1.0 }; let sensitivity_factor = if settings.is_zooming {
settings.zoom_sensitivity_factor
} else {
1.0
};
let mouseless_sensitivity = 8.0 * sensitivity_factor; let mouseless_sensitivity = 8.0 * sensitivity_factor;
let mouseless_rotation_sensitivity = 40.0 * sensitivity_factor; let mouseless_rotation_sensitivity = 40.0 * sensitivity_factor;
if key_input.pressed(settings.key_mouseup) { if key_input.pressed(settings.key_mouseup) {
@ -492,7 +558,8 @@ pub fn apply_input_to_player(
pitch_yaw_rot[0] += mouseless_sensitivity; pitch_yaw_rot[0] += mouseless_sensitivity;
} }
if key_input.pressed(settings.key_mouseleft) { if key_input.pressed(settings.key_mouseleft) {
pitch_yaw_rot[1] += mouseless_sensitivity; } pitch_yaw_rot[1] += mouseless_sensitivity;
}
if key_input.pressed(settings.key_mouseright) { if key_input.pressed(settings.key_mouseright) {
pitch_yaw_rot[1] -= mouseless_sensitivity; pitch_yaw_rot[1] -= mouseless_sensitivity;
} }
@ -522,19 +589,17 @@ pub fn apply_input_to_player(
}; };
if pitch_yaw_rot.length_squared() > 1.0e-18 { if pitch_yaw_rot.length_squared() > 1.0e-18 {
play_reactionwheel_sound = true; play_reactionwheel_sound = true;
pitch_yaw_rot *= settings.mouse_sensitivity * sensitivity_factor * engine.reaction_wheels; pitch_yaw_rot *=
settings.mouse_sensitivity * sensitivity_factor * engine.reaction_wheels;
torque.apply_torque(DVec3::from( torque.apply_torque(DVec3::from(
player_transform.rotation * Vec3::new( player_transform.rotation
pitch_yaw_rot[0], * Vec3::new(pitch_yaw_rot[0], pitch_yaw_rot[1], pitch_yaw_rot[2]),
pitch_yaw_rot[1], ));
pitch_yaw_rot[2])));
angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64; angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64;
} } else {
else {
if angularvelocity.length_squared() > 1.0e-18 { if angularvelocity.length_squared() > 1.0e-18 {
angularvelocity.0 *= angular_slowdown; angularvelocity.0 *= angular_slowdown;
} } else {
else {
angularvelocity.0 = DVec3::splat(0.0); angularvelocity.0 = DVec3::splat(0.0);
} }
} }
@ -563,8 +628,16 @@ pub fn apply_input_to_player(
} }
} }
let sinks = vec![ let sinks = vec![
(1.2, actor::EngineType::Monopropellant, sinks.get(&audio::Sfx::Thruster)), (
(1.0, actor::EngineType::Rocket, sinks.get(&audio::Sfx::Rocket)), 1.2,
actor::EngineType::Monopropellant,
sinks.get(&audio::Sfx::Thruster),
),
(
1.0,
actor::EngineType::Rocket,
sinks.get(&audio::Sfx::Rocket),
),
(1.4, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)), (1.4, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)),
]; ];
let seconds_to_max_vol = 0.05; let seconds_to_max_vol = 0.05;
@ -573,8 +646,7 @@ pub fn apply_input_to_player(
if let (vol_boost, engine_type, Some(sink)) = sink_data { if let (vol_boost, engine_type, Some(sink)) = sink_data {
if settings.mute_sfx { if settings.mute_sfx {
sink.pause(); sink.pause();
} } else {
else {
let volume = sink.volume(); let volume = sink.volume();
if engine.engine_type == engine_type { if engine.engine_type == engine_type {
if play_thruster_sound { if play_thruster_sound {
@ -582,13 +654,14 @@ pub fn apply_input_to_player(
if volume < 1.0 { if volume < 1.0 {
let maxvol = vol_boost; let maxvol = vol_boost;
//let maxvol = engine.current_warmup * vol_boost; //let maxvol = engine.current_warmup * vol_boost;
sink.set_volume((volume + dt / seconds_to_max_vol).clamp(0.0, maxvol)); sink.set_volume(
(volume + dt / seconds_to_max_vol).clamp(0.0, maxvol),
);
} }
} else { } else {
sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0)); sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0));
} }
} } else if volume > 0.0 {
else if volume > 0.0 {
sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0)); sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0));
} }
if volume < 0.0001 { if volume < 0.0001 {
@ -619,12 +692,14 @@ pub fn update_map_only_object_visibility(
let dist = cam_pos.distance(pos.as_vec3()); let dist = cam_pos.distance(pos.as_vec3());
if dist >= onlyinmap.min_distance as f32 { if dist >= onlyinmap.min_distance as f32 {
*vis = Visibility::Inherited; *vis = Visibility::Inherited;
} } else {
else {
*vis = Visibility::Hidden; *vis = Visibility::Hidden;
} }
} else { } else {
error!("Failed get position of actor ID '{}'", &onlyinmap.distance_to_id); error!(
"Failed get position of actor ID '{}'",
&onlyinmap.distance_to_id
);
*vis = Visibility::Hidden; *vis = Visibility::Hidden;
} }
} else { } else {
@ -639,17 +714,18 @@ pub fn find_closest_target<TargetSpecifier>(
objects: Vec<(TargetSpecifier, &Transform)>, objects: Vec<(TargetSpecifier, &Transform)>,
camera_transform: &Transform, camera_transform: &Transform,
) -> (Option<TargetSpecifier>, f32) ) -> (Option<TargetSpecifier>, f32)
where TargetSpecifier: Clone where
TargetSpecifier: Clone,
{ {
let mut closest_entity: Option<TargetSpecifier> = None; let mut closest_entity: Option<TargetSpecifier> = None;
let mut closest_distance: f32 = f32::MAX; let mut closest_distance: f32 = f32::MAX;
let target_vector: Vec3 = (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0)) let target_vector: Vec3 =
.normalize_or_zero(); (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0)).normalize_or_zero();
for (entity, trans) in objects { for (entity, trans) in objects {
// Use Transform instead of Position because we're basing this // Use Transform instead of Position because we're basing this
// not on the player mesh but on the camera, which doesn't have a position. // not on the player mesh but on the camera, which doesn't have a position.
let (angular_diameter, angle, distance) = calc_angular_diameter_known_target_vector( let (angular_diameter, angle, distance) =
trans, camera_transform, &target_vector); calc_angular_diameter_known_target_vector(trans, camera_transform, &target_vector);
if angle <= angular_diameter.clamp(0.01, PI32) { if angle <= angular_diameter.clamp(0.01, PI32) {
// It's in the field of view! // It's in the field of view!
//commands.entity(entity).insert(IsTargeted); //commands.entity(entity).insert(IsTargeted);
@ -669,8 +745,7 @@ pub fn calc_angular_diameter_known_target_vector(
camera: &Transform, camera: &Transform,
target_vector: &Vec3, target_vector: &Vec3,
) -> (f32, f32, f32) { ) -> (f32, f32, f32) {
let pos_vector: Vec3 = (target.translation - camera.translation) let pos_vector: Vec3 = (target.translation - camera.translation).normalize_or_zero();
.normalize_or_zero();
let cosine_of_angle: f32 = target_vector.dot(pos_vector); let cosine_of_angle: f32 = target_vector.dot(pos_vector);
let angle: f32 = cosine_of_angle.acos(); let angle: f32 = cosine_of_angle.acos();
let distance: f32 = target.translation.distance(camera.translation); let distance: f32 = target.translation.distance(camera.translation);
@ -678,20 +753,15 @@ pub fn calc_angular_diameter_known_target_vector(
let angular_diameter: f32 = if distance > 0.0 { let angular_diameter: f32 = if distance > 0.0 {
// Angular Diameter // Angular Diameter
leeway * (target.scale[0] / distance).asin() leeway * (target.scale[0] / distance).asin()
} } else {
else {
0.0 0.0
}; };
return (angular_diameter, angle, distance); return (angular_diameter, angle, distance);
} }
#[inline] #[inline]
pub fn calc_angular_diameter( pub fn calc_angular_diameter(target: &Transform, camera: &Transform) -> (f32, f32, f32) {
target: &Transform, let target_vector: Vec3 = (camera.rotation * Vec3::new(0.0, 0.0, -1.0)).normalize_or_zero();
camera: &Transform,
) -> (f32, f32, f32) {
let target_vector: Vec3 = (camera.rotation * Vec3::new(0.0, 0.0, -1.0))
.normalize_or_zero();
return calc_angular_diameter_known_target_vector(target, camera, &target_vector); return calc_angular_diameter_known_target_vector(target, camera, &target_vector);
} }
@ -702,7 +772,10 @@ pub fn position_to_transform(
mapcam: Res<MapCam>, mapcam: Res<MapCam>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
q_player: Query<&Position, With<actor::PlayerCamera>>, q_player: Query<&Position, With<actor::PlayerCamera>>,
mut q_trans: Query<(&'static mut Transform, &'static Position, &'static Rotation), Without<Parent>>, mut q_trans: Query<
(&'static mut Transform, &'static Position, &'static Rotation),
Without<Parent>,
>,
) { ) {
let center: DVec3 = if settings.map_active { let center: DVec3 = if settings.map_active {
mapcam.center mapcam.center

View file

@ -14,8 +14,8 @@
use crate::prelude::*; use crate::prelude::*;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use serde_yaml::Value;
use serde::Deserialize; use serde::Deserialize;
use serde_yaml::Value;
use std::collections::HashMap; use std::collections::HashMap;
pub const CHATS: &[&str] = &[ pub const CHATS: &[&str] = &[
@ -65,24 +65,22 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[
TOKEN_SOUND, TOKEN_SOUND,
TOKEN_NOWAIT, TOKEN_NOWAIT,
]; ];
pub const SKIPPABLE_TOKENS: &[&str] = &[ pub const SKIPPABLE_TOKENS: &[&str] = &[TOKEN_CHAT, TOKEN_LABEL, TOKEN_GOTO, TOKEN_NOWAIT];
TOKEN_CHAT,
TOKEN_LABEL,
TOKEN_GOTO,
TOKEN_NOWAIT,
];
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.add_systems(Startup, load_chats); app.add_systems(Startup, load_chats);
app.add_systems(Update, ( app.add_systems(
Update,
(
handle_reply_keys.before(handle_chat_timer), handle_reply_keys.before(handle_chat_timer),
handle_chat_timer.before(handle_chat_events), handle_chat_timer.before(handle_chat_events),
handle_new_conversations.before(handle_chat_events), handle_new_conversations.before(handle_chat_events),
handle_chat_events.before(handle_chat_scripts), handle_chat_events.before(handle_chat_scripts),
handle_chat_scripts, handle_chat_scripts,
)); ),
);
app.add_event::<StartConversationEvent>(); app.add_event::<StartConversationEvent>();
app.add_event::<ChatEvent>(); app.add_event::<ChatEvent>();
app.add_event::<ChatScriptEvent>(); app.add_event::<ChatScriptEvent>();
@ -107,8 +105,7 @@ pub struct Choice {
pub goto: ChatPos, pub goto: ChatPos,
} }
#[derive(Component)] #[derive(Component, Clone)]
#[derive(Clone)]
pub struct Talker { pub struct Talker {
pub chat_name: String, pub chat_name: String,
pub actor_id: String, pub actor_id: String,
@ -152,7 +149,6 @@ impl Default for Extracted {
} }
} }
// This is the only place where any YAML interaction should be happening. // This is the only place where any YAML interaction should be happening.
#[derive(Resource)] #[derive(Resource)]
pub struct ChatDB(Vec<Value>); pub struct ChatDB(Vec<Value>);
@ -165,8 +161,7 @@ impl ChatDB {
if let Value::Sequence(yaml_sequence) = yaml_data { if let Value::Sequence(yaml_sequence) = yaml_data {
self.0.push(Value::Sequence(yaml_sequence)); self.0.push(Value::Sequence(yaml_sequence));
count += 1; count += 1;
} } else {
else {
error!("Could not load YAML: {:?}", yaml_data); error!("Could not load YAML: {:?}", yaml_data);
} }
} }
@ -202,7 +197,10 @@ impl ChatDB {
} }
} }
fn preprocess_includes_recursively(sequence: &mut Value, include_db: &HashMap<String, Vec<Value>>) { fn preprocess_includes_recursively(
sequence: &mut Value,
include_db: &HashMap<String, Vec<Value>>,
) {
let mut changes: Vec<(usize, String)> = Vec::new(); let mut changes: Vec<(usize, String)> = Vec::new();
if let Some(vector) = sequence.as_sequence_mut() { if let Some(vector) = sequence.as_sequence_mut() {
for (index, item) in vector.iter_mut().enumerate() { for (index, item) in vector.iter_mut().enumerate() {
@ -213,8 +211,7 @@ impl ChatDB {
if key == TOKEN_INCLUDE { if key == TOKEN_INCLUDE {
changes.push((index, value.to_string())); changes.push((index, value.to_string()));
} }
} } else if value.is_sequence() {
else if value.is_sequence() {
ChatDB::preprocess_includes_recursively(value, include_db); ChatDB::preprocess_includes_recursively(value, include_db);
} }
} }
@ -248,7 +245,9 @@ impl ChatDB {
if let Some(result) = found { if let Some(result) = found {
return Ok(result); return Ok(result);
} }
return Err(format!("No chat with the conversation ID `{id}` was found.")); return Err(format!(
"No chat with the conversation ID `{id}` was found."
));
} }
// For a given Value, check whether it's a Value::Mapping and whether it // For a given Value, check whether it's a Value::Mapping and whether it
@ -273,19 +272,15 @@ impl ChatDB {
if let Value::String(key) = key { if let Value::String(key) = key {
if key == TOKEN_NOWAIT && value.as_bool() == Some(true) { if key == TOKEN_NOWAIT && value.as_bool() == Some(true) {
result.nowait = true; result.nowait = true;
} } else if key == TOKEN_IF {
else if key == TOKEN_IF {
if let Some(condition) = value.as_str() { if let Some(condition) = value.as_str() {
result.condition = Some(condition.to_string()); result.condition = Some(condition.to_string());
} }
} } else if non_choice_tokens.contains(&key.as_str()) {
else if non_choice_tokens.contains(&key.as_str()) {
// skip over the other non-choice tokens // skip over the other non-choice tokens
} } else if key.as_str().starts_with(TOKEN_IF_INLINE) {
else if key.as_str().starts_with(TOKEN_IF_INLINE) {
// skip over inlined if-statements // skip over inlined if-statements
} } else {
else {
result.choice_text = Some(key.to_string()); result.choice_text = Some(key.to_string());
} }
} }
@ -295,7 +290,12 @@ impl ChatDB {
return None; return None;
} }
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> { fn search_label_recursively(
&self,
sequence: &Value,
label: &String,
mut pos: ChatPos,
) -> Option<ChatPos> {
if pos.len() > MAX_BRANCH_DEPTH { if pos.len() > MAX_BRANCH_DEPTH {
return None; return None;
} }
@ -315,9 +315,10 @@ impl ChatDB {
} }
if value.is_sequence() { if value.is_sequence() {
pos.push(index); pos.push(index);
if let Some(result) = self.search_label_recursively( if let Some(result) =
value, label, pos.clone()) { self.search_label_recursively(value, label, pos.clone())
return Some(result) {
return Some(result);
} }
pos.pop(); pos.pop();
} }
@ -357,7 +358,7 @@ impl ChatDB {
let index = chat.position.len() - 1; let index = chat.position.len() - 1;
chat.position[index] += 1; chat.position[index] += 1;
} }
}, }
Some(Value::Mapping(map)) => { Some(Value::Mapping(map)) => {
if seek_past_dialog_choices && self.is_choice(Some(&Value::Mapping(map))) { if seek_past_dialog_choices && self.is_choice(Some(&Value::Mapping(map))) {
// we just dropped out of a branch and ended up in a dialog // we just dropped out of a branch and ended up in a dialog
@ -367,8 +368,7 @@ impl ChatDB {
let index = chat.position.len() - 1; let index = chat.position.len() - 1;
chat.position[index] += 1; chat.position[index] += 1;
} }
} } else {
else {
break; break;
} }
} }
@ -441,8 +441,7 @@ impl ChatDB {
if !SKIPPABLE_TOKENS.contains(&key) { if !SKIPPABLE_TOKENS.contains(&key) {
return false; return false;
} }
} } else {
else {
return false; return false;
} }
} }
@ -452,17 +451,16 @@ impl ChatDB {
return false; return false;
} }
fn process_yaml_entry( fn process_yaml_entry(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) -> bool {
&self,
chat: &mut Chat,
event: &mut EventWriter<ChatEvent>,
) -> bool {
let current_item = self.at(chat.internal_id, &chat.position); let current_item = self.at(chat.internal_id, &chat.position);
let mut processed_a_choice = false; let mut processed_a_choice = false;
match current_item { match current_item {
Some(Value::String(message)) => { Some(Value::String(message)) => {
event.send(ChatEvent::SpawnMessage(message.to_string(), event.send(ChatEvent::SpawnMessage(
hud::LogLevel::Chat, DEFAULT_SOUND.to_string())); message.to_string(),
hud::LogLevel::Chat,
DEFAULT_SOUND.to_string(),
));
} }
Some(Value::Mapping(map)) => { Some(Value::Mapping(map)) => {
let mut sound = DEFAULT_SOUND.to_string(); let mut sound = DEFAULT_SOUND.to_string();
@ -506,15 +504,24 @@ impl ChatDB {
match (key, value) { match (key, value) {
(Some(TOKEN_MSG), Value::String(message)) => { (Some(TOKEN_MSG), Value::String(message)) => {
event.send(ChatEvent::SpawnMessage( event.send(ChatEvent::SpawnMessage(
message.to_string(), hud::LogLevel::Chat, sound.clone())); message.to_string(),
hud::LogLevel::Chat,
sound.clone(),
));
} }
(Some(TOKEN_SYSTEM), Value::String(message)) => { (Some(TOKEN_SYSTEM), Value::String(message)) => {
event.send(ChatEvent::SpawnMessage( event.send(ChatEvent::SpawnMessage(
message.to_string(), hud::LogLevel::Info, sound.clone())); message.to_string(),
hud::LogLevel::Info,
sound.clone(),
));
} }
(Some(TOKEN_WARN), Value::String(message)) => { (Some(TOKEN_WARN), Value::String(message)) => {
event.send(ChatEvent::SpawnMessage( event.send(ChatEvent::SpawnMessage(
message.to_string(), hud::LogLevel::Warning, sound.clone())); message.to_string(),
hud::LogLevel::Warning,
sound.clone(),
));
} }
(Some(TOKEN_SET), Value::String(instructions)) => { (Some(TOKEN_SET), Value::String(instructions)) => {
event.send(ChatEvent::SetVariable(instructions.to_string())); event.send(ChatEvent::SetVariable(instructions.to_string()));
@ -551,8 +558,11 @@ impl ChatDB {
} }
None => { None => {
if chat.position.len() == 0 { if chat.position.len() == 0 {
event.send(ChatEvent::SpawnMessage("Disconnected.".to_string(), event.send(ChatEvent::SpawnMessage(
hud::LogLevel::Info, DEFAULT_SOUND.to_string())); "Disconnected.".to_string(),
hud::LogLevel::Info,
DEFAULT_SOUND.to_string(),
));
event.send(ChatEvent::DespawnAllChats); event.send(ChatEvent::DespawnAllChats);
} }
} }
@ -584,19 +594,25 @@ impl ChatDB {
// Spawn choices until we reach a non-choice item or the end of the branch // Spawn choices until we reach a non-choice item or the end of the branch
let mut key: usize = 0; let mut key: usize = 0;
let mut reached_end_of_branch = false; let mut reached_end_of_branch = false;
while let Some(data) = self.extract_choice(self.at(chat.internal_id, &chat.position).as_ref()) { while let Some(data) =
self.extract_choice(self.at(chat.internal_id, &chat.position).as_ref())
{
if let Some(choice_text) = data.choice_text { if let Some(choice_text) = data.choice_text {
if reached_end_of_branch { if reached_end_of_branch {
break; break;
} }
let mut goto: Vec<usize> = chat.position.clone(); let mut goto: Vec<usize> = chat.position.clone();
goto.push(0); goto.push(0);
event.send(ChatEvent::SpawnChoice(choice_text, event.send(ChatEvent::SpawnChoice(
key, goto, data.nowait, data.condition)); choice_text,
key,
goto,
data.nowait,
data.condition,
));
key += 1; key += 1;
reached_end_of_branch = self.advance_pointer(chat); reached_end_of_branch = self.advance_pointer(chat);
} } else {
else {
break; break;
} }
} }
@ -640,10 +656,7 @@ pub fn handle_new_conversations(
talker: event.talker.clone(), talker: event.talker.clone(),
}; };
chatdb.advance_chat(&mut chat, &mut ew_chatevent); chatdb.advance_chat(&mut chat, &mut ew_chatevent);
commands.spawn(( commands.spawn((chat, world::DespawnOnPlayerDeath));
chat,
world::DespawnOnPlayerDeath,
));
} }
Err(error) => { Err(error) => {
error!("Error while looking for chat ID: {error}"); error!("Error while looking for chat ID: {error}");
@ -700,7 +713,10 @@ pub fn handle_chat_events(
ChatEvent::SpawnMessage(message, level, sound) => { ChatEvent::SpawnMessage(message, level, sound) => {
match level { match level {
hud::LogLevel::Chat => { hud::LogLevel::Chat => {
log.chat(message.into(), chat.talker.name.clone().unwrap_or("".to_string())); log.chat(
message.into(),
chat.talker.name.clone().unwrap_or("".to_string()),
);
} }
hud::LogLevel::Info => { hud::LogLevel::Info => {
log.info(message.into()); log.info(message.into());
@ -712,18 +728,23 @@ pub fn handle_chat_events(
log.warning(message.into()); log.warning(message.into());
} }
hud::LogLevel::Always => { hud::LogLevel::Always => {
log.add(message.into(), log.add(
message.into(),
chat.talker.name.clone().unwrap_or("".to_string()), chat.talker.name.clone().unwrap_or("".to_string()),
hud::LogLevel::Always); hud::LogLevel::Always,
);
} }
} }
chat.timer = now + ((message.len() as f32).max(CHAT_SPEED_MIN_LEN) * TALKER_SPEED_FACTOR * chat.talker.talking_speed / settings.chat_speed) as f64; chat.timer = now
+ ((message.len() as f32).max(CHAT_SPEED_MIN_LEN)
* TALKER_SPEED_FACTOR
* chat.talker.talking_speed
/ settings.chat_speed) as f64;
let sfx = audio::str2sfx(sound); let sfx = audio::str2sfx(sound);
ew_sfx.send(audio::PlaySfxEvent(sfx)); ew_sfx.send(audio::PlaySfxEvent(sfx));
} }
ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => { ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => 'out: {
'out: {
if let Some(condition) = condition { if let Some(condition) = condition {
if !vars.evaluate_condition(condition, &chat.talker.actor_id) { if !vars.evaluate_condition(condition, &chat.talker.actor_id) {
break 'out; break 'out;
@ -735,14 +756,13 @@ pub fn handle_chat_events(
text: replytext.into(), text: replytext.into(),
key: choice_key, key: choice_key,
goto: goto.clone(), goto: goto.clone(),
} },
)); ));
choice_key += 1; choice_key += 1;
if !nowait { if !nowait {
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64; chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
} }
} }
}
ChatEvent::RunScript(script) => { ChatEvent::RunScript(script) => {
ew_chatscript.send(ChatScriptEvent(script.clone())); ew_chatscript.send(ChatScriptEvent(script.clone()));
} }
@ -794,7 +814,14 @@ fn handle_reply_keys(
pub fn handle_chat_scripts( pub fn handle_chat_scripts(
mut er_chatscript: EventReader<ChatScriptEvent>, mut er_chatscript: EventReader<ChatScriptEvent>,
mut q_actor: Query<(&mut actor::Actor, &mut actor::Suit), Without<actor::Player>>, 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_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 q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>, mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
@ -815,12 +842,12 @@ pub fn handle_chat_scripts(
// Process the script // Process the script
match name { match name {
"refilloxygen" => if let Ok(mut amount) = param1.to_string().parse::<f32>() { "refilloxygen" => {
if let Ok(mut amount) = param1.to_string().parse::<f32>() {
for (_, mut suit, _) in q_player.iter_mut() { for (_, mut suit, _) in q_player.iter_mut() {
if param2.is_empty() { if param2.is_empty() {
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max); suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
} } else {
else {
let mut found_other = false; let mut found_other = false;
info!("param2={}", param2); info!("param2={}", param2);
for (other_actor, mut other_suit) in q_actor.iter_mut() { for (other_actor, mut other_suit) in q_actor.iter_mut() {
@ -833,7 +860,8 @@ pub fn handle_chat_scripts(
.clamp(0.0, other_suit.oxygen) .clamp(0.0, other_suit.oxygen)
.clamp(0.0, suit.oxygen_max - suit.oxygen); .clamp(0.0, suit.oxygen_max - suit.oxygen);
other_suit.oxygen = other_suit.oxygen - amount; other_suit.oxygen = other_suit.oxygen - amount;
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max); suit.oxygen =
(suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
break; break;
} }
} }
@ -845,6 +873,7 @@ pub fn handle_chat_scripts(
} else { } else {
error!("Invalid parameter for command `{}`: `{}`", name, param1); error!("Invalid parameter for command `{}`: `{}`", name, param1);
} }
}
"repairsuit" => { "repairsuit" => {
ew_achievement.send(game::AchievementEvent::RepairSuit); ew_achievement.send(game::AchievementEvent::RepairSuit);
for (_, mut suit, _) in q_player.iter_mut() { for (_, mut suit, _) in q_player.iter_mut() {
@ -854,21 +883,23 @@ pub fn handle_chat_scripts(
"cryotrip" => { "cryotrip" => {
if param1.is_empty() { if param1.is_empty() {
error!("Chat script cryotrip needs a parameter"); error!("Chat script cryotrip needs a parameter");
} } else {
else {
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() { if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
let busstop = match param1 { let busstop = match param1 {
"serenity" => Some("busstopclippy"), "serenity" => Some("busstopclippy"),
"farview" => Some("busstopclippy2"), "farview" => Some("busstopclippy2"),
"metisprime" => Some("busstopclippy3"), "metisprime" => Some("busstopclippy3"),
_ => None _ => None,
}; };
if let Some(station) = busstop { if let Some(station) = busstop {
if let Some(target) = id2pos.0.get(&station.to_string()) { if let Some(target) = id2pos.0.get(&station.to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0); pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
v.0 = DVec3::ZERO; v.0 = DVec3::ZERO;
} else { } else {
error!("Could not determine position of actor with ID: '{}'", station); error!(
"Could not determine position of actor with ID: '{}'",
station
);
} }
} else { } else {
error!("Invalid destination for cryotrip chat script: '{}'", param1); error!("Invalid destination for cryotrip chat script: '{}'", param1);

View file

@ -11,10 +11,10 @@
// This module populates the world with actors as defined in "defs.txt" // This module populates the world with actors as defined in "defs.txt"
extern crate regex; extern crate regex;
use crate::prelude::*;
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use crate::prelude::*;
use regex::Regex; use regex::Regex;
use std::time::SystemTime; use std::time::SystemTime;
@ -28,14 +28,18 @@ impl Plugin for CmdPlugin {
app.add_systems(Startup, load_defs); app.add_systems(Startup, load_defs);
app.add_systems(Update, spawn_entities); app.add_systems(Update, spawn_entities);
app.add_systems(Update, process_mesh); app.add_systems(Update, process_mesh);
app.add_systems(PreUpdate, hide_colliders app.add_systems(
.run_if(any_with_component::<NeedsSceneColliderRemoved>)); PreUpdate,
hide_colliders.run_if(any_with_component::<NeedsSceneColliderRemoved>),
);
app.add_event::<SpawnEvent>(); app.add_event::<SpawnEvent>();
} }
} }
#[derive(Component)] pub struct NeedsSceneColliderRemoved; #[derive(Component)]
#[derive(Event)] pub struct SpawnEvent(ParserState); pub struct NeedsSceneColliderRemoved;
#[derive(Event)]
pub struct SpawnEvent(ParserState);
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
enum DefClass { enum DefClass {
Actor, Actor,
@ -157,11 +161,11 @@ impl Default for ParserState {
} }
} }
pub fn load_defs( pub fn load_defs(mut ew_spawn: EventWriter<SpawnEvent>) {
mut ew_spawn: EventWriter<SpawnEvent>,
) {
let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap(); let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap();
let re2 = Regex::new("\"([^\"]*)\"|(-?[0-9]+[0-9e-]*(?:\\.[0-9e-]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)").unwrap(); let re2 =
Regex::new("\"([^\"]*)\"|(-?[0-9]+[0-9e-]*(?:\\.[0-9e-]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)")
.unwrap();
let defs_string = include_str!("data/defs.txt"); let defs_string = include_str!("data/defs.txt");
let mut lines = defs_string.lines(); let mut lines = defs_string.lines();
let mut state = ParserState::default(); let mut state = ParserState::default();
@ -181,9 +185,11 @@ pub fn load_defs(
if let Some(caps) = caps { if let Some(caps) = caps {
command = caps.get(1).unwrap().as_str(); command = caps.get(1).unwrap().as_str();
parameters = caps.get(2).unwrap().as_str(); parameters = caps.get(2).unwrap().as_str();
} } else {
else { error!(
error!("Failed to read regex captures in line {}: `{}`", line_nr, line); "Failed to read regex captures in line {}: `{}`",
line_nr, line
);
continue; continue;
} }
@ -214,10 +220,10 @@ pub fn load_defs(
state.class = DefClass::Actor; state.class = DefClass::Actor;
state.model = Some(model.to_string()); state.model = Some(model.to_string());
if let (Ok(x_float), Ok(y_float), Ok(z_float)) = if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) { (x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
{
state.pos = DVec3::new(x_float, y_float, z_float); state.pos = DVec3::new(x_float, y_float, z_float);
} } else {
else {
error!("Can't parse coordinates as floats in def: {line}"); error!("Can't parse coordinates as floats in def: {line}");
state = ParserState::default(); state = ParserState::default();
continue; continue;
@ -228,10 +234,10 @@ pub fn load_defs(
state = ParserState::default(); state = ParserState::default();
state.class = DefClass::Actor; state.class = DefClass::Actor;
if let (Ok(x_float), Ok(y_float), Ok(z_float)) = if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) { (x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
{
state.pos = DVec3::new(x_float, y_float, z_float); state.pos = DVec3::new(x_float, y_float, z_float);
} } else {
else {
error!("Can't parse coordinates as floats in def: {line}"); error!("Can't parse coordinates as floats in def: {line}");
state = ParserState::default(); state = ParserState::default();
continue; continue;
@ -244,8 +250,7 @@ pub fn load_defs(
if let Ok(r) = radius_str.parse::<f64>() { if let Ok(r) = radius_str.parse::<f64>() {
state.orbit_distance = Some(r); state.orbit_distance = Some(r);
state.orbit_object_id = Some(object_id.to_string()); state.orbit_object_id = Some(object_id.to_string());
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -254,8 +259,7 @@ pub fn load_defs(
if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) { if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) {
state.orbit_distance = Some(r); state.orbit_distance = Some(r);
state.orbit_phase = Some(phase * PI * 2.0); state.orbit_phase = Some(phase * PI * 2.0);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -265,12 +269,10 @@ pub fn load_defs(
let offset_radians = 2.0 * PI * value_float; let offset_radians = 2.0 * PI * value_float;
if let Some(phase_radians) = state.orbit_phase { if let Some(phase_radians) = state.orbit_phase {
state.orbit_phase = Some(phase_radians + offset_radians); state.orbit_phase = Some(phase_radians + offset_radians);
} } else {
else {
state.orbit_phase = Some(offset_radians); state.orbit_phase = Some(offset_radians);
} }
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -312,8 +314,7 @@ pub fn load_defs(
state.is_lifeform = true; state.is_lifeform = true;
state.is_suited = true; state.is_suited = true;
state.oxygen = amount; state.oxygen = amount;
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -327,8 +328,7 @@ pub fn load_defs(
["scale", scale] => { ["scale", scale] => {
if let Ok(scale_float) = scale.parse::<f32>() { if let Ok(scale_float) = scale.parse::<f32>() {
state.model_scale = scale_float; state.model_scale = scale_float;
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -336,8 +336,7 @@ pub fn load_defs(
["rotationx", rotation_x] => { ["rotationx", rotation_x] => {
if let Ok(rotation_x_float) = rotation_x.parse::<f32>() { if let Ok(rotation_x_float) = rotation_x.parse::<f32>() {
state.rotation *= Quat::from_rotation_x(rotation_x_float.to_radians()); state.rotation *= Quat::from_rotation_x(rotation_x_float.to_radians());
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -345,8 +344,7 @@ pub fn load_defs(
["rotationy", rotation_y] => { ["rotationy", rotation_y] => {
if let Ok(rotation_y_float) = rotation_y.parse::<f32>() { if let Ok(rotation_y_float) = rotation_y.parse::<f32>() {
state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians()); state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians());
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -354,8 +352,7 @@ pub fn load_defs(
["rotationz", rotation_z] => { ["rotationz", rotation_z] => {
if let Ok(rotation_z_float) = rotation_z.parse::<f32>() { if let Ok(rotation_z_float) = rotation_z.parse::<f32>() {
state.rotation *= Quat::from_rotation_z(rotation_z_float.to_radians()); state.rotation *= Quat::from_rotation_z(rotation_z_float.to_radians());
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -364,34 +361,45 @@ pub fn load_defs(
if let Ok(rotation_y_float) = rotation_y.parse::<f32>() { if let Ok(rotation_y_float) = rotation_y.parse::<f32>() {
state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians()); state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians());
state.axialtilt = rotation_y_float; state.axialtilt = rotation_y_float;
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
} }
["velocity", x, y, z] => { ["velocity", x, y, z] => {
if let (Ok(x_float), Ok(y_float), Ok(z_float)) = if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) { (x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
{
state.velocity = DVec3::new(x_float, y_float, z_float); state.velocity = DVec3::new(x_float, y_float, z_float);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
} }
["angularmomentum", x, y, z] => { ["angularmomentum", x, y, z] => {
if let (Ok(x_float), Ok(y_float), Ok(z_float)) = if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) { (x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
{
state.angular_momentum = DVec3::new(x_float, y_float, z_float); state.angular_momentum = DVec3::new(x_float, y_float, z_float);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
} }
["thrust", forward, back, sideways, reaction_wheels, warmup_time] => { ["thrust", forward, back, sideways, reaction_wheels, warmup_time] => {
if let (Ok(forward_float), Ok(back_float), Ok(sideways_float), Ok(reaction_wheels_float), Ok(warmup_time_float)) = (forward.parse::<f32>(), back.parse::<f32>(), sideways.parse::<f32>(), reaction_wheels.parse::<f32>(), warmup_time.parse::<f32>()) { if let (
Ok(forward_float),
Ok(back_float),
Ok(sideways_float),
Ok(reaction_wheels_float),
Ok(warmup_time_float),
) = (
forward.parse::<f32>(),
back.parse::<f32>(),
sideways.parse::<f32>(),
reaction_wheels.parse::<f32>(),
warmup_time.parse::<f32>(),
) {
state.thrust_forward = forward_float; state.thrust_forward = forward_float;
state.thrust_back = back_float; state.thrust_back = back_float;
state.thrust_sideways = sideways_float; state.thrust_sideways = sideways_float;
@ -411,8 +419,7 @@ pub fn load_defs(
["health", value] => { ["health", value] => {
if let Ok(value_float) = value.parse::<f32>() { if let Ok(value_float) = value.parse::<f32>() {
state.suit_integrity = value_float; state.suit_integrity = value_float;
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -420,8 +427,7 @@ pub fn load_defs(
["density", value] => { ["density", value] => {
if let Ok(value_float) = value.parse::<f64>() { if let Ok(value_float) = value.parse::<f64>() {
state.density = value_float; state.density = value_float;
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -432,17 +438,17 @@ pub fn load_defs(
["collider", "sphere", radius] => { ["collider", "sphere", radius] => {
if let Ok(radius_float) = radius.parse::<f64>() { if let Ok(radius_float) = radius.parse::<f64>() {
state.collider = Collider::sphere(radius_float); state.collider = Collider::sphere(radius_float);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
} }
["collider", "capsule", height, radius] => { ["collider", "capsule", height, radius] => {
if let (Ok(height_float), Ok(radius_float)) = (height.parse::<f64>(), radius.parse::<f64>()) { if let (Ok(height_float), Ok(radius_float)) =
(height.parse::<f64>(), radius.parse::<f64>())
{
state.collider = Collider::capsule(height_float, radius_float); state.collider = Collider::capsule(height_float, radius_float);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -460,8 +466,7 @@ pub fn load_defs(
["camdistance", value] => { ["camdistance", value] => {
if let Ok(value_float) = value.parse::<f32>() { if let Ok(value_float) = value.parse::<f32>() {
state.camdistance = value_float; state.camdistance = value_float;
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -471,13 +476,11 @@ pub fn load_defs(
if let Ok(color) = Color::hex(color_hex) { if let Ok(color) = Color::hex(color_hex) {
state.light_color = Some(color); state.light_color = Some(color);
state.light_brightness = brightness_float; state.light_brightness = brightness_float;
} } else {
else {
error!("Can't parse hexadecimal color code: {line}"); error!("Can't parse hexadecimal color code: {line}");
continue; continue;
} }
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -486,8 +489,7 @@ pub fn load_defs(
// NOTE: requires an engine to slow down velocity // NOTE: requires an engine to slow down velocity
if let Ok(value_float) = value.parse::<f64>() { if let Ok(value_float) = value.parse::<f64>() {
state.wants_maxrotation = Some(value_float); state.wants_maxrotation = Some(value_float);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -496,8 +498,7 @@ pub fn load_defs(
// NOTE: requires an engine to slow down velocity // NOTE: requires an engine to slow down velocity
if let Ok(value_float) = value.parse::<f64>() { if let Ok(value_float) = value.parse::<f64>() {
state.wants_maxvelocity = Some(value_float); state.wants_maxvelocity = Some(value_float);
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -511,8 +512,7 @@ pub fn load_defs(
["only_in_map_at_dist", value, id] => { ["only_in_map_at_dist", value, id] => {
if let Ok(value_float) = value.parse::<f64>() { if let Ok(value_float) = value.parse::<f64>() {
state.show_only_in_map_at_distance = Some((value_float, id.to_string())); state.show_only_in_map_at_distance = Some((value_float, id.to_string()));
} } else {
else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
continue; continue;
} }
@ -543,9 +543,7 @@ fn spawn_entities(
// Preprocessing // Preprocessing
let mut absolute_pos = if let Some(id) = &state.relative_to { let mut absolute_pos = if let Some(id) = &state.relative_to {
match id2pos.0.get(&id.to_string()) { match id2pos.0.get(&id.to_string()) {
Some(pos) => { Some(pos) => state.pos + *pos,
state.pos + *pos
}
None => { None => {
error!("Specified `relativeto` command but could not find id `{id}`"); error!("Specified `relativeto` command but could not find id `{id}`");
continue; continue;
@ -569,7 +567,9 @@ fn spawn_entities(
} }
}; };
let orbital_period = nature::simple_orbital_period(mass, r); let orbital_period = nature::simple_orbital_period(mass, r);
phase_radians += if let Ok(epoch) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { phase_radians += if let Ok(epoch) =
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
{
let now = epoch.as_secs_f64() + 614533234154.0; // random let now = epoch.as_secs_f64() + 614533234154.0; // random
PI * 2.0 * (now % orbital_period) / orbital_period PI * 2.0 * (now % orbital_period) / orbital_period
} else { } else {
@ -579,13 +579,15 @@ fn spawn_entities(
} }
absolute_pos += nature::phase_dist_to_coords(-phase_radians, r); absolute_pos += nature::phase_dist_to_coords(-phase_radians, r);
} }
let scale = Vec3::splat(if state.is_sun { let scale = Vec3::splat(
if state.is_sun {
5.0 5.0
} else if state.is_moon && settings.large_moons { } else if state.is_moon && settings.large_moons {
3.0 3.0
} else { } else {
1.0 1.0
} * state.model_scale); } * state.model_scale,
);
// Spawn the actor // Spawn the actor
let actor_entity; let actor_entity;
@ -638,23 +640,24 @@ fn spawn_entities(
actor.insert(ColliderDensity(state.density)); actor.insert(ColliderDensity(state.density));
if state.collider_is_mesh { if state.collider_is_mesh {
actor.insert(MassPropertiesBundle::new_computed( actor.insert(MassPropertiesBundle::new_computed(
&Collider::sphere(0.5 * state.model_scale as f64), state.density)); &Collider::sphere(0.5 * state.model_scale as f64),
state.density,
));
actor.insert(AsyncSceneCollider::new(Some( actor.insert(AsyncSceneCollider::new(Some(
ComputedCollider::TriMesh ComputedCollider::TriMesh, //ComputedCollider::ConvexDecomposition(VHACDParameters::default())
//ComputedCollider::ConvexDecomposition(VHACDParameters::default())
))); )));
} } else if state.collider_is_one_mesh_of_scene {
else if state.collider_is_one_mesh_of_scene {
actor.insert(MassPropertiesBundle::new_computed( actor.insert(MassPropertiesBundle::new_computed(
&Collider::sphere(0.5 * state.model_scale as f64), state.density)); &Collider::sphere(0.5 * state.model_scale as f64),
actor.insert(AsyncSceneCollider::new(None) state.density,
));
actor.insert(
AsyncSceneCollider::new(None)
.with_shape_for_name("Collider", ComputedCollider::TriMesh) .with_shape_for_name("Collider", ComputedCollider::TriMesh)
.with_layers_for_name("Collider", CollisionLayers::ALL) .with_layers_for_name("Collider", CollisionLayers::ALL), //.with_density_for_name("Collider", state.density)
//.with_density_for_name("Collider", state.density)
); );
actor.insert(NeedsSceneColliderRemoved); actor.insert(NeedsSceneColliderRemoved);
} } else {
else {
actor.insert(state.collider.clone()); actor.insert(state.collider.clone());
} }
} }
@ -672,10 +675,7 @@ fn spawn_entities(
unlit: true, unlit: true,
..default() ..default()
})); }));
actor.insert(( actor.insert((NotShadowCaster, NotShadowReceiver));
NotShadowCaster,
NotShadowReceiver,
));
} }
if state.is_targeted_on_startup { if state.is_targeted_on_startup {
actor.insert(hud::IsTargeted); actor.insert(hud::IsTargeted);
@ -683,7 +683,7 @@ fn spawn_entities(
if let Some((mindist, id)) = &state.show_only_in_map_at_distance { if let Some((mindist, id)) = &state.show_only_in_map_at_distance {
actor.insert(camera::ShowOnlyInMap { actor.insert(camera::ShowOnlyInMap {
min_distance: *mindist, min_distance: *mindist,
distance_to_id: id.clone() distance_to_id: id.clone(),
}); });
} }
if state.is_player || state.is_vehicle { if state.is_player || state.is_vehicle {
@ -791,7 +791,7 @@ fn spawn_entities(
}, },
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
} },
)); ));
}); });
} }
@ -837,7 +837,8 @@ fn spawn_entities(
} }
if state.has_ring { if state.has_ring {
let ring_radius = state.model_scale * (nature::JUPITER_RING_RADIUS / nature::JUPITER_RADIUS) as f32; let ring_radius = state.model_scale
* (nature::JUPITER_RING_RADIUS / nature::JUPITER_RADIUS) as f32;
commands.spawn(( commands.spawn((
world::DespawnOnPlayerDeath, world::DespawnOnPlayerDeath,
MaterialMeshBundle { MaterialMeshBundle {
@ -860,7 +861,9 @@ fn spawn_entities(
} }
} }
pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibility>, With<Handle<Mesh>>)>) { pub fn hide_colliders(
mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibility>, With<Handle<Mesh>>)>,
) {
for (mut visibility, name) in &mut q_mesh { for (mut visibility, name) in &mut q_mesh {
if name.as_str() == "Collider" { if name.as_str() == "Collider" {
*visibility = Visibility::Hidden; *visibility = Visibility::Hidden;
@ -871,7 +874,12 @@ pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibil
pub fn process_mesh( pub fn process_mesh(
mut commands: Commands, mut commands: Commands,
mut q_mesh: Query<(Entity, &Name, &Parent), Added<Handle<Mesh>>>, mut q_mesh: Query<(Entity, &Name, &Parent), Added<Handle<Mesh>>>,
q_parents: Query<(Option<&Parent>, Option<&actor::Player>, Option<&NotShadowCaster>, Option<&NotShadowReceiver>)>, q_parents: Query<(
Option<&Parent>,
Option<&actor::Player>,
Option<&NotShadowCaster>,
Option<&NotShadowReceiver>,
)>,
) { ) {
// Add "PlayerCollider" component to the player's collider mesh entity // Add "PlayerCollider" component to the player's collider mesh entity
for (child_entity, child_name, child_parent) in &mut q_mesh { for (child_entity, child_name, child_parent) in &mut q_mesh {

View file

@ -10,10 +10,10 @@
// //
// Various common functions and constants // Various common functions and constants
use bevy::prelude::*;
use crate::prelude::*; use crate::prelude::*;
use bevy::prelude::*;
pub use bevy::math::{DVec3, DQuat}; pub use bevy::math::{DQuat, DVec3};
pub use std::f32::consts::PI as PI32; pub use std::f32::consts::PI as PI32;
pub use std::f64::consts::PI; pub use std::f64::consts::PI;
@ -77,7 +77,7 @@ pub fn in_shadow(
light_source_r: f64, light_source_r: f64,
shadow_caster_pos: DVec3, shadow_caster_pos: DVec3,
shadow_caster_r: f64, shadow_caster_r: f64,
camera_pos: DVec3 camera_pos: DVec3,
) -> bool { ) -> bool {
if light_source_r < shadow_caster_r { if light_source_r < shadow_caster_r {
error!("common::in_shadow only works if light_source_r > shadow_caster_r"); error!("common::in_shadow only works if light_source_r > shadow_caster_r");
@ -101,7 +101,9 @@ pub fn in_shadow(
let closest_vector = shadow_caster_to_player - projection; let closest_vector = shadow_caster_to_player - projection;
let distance_between_light_and_caster = (shadow_caster_pos - light_source_pos).length(); let distance_between_light_and_caster = (shadow_caster_pos - light_source_pos).length();
let max_distance = shadow_caster_r + ((shadow_caster_r - light_source_r) / distance_between_light_and_caster) * projection_length; let max_distance = shadow_caster_r
+ ((shadow_caster_r - light_source_r) / distance_between_light_and_caster)
* projection_length;
return closest_vector.length() < max_distance; return closest_vector.length() < max_distance;
} }

View file

@ -11,10 +11,10 @@
// This module handles player input, and coordinates interplay between other modules // This module handles player input, and coordinates interplay between other modules
use crate::prelude::*; use crate::prelude::*;
use bevy::prelude::*;
use bevy::pbr::ExtendedMaterial; use bevy::pbr::ExtendedMaterial;
use bevy::prelude::*;
use bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
use bevy::window::{Window, WindowMode, PrimaryWindow}; use bevy::window::{PrimaryWindow, Window, WindowMode};
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
@ -26,21 +26,29 @@ impl Plugin for GamePlugin {
app.add_systems(PostUpdate, handle_game_event); app.add_systems(PostUpdate, handle_game_event);
app.add_systems(PreUpdate, handle_player_death); app.add_systems(PreUpdate, handle_player_death);
app.add_systems(PostUpdate, update_id2pos); app.add_systems(PostUpdate, update_id2pos);
app.add_systems(Update, handle_achievement_event.run_if(on_event::<AchievementEvent>())); app.add_systems(
Update,
handle_achievement_event.run_if(on_event::<AchievementEvent>()),
);
app.add_systems(Update, check_achievements); app.add_systems(Update, check_achievements);
app.insert_resource(Id2Pos(HashMap::new())); app.insert_resource(Id2Pos(HashMap::new()));
app.insert_resource(var::AchievementTracker::default()); app.insert_resource(var::AchievementTracker::default());
app.insert_resource(AchievementCheckTimer( app.insert_resource(AchievementCheckTimer(Timer::from_seconds(
Timer::from_seconds(1.0, TimerMode::Repeating))); 1.0,
TimerMode::Repeating,
)));
app.add_event::<PlayerDiesEvent>(); app.add_event::<PlayerDiesEvent>();
app.add_event::<GameEvent>(); app.add_event::<GameEvent>();
app.add_event::<AchievementEvent>(); app.add_event::<AchievementEvent>();
} }
} }
#[derive(Event)] pub struct PlayerDiesEvent(pub actor::DamageType); #[derive(Event)]
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>); pub struct PlayerDiesEvent(pub actor::DamageType);
#[derive(Resource)] pub struct AchievementCheckTimer(pub Timer); #[derive(Resource)]
pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Resource)]
pub struct AchievementCheckTimer(pub Timer);
#[derive(Event)] #[derive(Event)]
pub enum AchievementEvent { pub enum AchievementEvent {
@ -125,7 +133,7 @@ pub fn handle_game_event(
let current_state = window.mode != WindowMode::Windowed; let current_state = window.mode != WindowMode::Windowed;
window.mode = match turn.to_bool(current_state) { window.mode = match turn.to_bool(current_state) {
true => opt.window_mode_fullscreen, true => opt.window_mode_fullscreen,
false => WindowMode::Windowed false => WindowMode::Windowed,
}; };
} }
} }
@ -137,8 +145,8 @@ pub fn handle_game_event(
settings.third_person = turn.to_bool(settings.third_person); settings.third_person = turn.to_bool(settings.third_person);
} }
GameEvent::SetRotationStabilizer(turn) => { GameEvent::SetRotationStabilizer(turn) => {
settings.rotation_stabilizer_active settings.rotation_stabilizer_active =
= turn.to_bool(settings.rotation_stabilizer_active); turn.to_bool(settings.rotation_stabilizer_active);
} }
GameEvent::SetShadows(turn) => { GameEvent::SetShadows(turn) => {
settings.shadows_sun = turn.to_bool(settings.shadows_sun); settings.shadows_sun = turn.to_bool(settings.shadows_sun);
@ -148,8 +156,11 @@ pub fn handle_game_event(
} }
GameEvent::Achievement(name) => { GameEvent::Achievement(name) => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Achieve)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Achieve));
log.add(format!("Achievement accomplished: {name}!"), log.add(
"".to_string(), hud::LogLevel::Achievement); format!("Achievement accomplished: {name}!"),
"".to_string(),
hud::LogLevel::Achievement,
);
} }
} }
} }
@ -234,9 +245,15 @@ fn handle_player_death(
fn handle_cheats( fn handle_cheats(
key_input: Res<ButtonInput<KeyCode>>, key_input: Res<ButtonInput<KeyCode>>,
mut q_player: Query<(&Transform, &mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>, mut q_player: Query<
(&Transform, &mut Position, &mut LinearVelocity),
With<actor::PlayerCamera>,
>,
mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With<actor::Player>>, mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With<actor::Player>>,
q_target: Query<(&Transform, &Position, Option<&LinearVelocity>), (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>, q_target: Query<
(&Transform, &Position, Option<&LinearVelocity>),
(With<hud::IsTargeted>, Without<actor::PlayerCamera>),
>,
mut ew_playerdies: EventWriter<PlayerDiesEvent>, mut ew_playerdies: EventWriter<PlayerDiesEvent>,
mut settings: ResMut<Settings>, mut settings: ResMut<Settings>,
id2pos: Res<Id2Pos>, id2pos: Res<Id2Pos>,
@ -266,9 +283,15 @@ fn handle_cheats(
gforce.ignore_gforce_seconds = 1.0; gforce.ignore_gforce_seconds = 1.0;
v.0 = DVec3::ZERO; v.0 = DVec3::ZERO;
} }
if key_input.pressed(settings.key_cheat_speed) || key_input.pressed(settings.key_cheat_speed_backward) { if key_input.pressed(settings.key_cheat_speed)
|| key_input.pressed(settings.key_cheat_speed_backward)
{
gforce.ignore_gforce_seconds = 1.0; gforce.ignore_gforce_seconds = 1.0;
let sign = if key_input.pressed(settings.key_cheat_speed) { 1.0 } else { -1.0 }; let sign = if key_input.pressed(settings.key_cheat_speed) {
1.0
} else {
-1.0
};
let dv = DVec3::from(trans.rotation * Vec3::new(0.0, 0.0, sign * boost)); let dv = DVec3::from(trans.rotation * Vec3::new(0.0, 0.0, sign * boost));
let current_speed = v.0.length(); let current_speed = v.0.length();
let next_speed = (v.0 + dv).length(); let next_speed = (v.0 + dv).length();
@ -278,7 +301,8 @@ fn handle_cheats(
} }
if key_input.just_pressed(settings.key_cheat_teleport) { if key_input.just_pressed(settings.key_cheat_teleport) {
if let Ok((transform, target_pos, target_v)) = q_target.get_single() { if let Ok((transform, target_pos, target_v)) = q_target.get_single() {
let offset: DVec3 = 4.0 * (**pos - **target_pos).normalize() * transform.scale.as_dvec3(); let offset: DVec3 =
4.0 * (**pos - **target_pos).normalize() * transform.scale.as_dvec3();
pos.0 = **target_pos + offset; pos.0 = **target_pos + offset;
if let Some(target_v) = target_v { if let Some(target_v) = target_v {
*v = target_v.clone(); *v = target_v.clone();
@ -323,10 +347,7 @@ fn handle_cheats(
} }
} }
fn update_id2pos( fn update_id2pos(mut id2pos: ResMut<Id2Pos>, q_id: Query<(&Position, &actor::Identifier)>) {
mut id2pos: ResMut<Id2Pos>,
q_id: Query<(&Position, &actor::Identifier)>,
) {
id2pos.0.clear(); id2pos.0.clear();
for (pos, id) in &q_id { for (pos, id) in &q_id {
id2pos.0.insert(id.0.clone(), pos.0); id2pos.0.insert(id.0.clone(), pos.0);
@ -337,7 +358,9 @@ fn debug(
settings: Res<var::Settings>, settings: Res<var::Settings>,
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mut commands: Commands, mut commands: Commands,
mut extended_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>>, mut extended_materials: ResMut<
Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>,
>,
mut achievement_tracker: ResMut<var::AchievementTracker>, mut achievement_tracker: ResMut<var::AchievementTracker>,
materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>, materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
) { ) {
@ -370,7 +393,9 @@ fn handle_achievement_event(
} }
AchievementEvent::InJupitersShadow => { AchievementEvent::InJupitersShadow => {
if !tracker.in_jupiters_shadow { if !tracker.in_jupiters_shadow {
ew_game.send(GameEvent::Achievement("Eclipse the sun with Jupiter".into())); ew_game.send(GameEvent::Achievement(
"Eclipse the sun with Jupiter".into(),
));
} }
tracker.in_jupiters_shadow = true; tracker.in_jupiters_shadow = true;
} }
@ -415,13 +440,32 @@ fn check_achievements(
mut ew_achievement: EventWriter<AchievementEvent>, mut ew_achievement: EventWriter<AchievementEvent>,
mut timer: ResMut<AchievementCheckTimer>, mut timer: ResMut<AchievementCheckTimer>,
) { ) {
if !timer.0.tick(time.delta()).just_finished() { return; } if !timer.0.tick(time.delta()).just_finished() {
let pos_player = if let Ok(pos) = q_player.get_single() { pos } else { return; }; return;
let pos_sun = if let Some(pos) = id2pos.0.get("sol") { pos } else { return; }; }
let pos_jupiter = if let Some(pos) = id2pos.0.get("jupiter") { pos } else { return; }; let pos_player = if let Ok(pos) = q_player.get_single() {
pos
} else {
return;
};
let pos_sun = if let Some(pos) = id2pos.0.get("sol") {
pos
} else {
return;
};
let pos_jupiter = if let Some(pos) = id2pos.0.get("jupiter") {
pos
} else {
return;
};
let shadowed = in_shadow(*pos_sun, nature::SOL_RADIUS, let shadowed = in_shadow(
*pos_jupiter, nature::JUPITER_RADIUS, **pos_player); *pos_sun,
nature::SOL_RADIUS,
*pos_jupiter,
nature::JUPITER_RADIUS,
**pos_player,
);
if shadowed { if shadowed {
ew_achievement.send(AchievementEvent::InJupitersShadow); ew_achievement.send(AchievementEvent::InJupitersShadow);

View file

@ -11,9 +11,9 @@
// This module manages the heads-up display and augmented reality overlays. // This module manages the heads-up display and augmented reality overlays.
use crate::prelude::*; use crate::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::pbr::{NotShadowCaster, NotShadowReceiver}; use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use std::collections::VecDeque; use std::collections::VecDeque;
@ -45,15 +45,20 @@ pub struct HudPlugin;
impl Plugin for HudPlugin { impl Plugin for HudPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, ( app.add_systems(
Update,
(
update_hud, update_hud,
update_dashboard, update_dashboard,
update_speedometer, update_speedometer,
update_gauges, update_gauges,
handle_input.run_if(in_control), handle_input.run_if(in_control),
handle_target_event, handle_target_event,
)); ),
app.add_systems(PostUpdate, ( );
app.add_systems(
PostUpdate,
(
update_overlay_visibility, update_overlay_visibility,
update_ar_overlays update_ar_overlays
.after(camera::position_to_transform) .after(camera::position_to_transform)
@ -65,7 +70,8 @@ impl Plugin for HudPlugin {
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
.after(camera::apply_input_to_player) .after(camera::apply_input_to_player)
.before(TransformSystem::TransformPropagate), .before(TransformSystem::TransformPropagate),
)); ),
);
app.insert_resource(AugmentedRealityState { app.insert_resource(AugmentedRealityState {
overlays_visible: false, overlays_visible: false,
}); });
@ -73,29 +79,47 @@ impl Plugin for HudPlugin {
logs: VecDeque::with_capacity(LOG_MAX), logs: VecDeque::with_capacity(LOG_MAX),
needs_rerendering: true, needs_rerendering: true,
}); });
app.insert_resource(FPSUpdateTimer( app.insert_resource(FPSUpdateTimer(Timer::from_seconds(
Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating))); HUD_REFRESH_TIME,
TimerMode::Repeating,
)));
app.add_event::<TargetEvent>(); app.add_event::<TargetEvent>();
app.add_event::<UpdateOverlayVisibility>(); app.add_event::<UpdateOverlayVisibility>();
} }
} }
#[derive(Event)] pub struct TargetEvent(pub Option<Entity>); #[derive(Event)]
#[derive(Event)] pub struct UpdateOverlayVisibility; pub struct TargetEvent(pub Option<Entity>);
#[derive(Component)] struct NodeHud; #[derive(Event)]
#[derive(Component)] struct NodeConsole; pub struct UpdateOverlayVisibility;
#[derive(Component)] struct NodeChoiceText; #[derive(Component)]
#[derive(Component)] struct NodeSpeedometerText; struct NodeHud;
#[derive(Component)] struct NodeCurrentChatLine; #[derive(Component)]
#[derive(Component)] struct Reticule; struct NodeConsole;
#[derive(Component)] struct Speedometer; #[derive(Component)]
#[derive(Component)] struct Speedometer2; struct NodeChoiceText;
#[derive(Component)] struct GaugeLength(f32); #[derive(Component)]
#[derive(Component)] pub struct ToggleableHudElement; struct NodeSpeedometerText;
#[derive(Component)] pub struct ToggleableHudMapElement; #[derive(Component)]
#[derive(Component)] struct Selectagon; struct NodeCurrentChatLine;
#[derive(Component)] pub struct IsTargeted; #[derive(Component)]
#[derive(Component)] pub struct PointOfInterestMarker(pub Entity); struct Reticule;
#[derive(Component)]
struct Speedometer;
#[derive(Component)]
struct Speedometer2;
#[derive(Component)]
struct GaugeLength(f32);
#[derive(Component)]
pub struct ToggleableHudElement;
#[derive(Component)]
pub struct ToggleableHudMapElement;
#[derive(Component)]
struct Selectagon;
#[derive(Component)]
pub struct IsTargeted;
#[derive(Component)]
pub struct PointOfInterestMarker(pub Entity);
#[derive(Component, Debug, Copy, Clone)] #[derive(Component, Debug, Copy, Clone)]
pub enum Dashboard { pub enum Dashboard {
@ -119,7 +143,8 @@ pub struct AugmentedRealityState {
pub overlays_visible: bool, pub overlays_visible: bool,
} }
#[derive(Component)] pub struct AugmentedRealityOverlayBroadcaster; #[derive(Component)]
pub struct AugmentedRealityOverlayBroadcaster;
#[derive(Component)] #[derive(Component)]
pub struct AugmentedRealityOverlay { pub struct AugmentedRealityOverlay {
pub owner: Entity, pub owner: Entity,
@ -155,8 +180,7 @@ impl Message {
pub fn format(&self) -> String { pub fn format(&self) -> String {
if self.sender.is_empty() { if self.sender.is_empty() {
return self.text.clone() + "\n"; return self.text.clone() + "\n";
} } else {
else {
return format!("{}: {}\n", self.sender, self.text); return format!("{}: {}\n", self.sender, self.text);
} }
} }
@ -168,11 +192,15 @@ pub struct IsClickable {
pub pronoun: Option<String>, pub pronoun: Option<String>,
pub distance: Option<f64>, pub distance: Option<f64>,
} }
impl Default for IsClickable { fn default() -> Self { Self { impl Default for IsClickable {
fn default() -> Self {
Self {
name: None, name: None,
pronoun: None, pronoun: None,
distance: None, distance: None,
}}} }
}
}
#[derive(Resource)] #[derive(Resource)]
pub struct Log { pub struct Log {
@ -277,33 +305,33 @@ pub fn setup(
let mut bundle_fps = TextBundle::from_sections([ let mut bundle_fps = TextBundle::from_sections([
TextSection::new("", style), // Target TextSection::new("", style), // Target
TextSection::new("", style_fps), // Frames per second TextSection::new("", style_fps), // Frames per second
]).with_style(Style { ])
.with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::VMin(2.0), top: Val::VMin(2.0),
left: Val::VMin(3.0), left: Val::VMin(3.0),
..default() ..default()
}).with_text_justify(JustifyText::Left); })
.with_text_justify(JustifyText::Left);
bundle_fps.visibility = visibility; bundle_fps.visibility = visibility;
commands.spawn(( commands.spawn((NodeHud, ToggleableHudElement, bundle_fps));
NodeHud,
ToggleableHudElement,
bundle_fps,
));
// Add Console // Add Console
// This one is intentionally NOT a ToggleableHudElement. Instead, console entries // This one is intentionally NOT a ToggleableHudElement. Instead, console entries
// are filtered based on whether the hud is active or not. LogLevel::Always is // are filtered based on whether the hud is active or not. LogLevel::Always is
// even shown when hud is inactive. // even shown when hud is inactive.
let bundle_chatbox = TextBundle::from_sections((0..LOG_MAX_ROWS).map(|_| let bundle_chatbox = TextBundle::from_sections(
TextSection::new("", style_console.clone())) (0..LOG_MAX_ROWS).map(|_| TextSection::new("", style_console.clone())),
).with_style(Style { )
.with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::VMin(0.0), top: Val::VMin(0.0),
right: Val::VMin(0.0), right: Val::VMin(0.0),
..default() ..default()
}).with_text_justify(JustifyText::Right); })
commands.spawn(( .with_text_justify(JustifyText::Right);
NodeBundle { commands
.spawn((NodeBundle {
style: Style { style: Style {
width: Val::Percent(50.0), width: Val::Percent(50.0),
align_items: AlignItems::Start, align_items: AlignItems::Start,
@ -313,24 +341,23 @@ pub fn setup(
..default() ..default()
}, },
..default() ..default()
}, },))
)).with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((bundle_chatbox, NodeConsole));
bundle_chatbox,
NodeConsole,
));
}); });
// Add Reticule // Add Reticule
let reticule_handle: Handle<Image> = asset_server.load("sprites/reticule4.png"); let reticule_handle: Handle<Image> = asset_server.load("sprites/reticule4.png");
commands.spawn(( commands
.spawn((
NodeBundle { NodeBundle {
style: style_centered(), style: style_centered(),
visibility, visibility,
..default() ..default()
}, },
ToggleableHudElement, ToggleableHudElement,
)).with_children(|builder| { ))
.with_children(|builder| {
builder.spawn(( builder.spawn((
ImageBundle { ImageBundle {
image: UiImage::new(reticule_handle), image: UiImage::new(reticule_handle),
@ -359,7 +386,8 @@ pub fn setup(
let bar_length = if i == 0 { 32.0 * 8.0 } else { 32.0 * 5.0 }; let bar_length = if i == 0 { 32.0 * 8.0 } else { 32.0 * 5.0 };
// The bar with variable width // The bar with variable width
commands.spawn(( commands
.spawn((
NodeBundle { NodeBundle {
style: Style { style: Style {
width: Val::Percent(30.0), width: Val::Percent(30.0),
@ -374,7 +402,8 @@ pub fn setup(
..default() ..default()
}, },
ToggleableHudElement, ToggleableHudElement,
)).with_children(|builder| { ))
.with_children(|builder| {
builder.spawn(( builder.spawn((
NodeBundle { NodeBundle {
style: Style { style: Style {
@ -394,7 +423,8 @@ pub fn setup(
}); });
// The decorator sprites surrounding the bar // The decorator sprites surrounding the bar
commands.spawn(( commands
.spawn((
NodeBundle { NodeBundle {
style: Style { style: Style {
width: Val::Percent(30.0), width: Val::Percent(30.0),
@ -409,10 +439,10 @@ pub fn setup(
..default() ..default()
}, },
ToggleableHudElement, ToggleableHudElement,
)).with_children(|builder| { ))
.with_children(|builder| {
// The gauge symbol // The gauge symbol
builder.spawn(( builder.spawn((ImageBundle {
ImageBundle {
image: UiImage::new(asset_server.load(sprite.to_string())), image: UiImage::new(asset_server.load(sprite.to_string())),
style: Style { style: Style {
width: Val::Px(icon_size), width: Val::Px(icon_size),
@ -421,11 +451,9 @@ pub fn setup(
}, },
visibility, visibility,
..Default::default() ..Default::default()
}, },));
));
// The gauge bar border // The gauge bar border
builder.spawn(( builder.spawn((ImageBundle {
ImageBundle {
image: UiImage::new(gauges_handle.clone()), image: UiImage::new(gauges_handle.clone()),
style: Style { style: Style {
width: Val::Px(bar_length), width: Val::Px(bar_length),
@ -436,19 +464,18 @@ pub fn setup(
}, },
visibility, visibility,
..Default::default() ..Default::default()
}, },));
));
}); });
} }
// Car-Dashboard-Style icons // Car-Dashboard-Style icons
let style_dashboard = Style { let style_dashboard = Style {
width: Val::Px(DASHBOARD_ICON_SIZE), width: Val::Px(DASHBOARD_ICON_SIZE),
height: Val::Px(DASHBOARD_ICON_SIZE), height: Val::Px(DASHBOARD_ICON_SIZE),
..Default::default() ..Default::default()
}; };
commands.spawn(( commands
.spawn((
NodeBundle { NodeBundle {
style: Style { style: Style {
width: Val::Percent(30.0), width: Val::Percent(30.0),
@ -463,13 +490,15 @@ pub fn setup(
..default() ..default()
}, },
ToggleableHudElement, ToggleableHudElement,
)).with_children(|builder| { ))
.with_children(|builder| {
for (component, filename) in DASHBOARD_DEF { for (component, filename) in DASHBOARD_DEF {
builder.spawn(( builder.spawn((
*component, *component,
ImageBundle { ImageBundle {
image: UiImage::new(asset_server.load( image: UiImage::new(
format!("sprites/dashboard_{}.png", filename))), asset_server.load(format!("sprites/dashboard_{}.png", filename)),
),
style: style_dashboard.clone(), style: style_dashboard.clone(),
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..Default::default() ..Default::default()
@ -480,7 +509,8 @@ pub fn setup(
// Add Speedometer // Add Speedometer
let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer.png"); let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer.png");
commands.spawn(( commands
.spawn((
NodeBundle { NodeBundle {
style: Style { style: Style {
width: Val::VMin(0.0), width: Val::VMin(0.0),
@ -495,9 +525,9 @@ pub fn setup(
}, },
Speedometer, Speedometer,
ToggleableHudElement, ToggleableHudElement,
)).with_children(|builder| { ))
builder.spawn(( .with_children(|builder| {
ImageBundle { builder.spawn((ImageBundle {
image: UiImage::new(speedometer_handle), image: UiImage::new(speedometer_handle),
style: Style { style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH), width: Val::Vw(SPEEDOMETER_WIDTH),
@ -505,11 +535,11 @@ pub fn setup(
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
}, },));
));
}); });
let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer_white.png"); let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer_white.png");
commands.spawn(( commands
.spawn((
NodeBundle { NodeBundle {
style: Style { style: Style {
width: Val::VMin(0.0), width: Val::VMin(0.0),
@ -524,9 +554,9 @@ pub fn setup(
}, },
Speedometer2, Speedometer2,
ToggleableHudElement, ToggleableHudElement,
)).with_children(|builder| { ))
builder.spawn(( .with_children(|builder| {
ImageBundle { builder.spawn((ImageBundle {
image: UiImage::new(speedometer_handle), image: UiImage::new(speedometer_handle),
style: Style { style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH), width: Val::Vw(SPEEDOMETER_WIDTH),
@ -534,19 +564,20 @@ pub fn setup(
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
}, },));
));
}); });
let mut bundle_speedometer_text = TextBundle::from_sections([ let mut bundle_speedometer_text = TextBundle::from_sections([
TextSection::new("", style_speedometer.clone()), // speed relative to target TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to target TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to orbit TextSection::new("", style_speedometer.clone()), // speed relative to orbit
]).with_style(Style { ])
.with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH + 2.0), left: Val::Vw(100.0 - SPEEDOMETER_WIDTH + 2.0),
bottom: Val::VMin(4.0), bottom: Val::VMin(4.0),
..default() ..default()
}).with_text_justify(JustifyText::Left); })
.with_text_justify(JustifyText::Left);
bundle_speedometer_text.visibility = visibility; bundle_speedometer_text.visibility = visibility;
commands.spawn(( commands.spawn((
NodeSpeedometerText, NodeSpeedometerText,
@ -555,7 +586,8 @@ pub fn setup(
)); ));
// Chat "subtitles" and choices // Chat "subtitles" and choices
commands.spawn(NodeBundle { commands
.spawn(NodeBundle {
style: Style { style: Style {
width: Val::Vw(100.0), width: Val::Vw(100.0),
align_items: AlignItems::Center, align_items: AlignItems::Center,
@ -566,13 +598,12 @@ pub fn setup(
..default() ..default()
}, },
..default() ..default()
}).with_children(|builder| { })
.with_children(|builder| {
builder.spawn(( builder.spawn((
TextBundle { TextBundle {
text: Text { text: Text {
sections: vec![ sections: vec![TextSection::new("", style_conversations)],
TextSection::new("", style_conversations),
],
justify: JustifyText::Center, justify: JustifyText::Center,
..default() ..default()
}, },
@ -588,8 +619,8 @@ pub fn setup(
}, },
NodeCurrentChatLine, NodeCurrentChatLine,
)); ));
let choice_sections = (0..MAX_CHOICES).map(|_| let choice_sections =
TextSection::new("", style_choices.clone())); (0..MAX_CHOICES).map(|_| TextSection::new("", style_choices.clone()));
builder.spawn(( builder.spawn((
TextBundle { TextBundle {
text: Text { text: Text {
@ -628,23 +659,17 @@ fn update_dashboard(
return; return;
} }
let player = q_player.get_single(); let player = q_player.get_single();
if player.is_err() { return; } if player.is_err() {
return;
}
let (suit, pos) = player.unwrap(); let (suit, pos) = player.unwrap();
for (mut vis, icon) in &mut q_dashboard { for (mut vis, icon) in &mut q_dashboard {
*vis = bool2vis(match icon { *vis = bool2vis(match icon {
Dashboard::Flashlight => { Dashboard::Flashlight => settings.flashlight_active,
settings.flashlight_active Dashboard::Leak => suit.integrity < 0.5,
} Dashboard::RotationStabiliser => !settings.rotation_stabilizer_active,
Dashboard::Leak => { Dashboard::CruiseControl => settings.cruise_control_active,
suit.integrity < 0.5
}
Dashboard::RotationStabiliser => {
!settings.rotation_stabilizer_active
}
Dashboard::CruiseControl => {
settings.cruise_control_active
}
Dashboard::Radioactivity => { Dashboard::Radioactivity => {
if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) { if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) {
pos_jupiter.distance(pos.0) < 140_000_000.0 pos_jupiter.distance(pos.0) < 140_000_000.0
@ -675,13 +700,21 @@ fn update_speedometer(
let speedometer_split = 5_000.0; let speedometer_split = 5_000.0;
if let Ok(mut speedometer) = q_speedometer.get_single_mut() { if let Ok(mut speedometer) = q_speedometer.get_single_mut() {
let custom_c = speedometer_split; let custom_c = speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32; let fraction = nature::inverse_lorentz_factor_custom_c(
(custom_c - speed).clamp(0.0, custom_c),
custom_c,
)
.clamp(0.0, 1.0) as f32;
let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0); let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0);
speedometer.width = Val::Vw(wid); speedometer.width = Val::Vw(wid);
} }
if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() { if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() {
let custom_c = nature::C - speedometer_split; let custom_c = nature::C - speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed + speedometer_split).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32; let fraction = nature::inverse_lorentz_factor_custom_c(
(custom_c - speed + speedometer_split).clamp(0.0, custom_c),
custom_c,
)
.clamp(0.0, 1.0) as f32;
let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0); let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0);
speedometer2.width = Val::Vw(wid); speedometer2.width = Val::Vw(wid);
} }
@ -729,7 +762,9 @@ fn update_gauges(
return; return;
} }
let player = q_player.get_single(); let player = q_player.get_single();
if player.is_err() { return; } if player.is_err() {
return;
}
let (hp, suit, battery) = player.unwrap(); let (hp, suit, battery) = player.unwrap();
for (mut style, mut bg, gauge, len) in &mut q_gauges { for (mut style, mut bg, gauge, len) in &mut q_gauges {
@ -741,8 +776,7 @@ fn update_gauges(
}; };
if value < 0.5 { if value < 0.5 {
*bg = settings.hud_color_alert.into(); *bg = settings.hud_color_alert.into();
} } else {
else {
*bg = settings.hud_color.into(); *bg = settings.hud_color.into();
} }
style.width = Val::Px(len.0 * value); style.width = Val::Px(len.0 * value);
@ -758,9 +792,23 @@ fn update_hud(
q_choices: Query<&chat::Choice>, q_choices: Query<&chat::Choice>,
q_chat: Query<&chat::Chat>, q_chat: Query<&chat::Chat>,
mut q_node_hud: Query<&mut Text, With<NodeHud>>, mut q_node_hud: Query<&mut Text, With<NodeHud>>,
mut q_node_console: Query<&mut Text, (With<NodeConsole>, Without<NodeHud>, Without<NodeChoiceText>)>, mut q_node_console: Query<
mut q_node_choice: Query<&mut Text, (With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>)>, &mut Text,
mut q_node_currentline: Query<&mut Text, (With<NodeCurrentChatLine>, Without<NodeHud>, Without<NodeConsole>, Without<NodeChoiceText>)>, (With<NodeConsole>, Without<NodeHud>, Without<NodeChoiceText>),
>,
mut q_node_choice: Query<
&mut Text,
(With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>),
>,
mut q_node_currentline: Query<
&mut Text,
(
With<NodeCurrentChatLine>,
Without<NodeHud>,
Without<NodeConsole>,
Without<NodeChoiceText>,
),
>,
settings: Res<Settings>, settings: Res<Settings>,
q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>, q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>,
) { ) {
@ -781,41 +829,42 @@ fn update_hud(
let dist_scalar: f64; let dist_scalar: f64;
let mut target_multiple = false; let mut target_multiple = false;
let mut target_error = false; let mut target_error = false;
if let Ok((IsClickable { distance: Some(dist), .. }, _, _)) = q_target.get_single() { if let Ok((
IsClickable {
distance: Some(dist),
..
},
_,
_,
)) = q_target.get_single()
{
dist_scalar = *dist; dist_scalar = *dist;
} } else {
else {
let target: Option<DVec3>; let target: Option<DVec3>;
if let Ok((_, Some(targetpos), _)) = q_target.get_single() { if let Ok((_, Some(targetpos), _)) = q_target.get_single() {
target = Some(targetpos.0); target = Some(targetpos.0);
} } else if q_target.is_empty() {
else if q_target.is_empty() {
target = Some(DVec3::new(0.0, 0.0, 0.0)); target = Some(DVec3::new(0.0, 0.0, 0.0));
} } else if q_target.iter().len() > 1 {
else if q_target.iter().len() > 1 {
target_multiple = true; target_multiple = true;
target = None; target = None;
} } else {
else {
target_error = true; target_error = true;
target = None; target = None;
} }
if let Some(target_pos) = target { if let Some(target_pos) = target {
let dist = pos.0 - target_pos; let dist = pos.0 - target_pos;
dist_scalar = dist.length(); dist_scalar = dist.length();
} } else {
else {
dist_scalar = 0.0; dist_scalar = 0.0;
} }
} }
if target_multiple { if target_multiple {
text.sections[0].value = "ERROR: MULTIPLE TARGETS\n\n".to_string(); text.sections[0].value = "ERROR: MULTIPLE TARGETS\n\n".to_string();
} } else if target_error {
else if target_error {
text.sections[0].value = "ERROR: FAILED TO AQUIRE TARGET\n\n".to_string(); text.sections[0].value = "ERROR: FAILED TO AQUIRE TARGET\n\n".to_string();
} } else if let Ok((clickable, _, _)) = q_target.get_single() {
else if let Ok((clickable, _, _)) = q_target.get_single() {
let distance = if dist_scalar.is_nan() { let distance = if dist_scalar.is_nan() {
"UNKNOWN".to_string() "UNKNOWN".to_string()
} else if dist_scalar != 0.0 { } else if dist_scalar != 0.0 {
@ -829,9 +878,9 @@ fn update_hud(
} else { } else {
"".to_string() "".to_string()
}; };
text.sections[0].value = format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n"); text.sections[0].value =
} format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n");
else { } else {
text.sections[0].value = "".to_string(); text.sections[0].value = "".to_string();
} }
} }
@ -862,15 +911,17 @@ fn update_hud(
// Chat Log and System Log // Chat Log and System Log
let logfilter = if settings.hud_active { let logfilter = if settings.hud_active {
|_msg: &&Message| { true } |_msg: &&Message| true
} else { } else {
|msg: &&Message| { match msg.level { |msg: &&Message| match msg.level {
LogLevel::Always => true, LogLevel::Always => true,
LogLevel::Achievement => true, LogLevel::Achievement => true,
_ => false _ => false,
}} }
}; };
let messages: Vec<&Message> = log.logs.iter() let messages: Vec<&Message> = log
.logs
.iter()
.filter(logfilter) .filter(logfilter)
.rev() .rev()
.take(LOG_MAX_ROWS) .take(LOG_MAX_ROWS)
@ -893,11 +944,13 @@ fn update_hud(
// Display the last chat line as "subtitles" // Display the last chat line as "subtitles"
if !q_chat.is_empty() { if !q_chat.is_empty() {
let messages: Vec<&Message> = log.logs.iter() let messages: Vec<&Message> = log
.filter(|msg: &&Message| { match msg.level { .logs
.iter()
.filter(|msg: &&Message| match msg.level {
LogLevel::Chat => true, LogLevel::Chat => true,
_ => false _ => false,
}}) })
.rev() .rev()
.take(1) .take(1)
.collect(); .collect();
@ -963,7 +1016,15 @@ fn handle_input(
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_target: EventWriter<TargetEvent>, mut ew_target: EventWriter<TargetEvent>,
mut ew_game: EventWriter<GameEvent>, mut ew_game: EventWriter<GameEvent>,
q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>, q_objects: Query<
(Entity, &Transform),
(
With<IsClickable>,
Without<IsTargeted>,
Without<actor::PlayerDrivesThis>,
Without<actor::Player>,
),
>,
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
) { ) {
if keyboard_input.just_pressed(settings.key_togglehud) { if keyboard_input.just_pressed(settings.key_togglehud) {
@ -973,10 +1034,11 @@ fn handle_input(
if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) { if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) {
if let Ok(camtrans) = q_camera.get_single() { if let Ok(camtrans) = q_camera.get_single() {
let objects: Vec<(Entity, &Transform)> = q_objects.iter().collect(); let objects: Vec<(Entity, &Transform)> = q_objects.iter().collect();
if let (Some(new_target), _dist) = camera::find_closest_target::<Entity>(objects, camtrans) { if let (Some(new_target), _dist) =
camera::find_closest_target::<Entity>(objects, camtrans)
{
ew_target.send(TargetEvent(Some(new_target))); ew_target.send(TargetEvent(Some(new_target)));
} } else {
else {
ew_target.send(TargetEvent(None)); ew_target.send(TargetEvent(None));
} }
} }
@ -1018,7 +1080,10 @@ fn handle_target_event(
fn update_target_selectagon( fn update_target_selectagon(
settings: Res<Settings>, settings: Res<Settings>,
mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With<Selectagon>, Without<IsTargeted>, Without<Camera>)>, mut q_selectagon: Query<
(&mut Transform, &mut Visibility),
(With<Selectagon>, Without<IsTargeted>, Without<Camera>),
>,
q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>, q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>,
q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>, q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>,
) { ) {
@ -1030,7 +1095,9 @@ fn update_target_selectagon(
if let Ok((mut selectagon_trans, mut selectagon_vis)) = q_selectagon.get_single_mut() { if let Ok((mut selectagon_trans, mut selectagon_vis)) = q_selectagon.get_single_mut() {
if let Ok(target_trans) = q_target.get_single() { if let Ok(target_trans) = q_target.get_single() {
match *selectagon_vis { match *selectagon_vis {
Visibility::Hidden => { *selectagon_vis = Visibility::Visible; }, Visibility::Hidden => {
*selectagon_vis = Visibility::Visible;
}
_ => {} _ => {}
} }
selectagon_trans.translation = target_trans.translation; selectagon_trans.translation = target_trans.translation;
@ -1038,25 +1105,36 @@ fn update_target_selectagon(
selectagon_trans.look_at(camera_trans.translation, camera_trans.up().into()); selectagon_trans.look_at(camera_trans.translation, camera_trans.up().into());
// Enlarge Selectagon to a minimum angular diameter // Enlarge Selectagon to a minimum angular diameter
let (angular_diameter, _, _) = camera::calc_angular_diameter( let (angular_diameter, _, _) =
&selectagon_trans, camera_trans); camera::calc_angular_diameter(&selectagon_trans, camera_trans);
let min_angular_diameter = 2.0f32.to_radians(); let min_angular_diameter = 2.0f32.to_radians();
if angular_diameter < min_angular_diameter { if angular_diameter < min_angular_diameter {
selectagon_trans.scale *= min_angular_diameter / angular_diameter; selectagon_trans.scale *= min_angular_diameter / angular_diameter;
} }
} } else {
else {
match *selectagon_vis { match *selectagon_vis {
Visibility::Hidden => {}, Visibility::Hidden => {}
_ => { *selectagon_vis = Visibility::Hidden; } _ => {
*selectagon_vis = Visibility::Hidden;
}
} }
} }
} }
} }
fn update_ar_overlays( fn update_ar_overlays(
q_owners: Query<(Entity, &Transform, &Visibility), (With<AugmentedRealityOverlayBroadcaster>, Without<AugmentedRealityOverlay>)>, q_owners: Query<
mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>, (Entity, &Transform, &Visibility),
(
With<AugmentedRealityOverlayBroadcaster>,
Without<AugmentedRealityOverlay>,
),
>,
mut q_overlays: Query<(
&mut Transform,
&mut Visibility,
&mut AugmentedRealityOverlay,
)>,
settings: ResMut<Settings>, settings: ResMut<Settings>,
mut state: ResMut<AugmentedRealityState>, mut state: ResMut<AugmentedRealityState>,
) { ) {
@ -1064,8 +1142,7 @@ fn update_ar_overlays (
if settings.hud_active { if settings.hud_active {
need_activate = !state.overlays_visible; need_activate = !state.overlays_visible;
need_clean = false; need_clean = false;
} } else {
else {
need_activate = false; need_activate = false;
need_clean = state.overlays_visible; need_clean = state.overlays_visible;
} }
@ -1079,8 +1156,7 @@ fn update_ar_overlays (
*trans = *owner_trans; *trans = *owner_trans;
if need_clean { if need_clean {
*vis = Visibility::Hidden; *vis = Visibility::Hidden;
} } else {
else {
*vis = *owner_vis; *vis = *owner_vis;
} }
break; break;
@ -1105,8 +1181,7 @@ fn update_poi_overlays (
// Enlarge POI marker to a minimum angular diameter // Enlarge POI marker to a minimum angular diameter
trans.translation = parent_trans.translation; trans.translation = parent_trans.translation;
trans.scale = Vec3::splat(1.0); trans.scale = Vec3::splat(1.0);
let (angular_diameter, _, _) = camera::calc_angular_diameter( let (angular_diameter, _, _) = camera::calc_angular_diameter(&trans, camera_trans);
&trans, camera_trans);
let min_angular_diameter = 3.0f32.to_radians(); let min_angular_diameter = 3.0f32.to_radians();
if angular_diameter < min_angular_diameter { if angular_diameter < min_angular_diameter {
trans.scale *= min_angular_diameter / angular_diameter; trans.scale *= min_angular_diameter / angular_diameter;
@ -1118,9 +1193,27 @@ fn update_poi_overlays (
fn update_overlay_visibility( fn update_overlay_visibility(
mut q_marker: Query<&mut Visibility, With<PointOfInterestMarker>>, mut q_marker: Query<&mut Visibility, With<PointOfInterestMarker>>,
mut q_hudelement: Query<&mut Visibility, (With<ToggleableHudElement>, Without<PointOfInterestMarker>)>, mut q_hudelement: Query<
mut q_selectagon: Query<&mut Visibility, (With<Selectagon>, Without<ToggleableHudElement>, Without<PointOfInterestMarker>)>, &mut Visibility,
q_target: Query<&IsTargeted, (Without<Camera>, Without<Selectagon>, Without<PointOfInterestMarker>, Without<ToggleableHudElement>)>, (With<ToggleableHudElement>, Without<PointOfInterestMarker>),
>,
mut q_selectagon: Query<
&mut Visibility,
(
With<Selectagon>,
Without<ToggleableHudElement>,
Without<PointOfInterestMarker>,
),
>,
q_target: Query<
&IsTargeted,
(
Without<Camera>,
Without<Selectagon>,
Without<PointOfInterestMarker>,
Without<ToggleableHudElement>,
),
>,
mut ambient_light: ResMut<AmbientLight>, mut ambient_light: ResMut<AmbientLight>,
er_target: EventReader<UpdateOverlayVisibility>, er_target: EventReader<UpdateOverlayVisibility>,
settings: Res<Settings>, settings: Res<Settings>,
@ -1128,8 +1221,14 @@ fn update_overlay_visibility(
if er_target.is_empty() { if er_target.is_empty() {
return; return;
} }
let check = {|check: bool| let check = {
if check { Visibility::Inherited } else { Visibility::Hidden } |check: bool| {
if check {
Visibility::Inherited
} else {
Visibility::Hidden
}
}
}; };
let show_poi = check(settings.hud_active && settings.map_active); let show_poi = check(settings.hud_active && settings.map_active);
let show_hud = check(settings.hud_active); let show_hud = check(settings.hud_active);

View file

@ -11,16 +11,18 @@
// This module manages asset loading. // This module manages asset loading.
use bevy::ecs::system::EntityCommands; use bevy::ecs::system::EntityCommands;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod}; use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
pub struct LoadPlugin; pub struct LoadPlugin;
impl Plugin for LoadPlugin { impl Plugin for LoadPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(MaterialPlugin::<JupitersRing>::default()); app.add_plugins(MaterialPlugin::<JupitersRing>::default());
app.add_plugins(MaterialPlugin::<SkyBox>::default()); app.add_plugins(MaterialPlugin::<SkyBox>::default());
app.add_plugins(MaterialPlugin::<ExtendedMaterial<StandardMaterial, AsteroidSurface, >>::default()); app.add_plugins(MaterialPlugin::<
ExtendedMaterial<StandardMaterial, AsteroidSurface>,
>::default());
} }
} }
@ -55,19 +57,12 @@ pub fn asset_name_to_path(name: &str) -> &'static str {
} }
} }
pub fn load_asset( pub fn load_asset(name: &str, entity_commands: &mut EntityCommands, asset_server: &AssetServer) {
name: &str,
entity_commands: &mut EntityCommands,
asset_server: &AssetServer,
) {
entity_commands.insert(load_scene_by_path(asset_name_to_path(name), asset_server)); entity_commands.insert(load_scene_by_path(asset_name_to_path(name), asset_server));
} }
#[inline] #[inline]
pub fn load_scene_by_path( pub fn load_scene_by_path(path: &str, asset_server: &AssetServer) -> Handle<Scene> {
path: &str,
asset_server: &AssetServer
) -> Handle<Scene> {
let path_string = path.to_string(); let path_string = path.to_string();
if let Some(handle) = asset_server.get_handle(&path_string) { if let Some(handle) = asset_server.get_handle(&path_string) {
handle handle
@ -108,7 +103,7 @@ impl Material for SkyBox {
#[derive(Asset, Reflect, AsBindGroup, Debug, Clone)] #[derive(Asset, Reflect, AsBindGroup, Debug, Clone)]
pub struct AsteroidSurface { pub struct AsteroidSurface {
#[uniform(100)] #[uniform(100)]
quantize_steps: u32 quantize_steps: u32,
} }
impl MaterialExtension for AsteroidSurface { impl MaterialExtension for AsteroidSurface {
@ -135,8 +130,6 @@ impl AsteroidSurface {
} }
impl Default for AsteroidSurface { impl Default for AsteroidSurface {
fn default() -> Self { fn default() -> Self {
Self { Self { quantize_steps: 3 }
quantize_steps: 3,
}
} }
} }

View file

@ -28,18 +28,19 @@ pub mod visual;
pub mod world; pub mod world;
pub mod prelude { pub mod prelude {
pub use crate::{actor, audio, camera, chat, cmd, common, game, hud,
load, menu, nature, var, visual, world};
pub use crate::common::*; pub use crate::common::*;
pub use crate::var::Settings;
pub use crate::load::load_asset; pub use crate::load::load_asset;
pub use game::{GameEvent, Turn}; pub use crate::var::Settings;
pub use crate::{
actor, audio, camera, chat, cmd, common, game, hud, load, menu, nature, var, visual, world,
};
pub use game::Turn::Toggle; pub use game::Turn::Toggle;
pub use game::{GameEvent, Turn};
} }
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
use bevy::diagnostic::FrameTimeDiagnosticsPlugin; use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::window::{CursorGrabMode, PrimaryWindow, Window, WindowMode};
use std::env; use std::env;
const HELP: &str = "./outfly [options] const HELP: &str = "./outfly [options]
@ -68,8 +69,7 @@ fn main() {
if arg == "--help" || arg == "-h" { if arg == "--help" || arg == "-h" {
println!("{}", HELP); println!("{}", HELP);
return; return;
} } else if arg == "--version" || arg == "-v" {
else if arg == "--version" || arg == "-v" {
let version = option_env!("CARGO_PKG_VERSION").unwrap(); let version = option_env!("CARGO_PKG_VERSION").unwrap();
let name = option_env!("CARGO_PKG_NAME").unwrap(); let name = option_env!("CARGO_PKG_NAME").unwrap();
let homepage = option_env!("CARGO_PKG_HOMEPAGE").unwrap(); let homepage = option_env!("CARGO_PKG_HOMEPAGE").unwrap();
@ -77,28 +77,23 @@ fn main() {
println!("License: GNU GPL version 3: https://gnu.org/licenses/gpl.html"); println!("License: GNU GPL version 3: https://gnu.org/licenses/gpl.html");
println!("{homepage}"); println!("{homepage}");
return; return;
} } else if arg == "--gl" {
else if arg == "--gl" {
opt.use_gl = true; opt.use_gl = true;
} } else if arg == "--windowed" {
else if arg == "--windowed" {
opt.window_mode_initial = WindowMode::Windowed; opt.window_mode_initial = WindowMode::Windowed;
} } else if arg == "--fs-legacy" {
else if arg == "--fs-legacy" {
let mode = WindowMode::Fullscreen; let mode = WindowMode::Fullscreen;
if opt.window_mode_initial == opt.window_mode_fullscreen { if opt.window_mode_initial == opt.window_mode_fullscreen {
opt.window_mode_initial = mode; opt.window_mode_initial = mode;
} }
opt.window_mode_fullscreen = mode; opt.window_mode_fullscreen = mode;
} } else if arg == "--fs-sized" {
else if arg == "--fs-sized" {
let mode = WindowMode::SizedFullscreen; let mode = WindowMode::SizedFullscreen;
if opt.window_mode_initial == opt.window_mode_fullscreen { if opt.window_mode_initial == opt.window_mode_fullscreen {
opt.window_mode_initial = mode; opt.window_mode_initial = mode;
} }
opt.window_mode_fullscreen = mode; opt.window_mode_fullscreen = mode;
} } else {
else {
println!("{}", HELP); println!("{}", HELP);
println!("\nERROR: no such option: `{}`", arg); println!("\nERROR: no such option: `{}`", arg);
return; return;
@ -114,7 +109,7 @@ fn main() {
#[cfg(feature = "embed_assets")] #[cfg(feature = "embed_assets")]
app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin { app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin {
mode: bevy_embedded_assets::PluginMode::ReplaceDefault mode: bevy_embedded_assets::PluginMode::ReplaceDefault,
}); });
app.add_plugins(OutFlyPlugin); app.add_plugins(OutFlyPlugin);
@ -132,7 +127,6 @@ impl Plugin for OutFlyPlugin {
app.add_plugins(( app.add_plugins((
DefaultPlugins, //.set(ImagePlugin::default_nearest()), DefaultPlugins, //.set(ImagePlugin::default_nearest()),
FrameTimeDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
actor::ActorPlugin, actor::ActorPlugin,
audio::AudioPlugin, audio::AudioPlugin,
camera::CameraPlugin, camera::CameraPlugin,
@ -148,10 +142,7 @@ impl Plugin for OutFlyPlugin {
} }
} }
fn setup( fn setup(mut windows: Query<&mut Window, With<PrimaryWindow>>, opt: Res<var::CommandLineOptions>) {
mut windows: Query<&mut Window, With<PrimaryWindow>>,
opt: Res<var::CommandLineOptions>,
) {
for mut window in &mut windows { for mut window in &mut windows {
window.cursor.grab_mode = CursorGrabMode::Locked; window.cursor.grab_mode = CursorGrabMode::Locked;
window.cursor.visible = false; window.cursor.visible = false;

View file

@ -20,28 +20,47 @@ pub struct MenuPlugin;
impl Plugin for MenuPlugin { impl Plugin for MenuPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup.after(hud::setup)); app.add_systems(Startup, setup.after(hud::setup));
app.add_systems(PreUpdate, show_deathscreen.run_if(on_event::<DeathScreenEvent>())); app.add_systems(
PreUpdate,
show_deathscreen.run_if(on_event::<DeathScreenEvent>()),
);
app.add_systems(Update, handle_deathscreen_input); app.add_systems(Update, handle_deathscreen_input);
app.add_systems(PostUpdate, update_menu app.add_systems(
PostUpdate,
update_menu
.after(game::handle_game_event) .after(game::handle_game_event)
.run_if(on_event::<UpdateMenuEvent>())); .run_if(on_event::<UpdateMenuEvent>()),
);
app.add_systems(Update, handle_input.run_if(alive)); app.add_systems(Update, handle_input.run_if(alive));
app.insert_resource(DeathScreenInputDelayTimer( app.insert_resource(DeathScreenInputDelayTimer(Timer::from_seconds(
Timer::from_seconds(1.0, TimerMode::Once))); 1.0,
TimerMode::Once,
)));
app.insert_resource(MenuState::default()); app.insert_resource(MenuState::default());
app.add_event::<DeathScreenEvent>(); app.add_event::<DeathScreenEvent>();
app.add_event::<UpdateMenuEvent>(); app.add_event::<UpdateMenuEvent>();
} }
} }
#[derive(Resource)] pub struct DeathScreenInputDelayTimer(pub Timer); #[derive(Resource)]
#[derive(Component)] pub struct MenuElement; pub struct DeathScreenInputDelayTimer(pub Timer);
#[derive(Component)] pub struct MenuTopLevel; #[derive(Component)]
#[derive(Component)] pub struct MenuAchievements; pub struct MenuElement;
#[derive(Component)] pub struct DeathScreenElement; #[derive(Component)]
#[derive(Component)] pub struct DeathText; pub struct MenuTopLevel;
#[derive(Event)] pub struct UpdateMenuEvent; #[derive(Component)]
#[derive(Event, PartialEq)] pub enum DeathScreenEvent { Show, Hide } pub struct MenuAchievements;
#[derive(Component)]
pub struct DeathScreenElement;
#[derive(Component)]
pub struct DeathText;
#[derive(Event)]
pub struct UpdateMenuEvent;
#[derive(Event, PartialEq)]
pub enum DeathScreenEvent {
Show,
Hide,
}
pub const MENUDEF: &[(&str, MenuAction)] = &[ pub const MENUDEF: &[(&str, MenuAction)] = &[
("", MenuAction::ToggleMap), ("", MenuAction::ToggleMap),
@ -115,14 +134,16 @@ pub fn setup(
color: settings.hud_color_death_achievements, color: settings.hud_color_death_achievements,
..default() ..default()
}; };
commands.spawn(( commands
.spawn((
DeathScreenElement, DeathScreenElement,
NodeBundle { NodeBundle {
style: style_centered(), style: style_centered(),
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)).with_children(|builder| { ))
.with_children(|builder| {
builder.spawn(( builder.spawn((
DeathText, DeathText,
TextBundle { TextBundle {
@ -133,7 +154,10 @@ pub fn setup(
TextSection::new("Cause: ", style_death_subtext.clone()), TextSection::new("Cause: ", style_death_subtext.clone()),
TextSection::new("Unknown", style_death_subtext), TextSection::new("Unknown", style_death_subtext),
TextSection::new("", style_death_achievements), TextSection::new("", style_death_achievements),
TextSection::new("\n\n\n\nPress E to begin anew.", style_death_subsubtext), TextSection::new(
"\n\n\n\nPress E to begin anew.",
style_death_subsubtext,
),
], ],
justify: JustifyText::Center, justify: JustifyText::Center,
..default() ..default()
@ -150,9 +174,11 @@ pub fn setup(
..default() ..default()
}; };
let sections: Vec<TextSection> = Vec::from_iter(MENUDEF.iter().map(|(label, _)| let sections: Vec<TextSection> = Vec::from_iter(
TextSection::new(label.to_string() + "\n", style_menu.clone()) MENUDEF
)); .iter()
.map(|(label, _)| TextSection::new(label.to_string() + "\n", style_menu.clone())),
);
commands.spawn(( commands.spawn((
MenuElement, MenuElement,
@ -164,7 +190,8 @@ pub fn setup(
}, },
)); ));
commands.spawn(( commands
.spawn((
MenuElement, MenuElement,
NodeBundle { NodeBundle {
style: Style { style: Style {
@ -177,7 +204,8 @@ pub fn setup(
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)).with_children(|builder| { ))
.with_children(|builder| {
builder.spawn(( builder.spawn((
MenuTopLevel, MenuTopLevel,
TextBundle { TextBundle {
@ -205,7 +233,8 @@ pub fn setup(
}; };
let achievement_count = achievement_tracker.to_bool_vec().len(); let achievement_count = achievement_tracker.to_bool_vec().len();
commands.spawn(( commands
.spawn((
MenuElement, MenuElement,
NodeBundle { NodeBundle {
style: Style { style: Style {
@ -220,13 +249,15 @@ pub fn setup(
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)).with_children(|builder| { ))
let mut sections = vec![ .with_children(|builder| {
TextSection::new("Achievements\n", style_achievement_header.clone()) let mut sections = vec![TextSection::new(
]; "Achievements\n",
sections.extend(Vec::from_iter((0..achievement_count).map(|_| style_achievement_header.clone(),
TextSection::new("", style_achievement.clone()) )];
))); sections.extend(Vec::from_iter(
(0..achievement_count).map(|_| TextSection::new("", style_achievement.clone())),
));
builder.spawn(( builder.spawn((
MenuAchievements, MenuAchievements,
TextBundle { TextBundle {
@ -248,7 +279,8 @@ pub fn setup(
..default() ..default()
}; };
commands.spawn(( commands
.spawn((
MenuElement, MenuElement,
NodeBundle { NodeBundle {
style: Style { style: Style {
@ -263,20 +295,19 @@ pub fn setup(
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)).with_children(|builder| { ))
builder.spawn(( .with_children(|builder| {
TextBundle { builder.spawn((TextBundle {
text: Text { text: Text {
sections: vec![ sections: vec![
TextSection::new("Controls\n", style_achievement_header), TextSection::new("Controls\n", style_achievement_header),
TextSection::new(keybindings, style_keybindings) TextSection::new(keybindings, style_keybindings),
], ],
justify: JustifyText::Right, justify: JustifyText::Right,
..default() ..default()
}, },
..default() ..default()
}, },));
));
}); });
let style_version = TextStyle { let style_version = TextStyle {
@ -286,7 +317,8 @@ pub fn setup(
..default() ..default()
}; };
commands.spawn(( commands
.spawn((
MenuElement, MenuElement,
NodeBundle { NodeBundle {
style: Style { style: Style {
@ -301,20 +333,19 @@ pub fn setup(
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)).with_children(|builder| { ))
builder.spawn(( .with_children(|builder| {
TextBundle { builder.spawn((TextBundle {
text: Text { text: Text {
sections: vec![ sections: vec![TextSection::new(
TextSection::new(format!("{} {}", GAME_NAME, format!("{} {}", GAME_NAME, settings.version.as_str()),
settings.version.as_str()), style_version), style_version,
], )],
justify: JustifyText::Right, justify: JustifyText::Right,
..default() ..default()
}, },
..default() ..default()
}, },));
));
}); });
} }
@ -359,7 +390,10 @@ pub fn show_deathscreen(
} else { } else {
ew_respawnaudiosinks.send(audio::RespawnSinksEvent); ew_respawnaudiosinks.send(audio::RespawnSinksEvent);
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(visual::SpawnEffectEvent { class: visual::Effects::FadeIn(Color::BLACK), duration: 0.3 }); ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 0.3,
});
ew_respawn.send(world::RespawnEvent); ew_respawn.send(world::RespawnEvent);
} }
} }
@ -512,37 +546,37 @@ pub fn handle_input(
ew_game.send(GameEvent::SetMap(Toggle)); ew_game.send(GameEvent::SetMap(Toggle));
ew_game.send(GameEvent::SetMenu(Turn::Off)); ew_game.send(GameEvent::SetMenu(Turn::Off));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
}, }
MenuAction::ToggleAR => { MenuAction::ToggleAR => {
ew_game.send(GameEvent::SetAR(Toggle)); ew_game.send(GameEvent::SetAR(Toggle));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
}, }
MenuAction::ToggleMusic => { MenuAction::ToggleMusic => {
ew_game.send(GameEvent::SetMusic(Toggle)); ew_game.send(GameEvent::SetMusic(Toggle));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
}, }
MenuAction::ToggleSound => { MenuAction::ToggleSound => {
ew_game.send(GameEvent::SetSound(Toggle)); ew_game.send(GameEvent::SetSound(Toggle));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
}, }
MenuAction::ToggleCamera => { MenuAction::ToggleCamera => {
ew_game.send(GameEvent::SetThirdPerson(Toggle)); ew_game.send(GameEvent::SetThirdPerson(Toggle));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
}, }
MenuAction::ToggleFullscreen => { MenuAction::ToggleFullscreen => {
ew_game.send(GameEvent::SetFullscreen(Toggle)); ew_game.send(GameEvent::SetFullscreen(Toggle));
}, }
MenuAction::ToggleShadows => { MenuAction::ToggleShadows => {
ew_game.send(GameEvent::SetShadows(Toggle)); ew_game.send(GameEvent::SetShadows(Toggle));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
}, }
MenuAction::Restart => { MenuAction::Restart => {
settings.god_mode = false; settings.god_mode = false;
ew_playerdies.send(game::PlayerDiesEvent(actor::DamageType::Depressurization)); ew_playerdies.send(game::PlayerDiesEvent(actor::DamageType::Depressurization));
}, }
MenuAction::Quit => { MenuAction::Quit => {
app_exit_events.send(bevy::app::AppExit); app_exit_events.send(bevy::app::AppExit);
}, }
}; };
} }
} }

View file

@ -35,7 +35,8 @@ pub const JUPITER_MASS: f64 = 1.8982e27;
pub const STARS: &[(f32, f32, f32, f32, f32, f32, &str)] = &include!("data/stars.in"); pub const STARS: &[(f32, f32, f32, f32, f32, f32, &str)] = &include!("data/stars.in");
pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) { pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) {
let temperature = 4600.0 * ((1.0 / (0.92 * color_index + 1.7)) + (1.0 / (0.92 * color_index + 0.62))); let temperature =
4600.0 * ((1.0 / (0.92 * color_index + 1.7)) + (1.0 / (0.92 * color_index + 0.62)));
let (red, green, blue) = if temperature <= 6600.0 { let (red, green, blue) = if temperature <= 6600.0 {
let red = 255.0; let red = 255.0;
@ -55,7 +56,7 @@ pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) {
let clamp = |x: f32| -> f32 { (x / 255.0).max(0.0).min(1.0) }; let clamp = |x: f32| -> f32 { (x / 255.0).max(0.0).min(1.0) };
return (clamp(red), clamp(green), clamp(blue)) return (clamp(red), clamp(green), clamp(blue));
} }
fn smooth_edge(start: f32, end: f32, value: f32) -> f32 { fn smooth_edge(start: f32, end: f32, value: f32) -> f32 {
@ -89,10 +90,19 @@ pub fn ring_density(radius: f32) -> f32 {
density = halo_brightness * smooth_edge(halo_inner, halo_outer, radius); density = halo_brightness * smooth_edge(halo_inner, halo_outer, radius);
} else if radius >= main_inner && radius <= main_outer { } else if radius >= main_inner && radius <= main_outer {
let mut metis_notch_effect: f32 = 1.0; let mut metis_notch_effect: f32 = 1.0;
if radius > metis_notch_center - metis_notch_width * 0.5 && radius < metis_notch_center + metis_notch_width * 0.5 { if radius > metis_notch_center - metis_notch_width * 0.5
metis_notch_effect = 0.8 * (1.0 - smooth_edge(metis_notch_center - metis_notch_width * 0.5, metis_notch_center + metis_notch_width * 0.5, radius)); && radius < metis_notch_center + metis_notch_width * 0.5
{
metis_notch_effect = 0.8
* (1.0
- smooth_edge(
metis_notch_center - metis_notch_width * 0.5,
metis_notch_center + metis_notch_width * 0.5,
radius,
));
} }
density = main_brightness * metis_notch_effect * smooth_edge(main_inner, main_outer, radius); density =
main_brightness * metis_notch_effect * smooth_edge(main_inner, main_outer, radius);
} else { } else {
if radius >= amalthea_inner && radius <= amalthea_outer { if radius >= amalthea_inner && radius <= amalthea_outer {
density = almathea_brightness * smooth_edge(amalthea_inner, amalthea_outer, radius); density = almathea_brightness * smooth_edge(amalthea_inner, amalthea_outer, radius);
@ -131,8 +141,7 @@ pub fn readable_speed(speed: f64) -> String {
if abs > C * 0.0005 { if abs > C * 0.0005 {
let lightyears = abs / C; let lightyears = abs / C;
return format!("{lightyears:.4} c"); return format!("{lightyears:.4} c");
} } else {
else {
let kmh = abs * 1.0e-3 * 3600.0; let kmh = abs * 1.0e-3 * 3600.0;
return format!("{kmh:.0} km/h"); return format!("{kmh:.0} km/h");
} }

View file

@ -12,13 +12,13 @@
// "if"-conditions in chats. // "if"-conditions in chats.
use crate::prelude::*; use crate::prelude::*;
use bevy::window::WindowMode;
use bevy::prelude::*; use bevy::prelude::*;
use std::collections::{HashMap, HashSet}; use bevy::window::WindowMode;
use serde::Deserialize; use serde::Deserialize;
use toml_edit::DocumentMut; use std::collections::{HashMap, HashSet};
use std::env; use std::env;
use std::fs; use std::fs;
use toml_edit::DocumentMut;
pub const SCOPE_SEPARATOR: &str = "$"; pub const SCOPE_SEPARATOR: &str = "$";
@ -379,28 +379,29 @@ impl AchievementTracker {
(self.ride_every_vehicle, "ride every vehicle".into()), (self.ride_every_vehicle, "ride every vehicle".into()),
(self.talk_to_everyone, "talk to everyone".into()), (self.talk_to_everyone, "talk to everyone".into()),
(self.find_earth, "find Earth".into()), (self.find_earth, "find Earth".into()),
(self.in_jupiters_shadow, "eclipse the Sun with Jupiter".into()), (
self.in_jupiters_shadow,
"eclipse the Sun with Jupiter".into(),
),
] ]
} }
pub fn to_summary(&self) -> String { pub fn to_summary(&self) -> String {
let list = self.to_overview(); let list = self.to_overview();
let count = list.iter().filter(|(achieved, _)| *achieved).count(); let count = list.iter().filter(|(achieved, _)| *achieved).count();
if count == 0 { if count == 0 {
return "".to_string() return "".to_string();
} }
let mut summary = "\n\n\nYou managed to ".to_string(); let mut summary = "\n\n\nYou managed to ".to_string();
for (i, (_, text)) in list.iter().filter(|(achieved, _)| *achieved).enumerate() { for (i, (_, text)) in list.iter().filter(|(achieved, _)| *achieved).enumerate() {
summary += text.as_str(); summary += text.as_str();
if i + 2 == count { if i + 2 == count {
summary += ", and "; summary += ", and ";
} } else if i + 1 == count {
else if i + 1 == count {
summary += " before you perished."; summary += " before you perished.";
if count == list.len() { if count == list.len() {
summary += "\nA truly astounding achievement, a glimmer in the void, before it all fades, into nothingness."; summary += "\nA truly astounding achievement, a glimmer in the void, before it all fades, into nothingness.";
} }
} } else {
else {
summary += ", "; summary += ", ";
} }
} }
@ -439,7 +440,9 @@ impl Preferences {
} }
fn file_is_readable(file_path: &str) -> bool { fn file_is_readable(file_path: &str) -> bool {
fs::metadata(file_path).map(|metadata| metadata.is_file()).unwrap_or(false) fs::metadata(file_path)
.map(|metadata| metadata.is_file())
.unwrap_or(false)
} }
fn get_prefs_path() -> Option<String> { fn get_prefs_path() -> Option<String> {
@ -479,8 +482,7 @@ pub fn load_prefs() -> Preferences {
} }
}; };
match toml.parse::<DocumentMut>() { match toml.parse::<DocumentMut>() {
Ok(doc) => { Ok(doc) => match toml_edit::de::from_document::<Preferences>(doc) {
match toml_edit::de::from_document::<Preferences>(doc) {
Ok(mut pref) => { Ok(mut pref) => {
if let Some(path) = &path { if let Some(path) = &path {
info!("Loaded preference file from {path}"); info!("Loaded preference file from {path}");
@ -495,8 +497,7 @@ pub fn load_prefs() -> Preferences {
error!("Failed to read preference line: {error}"); error!("Failed to read preference line: {error}");
return Preferences::default(); return Preferences::default();
} }
} },
}
Err(error) => { Err(error) => {
error!("Failed to open preferences: {error}"); error!("Failed to open preferences: {error}");
return Preferences::default(); return Preferences::default();
@ -511,9 +512,7 @@ pub struct GameVars {
impl Default for GameVars { impl Default for GameVars {
fn default() -> Self { fn default() -> Self {
Self { Self { db: HashMap::new() }
db: HashMap::new(),
}
} }
} }
@ -573,8 +572,7 @@ impl GameVars {
if scope_part.is_empty() { if scope_part.is_empty() {
// we got a key like "$foo", just prefix the fallback scope // we got a key like "$foo", just prefix the fallback scope
fallback_scope.to_string() + key fallback_scope.to_string() + key
} } else {
else {
// we got a key like "Ke$ha$foo" or "$$foo" (which is the convention for // we got a key like "Ke$ha$foo" or "$$foo" (which is the convention for
// global variables), leave the scope intact // global variables), leave the scope intact
key.to_string() key.to_string()
@ -609,11 +607,9 @@ impl GameVars {
let part = Self::normalize_varname(scope, part); let part = Self::normalize_varname(scope, part);
let value_bool = self.getb(part.as_str()); let value_bool = self.getb(part.as_str());
return value_bool ^ negate; return value_bool ^ negate;
} } else {
else {
return Self::evaluate_str_as_bool(part) ^ negate; return Self::evaluate_str_as_bool(part) ^ negate;
} }
} else if parts.len() == 2 { } else if parts.len() == 2 {
// Got something like "if $something somethingelse" // Got something like "if $something somethingelse"
// Check whether the two are identical. // Check whether the two are identical.

View file

@ -10,15 +10,18 @@
// //
// This module manages visual effects. // This module manages visual effects.
use bevy::prelude::*;
use crate::prelude::*; use crate::prelude::*;
use bevy::prelude::*;
pub struct VisualPlugin; pub struct VisualPlugin;
impl Plugin for VisualPlugin { impl Plugin for VisualPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup.after(menu::setup).after(hud::setup)); app.add_systems(Startup, setup.after(menu::setup).after(hud::setup));
app.add_systems(Startup, spawn_effects.after(setup).after(camera::setup_camera)); app.add_systems(
Startup,
spawn_effects.after(setup).after(camera::setup_camera),
);
app.add_systems(Update, spawn_effects); app.add_systems(Update, spawn_effects);
app.add_systems(Update, update_fadein); app.add_systems(Update, update_fadein);
app.add_systems(Update, update_fadeout); app.add_systems(Update, update_fadeout);
@ -38,8 +41,10 @@ pub enum Effects {
// Blackout disabled for now // Blackout disabled for now
//#[derive(Component)] pub struct BlackOutOverlay; //#[derive(Component)] pub struct BlackOutOverlay;
#[derive(Component)] pub struct FadeIn; #[derive(Component)]
#[derive(Component)] pub struct FadeOut; pub struct FadeIn;
#[derive(Component)]
pub struct FadeOut;
#[derive(Component)] #[derive(Component)]
pub struct Effect { pub struct Effect {
pub class: Effects, pub class: Effects,
@ -52,12 +57,12 @@ pub struct SpawnEffectEvent {
pub duration: f64, pub duration: f64,
} }
pub fn setup( pub fn setup(settings: Res<var::Settings>, mut ew_effect: EventWriter<SpawnEffectEvent>) {
settings: Res<var::Settings>,
mut ew_effect: EventWriter<SpawnEffectEvent>,
) {
if !settings.dev_mode { if !settings.dev_mode {
ew_effect.send(SpawnEffectEvent { class: Effects::FadeIn(Color::BLACK), duration: 4.0 }); ew_effect.send(SpawnEffectEvent {
class: Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
} }
// Blackout disabled for now // Blackout disabled for now
// commands.spawn(( // commands.spawn((
@ -99,7 +104,7 @@ pub fn spawn_effects(
..default() ..default()
}, },
)); ));
}, }
Effects::FadeOut(color) => { Effects::FadeOut(color) => {
commands.spawn(( commands.spawn((
Effect { Effect {
@ -114,8 +119,7 @@ pub fn spawn_effects(
..default() ..default()
}, },
)); ));
}, }
//_ => {},
} }
} }
} }

View file

@ -11,13 +11,13 @@
// This module populates the world with stars and asteroids. // This module populates the world with stars and asteroids.
use crate::prelude::*; use crate::prelude::*;
use bevy::prelude::*;
use bevy::math::I64Vec3; use bevy::math::I64Vec3;
use bevy::scene::{InstanceId, SceneInstance}; use bevy::prelude::*;
use bevy::render::mesh::Indices; use bevy::render::mesh::Indices;
use bevy::scene::{InstanceId, SceneInstance};
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap;
use fastrand; use fastrand;
use std::collections::HashMap;
const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds
const ASTEROID_SIZE_FACTOR: f32 = 10.0; const ASTEROID_SIZE_FACTOR: f32 = 10.0;
@ -42,20 +42,28 @@ impl Plugin for WorldPlugin {
app.add_plugins(PhysicsPlugins::default()); app.add_plugins(PhysicsPlugins::default());
//app.add_plugins(PhysicsDebugPlugin::default()); //app.add_plugins(PhysicsDebugPlugin::default());
app.insert_resource(Gravity(DVec3::splat(0.0))); app.insert_resource(Gravity(DVec3::splat(0.0)));
app.insert_resource(AsteroidUpdateTimer( app.insert_resource(AsteroidUpdateTimer(Timer::from_seconds(
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating))); ASTEROID_UPDATE_INTERVAL,
TimerMode::Repeating,
)));
app.insert_resource(ActiveAsteroids(HashMap::new())); app.insert_resource(ActiveAsteroids(HashMap::new()));
app.add_event::<DespawnAsteroidEvent>(); app.add_event::<DespawnAsteroidEvent>();
app.add_event::<RespawnEvent>(); app.add_event::<RespawnEvent>();
} }
} }
#[derive(Resource)] struct AsteroidUpdateTimer(Timer); #[derive(Resource)]
#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>); struct AsteroidUpdateTimer(Timer);
#[derive(Component)] struct Asteroid; #[derive(Resource)]
#[derive(Component)] pub struct Star; pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
#[derive(Component)] pub struct DespawnOnPlayerDeath; #[derive(Component)]
#[derive(Event)] pub struct RespawnEvent; struct Asteroid;
#[derive(Component)]
pub struct Star;
#[derive(Component)]
pub struct DespawnOnPlayerDeath;
#[derive(Event)]
pub struct RespawnEvent;
pub struct AsteroidData { pub struct AsteroidData {
entity: Entity, entity: Entity,
@ -93,14 +101,12 @@ pub fn setup(
let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation
let mag = mag.min(6.0); let mag = mag.min(6.0);
let scale_size = {|mag: f32| let scale_size =
scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) { |mag: f32| scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) };
};
let scale = scale_size(mag); let scale = scale_size(mag);
let scale_color = {|color: f32| let scale_color =
1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) { |color: f32| 1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) };
};
//let scale = translation.length().powf(0.84); //let scale = translation.length().powf(0.84);
//pos_render.length().powf(0.64) //pos_render.length().powf(0.64)
//(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 * //(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 *
@ -211,9 +217,12 @@ fn spawn_despawn_asteroids(
let z_min = player_cell.z - stepmax; let z_min = player_cell.z - stepmax;
let z_max = player_cell.z + stepmax; let z_max = player_cell.z + stepmax;
for (origin, asteroid) in db.0.iter() { for (origin, asteroid) in db.0.iter() {
if origin.x < x_min || origin.x > x_max if origin.x < x_min
|| origin.y < y_min || origin.y > y_max || origin.x > x_max
|| origin.z < z_min || origin.z > z_max || origin.y < y_min
|| origin.y > y_max
|| origin.z < z_min
|| origin.z > z_max
{ {
if let Ok((pos, sceneinstance)) = q_asteroid.get(asteroid.entity) { if let Ok((pos, sceneinstance)) = q_asteroid.get(asteroid.entity) {
if pos.0.distance(player.0) > 1000.0 { if pos.0.distance(player.0) > 1000.0 {
@ -288,7 +297,8 @@ fn spawn_despawn_asteroids(
//let max_viewdist = ASTEROID_VIEW_RADIUS / ASTEROID_SPAWN_STEP; //let max_viewdist = ASTEROID_VIEW_RADIUS / ASTEROID_SPAWN_STEP;
let wobble = ASTEROID_SPAWN_STEP * 0.5; let wobble = ASTEROID_SPAWN_STEP * 0.5;
let pos = jupiter_pos + DVec3::new( let pos = jupiter_pos
+ DVec3::new(
origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x * 2.0 - 1.0, origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x * 2.0 - 1.0,
origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y * 2.0 - 1.0, origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y * 2.0 - 1.0,
origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0, origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0,
@ -322,10 +332,13 @@ fn spawn_despawn_asteroids(
..default() ..default()
}); });
load_asset(model, &mut entity_commands, &*asset_server); load_asset(model, &mut entity_commands, &*asset_server);
db.0.insert(origin, AsteroidData { db.0.insert(
origin,
AsteroidData {
entity: entity_commands.id(), entity: entity_commands.id(),
//viewdistance: 99999999.0, //viewdistance: 99999999.0,
}); },
);
} }
} }
} }