diff --git a/src/actor.rs b/src/actor.rs index ab71434..f5da67b 100644 --- a/src/actor.rs +++ b/src/actor.rs @@ -14,9 +14,9 @@ // // This module should never handle any visual aspects directly. +use crate::prelude::*; use bevy::prelude::*; use bevy_xpbd_3d::prelude::*; -use crate::prelude::*; pub const ENGINE_SPEED_FACTOR: f32 = 30.0; const MAX_TRANSMISSION_DISTANCE: f32 = 100.0; @@ -25,23 +25,30 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0; pub struct ActorPlugin; impl Plugin for ActorPlugin { fn build(&self, app: &mut App) { - app.add_systems(FixedUpdate, ( - update_physics_lifeforms, - update_power, - handle_wants_maxrotation, - handle_wants_maxvelocity, - )); - app.add_systems(PostUpdate, handle_gforce - .after(PhysicsSet::Sync) - .after(sync::position_to_transform)); - app.add_systems(Update, ( - handle_input.run_if(in_control), - handle_collisions, - handle_damage, - )); - app.add_systems(PostUpdate, ( - handle_vehicle_enter_exit, - )); + app.add_systems( + FixedUpdate, + ( + update_physics_lifeforms, + update_power, + handle_wants_maxrotation, + handle_wants_maxvelocity, + ), + ); + app.add_systems( + PostUpdate, + handle_gforce + .after(PhysicsSet::Sync) + .after(sync::position_to_transform), + ); + app.add_systems( + Update, + ( + handle_input.run_if(in_control), + handle_collisions, + handle_damage, + ), + ); + app.add_systems(PostUpdate, (handle_vehicle_enter_exit,)); app.add_event::(); } } @@ -66,7 +73,7 @@ pub struct VehicleEnterExitEvent { driver: Entity, name: Option, is_entering: bool, - is_player: bool + is_player: bool, } #[derive(Component)] @@ -112,26 +119,41 @@ pub struct ExperiencesGForce { pub last_linear_velocity: DVec3, pub ignore_gforce_seconds: f32, } -impl Default for ExperiencesGForce { fn default() -> Self { Self { - gforce: 0.0, - damage_threshold: 100.0, - visual_effect_threshold: 20.0, - visual_effect: 0.0, - last_linear_velocity: DVec3::splat(0.0), - ignore_gforce_seconds: 0.0, -}}} +impl Default for ExperiencesGForce { + fn default() -> Self { + Self { + gforce: 0.0, + damage_threshold: 100.0, + visual_effect_threshold: 20.0, + visual_effect: 0.0, + last_linear_velocity: DVec3::splat(0.0), + ignore_gforce_seconds: 0.0, + } + } +} -#[derive(Component)] pub struct Player; // Attached to the suit of the player -#[derive(Component)] pub struct PlayerCollider; // Attached to the collider of the suit of the player -#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle -#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view -#[derive(Component)] pub struct JustNowEnteredVehicle; -#[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)] +pub struct Player; // Attached to the suit of the player +#[derive(Component)] +pub struct PlayerCollider; // Attached to the collider of the suit of the player +#[derive(Component)] +pub struct PlayerDrivesThis; // Attached to the entered vehicle +#[derive(Component)] +pub struct PlayerCamera; // Attached to the actor to use as point of view +#[derive(Component)] +pub struct JustNowEnteredVehicle; +#[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)] pub struct LifeForm { @@ -140,20 +162,28 @@ pub struct LifeForm { pub adrenaline_baseline: f32, pub adrenaline_jolt: f32, } -impl Default for LifeForm { fn default() -> Self { Self { - is_alive: true, - adrenaline: 0.3, - adrenaline_baseline: 0.3, - adrenaline_jolt: 0.0, -}}} +impl Default for LifeForm { + fn default() -> Self { + Self { + is_alive: true, + adrenaline: 0.3, + adrenaline_baseline: 0.3, + adrenaline_jolt: 0.0, + } + } +} #[derive(Component)] pub struct Vehicle { stored_drivers_collider: Option, } -impl Default for Vehicle { fn default() -> Self { Self { - stored_drivers_collider: None, -}}} +impl Default for Vehicle { + fn default() -> Self { + Self { + stored_drivers_collider: None, + } + } +} #[derive(Copy, Clone, PartialEq)] pub enum EngineType { @@ -192,7 +222,11 @@ pub struct Suit { pub oxygen_max: f32, 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 { oxygen: nature::OXY_D, @@ -202,9 +236,9 @@ const SUIT_SIMPLE: Suit = Suit { #[derive(Component)] pub struct Battery { - pub power: f32, // Watt-seconds + pub power: f32, // Watt-seconds pub capacity: f32, // Watt-seconds - pub reactor: f32, // Watt (production) + pub reactor: f32, // Watt (production) } impl Default for Battery { @@ -212,7 +246,7 @@ impl Default for Battery { Self { power: 10e3 * 3600.0, capacity: 10e3 * 3600.0, // 10kWh - reactor: 2000e3, // 2MW + reactor: 2000e3, // 2MW } } } @@ -236,8 +270,7 @@ pub fn update_power( } } } - battery.power = (battery.power + battery.reactor * d) - .clamp(0.0, battery.capacity); + battery.power = (battery.power + battery.reactor * d).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() { if lifeform.adrenaline_jolt.abs() > 1e-3 { lifeform.adrenaline_jolt *= 0.99; - } - else { + } else { lifeform.adrenaline_jolt = 0.0 } let speed = velocity.length(); if speed > 1000.0 { lifeform.adrenaline += 0.001; } - lifeform.adrenaline = (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0); + lifeform.adrenaline = + (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0); let mut oxygen_drain = nature::OXY_S; let integr_threshold = 0.5; @@ -278,7 +311,7 @@ pub fn update_physics_lifeforms( let drain_scaling = (2.0 - 2.0 * suit.integrity / integr_threshold).powf(4.0); oxygen_drain += suit.oxygen * 0.01 * drain_scaling; } - suit.oxygen = (suit.oxygen - oxygen_drain*d).clamp(0.0, suit.oxygen_max); + suit.oxygen = (suit.oxygen - oxygen_drain * d).clamp(0.0, suit.oxygen_max); if suit.oxygen <= 0.0 { hp.damage += 1.0 * d; hp.damagetype = DamageType::Asphyxiation; @@ -294,7 +327,14 @@ pub fn handle_input( player: Query>, q_camera: Query<&Transform, With>, mut q_flashlight: Query<&mut Visibility, With>, - q_vehicles: Query<(Entity, &Actor, &Transform), (With, Without, Without)>, + q_vehicles: Query< + (Entity, &Actor, &Transform), + ( + With, + Without, + Without, + ), + >, mut ew_conv: EventWriter, mut ew_vehicle: EventWriter, mut ew_sfx: EventWriter, @@ -313,9 +353,12 @@ pub fn handle_input( .map(|(talker, transform)| (talker.clone(), transform)) .collect(); // TODO: replace Transform.translation with Position - if let (Some(talker), dist) = camera::find_closest_target::(objects, camtrans) { + if let (Some(talker), dist) = camera::find_closest_target::(objects, camtrans) + { if dist <= MAX_TRANSMISSION_DISTANCE { - ew_conv.send(chat::StartConversationEvent{talker: talker.clone()}); + ew_conv.send(chat::StartConversationEvent { + talker: talker.clone(), + }); } } // Entering Vehicles @@ -325,12 +368,12 @@ pub fn handle_input( .map(|(entity, actor, transform)| ((entity, actor), transform)) .collect(); if let (Some((entity, actor)), dist) = - camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans) + camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans) { if dist <= MAX_INTERACT_DISTANCE { commands.entity(entity).insert(ActorVehicleBeingEntered); commands.entity(player_entity).insert(ActorEnteringVehicle); - ew_vehicle.send(VehicleEnterExitEvent{ + ew_vehicle.send(VehicleEnterExitEvent { vehicle: entity, driver: player_entity, name: actor.name.clone(), @@ -340,13 +383,14 @@ pub fn handle_input( } } } - } - else if keyboard_input.just_pressed(settings.key_vehicle) { + } else if keyboard_input.just_pressed(settings.key_vehicle) { // Exiting Vehicles 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); - ew_vehicle.send(VehicleEnterExitEvent{ + ew_vehicle.send(VehicleEnterExitEvent { vehicle: vehicle_entity, driver: player_entity, name: None, @@ -355,8 +399,7 @@ pub fn handle_input( }); 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 { ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); if *flashlight_vis == Visibility::Hidden { @@ -367,8 +410,7 @@ pub fn handle_input( 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)); settings.cruise_control_active ^= true; } @@ -379,9 +421,28 @@ pub fn handle_vehicle_enter_exit( mut settings: ResMut, mut er_vehicle: EventReader, mut ew_achievement: EventWriter, - mut q_playerflashlight: Query<&mut Visibility, (With, Without, Without)>, - mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without, With)>, - mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With, Without)>, + mut q_playerflashlight: Query< + &mut Visibility, + ( + With, + Without, + Without, + ), + >, + mut q_drivers: Query< + (Entity, &mut Visibility, Option<&Collider>), + ( + Without, + With, + ), + >, + mut q_vehicles: Query< + (Entity, &mut Vehicle, &mut Visibility), + ( + With, + Without, + ), + >, mut ew_sfx: EventWriter, ) { for event in er_vehicle.read() { @@ -410,11 +471,11 @@ pub fn handle_vehicle_enter_exit( settings.flashlight_active = false; } if let Some(vehicle_name) = &event.name { - ew_achievement.send(game::AchievementEvent - ::RideVehicle(vehicle_name.clone())); + ew_achievement.send(game::AchievementEvent::RideVehicle( + vehicle_name.clone(), + )); } - } - else { + } else { // Exiting Vehicle if let Some(collider) = &vehicle_component.stored_drivers_collider { commands.entity(driver).insert(collider.clone()); @@ -439,7 +500,9 @@ fn handle_collisions( q_player: Query>, mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With>, ) { - 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() { if *entity1 == player || *entity2 == player { ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash)); @@ -461,9 +524,9 @@ fn handle_wants_maxrotation( if total > maxrot.0 { v_ang.0 = DVec3::splat(0.0); } - } - else { - let angular_slowdown: f64 = (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64; + } else { + let angular_slowdown: f64 = + (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64; v_ang.0 *= angular_slowdown; } } @@ -485,7 +548,8 @@ fn handle_wants_maxvelocity( } // 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; v.0 += acceleration; if v.0.length() + EPSILON < acceleration.length() { @@ -507,8 +571,7 @@ fn handle_damage( if hp.current <= 0.0 { ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype)); } - } - else { + } else { hp.current -= hp.damage; } hp.damage = 0.0; @@ -535,12 +598,12 @@ fn handle_gforce( if gforce.visual_effect > 0.0001 { gforce.visual_effect *= 0.984; - } - else if gforce.visual_effect > 0.0 { + } else if gforce.visual_effect > 0.0 { gforce.visual_effect = 0.0; } 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 } } } diff --git a/src/audio.rs b/src/audio.rs index 0df7826..393e122 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -10,16 +10,19 @@ // // This module manages sound effects and music. -use bevy::prelude::*; -use bevy::audio::{PlaybackMode, Volume}; use crate::prelude::*; +use bevy::audio::{PlaybackMode, Volume}; +use bevy::prelude::*; use std::collections::HashMap; pub struct AudioPlugin; impl Plugin for AudioPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); - app.add_systems(Update, respawn_sinks.run_if(on_event::())); + app.add_systems( + Update, + respawn_sinks.run_if(on_event::()), + ); app.add_systems(Update, play_zoom_sfx); app.add_systems(Update, pause_all.run_if(on_event::())); app.add_systems(PostUpdate, play_sfx); @@ -28,27 +31,47 @@ impl Plugin for AudioPlugin { app.add_event::(); app.add_event::(); app.add_event::(); - app.insert_resource(ZoomTimer( - Timer::from_seconds(0.09, TimerMode::Repeating))); + app.insert_resource(ZoomTimer(Timer::from_seconds(0.09, TimerMode::Repeating))); } } -#[derive(Resource)] pub struct ZoomTimer(Timer); +#[derive(Resource)] +pub struct ZoomTimer(Timer); 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::Rocket, "sounds/rocket.ogg"), (SfxType::LoopSfx, Sfx::Thruster, "sounds/thruster.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::Crash, "sounds/crash.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::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::Woosh, "sounds/woosh.ogg"), (SfxType::OneOff, Sfx::Zoom, "sounds/zoom.ogg"), @@ -96,11 +119,16 @@ pub enum SfxType { OneOff, } -#[derive(Event)] pub struct PlaySfxEvent(pub Sfx); -#[derive(Event)] pub struct PauseAllSfxEvent; -#[derive(Event)] pub struct RespawnSinksEvent; -#[derive(Event)] pub struct ToggleMusicEvent(); -#[derive(Resource)] pub struct Sounds(HashMap>); +#[derive(Event)] +pub struct PlaySfxEvent(pub Sfx); +#[derive(Event)] +pub struct PauseAllSfxEvent; +#[derive(Event)] +pub struct RespawnSinksEvent; +#[derive(Event)] +pub struct ToggleMusicEvent(); +#[derive(Resource)] +pub struct Sounds(HashMap>); pub fn setup( mut commands: Commands, @@ -140,7 +168,7 @@ pub fn respawn_sinks( }, }, )); - }, + } SfxType::LoopSfx => { commands.spawn(( *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 { bgm_sink.pause(); - } - else { + } else { bgm_sink.play(); } } @@ -211,7 +238,9 @@ pub fn play_zoom_sfx( if !timer.0.tick(time.delta()).just_finished() { 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 { ew_sfx.send(PlaySfxEvent(Sfx::Zoom)); } @@ -219,9 +248,7 @@ pub fn play_zoom_sfx( } } -pub fn pause_all( - q_audiosinks: Query<&AudioSink, With>, -) { +pub fn pause_all(q_audiosinks: Query<&AudioSink, With>) { for sink in &q_audiosinks { sink.pause(); } diff --git a/src/camera.rs b/src/camera.rs index ca18702..87256c7 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -12,16 +12,16 @@ // movement-related keyboard input, and provides some camera- // related computation functions. -use bevy::prelude::*; -use bevy::input::mouse::{MouseMotion, MouseWheel}; -use bevy::window::PrimaryWindow; +use crate::prelude::*; use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings}; use bevy::core_pipeline::tonemapping::Tonemapping; +use bevy::input::mouse::{MouseMotion, MouseWheel}; use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}; +use bevy::prelude::*; use bevy::transform::TransformSystem; -use bevy_xpbd_3d::prelude::*; +use bevy::window::PrimaryWindow; use bevy_xpbd_3d::plugins::sync; -use crate::prelude::*; +use bevy_xpbd_3d::prelude::*; use std::collections::HashMap; 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, update_map_only_object_visibility.run_if(alive)); app.add_systems(Update, manage_player_actor.after(handle_input)); - app.add_systems(PostUpdate, sync_camera_to_player - .after(PhysicsSet::Sync) - .after(apply_input_to_player) - .before(TransformSystem::TransformPropagate)); - app.add_systems(PostUpdate, update_mapcam_center - .before(sync::position_to_transform) - .in_set(sync::SyncSet::PositionToTransform)); + app.add_systems( + PostUpdate, + sync_camera_to_player + .after(PhysicsSet::Sync) + .after(apply_input_to_player) + .before(TransformSystem::TransformPropagate), + ); + app.add_systems( + PostUpdate, + update_mapcam_center + .before(sync::position_to_transform) + .in_set(sync::SyncSet::PositionToTransform), + ); app.add_systems(Update, update_map_camera.run_if(in_control)); app.add_systems(Update, update_fov.run_if(alive)); app.add_systems(PreUpdate, apply_input_to_player); @@ -53,9 +59,12 @@ impl Plugin for CameraPlugin { transform_to_position: false, }); // 2. Add own position->transform sync function - app.add_systems(PostUpdate, position_to_transform - .after(sync::position_to_transform) - .in_set(sync::SyncSet::PositionToTransform)); + app.add_systems( + PostUpdate, + position_to_transform + .after(sync::position_to_transform) + .in_set(sync::SyncSet::PositionToTransform), + ); } } @@ -93,10 +102,7 @@ impl Default for MapCam { } } -pub fn setup_camera( - mut commands: Commands, - settings: Res, -) { +pub fn setup_camera(mut commands: Commands, settings: Res) { // Add player commands.spawn(( Camera3dBundle { @@ -122,13 +128,14 @@ pub fn setup_camera( shadows_enabled: settings.shadows_sun, ..default() }, - transform: Transform::from_rotation(Quat::from_rotation_y(PI32/2.0)), + transform: Transform::from_rotation(Quat::from_rotation_y(PI32 / 2.0)), cascade_shadow_config: CascadeShadowConfigBuilder { num_cascades: 4, minimum_distance: 0.1, maximum_distance: 5000.0, ..default() - }.into(), + } + .into(), ..default() }); @@ -153,10 +160,10 @@ pub fn sync_camera_to_player( // Translation 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); - } - else { + } else { camera_transform.translation = player_transform.translation; camera_transform.rotation = rotation; } @@ -167,7 +174,14 @@ pub fn update_map_camera( mut mapcam: ResMut, mut q_camera: Query<&mut Transform, (With, Without)>, q_playercam: Query<(Entity, &Transform), (With, Without)>, - q_target: Query<(Entity, &Transform), (With, Without, Without)>, + q_target: Query< + (Entity, &Transform), + ( + With, + Without, + Without, + ), + >, q_target_changed: Query<(), Changed>, mut mouse_events: EventReader, mut er_mousewheel: EventReader, @@ -195,7 +209,9 @@ pub fn update_map_camera( // at the extreme values and the orientation will flicker back/forth. let min_zoom: f64 = target_trans.scale.x as f64 * 2.0; 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; // Reset movement offset if target changes @@ -226,7 +242,11 @@ pub fn update_map_camera( // Update zoom level 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.zoom_level *= target_trans.scale.x as f64 * factor; mapcam.initialized = true; @@ -241,12 +261,16 @@ pub fn update_map_camera( for wheel_event in er_mousewheel.read() { 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) - 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 - 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)); // Update movement offset @@ -285,8 +309,7 @@ pub fn update_fov( mut settings: ResMut, mut q_camera: Query<&mut Projection, With>, ) { - 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; if mouse_input.pressed(settings.key_zoom) { fov = settings.zoom_fov.to_radians(); @@ -294,12 +317,16 @@ pub fn update_fov( settings.is_zooming = true; } } 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 { 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, settings: Res, mut q_playercam: Query<&mut Visibility, With>, - mut q_hiddenplayer: Query<(Entity, &mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity, Option<&mut actor::ExperiencesGForce>, Option<&actor::JustNowEnteredVehicle>), (With, Without)>, - q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With, Without)>, + mut q_hiddenplayer: Query< + ( + Entity, + &mut Visibility, + &mut Position, + &mut Rotation, + &mut LinearVelocity, + &mut AngularVelocity, + Option<&mut actor::ExperiencesGForce>, + Option<&actor::JustNowEnteredVehicle>, + ), + (With, Without), + >, + q_ride: Query< + ( + &Transform, + &Position, + &Rotation, + &LinearVelocity, + &AngularVelocity, + ), + (With, Without), + >, ) { for mut vis in &mut q_playercam { if settings.third_person || settings.map_active { *vis = Visibility::Inherited; - } - else { + } else { *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 // it would be after exiting the vehicle. // 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. *vis = Visibility::Hidden; 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]); *v = ride_v.clone(); *angv = ride_angv.clone(); @@ -353,7 +403,9 @@ fn manage_player_actor( // vehicles at high relative speed, even though they probably should. if let (Some(gforce), Some(_)) = (&mut gforce, entering) { gforce.last_linear_velocity = v.0; - commands.entity(entity).remove::(); + commands + .entity(entity) + .remove::(); } } } @@ -368,14 +420,17 @@ pub fn apply_input_to_player( key_input: Res>, q_audiosinks: Query<(&audio::Sfx, &AudioSink)>, q_target: Query<&LinearVelocity, (With, Without)>, - mut q_playercam: Query<( + mut q_playercam: Query< + ( &Transform, &mut actor::Engine, &mut AngularVelocity, &mut LinearVelocity, &mut ExternalTorque, Option<&actor::PlayerDrivesThis>, - ), (With, Without)>, + ), + (With, Without), + >, ) { if settings.map_active || !settings.in_control() { return; @@ -390,8 +445,7 @@ pub fn apply_input_to_player( focused = window.focused; win_res_x = window.resolution.width(); win_res_y = window.resolution.height(); - } - else { + } else { win_res_x = 1920.0; win_res_y = 1050.0; } @@ -402,7 +456,9 @@ pub fn apply_input_to_player( 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 if focused { 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) { let stop_direction = (target_v - v.0).normalize(); 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 { @@ -442,18 +501,21 @@ pub fn apply_input_to_player( axis_input = axis_input.clamp(DVec3::splat(-1.0), DVec3::splat(1.0)); // Apply movement update - let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 { - engine.thrust_forward - } else { - engine.thrust_back - }); + let forward_factor = engine.current_warmup + * (if axis_input.z > 0.0 { + engine.thrust_forward + } else { + engine.thrust_back + }); let right_factor = engine.thrust_sideways * engine.current_warmup; let up_factor = engine.thrust_sideways * engine.current_warmup; let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64); if axis_input.length_squared() > 0.003 { - let acceleration_global: DVec3 = 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 acceleration_global: DVec3 = + 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; if key_input.pressed(settings.key_stop) { // Decelerate (or match velocity to target_v) @@ -461,8 +523,7 @@ pub fn apply_input_to_player( for i in 0..3 { if dv[i].abs() < threshold { 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 v[i] = target_v[i]; acceleration_total[i] = 0.0; @@ -471,18 +532,23 @@ pub fn apply_input_to_player( } // TODO: handle mass 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; - } - else { - engine.current_warmup = (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0); + } else { + engine.current_warmup = + (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0); } // Handle mouse input and mouse-like key bindings let mut play_reactionwheel_sound = false; let mut mouse_delta = Vec2::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_rotation_sensitivity = 40.0 * sensitivity_factor; if key_input.pressed(settings.key_mouseup) { @@ -492,7 +558,8 @@ pub fn apply_input_to_player( pitch_yaw_rot[0] += mouseless_sensitivity; } 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) { 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 { 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( - player_transform.rotation * Vec3::new( - pitch_yaw_rot[0], - pitch_yaw_rot[1], - pitch_yaw_rot[2]))); + player_transform.rotation + * Vec3::new(pitch_yaw_rot[0], pitch_yaw_rot[1], pitch_yaw_rot[2]), + )); angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64; - } - else { + } else { if angularvelocity.length_squared() > 1.0e-18 { angularvelocity.0 *= angular_slowdown; - } - else { + } else { angularvelocity.0 = DVec3::splat(0.0); } } @@ -563,8 +628,16 @@ pub fn apply_input_to_player( } } 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)), ]; 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 settings.mute_sfx { sink.pause(); - } - else { + } else { let volume = sink.volume(); if engine.engine_type == engine_type { if play_thruster_sound { @@ -582,13 +654,14 @@ pub fn apply_input_to_player( if volume < 1.0 { let maxvol = 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 { 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)); } if volume < 0.0001 { @@ -619,12 +692,14 @@ pub fn update_map_only_object_visibility( let dist = cam_pos.distance(pos.as_vec3()); if dist >= onlyinmap.min_distance as f32 { *vis = Visibility::Inherited; - } - else { + } else { *vis = Visibility::Hidden; } } 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; } } else { @@ -639,21 +714,22 @@ pub fn find_closest_target( objects: Vec<(TargetSpecifier, &Transform)>, camera_transform: &Transform, ) -> (Option, f32) - where TargetSpecifier: Clone +where + TargetSpecifier: Clone, { let mut closest_entity: Option = None; let mut closest_distance: f32 = f32::MAX; - let target_vector: Vec3 = (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0)) - .normalize_or_zero(); + let target_vector: Vec3 = + (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0)).normalize_or_zero(); for (entity, trans) in objects { // 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. - let (angular_diameter, angle, distance) = calc_angular_diameter_known_target_vector( - trans, camera_transform, &target_vector); + let (angular_diameter, angle, distance) = + calc_angular_diameter_known_target_vector(trans, camera_transform, &target_vector); if angle <= angular_diameter.clamp(0.01, PI32) { // It's in the field of view! //commands.entity(entity).insert(IsTargeted); - let distance_to_surface = distance - trans.scale.x; + let distance_to_surface = distance - trans.scale.x; if distance_to_surface < closest_distance { closest_distance = distance_to_surface; closest_entity = Some(entity); @@ -669,8 +745,7 @@ pub fn calc_angular_diameter_known_target_vector( camera: &Transform, target_vector: &Vec3, ) -> (f32, f32, f32) { - let pos_vector: Vec3 = (target.translation - camera.translation) - .normalize_or_zero(); + let pos_vector: Vec3 = (target.translation - camera.translation).normalize_or_zero(); let cosine_of_angle: f32 = target_vector.dot(pos_vector); let angle: f32 = cosine_of_angle.acos(); 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 { // Angular Diameter leeway * (target.scale[0] / distance).asin() - } - else { + } else { 0.0 }; return (angular_diameter, angle, distance); } #[inline] -pub fn calc_angular_diameter( - target: &Transform, - camera: &Transform, -) -> (f32, f32, f32) { - let target_vector: Vec3 = (camera.rotation * Vec3::new(0.0, 0.0, -1.0)) - .normalize_or_zero(); +pub fn calc_angular_diameter(target: &Transform, 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); } @@ -702,7 +772,10 @@ pub fn position_to_transform( mapcam: Res, settings: Res, q_player: Query<&Position, With>, - mut q_trans: Query<(&'static mut Transform, &'static Position, &'static Rotation), Without>, + mut q_trans: Query< + (&'static mut Transform, &'static Position, &'static Rotation), + Without, + >, ) { let center: DVec3 = if settings.map_active { mapcam.center diff --git a/src/chat.rs b/src/chat.rs index 905e1fb..6dc0506 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -14,8 +14,8 @@ use crate::prelude::*; use bevy::prelude::*; use bevy_xpbd_3d::prelude::*; -use serde_yaml::Value; use serde::Deserialize; +use serde_yaml::Value; use std::collections::HashMap; pub const CHATS: &[&str] = &[ @@ -40,7 +40,7 @@ pub const TOKEN_NOWAIT: &str = "nowait"; pub const TOKEN_INCLUDE: &str = "include"; pub const TOKEN_GOTO_EXIT: &str = "EXIT"; -pub const TOKEN_IF_INLINE: &str = "if "; // for lines like `- if foo:` +pub const TOKEN_IF_INLINE: &str = "if "; // for lines like `- if foo:` pub const DEFAULT_SOUND: &str = "chat"; pub const MAX_BRANCH_DEPTH: usize = 64; @@ -65,24 +65,22 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[ TOKEN_SOUND, TOKEN_NOWAIT, ]; -pub const SKIPPABLE_TOKENS: &[&str] = &[ - TOKEN_CHAT, - TOKEN_LABEL, - TOKEN_GOTO, - TOKEN_NOWAIT, -]; +pub const SKIPPABLE_TOKENS: &[&str] = &[TOKEN_CHAT, TOKEN_LABEL, TOKEN_GOTO, TOKEN_NOWAIT]; pub struct ChatPlugin; impl Plugin for ChatPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, load_chats); - app.add_systems(Update, ( - handle_reply_keys.before(handle_chat_timer), - handle_chat_timer.before(handle_chat_events), - handle_new_conversations.before(handle_chat_events), - handle_chat_events.before(handle_chat_scripts), - handle_chat_scripts, - )); + app.add_systems( + Update, + ( + handle_reply_keys.before(handle_chat_timer), + handle_chat_timer.before(handle_chat_events), + handle_new_conversations.before(handle_chat_events), + handle_chat_events.before(handle_chat_scripts), + handle_chat_scripts, + ), + ); app.add_event::(); app.add_event::(); app.add_event::(); @@ -107,8 +105,7 @@ pub struct Choice { pub goto: ChatPos, } -#[derive(Component)] -#[derive(Clone)] +#[derive(Component, Clone)] pub struct Talker { pub chat_name: 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. #[derive(Resource)] pub struct ChatDB(Vec); @@ -165,8 +161,7 @@ impl ChatDB { if let Value::Sequence(yaml_sequence) = yaml_data { self.0.push(Value::Sequence(yaml_sequence)); count += 1; - } - else { + } else { error!("Could not load YAML: {:?}", yaml_data); } } @@ -202,7 +197,10 @@ impl ChatDB { } } - fn preprocess_includes_recursively(sequence: &mut Value, include_db: &HashMap>) { + fn preprocess_includes_recursively( + sequence: &mut Value, + include_db: &HashMap>, + ) { let mut changes: Vec<(usize, String)> = Vec::new(); if let Some(vector) = sequence.as_sequence_mut() { for (index, item) in vector.iter_mut().enumerate() { @@ -213,8 +211,7 @@ impl ChatDB { if key == TOKEN_INCLUDE { changes.push((index, value.to_string())); } - } - else if value.is_sequence() { + } else if value.is_sequence() { ChatDB::preprocess_includes_recursively(value, include_db); } } @@ -248,7 +245,9 @@ impl ChatDB { if let Some(result) = found { 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 @@ -273,19 +272,15 @@ impl ChatDB { if let Value::String(key) = key { if key == TOKEN_NOWAIT && value.as_bool() == Some(true) { result.nowait = true; - } - else if key == TOKEN_IF { + } else if key == TOKEN_IF { if let Some(condition) = value.as_str() { 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 - } - 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 - } - else { + } else { result.choice_text = Some(key.to_string()); } } @@ -295,7 +290,12 @@ impl ChatDB { return None; } - fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option { + fn search_label_recursively( + &self, + sequence: &Value, + label: &String, + mut pos: ChatPos, + ) -> Option { if pos.len() > MAX_BRANCH_DEPTH { return None; } @@ -315,9 +315,10 @@ impl ChatDB { } if value.is_sequence() { pos.push(index); - if let Some(result) = self.search_label_recursively( - value, label, pos.clone()) { - return Some(result) + if let Some(result) = + self.search_label_recursively(value, label, pos.clone()) + { + return Some(result); } pos.pop(); } @@ -357,7 +358,7 @@ impl ChatDB { let index = chat.position.len() - 1; chat.position[index] += 1; } - }, + } 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 @@ -367,8 +368,7 @@ impl ChatDB { let index = chat.position.len() - 1; chat.position[index] += 1; } - } - else { + } else { break; } } @@ -441,8 +441,7 @@ impl ChatDB { if !SKIPPABLE_TOKENS.contains(&key) { return false; } - } - else { + } else { return false; } } @@ -452,17 +451,16 @@ impl ChatDB { return false; } - fn process_yaml_entry( - &self, - chat: &mut Chat, - event: &mut EventWriter, - ) -> bool { + fn process_yaml_entry(&self, chat: &mut Chat, event: &mut EventWriter) -> bool { let current_item = self.at(chat.internal_id, &chat.position); let mut processed_a_choice = false; match current_item { Some(Value::String(message)) => { - event.send(ChatEvent::SpawnMessage(message.to_string(), - hud::LogLevel::Chat, DEFAULT_SOUND.to_string())); + event.send(ChatEvent::SpawnMessage( + message.to_string(), + hud::LogLevel::Chat, + DEFAULT_SOUND.to_string(), + )); } Some(Value::Mapping(map)) => { let mut sound = DEFAULT_SOUND.to_string(); @@ -506,15 +504,24 @@ impl ChatDB { match (key, value) { (Some(TOKEN_MSG), Value::String(message)) => { 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)) => { 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)) => { 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)) => { event.send(ChatEvent::SetVariable(instructions.to_string())); @@ -551,8 +558,11 @@ impl ChatDB { } None => { if chat.position.len() == 0 { - event.send(ChatEvent::SpawnMessage("Disconnected.".to_string(), - hud::LogLevel::Info, DEFAULT_SOUND.to_string())); + event.send(ChatEvent::SpawnMessage( + "Disconnected.".to_string(), + hud::LogLevel::Info, + DEFAULT_SOUND.to_string(), + )); 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 let mut key: usize = 0; 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 reached_end_of_branch { break; } let mut goto: Vec = chat.position.clone(); goto.push(0); - event.send(ChatEvent::SpawnChoice(choice_text, - key, goto, data.nowait, data.condition)); + event.send(ChatEvent::SpawnChoice( + choice_text, + key, + goto, + data.nowait, + data.condition, + )); key += 1; reached_end_of_branch = self.advance_pointer(chat); - } - else { + } else { break; } } @@ -640,10 +656,7 @@ pub fn handle_new_conversations( talker: event.talker.clone(), }; chatdb.advance_chat(&mut chat, &mut ew_chatevent); - commands.spawn(( - chat, - world::DespawnOnPlayerDeath, - )); + commands.spawn((chat, world::DespawnOnPlayerDeath)); } Err(error) => { error!("Error while looking for chat ID: {error}"); @@ -700,7 +713,10 @@ pub fn handle_chat_events( ChatEvent::SpawnMessage(message, level, sound) => { match level { 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 => { log.info(message.into()); @@ -712,36 +728,40 @@ pub fn handle_chat_events( log.warning(message.into()); } hud::LogLevel::Always => { - log.add(message.into(), + log.add( + message.into(), 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); ew_sfx.send(audio::PlaySfxEvent(sfx)); } - ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => { - 'out: { - if let Some(condition) = condition { - if !vars.evaluate_condition(condition, &chat.talker.actor_id) { - break 'out; - } - } - commands.spawn(( - world::DespawnOnPlayerDeath, - Choice { - text: replytext.into(), - key: choice_key, - goto: goto.clone(), - } - )); - choice_key += 1; - if !nowait { - chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64; + ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => 'out: { + if let Some(condition) = condition { + if !vars.evaluate_condition(condition, &chat.talker.actor_id) { + break 'out; } } + commands.spawn(( + world::DespawnOnPlayerDeath, + Choice { + text: replytext.into(), + key: choice_key, + goto: goto.clone(), + }, + )); + choice_key += 1; + if !nowait { + chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64; + } } ChatEvent::RunScript(script) => { ew_chatscript.send(ChatScriptEvent(script.clone())); @@ -794,7 +814,14 @@ fn handle_reply_keys( pub fn handle_chat_scripts( mut er_chatscript: EventReader, mut q_actor: Query<(&mut actor::Actor, &mut actor::Suit), Without>, - mut q_player: Query<(&mut actor::Actor, &mut actor::Suit, &mut actor::ExperiencesGForce), With>, + mut q_player: Query< + ( + &mut actor::Actor, + &mut actor::Suit, + &mut actor::ExperiencesGForce, + ), + With, + >, mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With>, mut ew_sfx: EventWriter, mut ew_effect: EventWriter, @@ -815,35 +842,37 @@ pub fn handle_chat_scripts( // Process the script match name { - "refilloxygen" => if let Ok(mut amount) = param1.to_string().parse::() { - for (_, mut suit, _) in q_player.iter_mut() { - if param2.is_empty() { - suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max); - } - else { - let mut found_other = false; - info!("param2={}", param2); - for (other_actor, mut other_suit) in q_actor.iter_mut() { - if !other_actor.id.is_empty() { - info!("ID={}", other_actor.id); + "refilloxygen" => { + if let Ok(mut amount) = param1.to_string().parse::() { + for (_, mut suit, _) in q_player.iter_mut() { + if param2.is_empty() { + suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max); + } else { + let mut found_other = false; + info!("param2={}", param2); + for (other_actor, mut other_suit) in q_actor.iter_mut() { + if !other_actor.id.is_empty() { + info!("ID={}", other_actor.id); + } + if other_actor.id == param2 { + found_other = true; + amount = amount + .clamp(0.0, other_suit.oxygen) + .clamp(0.0, suit.oxygen_max - suit.oxygen); + other_suit.oxygen = other_suit.oxygen - amount; + suit.oxygen = + (suit.oxygen + amount).clamp(0.0, suit.oxygen_max); + break; + } } - if other_actor.id == param2 { - found_other = true; - amount = amount - .clamp(0.0, other_suit.oxygen) - .clamp(0.0, suit.oxygen_max - suit.oxygen); - other_suit.oxygen = other_suit.oxygen - amount; - suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max); - break; + if !found_other { + error!("Script error: could not find actor with ID `{}`", param2); } } - if !found_other { - error!("Script error: could not find actor with ID `{}`", param2); - } } + } else { + error!("Invalid parameter for command `{}`: `{}`", name, param1); } - } else { - error!("Invalid parameter for command `{}`: `{}`", name, param1); } "repairsuit" => { ew_achievement.send(game::AchievementEvent::RepairSuit); @@ -854,21 +883,23 @@ pub fn handle_chat_scripts( "cryotrip" => { if param1.is_empty() { error!("Chat script cryotrip needs a parameter"); - } - else { + } else { if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() { let busstop = match param1 { "serenity" => Some("busstopclippy"), "farview" => Some("busstopclippy2"), "metisprime" => Some("busstopclippy3"), - _ => None + _ => None, }; if let Some(station) = busstop { if let Some(target) = id2pos.0.get(&station.to_string()) { pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0); v.0 = DVec3::ZERO; } else { - error!("Could not determine position of actor with ID: '{}'", station); + error!( + "Could not determine position of actor with ID: '{}'", + station + ); } } else { error!("Invalid destination for cryotrip chat script: '{}'", param1); diff --git a/src/cmd.rs b/src/cmd.rs index 1350b20..873b05d 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -11,10 +11,10 @@ // This module populates the world with actors as defined in "defs.txt" extern crate regex; +use crate::prelude::*; +use bevy::pbr::{NotShadowCaster, NotShadowReceiver}; use bevy::prelude::*; use bevy_xpbd_3d::prelude::*; -use bevy::pbr::{NotShadowCaster, NotShadowReceiver}; -use crate::prelude::*; use regex::Regex; use std::time::SystemTime; @@ -28,14 +28,18 @@ impl Plugin for CmdPlugin { app.add_systems(Startup, load_defs); app.add_systems(Update, spawn_entities); app.add_systems(Update, process_mesh); - app.add_systems(PreUpdate, hide_colliders - .run_if(any_with_component::)); + app.add_systems( + PreUpdate, + hide_colliders.run_if(any_with_component::), + ); app.add_event::(); } } -#[derive(Component)] pub struct NeedsSceneColliderRemoved; -#[derive(Event)] pub struct SpawnEvent(ParserState); +#[derive(Component)] +pub struct NeedsSceneColliderRemoved; +#[derive(Event)] +pub struct SpawnEvent(ParserState); #[derive(PartialEq, Clone)] enum DefClass { Actor, @@ -157,11 +161,11 @@ impl Default for ParserState { } } -pub fn load_defs( - mut ew_spawn: EventWriter, -) { +pub fn load_defs(mut ew_spawn: EventWriter) { 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 mut lines = defs_string.lines(); let mut state = ParserState::default(); @@ -181,9 +185,11 @@ pub fn load_defs( if let Some(caps) = caps { command = caps.get(1).unwrap().as_str(); parameters = caps.get(2).unwrap().as_str(); - } - else { - error!("Failed to read regex captures in line {}: `{}`", line_nr, line); + } else { + error!( + "Failed to read regex captures in line {}: `{}`", + line_nr, line + ); continue; } @@ -214,10 +220,10 @@ pub fn load_defs( state.class = DefClass::Actor; state.model = Some(model.to_string()); if let (Ok(x_float), Ok(y_float), Ok(z_float)) = - (x.parse::(), y.parse::(), z.parse::()) { + (x.parse::(), y.parse::(), z.parse::()) + { state.pos = DVec3::new(x_float, y_float, z_float); - } - else { + } else { error!("Can't parse coordinates as floats in def: {line}"); state = ParserState::default(); continue; @@ -228,10 +234,10 @@ pub fn load_defs( state = ParserState::default(); state.class = DefClass::Actor; if let (Ok(x_float), Ok(y_float), Ok(z_float)) = - (x.parse::(), y.parse::(), z.parse::()) { + (x.parse::(), y.parse::(), z.parse::()) + { state.pos = DVec3::new(x_float, y_float, z_float); - } - else { + } else { error!("Can't parse coordinates as floats in def: {line}"); state = ParserState::default(); continue; @@ -244,8 +250,7 @@ pub fn load_defs( if let Ok(r) = radius_str.parse::() { state.orbit_distance = Some(r); state.orbit_object_id = Some(object_id.to_string()); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -254,8 +259,7 @@ pub fn load_defs( if let (Ok(r), Ok(phase)) = (radius_str.parse::(), phase_str.parse::()) { state.orbit_distance = Some(r); state.orbit_phase = Some(phase * PI * 2.0); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -265,12 +269,10 @@ pub fn load_defs( let offset_radians = 2.0 * PI * value_float; if let Some(phase_radians) = state.orbit_phase { state.orbit_phase = Some(phase_radians + offset_radians); - } - else { + } else { state.orbit_phase = Some(offset_radians); } - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -312,8 +314,7 @@ pub fn load_defs( state.is_lifeform = true; state.is_suited = true; state.oxygen = amount; - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -327,8 +328,7 @@ pub fn load_defs( ["scale", scale] => { if let Ok(scale_float) = scale.parse::() { state.model_scale = scale_float; - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -336,8 +336,7 @@ pub fn load_defs( ["rotationx", rotation_x] => { if let Ok(rotation_x_float) = rotation_x.parse::() { state.rotation *= Quat::from_rotation_x(rotation_x_float.to_radians()); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -345,8 +344,7 @@ pub fn load_defs( ["rotationy", rotation_y] => { if let Ok(rotation_y_float) = rotation_y.parse::() { state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians()); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -354,8 +352,7 @@ pub fn load_defs( ["rotationz", rotation_z] => { if let Ok(rotation_z_float) = rotation_z.parse::() { state.rotation *= Quat::from_rotation_z(rotation_z_float.to_radians()); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -364,34 +361,45 @@ pub fn load_defs( if let Ok(rotation_y_float) = rotation_y.parse::() { state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians()); state.axialtilt = rotation_y_float; - } - else { + } else { error!("Can't parse float: {line}"); continue; } } ["velocity", x, y, z] => { if let (Ok(x_float), Ok(y_float), Ok(z_float)) = - (x.parse::(), y.parse::(), z.parse::()) { + (x.parse::(), y.parse::(), z.parse::()) + { state.velocity = DVec3::new(x_float, y_float, z_float); - } - else { + } else { error!("Can't parse float: {line}"); continue; } } ["angularmomentum", x, y, z] => { if let (Ok(x_float), Ok(y_float), Ok(z_float)) = - (x.parse::(), y.parse::(), z.parse::()) { + (x.parse::(), y.parse::(), z.parse::()) + { state.angular_momentum = DVec3::new(x_float, y_float, z_float); - } - else { + } else { error!("Can't parse float: {line}"); continue; } } ["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::(), back.parse::(), sideways.parse::(), reaction_wheels.parse::(), warmup_time.parse::()) { + if let ( + Ok(forward_float), + Ok(back_float), + Ok(sideways_float), + Ok(reaction_wheels_float), + Ok(warmup_time_float), + ) = ( + forward.parse::(), + back.parse::(), + sideways.parse::(), + reaction_wheels.parse::(), + warmup_time.parse::(), + ) { state.thrust_forward = forward_float; state.thrust_back = back_float; state.thrust_sideways = sideways_float; @@ -411,8 +419,7 @@ pub fn load_defs( ["health", value] => { if let Ok(value_float) = value.parse::() { state.suit_integrity = value_float; - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -420,8 +427,7 @@ pub fn load_defs( ["density", value] => { if let Ok(value_float) = value.parse::() { state.density = value_float; - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -432,17 +438,17 @@ pub fn load_defs( ["collider", "sphere", radius] => { if let Ok(radius_float) = radius.parse::() { state.collider = Collider::sphere(radius_float); - } - else { + } else { error!("Can't parse float: {line}"); continue; } } ["collider", "capsule", height, radius] => { - if let (Ok(height_float), Ok(radius_float)) = (height.parse::(), radius.parse::()) { + if let (Ok(height_float), Ok(radius_float)) = + (height.parse::(), radius.parse::()) + { state.collider = Collider::capsule(height_float, radius_float); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -460,8 +466,7 @@ pub fn load_defs( ["camdistance", value] => { if let Ok(value_float) = value.parse::() { state.camdistance = value_float; - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -471,13 +476,11 @@ pub fn load_defs( if let Ok(color) = Color::hex(color_hex) { state.light_color = Some(color); state.light_brightness = brightness_float; - } - else { + } else { error!("Can't parse hexadecimal color code: {line}"); continue; } - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -486,8 +489,7 @@ pub fn load_defs( // NOTE: requires an engine to slow down velocity if let Ok(value_float) = value.parse::() { state.wants_maxrotation = Some(value_float); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -496,8 +498,7 @@ pub fn load_defs( // NOTE: requires an engine to slow down velocity if let Ok(value_float) = value.parse::() { state.wants_maxvelocity = Some(value_float); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -511,8 +512,7 @@ pub fn load_defs( ["only_in_map_at_dist", value, id] => { if let Ok(value_float) = value.parse::() { state.show_only_in_map_at_distance = Some((value_float, id.to_string())); - } - else { + } else { error!("Can't parse float: {line}"); continue; } @@ -543,9 +543,7 @@ fn spawn_entities( // Preprocessing let mut absolute_pos = if let Some(id) = &state.relative_to { match id2pos.0.get(&id.to_string()) { - Some(pos) => { - state.pos + *pos - } + Some(pos) => state.pos + *pos, None => { error!("Specified `relativeto` command but could not find id `{id}`"); continue; @@ -569,7 +567,9 @@ fn spawn_entities( } }; 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 PI * 2.0 * (now % orbital_period) / orbital_period } else { @@ -579,223 +579,223 @@ fn spawn_entities( } absolute_pos += nature::phase_dist_to_coords(-phase_radians, r); } - let scale = Vec3::splat(if state.is_sun { - 5.0 - } else if state.is_moon && settings.large_moons { - 3.0 - } else { - 1.0 - } * state.model_scale); + let scale = Vec3::splat( + if state.is_sun { + 5.0 + } else if state.is_moon && settings.large_moons { + 3.0 + } else { + 1.0 + } * state.model_scale, + ); // Spawn the actor let actor_entity; { - let mut actor = commands.spawn_empty(); - actor.insert(actor::Actor { - id: state.id.clone(), - name: state.name.clone(), - camdistance: state.camdistance, - ..default() - }); - actor.insert(SleepingDisabled); - actor.insert(world::DespawnOnPlayerDeath); - actor.insert(actor::HitPoints::default()); - actor.insert(Position::from(absolute_pos)); - if state.is_sphere { - let sphere_texture_handle = if let Some(model) = &state.model { - Some(asset_server.load(format!("textures/{}.jpg", model))) - } else { - None - }; - rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation; - let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128)); - let sphere_material_handle = materials.add(StandardMaterial { - base_color_texture: sphere_texture_handle, - perceptual_roughness: 1.0, - metallic: 0.0, - ..default() - }); - actor.insert(PbrBundle { - mesh: sphere_handle, - material: sphere_material_handle, - transform: Transform::from_scale(scale), - ..default() - }); - } else if let Some(model) = &state.model { - actor.insert(SpatialBundle { - transform: Transform::from_scale(scale), - ..default() - }); - load_asset(model.as_str(), &mut actor, &*asset_server); - } - actor.insert(Rotation::from(rotation)); - - // Physics Parameters - if state.has_physics { - actor.insert(RigidBody::Dynamic); - actor.insert(LinearVelocity(state.velocity)); - actor.insert(AngularVelocity(state.angular_momentum)); - actor.insert(ColliderDensity(state.density)); - if state.collider_is_mesh { - actor.insert(MassPropertiesBundle::new_computed( - &Collider::sphere(0.5 * state.model_scale as f64), state.density)); - actor.insert(AsyncSceneCollider::new(Some( - ComputedCollider::TriMesh - //ComputedCollider::ConvexDecomposition(VHACDParameters::default()) - ))); - } - else if state.collider_is_one_mesh_of_scene { - actor.insert(MassPropertiesBundle::new_computed( - &Collider::sphere(0.5 * state.model_scale as f64), state.density)); - actor.insert(AsyncSceneCollider::new(None) - .with_shape_for_name("Collider", ComputedCollider::TriMesh) - .with_layers_for_name("Collider", CollisionLayers::ALL) - //.with_density_for_name("Collider", state.density) - ); - actor.insert(NeedsSceneColliderRemoved); - } - else { - actor.insert(state.collider.clone()); - } - } - // TODO: angular velocity for objects without collisions, static objects - - // Optional Components - if state.is_player { - actor.insert(actor::Player); - actor.insert(actor::PlayerCamera); - } - if state.is_sun { - let (r, g, b) = nature::star_color_index_to_rgb(0.656); - actor.insert(materials.add(StandardMaterial { - base_color: Color::rgb(r, g, b) * 13.0, - unlit: true, - ..default() - })); - actor.insert(( - NotShadowCaster, - NotShadowReceiver, - )); - } - if state.is_targeted_on_startup { - actor.insert(hud::IsTargeted); - } - if let Some((mindist, id)) = &state.show_only_in_map_at_distance { - actor.insert(camera::ShowOnlyInMap { - min_distance: *mindist, - distance_to_id: id.clone() - }); - } - if state.is_player || state.is_vehicle { - // used to apply mouse movement to actor rotation - actor.insert(ExternalTorque::ZERO.with_persistence(false)); - } - if state.is_lifeform { - actor.insert(actor::LifeForm::default()); - actor.insert(actor::ExperiencesGForce::default()); - actor.insert(actor::Suit { - oxygen: state.oxygen, - oxygen_max: nature::OXY_D, - integrity: state.suit_integrity, - ..default() - }); - actor.insert(actor::Battery::default()); - } - if state.is_clickable { - actor.insert(hud::IsClickable { + let mut actor = commands.spawn_empty(); + actor.insert(actor::Actor { + id: state.id.clone(), name: state.name.clone(), - pronoun: state.pronoun.clone(), + camdistance: state.camdistance, ..default() }); - } - if let Some(value) = state.wants_maxrotation { - actor.insert(actor::WantsMaxRotation(value)); - } - if let Some(value) = state.wants_maxvelocity { - actor.insert(actor::WantsMaxVelocity(value)); - } - if let Some(color) = state.light_color { - actor.insert(( - PointLight { - intensity: state.light_brightness, - color, - range: 2000.0, - shadows_enabled: settings.shadows_pointlights, + actor.insert(SleepingDisabled); + actor.insert(world::DespawnOnPlayerDeath); + actor.insert(actor::HitPoints::default()); + actor.insert(Position::from(absolute_pos)); + if state.is_sphere { + let sphere_texture_handle = if let Some(model) = &state.model { + Some(asset_server.load(format!("textures/{}.jpg", model))) + } else { + None + }; + rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation; + let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128)); + let sphere_material_handle = materials.add(StandardMaterial { + base_color_texture: sphere_texture_handle, + perceptual_roughness: 1.0, + metallic: 0.0, ..default() - }, - bevy::pbr::CubemapVisibleEntities::default(), - bevy::render::primitives::CubemapFrusta::default(), - )); - } - if !state.id.is_empty() { - actor.insert(actor::Identifier(state.id.clone())); - id2pos.0.insert(state.id.clone(), absolute_pos); - } - if !state.chat.is_empty() { - actor.insert(chat::Talker { - actor_id: state.id.clone(), - chat_name: state.chat.clone(), - name: state.name.clone(), - pronoun: state.pronoun.clone(), - talking_speed: 1.0, - }); - if let Some(name) = &state.name { - achievement_tracker.all_people.insert(name.clone()); + }); + actor.insert(PbrBundle { + mesh: sphere_handle, + material: sphere_material_handle, + transform: Transform::from_scale(scale), + ..default() + }); + } else if let Some(model) = &state.model { + actor.insert(SpatialBundle { + transform: Transform::from_scale(scale), + ..default() + }); + load_asset(model.as_str(), &mut actor, &*asset_server); } - } - if state.is_vehicle { - actor.insert(actor::Vehicle::default()); - if let Some(name) = &state.name { - achievement_tracker.all_vehicles.insert(name.clone()); + actor.insert(Rotation::from(rotation)); + + // Physics Parameters + if state.has_physics { + actor.insert(RigidBody::Dynamic); + actor.insert(LinearVelocity(state.velocity)); + actor.insert(AngularVelocity(state.angular_momentum)); + actor.insert(ColliderDensity(state.density)); + if state.collider_is_mesh { + actor.insert(MassPropertiesBundle::new_computed( + &Collider::sphere(0.5 * state.model_scale as f64), + state.density, + )); + actor.insert(AsyncSceneCollider::new(Some( + ComputedCollider::TriMesh, //ComputedCollider::ConvexDecomposition(VHACDParameters::default()) + ))); + } else if state.collider_is_one_mesh_of_scene { + actor.insert(MassPropertiesBundle::new_computed( + &Collider::sphere(0.5 * state.model_scale as f64), + state.density, + )); + actor.insert( + AsyncSceneCollider::new(None) + .with_shape_for_name("Collider", ComputedCollider::TriMesh) + .with_layers_for_name("Collider", CollisionLayers::ALL), //.with_density_for_name("Collider", state.density) + ); + actor.insert(NeedsSceneColliderRemoved); + } else { + actor.insert(state.collider.clone()); + } } - } - if state.is_vehicle - || state.is_suited - || state.thrust_forward > 0.0 - || state.thrust_sideways > 0.0 - || state.thrust_back > 0.0 - || state.reaction_wheels > 0.0 - { - actor.insert(actor::Engine { - thrust_forward: state.thrust_forward, - thrust_back: state.thrust_back, - thrust_sideways: state.thrust_sideways, - reaction_wheels: state.reaction_wheels, - warmup_seconds: state.warmup_seconds, - engine_type: state.engine_type, - ..default() - }); - } - if let Some(_) = state.ar_model { - actor.insert(hud::AugmentedRealityOverlayBroadcaster); - } - if state.is_player { - actor.with_children(|builder| { - builder.spawn(( - world::DespawnOnPlayerDeath, - actor::PlayersFlashLight, - SpotLightBundle { - transform: Transform { - translation: Vec3::new(0.0, 0.0, 1.0), - rotation: Quat::from_rotation_y(180f32.to_radians()), - ..default() - }, - spot_light: SpotLight { - intensity: 40_000_000.0, // lumens - color: Color::WHITE, - shadows_enabled: true, - inner_angle: PI32 / 8.0 * 0.85, - outer_angle: PI32 / 4.0, - range: 2000.0, - ..default() - }, - visibility: Visibility::Hidden, + // TODO: angular velocity for objects without collisions, static objects + + // Optional Components + if state.is_player { + actor.insert(actor::Player); + actor.insert(actor::PlayerCamera); + } + if state.is_sun { + let (r, g, b) = nature::star_color_index_to_rgb(0.656); + actor.insert(materials.add(StandardMaterial { + base_color: Color::rgb(r, g, b) * 13.0, + unlit: true, + ..default() + })); + actor.insert((NotShadowCaster, NotShadowReceiver)); + } + if state.is_targeted_on_startup { + actor.insert(hud::IsTargeted); + } + if let Some((mindist, id)) = &state.show_only_in_map_at_distance { + actor.insert(camera::ShowOnlyInMap { + min_distance: *mindist, + distance_to_id: id.clone(), + }); + } + if state.is_player || state.is_vehicle { + // used to apply mouse movement to actor rotation + actor.insert(ExternalTorque::ZERO.with_persistence(false)); + } + if state.is_lifeform { + actor.insert(actor::LifeForm::default()); + actor.insert(actor::ExperiencesGForce::default()); + actor.insert(actor::Suit { + oxygen: state.oxygen, + oxygen_max: nature::OXY_D, + integrity: state.suit_integrity, + ..default() + }); + actor.insert(actor::Battery::default()); + } + if state.is_clickable { + actor.insert(hud::IsClickable { + name: state.name.clone(), + pronoun: state.pronoun.clone(), + ..default() + }); + } + if let Some(value) = state.wants_maxrotation { + actor.insert(actor::WantsMaxRotation(value)); + } + if let Some(value) = state.wants_maxvelocity { + actor.insert(actor::WantsMaxVelocity(value)); + } + if let Some(color) = state.light_color { + actor.insert(( + PointLight { + intensity: state.light_brightness, + color, + range: 2000.0, + shadows_enabled: settings.shadows_pointlights, ..default() - } + }, + bevy::pbr::CubemapVisibleEntities::default(), + bevy::render::primitives::CubemapFrusta::default(), )); - }); - } - actor_entity = actor.id(); + } + if !state.id.is_empty() { + actor.insert(actor::Identifier(state.id.clone())); + id2pos.0.insert(state.id.clone(), absolute_pos); + } + if !state.chat.is_empty() { + actor.insert(chat::Talker { + actor_id: state.id.clone(), + chat_name: state.chat.clone(), + name: state.name.clone(), + pronoun: state.pronoun.clone(), + talking_speed: 1.0, + }); + if let Some(name) = &state.name { + achievement_tracker.all_people.insert(name.clone()); + } + } + if state.is_vehicle { + actor.insert(actor::Vehicle::default()); + if let Some(name) = &state.name { + achievement_tracker.all_vehicles.insert(name.clone()); + } + } + if state.is_vehicle + || state.is_suited + || state.thrust_forward > 0.0 + || state.thrust_sideways > 0.0 + || state.thrust_back > 0.0 + || state.reaction_wheels > 0.0 + { + actor.insert(actor::Engine { + thrust_forward: state.thrust_forward, + thrust_back: state.thrust_back, + thrust_sideways: state.thrust_sideways, + reaction_wheels: state.reaction_wheels, + warmup_seconds: state.warmup_seconds, + engine_type: state.engine_type, + ..default() + }); + } + if let Some(_) = state.ar_model { + actor.insert(hud::AugmentedRealityOverlayBroadcaster); + } + if state.is_player { + actor.with_children(|builder| { + builder.spawn(( + world::DespawnOnPlayerDeath, + actor::PlayersFlashLight, + SpotLightBundle { + transform: Transform { + translation: Vec3::new(0.0, 0.0, 1.0), + rotation: Quat::from_rotation_y(180f32.to_radians()), + ..default() + }, + spot_light: SpotLight { + intensity: 40_000_000.0, // lumens + color: Color::WHITE, + shadows_enabled: true, + inner_angle: PI32 / 8.0 * 0.85, + outer_angle: PI32 / 4.0, + range: 2000.0, + ..default() + }, + visibility: Visibility::Hidden, + ..default() + }, + )); + }); + } + actor_entity = actor.id(); } if let Some(ar_asset_name) = &state.ar_model { @@ -837,7 +837,8 @@ fn spawn_entities( } 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(( world::DespawnOnPlayerDeath, MaterialMeshBundle { @@ -860,7 +861,9 @@ fn spawn_entities( } } -pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added, With>)>) { +pub fn hide_colliders( + mut q_mesh: Query<(&mut Visibility, &Name), (Added, With>)>, +) { for (mut visibility, name) in &mut q_mesh { if name.as_str() == "Collider" { *visibility = Visibility::Hidden; @@ -871,7 +874,12 @@ pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added>>, - 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 for (child_entity, child_name, child_parent) in &mut q_mesh { diff --git a/src/common.rs b/src/common.rs index bca26c8..a9490b0 100644 --- a/src/common.rs +++ b/src/common.rs @@ -10,10 +10,10 @@ // // Various common functions and constants -use bevy::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::f64::consts::PI; @@ -77,7 +77,7 @@ pub fn in_shadow( light_source_r: f64, shadow_caster_pos: DVec3, shadow_caster_r: f64, - camera_pos: DVec3 + camera_pos: DVec3, ) -> bool { 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 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; } diff --git a/src/game.rs b/src/game.rs index 9e50a6b..870cac1 100644 --- a/src/game.rs +++ b/src/game.rs @@ -11,10 +11,10 @@ // This module handles player input, and coordinates interplay between other modules use crate::prelude::*; -use bevy::prelude::*; use bevy::pbr::ExtendedMaterial; +use bevy::prelude::*; use bevy::scene::SceneInstance; -use bevy::window::{Window, WindowMode, PrimaryWindow}; +use bevy::window::{PrimaryWindow, Window, WindowMode}; use bevy_xpbd_3d::prelude::*; use std::collections::HashMap; @@ -26,21 +26,29 @@ impl Plugin for GamePlugin { app.add_systems(PostUpdate, handle_game_event); app.add_systems(PreUpdate, handle_player_death); app.add_systems(PostUpdate, update_id2pos); - app.add_systems(Update, handle_achievement_event.run_if(on_event::())); + app.add_systems( + Update, + handle_achievement_event.run_if(on_event::()), + ); app.add_systems(Update, check_achievements); app.insert_resource(Id2Pos(HashMap::new())); app.insert_resource(var::AchievementTracker::default()); - app.insert_resource(AchievementCheckTimer( - Timer::from_seconds(1.0, TimerMode::Repeating))); + app.insert_resource(AchievementCheckTimer(Timer::from_seconds( + 1.0, + TimerMode::Repeating, + ))); app.add_event::(); app.add_event::(); app.add_event::(); } } -#[derive(Event)] pub struct PlayerDiesEvent(pub actor::DamageType); -#[derive(Resource)] pub struct Id2Pos(pub HashMap); -#[derive(Resource)] pub struct AchievementCheckTimer(pub Timer); +#[derive(Event)] +pub struct PlayerDiesEvent(pub actor::DamageType); +#[derive(Resource)] +pub struct Id2Pos(pub HashMap); +#[derive(Resource)] +pub struct AchievementCheckTimer(pub Timer); #[derive(Event)] pub enum AchievementEvent { @@ -125,7 +133,7 @@ pub fn handle_game_event( let current_state = window.mode != WindowMode::Windowed; window.mode = match turn.to_bool(current_state) { 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); } GameEvent::SetRotationStabilizer(turn) => { - settings.rotation_stabilizer_active - = turn.to_bool(settings.rotation_stabilizer_active); + settings.rotation_stabilizer_active = + turn.to_bool(settings.rotation_stabilizer_active); } GameEvent::SetShadows(turn) => { settings.shadows_sun = turn.to_bool(settings.shadows_sun); @@ -148,8 +156,11 @@ pub fn handle_game_event( } GameEvent::Achievement(name) => { ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Achieve)); - log.add(format!("Achievement accomplished: {name}!"), - "".to_string(), hud::LogLevel::Achievement); + log.add( + format!("Achievement accomplished: {name}!"), + "".to_string(), + hud::LogLevel::Achievement, + ); } } } @@ -234,9 +245,15 @@ fn handle_player_death( fn handle_cheats( key_input: Res>, - mut q_player: Query<(&Transform, &mut Position, &mut LinearVelocity), With>, + mut q_player: Query< + (&Transform, &mut Position, &mut LinearVelocity), + With, + >, mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With>, - q_target: Query<(&Transform, &Position, Option<&LinearVelocity>), (With, Without)>, + q_target: Query< + (&Transform, &Position, Option<&LinearVelocity>), + (With, Without), + >, mut ew_playerdies: EventWriter, mut settings: ResMut, id2pos: Res, @@ -266,9 +283,15 @@ fn handle_cheats( gforce.ignore_gforce_seconds = 1.0; 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; - 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 current_speed = v.0.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 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; if let Some(target_v) = target_v { *v = target_v.clone(); @@ -323,10 +347,7 @@ fn handle_cheats( } } -fn update_id2pos( - mut id2pos: ResMut, - q_id: Query<(&Position, &actor::Identifier)>, -) { +fn update_id2pos(mut id2pos: ResMut, q_id: Query<(&Position, &actor::Identifier)>) { id2pos.0.clear(); for (pos, id) in &q_id { id2pos.0.insert(id.0.clone(), pos.0); @@ -337,7 +358,9 @@ fn debug( settings: Res, keyboard_input: Res>, mut commands: Commands, - mut extended_materials: ResMut>>, + mut extended_materials: ResMut< + Assets>, + >, mut achievement_tracker: ResMut, materials: Query<(Entity, Option<&Name>, &Handle)>, ) { @@ -370,7 +393,9 @@ fn handle_achievement_event( } AchievementEvent::InJupitersShadow => { 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; } @@ -415,13 +440,32 @@ fn check_achievements( mut ew_achievement: EventWriter, mut timer: ResMut, ) { - if !timer.0.tick(time.delta()).just_finished() { 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; }; + if !timer.0.tick(time.delta()).just_finished() { + 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, - *pos_jupiter, nature::JUPITER_RADIUS, **pos_player); + let shadowed = in_shadow( + *pos_sun, + nature::SOL_RADIUS, + *pos_jupiter, + nature::JUPITER_RADIUS, + **pos_player, + ); if shadowed { ew_achievement.send(AchievementEvent::InJupitersShadow); diff --git a/src/hud.rs b/src/hud.rs index 36d40cc..c6e20b4 100644 --- a/src/hud.rs +++ b/src/hud.rs @@ -11,9 +11,9 @@ // This module manages the heads-up display and augmented reality overlays. use crate::prelude::*; +use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy::pbr::{NotShadowCaster, NotShadowReceiver}; use bevy::prelude::*; -use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy::transform::TransformSystem; use bevy_xpbd_3d::prelude::*; use std::collections::VecDeque; @@ -45,27 +45,33 @@ pub struct HudPlugin; impl Plugin for HudPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); - app.add_systems(Update, ( - update_hud, - update_dashboard, - update_speedometer, - update_gauges, - handle_input.run_if(in_control), - handle_target_event, - )); - app.add_systems(PostUpdate, ( - update_overlay_visibility, - update_ar_overlays - .after(camera::position_to_transform) - .in_set(sync::SyncSet::PositionToTransform), - update_poi_overlays - .after(camera::position_to_transform) - .in_set(sync::SyncSet::PositionToTransform), - update_target_selectagon - .after(PhysicsSet::Sync) - .after(camera::apply_input_to_player) - .before(TransformSystem::TransformPropagate), - )); + app.add_systems( + Update, + ( + update_hud, + update_dashboard, + update_speedometer, + update_gauges, + handle_input.run_if(in_control), + handle_target_event, + ), + ); + app.add_systems( + PostUpdate, + ( + update_overlay_visibility, + update_ar_overlays + .after(camera::position_to_transform) + .in_set(sync::SyncSet::PositionToTransform), + update_poi_overlays + .after(camera::position_to_transform) + .in_set(sync::SyncSet::PositionToTransform), + update_target_selectagon + .after(PhysicsSet::Sync) + .after(camera::apply_input_to_player) + .before(TransformSystem::TransformPropagate), + ), + ); app.insert_resource(AugmentedRealityState { overlays_visible: false, }); @@ -73,29 +79,47 @@ impl Plugin for HudPlugin { logs: VecDeque::with_capacity(LOG_MAX), needs_rerendering: true, }); - app.insert_resource(FPSUpdateTimer( - Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating))); + app.insert_resource(FPSUpdateTimer(Timer::from_seconds( + HUD_REFRESH_TIME, + TimerMode::Repeating, + ))); app.add_event::(); app.add_event::(); } } -#[derive(Event)] pub struct TargetEvent(pub Option); -#[derive(Event)] pub struct UpdateOverlayVisibility; -#[derive(Component)] struct NodeHud; -#[derive(Component)] struct NodeConsole; -#[derive(Component)] struct NodeChoiceText; -#[derive(Component)] struct NodeSpeedometerText; -#[derive(Component)] struct NodeCurrentChatLine; -#[derive(Component)] 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(Event)] +pub struct TargetEvent(pub Option); +#[derive(Event)] +pub struct UpdateOverlayVisibility; +#[derive(Component)] +struct NodeHud; +#[derive(Component)] +struct NodeConsole; +#[derive(Component)] +struct NodeChoiceText; +#[derive(Component)] +struct NodeSpeedometerText; +#[derive(Component)] +struct NodeCurrentChatLine; +#[derive(Component)] +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)] pub enum Dashboard { @@ -119,7 +143,8 @@ pub struct AugmentedRealityState { pub overlays_visible: bool, } -#[derive(Component)] pub struct AugmentedRealityOverlayBroadcaster; +#[derive(Component)] +pub struct AugmentedRealityOverlayBroadcaster; #[derive(Component)] pub struct AugmentedRealityOverlay { pub owner: Entity, @@ -155,8 +180,7 @@ impl Message { pub fn format(&self) -> String { if self.sender.is_empty() { return self.text.clone() + "\n"; - } - else { + } else { return format!("{}: {}\n", self.sender, self.text); } } @@ -168,11 +192,15 @@ pub struct IsClickable { pub pronoun: Option, pub distance: Option, } -impl Default for IsClickable { fn default() -> Self { Self { - name: None, - pronoun: None, - distance: None, -}}} +impl Default for IsClickable { + fn default() -> Self { + Self { + name: None, + pronoun: None, + distance: None, + } + } +} #[derive(Resource)] pub struct Log { @@ -275,35 +303,35 @@ pub fn setup( // Add Statistics HUD let mut bundle_fps = TextBundle::from_sections([ - TextSection::new("", style), // Target + TextSection::new("", style), // Target TextSection::new("", style_fps), // Frames per second - ]).with_style(Style { + ]) + .with_style(Style { position_type: PositionType::Absolute, top: Val::VMin(2.0), left: Val::VMin(3.0), ..default() - }).with_text_justify(JustifyText::Left); + }) + .with_text_justify(JustifyText::Left); bundle_fps.visibility = visibility; - commands.spawn(( - NodeHud, - ToggleableHudElement, - bundle_fps, - )); + commands.spawn((NodeHud, ToggleableHudElement, bundle_fps)); // Add Console // This one is intentionally NOT a ToggleableHudElement. Instead, console entries // are filtered based on whether the hud is active or not. LogLevel::Always is // even shown when hud is inactive. - let bundle_chatbox = TextBundle::from_sections((0..LOG_MAX_ROWS).map(|_| - TextSection::new("", style_console.clone())) - ).with_style(Style { + let bundle_chatbox = TextBundle::from_sections( + (0..LOG_MAX_ROWS).map(|_| TextSection::new("", style_console.clone())), + ) + .with_style(Style { position_type: PositionType::Absolute, top: Val::VMin(0.0), right: Val::VMin(0.0), ..default() - }).with_text_justify(JustifyText::Right); - commands.spawn(( - NodeBundle { + }) + .with_text_justify(JustifyText::Right); + commands + .spawn((NodeBundle { style: Style { width: Val::Percent(50.0), align_items: AlignItems::Start, @@ -313,37 +341,36 @@ pub fn setup( ..default() }, ..default() - }, - )).with_children(|parent| { - parent.spawn(( - bundle_chatbox, - NodeConsole, - )); - }); + },)) + .with_children(|parent| { + parent.spawn((bundle_chatbox, NodeConsole)); + }); // Add Reticule let reticule_handle: Handle = asset_server.load("sprites/reticule4.png"); - commands.spawn(( - NodeBundle { - style: style_centered(), - visibility, - ..default() - }, - ToggleableHudElement, - )).with_children(|builder| { - builder.spawn(( - ImageBundle { - image: UiImage::new(reticule_handle), - style: Style { - width: Val::VMin(5.0), - height: Val::VMin(5.0), + commands + .spawn(( + NodeBundle { + style: style_centered(), + visibility, + ..default() + }, + ToggleableHudElement, + )) + .with_children(|builder| { + builder.spawn(( + ImageBundle { + image: UiImage::new(reticule_handle), + style: Style { + width: Val::VMin(5.0), + height: Val::VMin(5.0), + ..Default::default() + }, ..Default::default() }, - ..Default::default() - }, - Reticule, - )); - }); + Reticule, + )); + }); // HP/O2/Suit Integrity/Power Gauges let gauges_handle: Handle = asset_server.load("sprites/gauge_horizontal.png"); @@ -356,63 +383,66 @@ pub fn setup( let icon_size = 24.0; let gauge_bar_padding_left = 4.0; for (i, (sprite, gauge)) in gauges.iter().enumerate() { - 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 - commands.spawn(( - NodeBundle { - style: Style { - width: Val::Percent(30.0), - height: Val::Percent(100.0), - bottom: Val::Px(20.0 + 24.0 * i as f32), - left: Val::VMin(2.0), - align_items: AlignItems::End, - overflow: Overflow::clip(), - ..default() - }, - visibility, - ..default() - }, - ToggleableHudElement, - )).with_children(|builder| { - builder.spawn(( + commands + .spawn(( NodeBundle { style: Style { - width: Val::Px(118.0), - height: Val::Px(10.0), - bottom: Val::Px(8.0), - left: Val::Px(gauge_bar_padding_left + icon_size), - ..Default::default() + width: Val::Percent(30.0), + height: Val::Percent(100.0), + bottom: Val::Px(20.0 + 24.0 * i as f32), + left: Val::VMin(2.0), + align_items: AlignItems::End, + overflow: Overflow::clip(), + ..default() }, visibility, - background_color: settings.hud_color.into(), - ..Default::default() - }, - gauge.clone(), - GaugeLength(bar_length), - )); - }); - - // The decorator sprites surrounding the bar - commands.spawn(( - NodeBundle { - style: Style { - width: Val::Percent(30.0), - height: Val::Percent(100.0), - bottom: Val::Px(20.0 + 24.0 * i as f32), - left: Val::VMin(2.0), - align_items: AlignItems::End, - overflow: Overflow::clip(), ..default() }, - visibility, - ..default() - }, - ToggleableHudElement, - )).with_children(|builder| { - // The gauge symbol - builder.spawn(( - ImageBundle { + ToggleableHudElement, + )) + .with_children(|builder| { + builder.spawn(( + NodeBundle { + style: Style { + width: Val::Px(118.0), + height: Val::Px(10.0), + bottom: Val::Px(8.0), + left: Val::Px(gauge_bar_padding_left + icon_size), + ..Default::default() + }, + visibility, + background_color: settings.hud_color.into(), + ..Default::default() + }, + gauge.clone(), + GaugeLength(bar_length), + )); + }); + + // The decorator sprites surrounding the bar + commands + .spawn(( + NodeBundle { + style: Style { + width: Val::Percent(30.0), + height: Val::Percent(100.0), + bottom: Val::Px(20.0 + 24.0 * i as f32), + left: Val::VMin(2.0), + align_items: AlignItems::End, + overflow: Overflow::clip(), + ..default() + }, + visibility, + ..default() + }, + ToggleableHudElement, + )) + .with_children(|builder| { + // The gauge symbol + builder.spawn((ImageBundle { image: UiImage::new(asset_server.load(sprite.to_string())), style: Style { width: Val::Px(icon_size), @@ -421,11 +451,9 @@ pub fn setup( }, visibility, ..Default::default() - }, - )); - // The gauge bar border - builder.spawn(( - ImageBundle { + },)); + // The gauge bar border + builder.spawn((ImageBundle { image: UiImage::new(gauges_handle.clone()), style: Style { width: Val::Px(bar_length), @@ -436,68 +464,70 @@ pub fn setup( }, visibility, ..Default::default() - }, - )); - }); + },)); + }); } - // Car-Dashboard-Style icons let style_dashboard = Style { width: Val::Px(DASHBOARD_ICON_SIZE), height: Val::Px(DASHBOARD_ICON_SIZE), ..Default::default() }; - commands.spawn(( - NodeBundle { - style: Style { - width: Val::Percent(30.0), - height: Val::Percent(100.0), - bottom: Val::Px(40.0 + icon_size * gauges.len() as f32), - left: Val::VMin(4.0), - align_items: AlignItems::End, - overflow: Overflow::clip(), + commands + .spawn(( + NodeBundle { + style: Style { + width: Val::Percent(30.0), + height: Val::Percent(100.0), + bottom: Val::Px(40.0 + icon_size * gauges.len() as f32), + left: Val::VMin(4.0), + align_items: AlignItems::End, + overflow: Overflow::clip(), + ..default() + }, + visibility, ..default() }, - visibility, - ..default() - }, - ToggleableHudElement, - )).with_children(|builder| { - for (component, filename) in DASHBOARD_DEF { - builder.spawn(( - *component, - ImageBundle { - image: UiImage::new(asset_server.load( - format!("sprites/dashboard_{}.png", filename))), - style: style_dashboard.clone(), - visibility: Visibility::Hidden, - ..Default::default() - }, - )); - } - }); + ToggleableHudElement, + )) + .with_children(|builder| { + for (component, filename) in DASHBOARD_DEF { + builder.spawn(( + *component, + ImageBundle { + image: UiImage::new( + asset_server.load(format!("sprites/dashboard_{}.png", filename)), + ), + style: style_dashboard.clone(), + visibility: Visibility::Hidden, + ..Default::default() + }, + )); + } + }); // Add Speedometer let speedometer_handle: Handle = asset_server.load("sprites/speedometer.png"); - commands.spawn(( - NodeBundle { - style: Style { - width: Val::VMin(0.0), - height: Val::Percent(100.0), - left: Val::Vw(100.0 - SPEEDOMETER_WIDTH), - align_items: AlignItems::End, - overflow: Overflow::clip(), + commands + .spawn(( + NodeBundle { + style: Style { + width: Val::VMin(0.0), + height: Val::Percent(100.0), + left: Val::Vw(100.0 - SPEEDOMETER_WIDTH), + align_items: AlignItems::End, + overflow: Overflow::clip(), + ..default() + }, + visibility, ..default() }, - visibility, - ..default() - }, - Speedometer, - ToggleableHudElement, - )).with_children(|builder| { - builder.spawn(( - ImageBundle { + Speedometer, + ToggleableHudElement, + )) + .with_children(|builder| { + builder.spawn((ImageBundle { image: UiImage::new(speedometer_handle), style: Style { width: Val::Vw(SPEEDOMETER_WIDTH), @@ -505,28 +535,28 @@ pub fn setup( ..Default::default() }, ..Default::default() - }, - )); - }); + },)); + }); let speedometer_handle: Handle = asset_server.load("sprites/speedometer_white.png"); - commands.spawn(( - NodeBundle { - style: Style { - width: Val::VMin(0.0), - height: Val::Percent(100.0), - left: Val::Vw(100.0 - SPEEDOMETER_WIDTH), - align_items: AlignItems::End, - overflow: Overflow::clip(), + commands + .spawn(( + NodeBundle { + style: Style { + width: Val::VMin(0.0), + height: Val::Percent(100.0), + left: Val::Vw(100.0 - SPEEDOMETER_WIDTH), + align_items: AlignItems::End, + overflow: Overflow::clip(), + ..default() + }, + visibility, ..default() }, - visibility, - ..default() - }, - Speedometer2, - ToggleableHudElement, - )).with_children(|builder| { - builder.spawn(( - ImageBundle { + Speedometer2, + ToggleableHudElement, + )) + .with_children(|builder| { + builder.spawn((ImageBundle { image: UiImage::new(speedometer_handle), style: Style { width: Val::Vw(SPEEDOMETER_WIDTH), @@ -534,19 +564,20 @@ pub fn setup( ..Default::default() }, ..Default::default() - }, - )); - }); + },)); + }); 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 orbit - ]).with_style(Style { + ]) + .with_style(Style { position_type: PositionType::Absolute, left: Val::Vw(100.0 - SPEEDOMETER_WIDTH + 2.0), bottom: Val::VMin(4.0), ..default() - }).with_text_justify(JustifyText::Left); + }) + .with_text_justify(JustifyText::Left); bundle_speedometer_text.visibility = visibility; commands.spawn(( NodeSpeedometerText, @@ -555,52 +586,52 @@ pub fn setup( )); // Chat "subtitles" and choices - commands.spawn(NodeBundle { - style: Style { - width: Val::Vw(100.0), - align_items: AlignItems::Center, - flex_direction: FlexDirection::Column, - position_type: PositionType::Absolute, - bottom: Val::Vh(2.0), - left: Val::Px(0.0), + commands + .spawn(NodeBundle { + style: Style { + width: Val::Vw(100.0), + align_items: AlignItems::Center, + flex_direction: FlexDirection::Column, + position_type: PositionType::Absolute, + bottom: Val::Vh(2.0), + left: Val::Px(0.0), + ..default() + }, ..default() - }, - ..default() - }).with_children(|builder| { - builder.spawn(( - TextBundle { - text: Text { - sections: vec![ - TextSection::new("", style_conversations), - ], - justify: JustifyText::Center, - ..default() - }, - style: Style { - max_width: Val::Percent(50.0), - margin: UiRect { - bottom: Val::Vh(1.0), + }) + .with_children(|builder| { + builder.spawn(( + TextBundle { + text: Text { + sections: vec![TextSection::new("", style_conversations)], + justify: JustifyText::Center, + ..default() + }, + style: Style { + max_width: Val::Percent(50.0), + margin: UiRect { + bottom: Val::Vh(1.0), + ..default() + }, ..default() }, ..default() }, - ..default() - }, - NodeCurrentChatLine, - )); - let choice_sections = (0..MAX_CHOICES).map(|_| - TextSection::new("", style_choices.clone())); - builder.spawn(( - TextBundle { - text: Text { - sections: choice_sections.collect(), + NodeCurrentChatLine, + )); + let choice_sections = + (0..MAX_CHOICES).map(|_| TextSection::new("", style_choices.clone())); + builder.spawn(( + TextBundle { + text: Text { + sections: choice_sections.collect(), + ..default() + }, ..default() }, - ..default() - }, - NodeChoiceText, - )); - }); + NodeChoiceText, + )); + }); // Selectagon let mut entitycmd = commands.spawn(( @@ -628,23 +659,17 @@ fn update_dashboard( return; } let player = q_player.get_single(); - if player.is_err() { return; } + if player.is_err() { + return; + } let (suit, pos) = player.unwrap(); for (mut vis, icon) in &mut q_dashboard { *vis = bool2vis(match icon { - Dashboard::Flashlight => { - settings.flashlight_active - } - Dashboard::Leak => { - suit.integrity < 0.5 - } - Dashboard::RotationStabiliser => { - !settings.rotation_stabilizer_active - } - Dashboard::CruiseControl => { - settings.cruise_control_active - } + Dashboard::Flashlight => settings.flashlight_active, + Dashboard::Leak => suit.integrity < 0.5, + Dashboard::RotationStabiliser => !settings.rotation_stabilizer_active, + Dashboard::CruiseControl => settings.cruise_control_active, Dashboard::Radioactivity => { if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) { pos_jupiter.distance(pos.0) < 140_000_000.0 @@ -675,13 +700,21 @@ fn update_speedometer( let speedometer_split = 5_000.0; if let Ok(mut speedometer) = q_speedometer.get_single_mut() { 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); speedometer.width = Val::Vw(wid); } if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() { 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); speedometer2.width = Val::Vw(wid); } @@ -729,7 +762,9 @@ fn update_gauges( return; } let player = q_player.get_single(); - if player.is_err() { return; } + if player.is_err() { + return; + } let (hp, suit, battery) = player.unwrap(); for (mut style, mut bg, gauge, len) in &mut q_gauges { @@ -741,8 +776,7 @@ fn update_gauges( }; if value < 0.5 { *bg = settings.hud_color_alert.into(); - } - else { + } else { *bg = settings.hud_color.into(); } style.width = Val::Px(len.0 * value); @@ -758,9 +792,23 @@ fn update_hud( q_choices: Query<&chat::Choice>, q_chat: Query<&chat::Chat>, mut q_node_hud: Query<&mut Text, With>, - mut q_node_console: Query<&mut Text, (With, Without, Without)>, - mut q_node_choice: Query<&mut Text, (With, Without, Without)>, - mut q_node_currentline: Query<&mut Text, (With, Without, Without, Without)>, + mut q_node_console: Query< + &mut Text, + (With, Without, Without), + >, + mut q_node_choice: Query< + &mut Text, + (With, Without, Without), + >, + mut q_node_currentline: Query< + &mut Text, + ( + With, + Without, + Without, + Without, + ), + >, settings: Res, q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With>, ) { @@ -781,41 +829,42 @@ fn update_hud( let dist_scalar: f64; let mut target_multiple = 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; - } - else { + } else { let target: Option; if let Ok((_, Some(targetpos), _)) = q_target.get_single() { 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)); - } - else if q_target.iter().len() > 1 { + } else if q_target.iter().len() > 1 { target_multiple = true; target = None; - } - else { + } else { target_error = true; target = None; } if let Some(target_pos) = target { let dist = pos.0 - target_pos; dist_scalar = dist.length(); - } - else { + } else { dist_scalar = 0.0; } } if target_multiple { 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(); - } - 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() { "UNKNOWN".to_string() } else if dist_scalar != 0.0 { @@ -829,9 +878,9 @@ fn update_hud( } else { "".to_string() }; - text.sections[0].value = format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n"); - } - else { + text.sections[0].value = + format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n"); + } else { text.sections[0].value = "".to_string(); } } @@ -862,15 +911,17 @@ fn update_hud( // Chat Log and System Log let logfilter = if settings.hud_active { - |_msg: &&Message| { true } + |_msg: &&Message| true } else { - |msg: &&Message| { match msg.level { + |msg: &&Message| match msg.level { LogLevel::Always => true, LogLevel::Achievement => true, - _ => false - }} + _ => false, + } }; - let messages: Vec<&Message> = log.logs.iter() + let messages: Vec<&Message> = log + .logs + .iter() .filter(logfilter) .rev() .take(LOG_MAX_ROWS) @@ -893,11 +944,13 @@ fn update_hud( // Display the last chat line as "subtitles" if !q_chat.is_empty() { - let messages: Vec<&Message> = log.logs.iter() - .filter(|msg: &&Message| { match msg.level { + let messages: Vec<&Message> = log + .logs + .iter() + .filter(|msg: &&Message| match msg.level { LogLevel::Chat => true, - _ => false - }}) + _ => false, + }) .rev() .take(1) .collect(); @@ -963,7 +1016,15 @@ fn handle_input( mut ew_sfx: EventWriter, mut ew_target: EventWriter, mut ew_game: EventWriter, - q_objects: Query<(Entity, &Transform), (With, Without, Without, Without)>, + q_objects: Query< + (Entity, &Transform), + ( + With, + Without, + Without, + Without, + ), + >, q_camera: Query<&Transform, With>, ) { 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 let Ok(camtrans) = q_camera.get_single() { let objects: Vec<(Entity, &Transform)> = q_objects.iter().collect(); - if let (Some(new_target), _dist) = camera::find_closest_target::(objects, camtrans) { + if let (Some(new_target), _dist) = + camera::find_closest_target::(objects, camtrans) + { ew_target.send(TargetEvent(Some(new_target))); - } - else { + } else { ew_target.send(TargetEvent(None)); } } @@ -1018,7 +1080,10 @@ fn handle_target_event( fn update_target_selectagon( settings: Res, - mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With, Without, Without)>, + mut q_selectagon: Query< + (&mut Transform, &mut Visibility), + (With, Without, Without), + >, q_target: Query<&Transform, (With, Without, Without)>, q_camera: Query<&Transform, (With, Without, Without)>, ) { @@ -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(target_trans) = q_target.get_single() { match *selectagon_vis { - Visibility::Hidden => { *selectagon_vis = Visibility::Visible; }, + Visibility::Hidden => { + *selectagon_vis = Visibility::Visible; + } _ => {} } 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()); // Enlarge Selectagon to a minimum angular diameter - let (angular_diameter, _, _) = camera::calc_angular_diameter( - &selectagon_trans, camera_trans); + let (angular_diameter, _, _) = + camera::calc_angular_diameter(&selectagon_trans, camera_trans); let min_angular_diameter = 2.0f32.to_radians(); if angular_diameter < min_angular_diameter { selectagon_trans.scale *= min_angular_diameter / angular_diameter; } - } - else { + } else { match *selectagon_vis { - Visibility::Hidden => {}, - _ => { *selectagon_vis = Visibility::Hidden; } + Visibility::Hidden => {} + _ => { + *selectagon_vis = Visibility::Hidden; + } } } } } -fn update_ar_overlays ( - q_owners: Query<(Entity, &Transform, &Visibility), (With, Without)>, - mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>, +fn update_ar_overlays( + q_owners: Query< + (Entity, &Transform, &Visibility), + ( + With, + Without, + ), + >, + mut q_overlays: Query<( + &mut Transform, + &mut Visibility, + &mut AugmentedRealityOverlay, + )>, settings: ResMut, mut state: ResMut, ) { @@ -1064,8 +1142,7 @@ fn update_ar_overlays ( if settings.hud_active { need_activate = !state.overlays_visible; need_clean = false; - } - else { + } else { need_activate = false; need_clean = state.overlays_visible; } @@ -1079,8 +1156,7 @@ fn update_ar_overlays ( *trans = *owner_trans; if need_clean { *vis = Visibility::Hidden; - } - else { + } else { *vis = *owner_vis; } break; @@ -1090,7 +1166,7 @@ fn update_ar_overlays ( } } -fn update_poi_overlays ( +fn update_poi_overlays( mut q_marker: Query<(&mut Transform, &PointOfInterestMarker)>, q_parent: Query<&Transform, Without>, q_camera: Query<&Transform, (With, Without)>, @@ -1105,8 +1181,7 @@ fn update_poi_overlays ( // Enlarge POI marker to a minimum angular diameter trans.translation = parent_trans.translation; trans.scale = Vec3::splat(1.0); - let (angular_diameter, _, _) = camera::calc_angular_diameter( - &trans, camera_trans); + let (angular_diameter, _, _) = camera::calc_angular_diameter(&trans, camera_trans); let min_angular_diameter = 3.0f32.to_radians(); if angular_diameter < min_angular_diameter { trans.scale *= min_angular_diameter / angular_diameter; @@ -1118,9 +1193,27 @@ fn update_poi_overlays ( fn update_overlay_visibility( mut q_marker: Query<&mut Visibility, With>, - mut q_hudelement: Query<&mut Visibility, (With, Without)>, - mut q_selectagon: Query<&mut Visibility, (With, Without, Without)>, - q_target: Query<&IsTargeted, (Without, Without, Without, Without)>, + mut q_hudelement: Query< + &mut Visibility, + (With, Without), + >, + mut q_selectagon: Query< + &mut Visibility, + ( + With, + Without, + Without, + ), + >, + q_target: Query< + &IsTargeted, + ( + Without, + Without, + Without, + Without, + ), + >, mut ambient_light: ResMut, er_target: EventReader, settings: Res, @@ -1128,8 +1221,14 @@ fn update_overlay_visibility( if er_target.is_empty() { return; } - let check = {|check: bool| - if check { Visibility::Inherited } else { Visibility::Hidden } + let check = { + |check: bool| { + if check { + Visibility::Inherited + } else { + Visibility::Hidden + } + } }; let show_poi = check(settings.hud_active && settings.map_active); let show_hud = check(settings.hud_active); diff --git a/src/load.rs b/src/load.rs index b44b44c..5bce1bc 100644 --- a/src/load.rs +++ b/src/load.rs @@ -11,16 +11,18 @@ // This module manages asset loading. use bevy::ecs::system::EntityCommands; -use bevy::render::render_resource::{AsBindGroup, ShaderRef}; use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod}; use bevy::prelude::*; +use bevy::render::render_resource::{AsBindGroup, ShaderRef}; pub struct LoadPlugin; impl Plugin for LoadPlugin { fn build(&self, app: &mut App) { app.add_plugins(MaterialPlugin::::default()); app.add_plugins(MaterialPlugin::::default()); - app.add_plugins(MaterialPlugin::>::default()); + app.add_plugins(MaterialPlugin::< + ExtendedMaterial, + >::default()); } } @@ -55,19 +57,12 @@ pub fn asset_name_to_path(name: &str) -> &'static str { } } -pub fn load_asset( - name: &str, - entity_commands: &mut EntityCommands, - asset_server: &AssetServer, -) { +pub fn load_asset(name: &str, entity_commands: &mut EntityCommands, asset_server: &AssetServer) { entity_commands.insert(load_scene_by_path(asset_name_to_path(name), asset_server)); } #[inline] -pub fn load_scene_by_path( - path: &str, - asset_server: &AssetServer -) -> Handle { +pub fn load_scene_by_path(path: &str, asset_server: &AssetServer) -> Handle { let path_string = path.to_string(); if let Some(handle) = asset_server.get_handle(&path_string) { handle @@ -108,7 +103,7 @@ impl Material for SkyBox { #[derive(Asset, Reflect, AsBindGroup, Debug, Clone)] pub struct AsteroidSurface { #[uniform(100)] - quantize_steps: u32 + quantize_steps: u32, } impl MaterialExtension for AsteroidSurface { @@ -135,8 +130,6 @@ impl AsteroidSurface { } impl Default for AsteroidSurface { fn default() -> Self { - Self { - quantize_steps: 3, - } + Self { quantize_steps: 3 } } } diff --git a/src/main.rs b/src/main.rs index d85489f..c6194b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,18 +28,19 @@ pub mod visual; pub mod world; 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::var::Settings; 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::{GameEvent, Turn}; } -use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode}; use bevy::diagnostic::FrameTimeDiagnosticsPlugin; use bevy::prelude::*; +use bevy::window::{CursorGrabMode, PrimaryWindow, Window, WindowMode}; use std::env; const HELP: &str = "./outfly [options] @@ -68,8 +69,7 @@ fn main() { if arg == "--help" || arg == "-h" { println!("{}", HELP); return; - } - else if arg == "--version" || arg == "-v" { + } else if arg == "--version" || arg == "-v" { let version = option_env!("CARGO_PKG_VERSION").unwrap(); let name = option_env!("CARGO_PKG_NAME").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!("{homepage}"); return; - } - else if arg == "--gl" { + } else if arg == "--gl" { opt.use_gl = true; - } - else if arg == "--windowed" { + } else if arg == "--windowed" { opt.window_mode_initial = WindowMode::Windowed; - } - else if arg == "--fs-legacy" { + } else if arg == "--fs-legacy" { let mode = WindowMode::Fullscreen; if opt.window_mode_initial == opt.window_mode_fullscreen { opt.window_mode_initial = mode; } opt.window_mode_fullscreen = mode; - } - else if arg == "--fs-sized" { + } else if arg == "--fs-sized" { let mode = WindowMode::SizedFullscreen; if opt.window_mode_initial == opt.window_mode_fullscreen { opt.window_mode_initial = mode; } opt.window_mode_fullscreen = mode; - } - else { + } else { println!("{}", HELP); println!("\nERROR: no such option: `{}`", arg); return; @@ -114,7 +109,7 @@ fn main() { #[cfg(feature = "embed_assets")] app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin { - mode: bevy_embedded_assets::PluginMode::ReplaceDefault + mode: bevy_embedded_assets::PluginMode::ReplaceDefault, }); app.add_plugins(OutFlyPlugin); @@ -130,9 +125,8 @@ impl Plugin for OutFlyPlugin { app.insert_resource(var::Settings::default()); app.insert_resource(var::GameVars::default()); app.add_plugins(( - DefaultPlugins,//.set(ImagePlugin::default_nearest()), + DefaultPlugins, //.set(ImagePlugin::default_nearest()), FrameTimeDiagnosticsPlugin, - actor::ActorPlugin, audio::AudioPlugin, camera::CameraPlugin, @@ -148,10 +142,7 @@ impl Plugin for OutFlyPlugin { } } -fn setup( - mut windows: Query<&mut Window, With>, - opt: Res, -) { +fn setup(mut windows: Query<&mut Window, With>, opt: Res) { for mut window in &mut windows { window.cursor.grab_mode = CursorGrabMode::Locked; window.cursor.visible = false; diff --git a/src/menu.rs b/src/menu.rs index d750d46..af8ed06 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -20,28 +20,47 @@ pub struct MenuPlugin; impl Plugin for MenuPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup.after(hud::setup)); - app.add_systems(PreUpdate, show_deathscreen.run_if(on_event::())); + app.add_systems( + PreUpdate, + show_deathscreen.run_if(on_event::()), + ); app.add_systems(Update, handle_deathscreen_input); - app.add_systems(PostUpdate, update_menu - .after(game::handle_game_event) - .run_if(on_event::())); + app.add_systems( + PostUpdate, + update_menu + .after(game::handle_game_event) + .run_if(on_event::()), + ); app.add_systems(Update, handle_input.run_if(alive)); - app.insert_resource(DeathScreenInputDelayTimer( - Timer::from_seconds(1.0, TimerMode::Once))); + app.insert_resource(DeathScreenInputDelayTimer(Timer::from_seconds( + 1.0, + TimerMode::Once, + ))); app.insert_resource(MenuState::default()); app.add_event::(); app.add_event::(); } } -#[derive(Resource)] pub struct DeathScreenInputDelayTimer(pub Timer); -#[derive(Component)] pub struct MenuElement; -#[derive(Component)] pub struct MenuTopLevel; -#[derive(Component)] 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 } +#[derive(Resource)] +pub struct DeathScreenInputDelayTimer(pub Timer); +#[derive(Component)] +pub struct MenuElement; +#[derive(Component)] +pub struct MenuTopLevel; +#[derive(Component)] +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)] = &[ ("", MenuAction::ToggleMap), @@ -115,33 +134,38 @@ pub fn setup( color: settings.hud_color_death_achievements, ..default() }; - commands.spawn(( - DeathScreenElement, - NodeBundle { - style: style_centered(), - visibility: Visibility::Hidden, - ..default() - }, - )).with_children(|builder| { - builder.spawn(( - DeathText, - TextBundle { - text: Text { - sections: vec![ - TextSection::new("", style_death_poem), - TextSection::new("You are dead.\n", style_death), - TextSection::new("Cause: ", style_death_subtext.clone()), - TextSection::new("Unknown", style_death_subtext), - TextSection::new("", style_death_achievements), - TextSection::new("\n\n\n\nPress E to begin anew.", style_death_subsubtext), - ], - justify: JustifyText::Center, - ..default() - }, + commands + .spawn(( + DeathScreenElement, + NodeBundle { + style: style_centered(), + visibility: Visibility::Hidden, ..default() }, - )); - }); + )) + .with_children(|builder| { + builder.spawn(( + DeathText, + TextBundle { + text: Text { + sections: vec![ + TextSection::new("", style_death_poem), + TextSection::new("You are dead.\n", style_death), + TextSection::new("Cause: ", style_death_subtext.clone()), + TextSection::new("Unknown", style_death_subtext), + TextSection::new("", style_death_achievements), + TextSection::new( + "\n\n\n\nPress E to begin anew.", + style_death_subsubtext, + ), + ], + justify: JustifyText::Center, + ..default() + }, + ..default() + }, + )); + }); let style_menu = TextStyle { font: font_handle.clone(), @@ -150,9 +174,11 @@ pub fn setup( ..default() }; - let sections: Vec = Vec::from_iter(MENUDEF.iter().map(|(label, _)| - TextSection::new(label.to_string() + "\n", style_menu.clone()) - )); + let sections: Vec = Vec::from_iter( + MENUDEF + .iter() + .map(|(label, _)| TextSection::new(label.to_string() + "\n", style_menu.clone())), + ); commands.spawn(( MenuElement, @@ -164,32 +190,34 @@ pub fn setup( }, )); - commands.spawn(( - MenuElement, - NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - align_items: AlignItems::Center, - justify_content: JustifyContent::SpaceAround, - ..default() - }, - visibility: Visibility::Hidden, - ..default() - }, - )).with_children(|builder| { - builder.spawn(( - MenuTopLevel, - TextBundle { - text: Text { - sections, - justify: JustifyText::Center, + commands + .spawn(( + MenuElement, + NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::SpaceAround, ..default() }, + visibility: Visibility::Hidden, ..default() }, - )); - }); + )) + .with_children(|builder| { + builder.spawn(( + MenuTopLevel, + TextBundle { + text: Text { + sections, + justify: JustifyText::Center, + ..default() + }, + ..default() + }, + )); + }); let style_achievement_header = TextStyle { font: font_handle.clone(), @@ -205,40 +233,43 @@ pub fn setup( }; let achievement_count = achievement_tracker.to_bool_vec().len(); - commands.spawn(( - MenuElement, - NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - left: Val::Percent(2.0), - top: Val::Percent(2.0), - align_items: AlignItems::Start, - justify_content: JustifyContent::Start, - ..default() - }, - visibility: Visibility::Hidden, - ..default() - }, - )).with_children(|builder| { - let mut sections = vec![ - TextSection::new("Achievements\n", style_achievement_header.clone()) - ]; - sections.extend(Vec::from_iter((0..achievement_count).map(|_| - TextSection::new("", style_achievement.clone()) - ))); - builder.spawn(( - MenuAchievements, - TextBundle { - text: Text { - sections, - justify: JustifyText::Left, + commands + .spawn(( + MenuElement, + NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + left: Val::Percent(2.0), + top: Val::Percent(2.0), + align_items: AlignItems::Start, + justify_content: JustifyContent::Start, ..default() }, + visibility: Visibility::Hidden, ..default() }, - )); - }); + )) + .with_children(|builder| { + let mut sections = vec![TextSection::new( + "Achievements\n", + style_achievement_header.clone(), + )]; + sections.extend(Vec::from_iter( + (0..achievement_count).map(|_| TextSection::new("", style_achievement.clone())), + )); + builder.spawn(( + MenuAchievements, + TextBundle { + text: Text { + sections, + justify: JustifyText::Left, + ..default() + }, + ..default() + }, + )); + }); let keybindings = include_str!("data/keybindings.in"); let style_keybindings = TextStyle { @@ -248,36 +279,36 @@ pub fn setup( ..default() }; - commands.spawn(( - MenuElement, - NodeBundle { - style: Style { - width: Val::Percent(96.0), - height: Val::Percent(96.0), - left: Val::Percent(2.0), - top: Val::Percent(2.0), - align_items: AlignItems::Start, - justify_content: JustifyContent::End, + commands + .spawn(( + MenuElement, + NodeBundle { + style: Style { + width: Val::Percent(96.0), + height: Val::Percent(96.0), + left: Val::Percent(2.0), + top: Val::Percent(2.0), + align_items: AlignItems::Start, + justify_content: JustifyContent::End, + ..default() + }, + visibility: Visibility::Hidden, ..default() }, - visibility: Visibility::Hidden, - ..default() - }, - )).with_children(|builder| { - builder.spawn(( - TextBundle { + )) + .with_children(|builder| { + builder.spawn((TextBundle { text: Text { sections: vec![ TextSection::new("Controls\n", style_achievement_header), - TextSection::new(keybindings, style_keybindings) + TextSection::new(keybindings, style_keybindings), ], justify: JustifyText::Right, ..default() }, ..default() - }, - )); - }); + },)); + }); let style_version = TextStyle { font: font_handle.clone(), @@ -286,36 +317,36 @@ pub fn setup( ..default() }; - commands.spawn(( - MenuElement, - NodeBundle { - style: Style { - width: Val::Percent(96.0), - height: Val::Percent(96.0), - left: Val::Percent(2.0), - top: Val::Percent(2.0), - align_items: AlignItems::End, - justify_content: JustifyContent::End, + commands + .spawn(( + MenuElement, + NodeBundle { + style: Style { + width: Val::Percent(96.0), + height: Val::Percent(96.0), + left: Val::Percent(2.0), + top: Val::Percent(2.0), + align_items: AlignItems::End, + justify_content: JustifyContent::End, + ..default() + }, + visibility: Visibility::Hidden, ..default() }, - visibility: Visibility::Hidden, - ..default() - }, - )).with_children(|builder| { - builder.spawn(( - TextBundle { + )) + .with_children(|builder| { + builder.spawn((TextBundle { text: Text { - sections: vec![ - TextSection::new(format!("{} {}", GAME_NAME, - settings.version.as_str()), style_version), - ], + sections: vec![TextSection::new( + format!("{} {}", GAME_NAME, settings.version.as_str()), + style_version, + )], justify: JustifyText::Right, ..default() }, ..default() - }, - )); - }); + },)); + }); } pub fn show_deathscreen( @@ -359,7 +390,10 @@ pub fn show_deathscreen( } else { ew_respawnaudiosinks.send(audio::RespawnSinksEvent); 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); } } @@ -472,7 +506,7 @@ pub fn handle_input( let last_menu_entry = MENUDEF.len() - 1; if keyboard_input.just_pressed(settings.key_menu) - || keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active + || keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active { ew_game.send(GameEvent::SetMenu(Toggle)); } @@ -480,8 +514,8 @@ pub fn handle_input( return; } if keyboard_input.just_pressed(settings.key_forward) - || keyboard_input.just_pressed(settings.key_mouseup) - || keyboard_input.just_pressed(KeyCode::ArrowUp) + || keyboard_input.just_pressed(settings.key_mouseup) + || keyboard_input.just_pressed(KeyCode::ArrowUp) { menustate.cursor = if menustate.cursor == 0 { last_menu_entry @@ -492,8 +526,8 @@ pub fn handle_input( ew_updatemenu.send(UpdateMenuEvent); } if keyboard_input.just_pressed(settings.key_back) - || keyboard_input.just_pressed(settings.key_mousedown) - || keyboard_input.just_pressed(KeyCode::ArrowDown) + || keyboard_input.just_pressed(settings.key_mousedown) + || keyboard_input.just_pressed(KeyCode::ArrowDown) { menustate.cursor = if menustate.cursor == last_menu_entry { 0 @@ -504,7 +538,7 @@ pub fn handle_input( ew_updatemenu.send(UpdateMenuEvent); } if keyboard_input.just_pressed(settings.key_interact) - || keyboard_input.just_pressed(KeyCode::Enter) + || keyboard_input.just_pressed(KeyCode::Enter) { ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click)); match MENUDEF[menustate.cursor].1 { @@ -512,37 +546,37 @@ pub fn handle_input( ew_game.send(GameEvent::SetMap(Toggle)); ew_game.send(GameEvent::SetMenu(Turn::Off)); ew_updatemenu.send(UpdateMenuEvent); - }, + } MenuAction::ToggleAR => { ew_game.send(GameEvent::SetAR(Toggle)); ew_updatemenu.send(UpdateMenuEvent); - }, + } MenuAction::ToggleMusic => { ew_game.send(GameEvent::SetMusic(Toggle)); ew_updatemenu.send(UpdateMenuEvent); - }, + } MenuAction::ToggleSound => { ew_game.send(GameEvent::SetSound(Toggle)); ew_updatemenu.send(UpdateMenuEvent); - }, + } MenuAction::ToggleCamera => { ew_game.send(GameEvent::SetThirdPerson(Toggle)); ew_updatemenu.send(UpdateMenuEvent); - }, + } MenuAction::ToggleFullscreen => { ew_game.send(GameEvent::SetFullscreen(Toggle)); - }, + } MenuAction::ToggleShadows => { ew_game.send(GameEvent::SetShadows(Toggle)); ew_updatemenu.send(UpdateMenuEvent); - }, + } MenuAction::Restart => { settings.god_mode = false; ew_playerdies.send(game::PlayerDiesEvent(actor::DamageType::Depressurization)); - }, + } MenuAction::Quit => { app_exit_events.send(bevy::app::AppExit); - }, + } }; } } diff --git a/src/nature.rs b/src/nature.rs index 5704318..6b47701 100644 --- a/src/nature.rs +++ b/src/nature.rs @@ -24,8 +24,8 @@ pub const EARTH_GRAVITY: f32 = 9.81; pub const C: f64 = 299792458.0; // m/s pub const G: f64 = 6.6743015e-11; // Gravitational constant in Nm²/kg² -pub const SOL_RADIUS: f64 = 696_300_000.0; -pub const JUPITER_RADIUS: f64 = 71_492_000.0; +pub const SOL_RADIUS: f64 = 696_300_000.0; +pub const JUPITER_RADIUS: f64 = 71_492_000.0; pub const JUPITER_RING_RADIUS: f64 = 229_000_000.0; pub const SOL_MASS: f64 = 1.9885e30; @@ -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 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 = 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) }; - return (clamp(red), clamp(green), clamp(blue)) + return (clamp(red), clamp(green), clamp(blue)); } 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); } else if radius >= main_inner && radius <= main_outer { 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 { - 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)); + if radius > metis_notch_center - metis_notch_width * 0.5 + && 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 { if radius >= amalthea_inner && radius <= amalthea_outer { 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 { let lightyears = abs / C; return format!("{lightyears:.4} c"); - } - else { + } else { let kmh = abs * 1.0e-3 * 3600.0; return format!("{kmh:.0} km/h"); } diff --git a/src/var.rs b/src/var.rs index 6f2b679..eb06760 100644 --- a/src/var.rs +++ b/src/var.rs @@ -12,13 +12,13 @@ // "if"-conditions in chats. use crate::prelude::*; -use bevy::window::WindowMode; use bevy::prelude::*; -use std::collections::{HashMap, HashSet}; +use bevy::window::WindowMode; use serde::Deserialize; -use toml_edit::DocumentMut; +use std::collections::{HashMap, HashSet}; use std::env; use std::fs; +use toml_edit::DocumentMut; pub const SCOPE_SEPARATOR: &str = "$"; @@ -379,28 +379,29 @@ impl AchievementTracker { (self.ride_every_vehicle, "ride every vehicle".into()), (self.talk_to_everyone, "talk to everyone".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 { let list = self.to_overview(); let count = list.iter().filter(|(achieved, _)| *achieved).count(); if count == 0 { - return "".to_string() + return "".to_string(); } let mut summary = "\n\n\nYou managed to ".to_string(); for (i, (_, text)) in list.iter().filter(|(achieved, _)| *achieved).enumerate() { summary += text.as_str(); if i + 2 == count { summary += ", and "; - } - else if i + 1 == count { + } else if i + 1 == count { summary += " before you perished."; if count == list.len() { summary += "\nA truly astounding achievement, a glimmer in the void, before it all fades, into nothingness."; } - } - else { + } else { summary += ", "; } } @@ -439,7 +440,9 @@ impl Preferences { } 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 { @@ -479,24 +482,22 @@ pub fn load_prefs() -> Preferences { } }; match toml.parse::() { - Ok(doc) => { - match toml_edit::de::from_document::(doc) { - Ok(mut pref) => { - if let Some(path) = &path { - info!("Loaded preference file from {path}"); - } else { - info!("Loaded preferences from internal defaults"); - } - pref.source_file = path; - dbg!(&pref); - return pref; - } - Err(error) => { - error!("Failed to read preference line: {error}"); - return Preferences::default(); + Ok(doc) => match toml_edit::de::from_document::(doc) { + Ok(mut pref) => { + if let Some(path) = &path { + info!("Loaded preference file from {path}"); + } else { + info!("Loaded preferences from internal defaults"); } + pref.source_file = path; + dbg!(&pref); + return pref; } - } + Err(error) => { + error!("Failed to read preference line: {error}"); + return Preferences::default(); + } + }, Err(error) => { error!("Failed to open preferences: {error}"); return Preferences::default(); @@ -511,9 +512,7 @@ pub struct GameVars { impl Default for GameVars { fn default() -> Self { - Self { - db: HashMap::new(), - } + Self { db: HashMap::new() } } } @@ -573,8 +572,7 @@ impl GameVars { if scope_part.is_empty() { // we got a key like "$foo", just prefix the fallback scope fallback_scope.to_string() + key - } - else { + } else { // we got a key like "Ke$ha$foo" or "$$foo" (which is the convention for // global variables), leave the scope intact key.to_string() @@ -609,11 +607,9 @@ impl GameVars { let part = Self::normalize_varname(scope, part); let value_bool = self.getb(part.as_str()); return value_bool ^ negate; - } - else { + } else { return Self::evaluate_str_as_bool(part) ^ negate; } - } else if parts.len() == 2 { // Got something like "if $something somethingelse" // Check whether the two are identical. diff --git a/src/visual.rs b/src/visual.rs index b4bbe1a..229dd3c 100644 --- a/src/visual.rs +++ b/src/visual.rs @@ -10,15 +10,18 @@ // // This module manages visual effects. -use bevy::prelude::*; use crate::prelude::*; +use bevy::prelude::*; pub struct VisualPlugin; impl Plugin for VisualPlugin { fn build(&self, app: &mut App) { 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, update_fadein); app.add_systems(Update, update_fadeout); @@ -38,8 +41,10 @@ pub enum Effects { // Blackout disabled for now //#[derive(Component)] pub struct BlackOutOverlay; -#[derive(Component)] pub struct FadeIn; -#[derive(Component)] pub struct FadeOut; +#[derive(Component)] +pub struct FadeIn; +#[derive(Component)] +pub struct FadeOut; #[derive(Component)] pub struct Effect { pub class: Effects, @@ -52,29 +57,29 @@ pub struct SpawnEffectEvent { pub duration: f64, } -pub fn setup( - settings: Res, - mut ew_effect: EventWriter, -) { +pub fn setup(settings: Res, mut ew_effect: EventWriter) { 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 -// commands.spawn(( -// BlackOutOverlay, -// NodeBundle { -// style: Style { -// width: Val::Vw(100.0), -// height: Val::Vh(100.0), -// position_type: PositionType::Absolute, -// top: Val::Px(0.0), -// left: Val::Px(0.0), -// ..default() -// }, -// background_color: Color::BLACK.into(), -// ..default() -// }, -// )); + // Blackout disabled for now + // commands.spawn(( + // BlackOutOverlay, + // NodeBundle { + // style: Style { + // width: Val::Vw(100.0), + // height: Val::Vh(100.0), + // position_type: PositionType::Absolute, + // top: Val::Px(0.0), + // left: Val::Px(0.0), + // ..default() + // }, + // background_color: Color::BLACK.into(), + // ..default() + // }, + // )); } pub fn spawn_effects( @@ -99,7 +104,7 @@ pub fn spawn_effects( ..default() }, )); - }, + } Effects::FadeOut(color) => { commands.spawn(( Effect { @@ -114,8 +119,7 @@ pub fn spawn_effects( ..default() }, )); - }, - //_ => {}, + } } } } diff --git a/src/world.rs b/src/world.rs index 70e85ca..b4582ff 100644 --- a/src/world.rs +++ b/src/world.rs @@ -11,13 +11,13 @@ // This module populates the world with stars and asteroids. use crate::prelude::*; -use bevy::prelude::*; use bevy::math::I64Vec3; -use bevy::scene::{InstanceId, SceneInstance}; +use bevy::prelude::*; use bevy::render::mesh::Indices; +use bevy::scene::{InstanceId, SceneInstance}; use bevy_xpbd_3d::prelude::*; -use std::collections::HashMap; use fastrand; +use std::collections::HashMap; const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds const ASTEROID_SIZE_FACTOR: f32 = 10.0; @@ -42,20 +42,28 @@ impl Plugin for WorldPlugin { app.add_plugins(PhysicsPlugins::default()); //app.add_plugins(PhysicsDebugPlugin::default()); app.insert_resource(Gravity(DVec3::splat(0.0))); - app.insert_resource(AsteroidUpdateTimer( - Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating))); + app.insert_resource(AsteroidUpdateTimer(Timer::from_seconds( + ASTEROID_UPDATE_INTERVAL, + TimerMode::Repeating, + ))); app.insert_resource(ActiveAsteroids(HashMap::new())); app.add_event::(); app.add_event::(); } } -#[derive(Resource)] struct AsteroidUpdateTimer(Timer); -#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap); -#[derive(Component)] struct Asteroid; -#[derive(Component)] pub struct Star; -#[derive(Component)] pub struct DespawnOnPlayerDeath; -#[derive(Event)] pub struct RespawnEvent; +#[derive(Resource)] +struct AsteroidUpdateTimer(Timer); +#[derive(Resource)] +pub struct ActiveAsteroids(pub HashMap); +#[derive(Component)] +struct Asteroid; +#[derive(Component)] +pub struct Star; +#[derive(Component)] +pub struct DespawnOnPlayerDeath; +#[derive(Event)] +pub struct RespawnEvent; pub struct AsteroidData { entity: Entity, @@ -93,17 +101,15 @@ pub fn setup( let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation let mag = mag.min(6.0); - let scale_size = {|mag: f32| - scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) - }; + let scale_size = + { |mag: f32| scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) }; let scale = scale_size(mag); - let scale_color = {|color: f32| - 1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) - }; + let scale_color = + { |color: f32| 1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) }; //let scale = translation.length().powf(0.84); - //pos_render.length().powf(0.64) - //(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 * + //pos_render.length().powf(0.64) + //(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 * let star_color_handle = materials.add(StandardMaterial { base_color: Color::rgb(scale_color(r), scale_color(g), scale_color(b)), @@ -176,8 +182,8 @@ fn spawn_despawn_asteroids( id2pos: Res, asset_server: Res, ) { - if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() { - //if q_player.is_empty() { + if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() { + //if q_player.is_empty() { return; } let jupiter_pos = if let Some(jupiter_pos) = id2pos.0.get(&"jupiter".to_string()) { @@ -211,9 +217,12 @@ fn spawn_despawn_asteroids( let z_min = player_cell.z - stepmax; let z_max = player_cell.z + stepmax; for (origin, asteroid) in db.0.iter() { - if origin.x < x_min || origin.x > x_max - || origin.y < y_min || origin.y > y_max - || origin.z < z_min || origin.z > z_max + if origin.x < x_min + || origin.x > x_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 pos.0.distance(player.0) > 1000.0 { @@ -288,11 +297,12 @@ fn spawn_despawn_asteroids( //let max_viewdist = ASTEROID_VIEW_RADIUS / ASTEROID_SPAWN_STEP; let wobble = ASTEROID_SPAWN_STEP * 0.5; - let pos = jupiter_pos + DVec3::new( - 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.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0, - ); + let pos = jupiter_pos + + DVec3::new( + 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.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0, + ); // Spawn let mut entity_commands = commands.spawn(( @@ -322,10 +332,13 @@ fn spawn_despawn_asteroids( ..default() }); load_asset(model, &mut entity_commands, &*asset_server); - db.0.insert(origin, AsteroidData { - entity: entity_commands.id(), - //viewdistance: 99999999.0, - }); + db.0.insert( + origin, + AsteroidData { + entity: entity_commands.id(), + //viewdistance: 99999999.0, + }, + ); } } }