Compare commits

..

6 commits

17 changed files with 287 additions and 24 deletions

View file

@ -1,3 +1,9 @@
# v0.12.0-dev
- Add power-hungry optional thruster boost
- Add different flashlight power settings
- Add different light amplification settings
# v0.11.1 # v0.11.1
- Added space suit thruster particle effects - Added space suit thruster particle effects

View file

@ -47,6 +47,8 @@
- achieve.ogg: [UI Completed Status Alert Notification by Headphaze, CC BY 4.0](https://freesound.org/s/277033/) - achieve.ogg: [UI Completed Status Alert Notification by Headphaze, CC BY 4.0](https://freesound.org/s/277033/)
- drink.ogg: [Pouring Beer into Short Glass by megashroom, CC0](https://freesound.org/s/390336/) - drink.ogg: [Pouring Beer into Short Glass by megashroom, CC0](https://freesound.org/s/390336/)
- enter.ogg, exit.ogg: [Getting Out of Car.wav by kingsrow, CC0](https://freesound.org/s/181568/) - enter.ogg, exit.ogg: [Getting Out of Car.wav by kingsrow, CC0](https://freesound.org/s/181568/)
- powerdown.ogg: [Energy Drain by qubodup, CC0](https://freesound.org/people/qubodup/sounds/742835/)
- spark.ogg: [Socket Connect Disconnect by JustHallowed, CC BY 4.0](https://freesound.org/people/JustHallowed/sounds/691007/), Tempo -40, Amplify 4
- Takeoff.ogg: [By Serat, CC BY 4.0](https://freemusicarchive.org/music/serat/route-remastered/takeoff-remastered/) - Takeoff.ogg: [By Serat, CC BY 4.0](https://freemusicarchive.org/music/serat/route-remastered/takeoff-remastered/)
- JupiterRecording.ogg: An [actual Jupiter recording by NASA](https://archive.org/download/voyager-1-and-2-1990-jupiter-nasa-voyager-space-sounds-electronic), public domain. - JupiterRecording.ogg: An [actual Jupiter recording by NASA](https://archive.org/download/voyager-1-and-2-1990-jupiter-nasa-voyager-space-sounds-electronic), public domain.
- Processed by cutting out min 1:47-3:47 and applying a 10s linear crossfade at the end. Exported as ogg with quality=3, see [.kdenlive file](src/audio/JupiterRecording.kdenlive). - Processed by cutting out min 1:47-3:47 and applying a 10s linear crossfade at the end. Exported as ogg with quality=3, see [.kdenlive file](src/audio/JupiterRecording.kdenlive).

View file

@ -35,7 +35,7 @@ The `SPACE` key helps you move more intuitively by slowing you down. Hold it fo
When you're ready, take a look around, explore the starting area. There is a friendly person floating nearby in a red space suit that would love to talk to you. When you're ready, take a look around, explore the starting area. There is a friendly person floating nearby in a red space suit that would love to talk to you.
The game is dark. After all, space is dark. The planets and moons orbit in real time, the map changes depending on when you play, and if you're unlucky, you start in the shadow of Jupiter and everything's even darker! Try the flashlight (`f` key), turning off shadows in the menu, and make sure that Augmented Reality is on (`TAB` key) which gives you a little extra light amplification. The game is dark. After all, space is dark. The planets and moons orbit in real time, the map changes depending on when you play, and if you're unlucky, you start in the shadow of Jupiter and everything's even darker! Try the flashlight (`f` key), or stronger light amplification (menu), which requires Augmented Reality to be active (`TAB` key).
Press `Esc` for the menu to restart the game if you get lost, explore more key bindings, game features, and the **achievements** to get some guidance on what you can do in the game. Press `Esc` for the menu to restart the game if you get lost, explore more key bindings, game features, and the **achievements** to get some guidance on what you can do in the game.

BIN
assets/sounds/powerdown.ogg Normal file

Binary file not shown.

BIN
assets/sounds/spark.ogg Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -110,6 +110,16 @@ A variety of relatively simple game systems should interact with each other to c
- can we make it non-modal? Interweave it into regular gameplay? - can we make it non-modal? Interweave it into regular gameplay?
- G-forces and equipment/organ damage - G-forces and equipment/organ damage
- Death, and survival - Death, and survival
- Spacesuit energy distribution into:
- [X] Residual Light Amplification
- [X] Augmented Reality
- [X] Flashlight intensity
- [ ] AI assistance systems
- [ ] Thruster boost
- [ ] G-force dampeners
- [ ] Life support, material recyclers (air, water, etc)
- [ ] High energy particle shield?
- [ ] Micrometeorite shield?
# Quest Ideas # Quest Ideas
@ -150,6 +160,7 @@ A variety of relatively simple game systems should interact with each other to c
- Nethack - Nethack
- Planescape: Torment - Planescape: Torment
- Prey (2017 game) - Prey (2017 game)
- Primer (2004 film)
- Project Hail Mary - Project Hail Mary
- RimWorld - RimWorld
- Risk of Rain - Risk of Rain
@ -157,6 +168,7 @@ A variety of relatively simple game systems should interact with each other to c
- Shadowrun Returns: Dragonfall, Hong Kong - Shadowrun Returns: Dragonfall, Hong Kong
- Stardew Valley - Stardew Valley
- Stray (2022 game) - Stray (2022 game)
- Synthwave (music genre)
- System Shock 2 - System Shock 2
- The Expanse - The Expanse
- The Forgotten City - The Forgotten City

View file

@ -21,6 +21,10 @@ use bevy_xpbd_3d::prelude::*;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0; pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0; const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0; const MAX_INTERACT_DISTANCE: f32 = 50.0;
const POWER_DRAIN_THRUSTER: [f32; 3] = [3e6, 3e6, 0.0];
const THRUSTER_BOOST_FACTOR: [f64; 3] = [3.0, 3.0, 0.0];
const POWER_DRAIN_FLASHLIGHT: [f32; 3] = [200e3, 1500e3, 2500e3];
pub const FLASHLIGHT_INTENSITY: [f32; 3] = [10e6, 400e6, 2e9]; // in lumens
pub struct ActorPlugin; pub struct ActorPlugin;
impl Plugin for ActorPlugin { impl Plugin for ActorPlugin {
@ -210,6 +214,9 @@ pub struct Engine {
pub engine_type: EngineType, pub engine_type: EngineType,
pub warmup_seconds: f32, pub warmup_seconds: f32,
pub current_warmup: f32, // between 0.0 and 1.0 pub current_warmup: f32, // between 0.0 and 1.0
pub current_boost_factor: f64,
pub currently_firing: bool,
pub currently_matching_velocity: bool,
} }
impl Default for Engine { impl Default for Engine {
fn default() -> Self { fn default() -> Self {
@ -221,6 +228,9 @@ impl Default for Engine {
engine_type: EngineType::Monopropellant, engine_type: EngineType::Monopropellant,
warmup_seconds: 1.5, warmup_seconds: 1.5,
current_warmup: 0.0, current_warmup: 0.0,
current_boost_factor: 1.0,
currently_firing: false,
currently_matching_velocity: false,
} }
} }
} }
@ -248,6 +258,7 @@ pub struct Battery {
pub power: f32, // Watt-seconds pub power: f32, // Watt-seconds
pub capacity: f32, // Watt-seconds pub capacity: f32, // Watt-seconds
pub reactor: f32, // Watt (production) pub reactor: f32, // Watt (production)
pub overloaded_recovering: bool,
} }
impl Default for Battery { impl Default for Battery {
@ -256,6 +267,7 @@ impl Default for Battery {
power: 10e3 * 3600.0, power: 10e3 * 3600.0,
capacity: 10e3 * 3600.0, // 10kWh capacity: 10e3 * 3600.0, // 10kWh
reactor: 2000e3, // 2MW reactor: 2000e3, // 2MW
overloaded_recovering: false,
} }
} }
} }
@ -263,23 +275,63 @@ impl Default for Battery {
pub fn update_power( pub fn update_power(
time: Res<Time>, time: Res<Time>,
mut settings: ResMut<Settings>, mut settings: ResMut<Settings>,
mut q_battery: Query<(&mut Battery, Option<&Player>)>, prefs: Res<Preferences>,
mut q_battery: Query<(&mut Battery, &mut Engine), With<Player>>,
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>, mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_game: EventWriter<game::GameEvent>,
) { ) {
let mut power_down = false;
let d = time.delta_seconds(); let d = time.delta_seconds();
for (mut battery, player) in &mut q_battery { for (mut battery, mut engine) in &mut q_battery {
if player.is_some() && settings.flashlight_active { if settings.flashlight_active {
battery.power -= 2400000.0 * d; battery.power -= POWER_DRAIN_FLASHLIGHT[prefs.flashlight_power] * d; // 2.4MW
if battery.power <= 0.0 { if battery.power <= 0.0 {
power_down = true;
settings.flashlight_active = false; settings.flashlight_active = false;
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
for mut flashlight_vis in &mut q_flashlight { for mut flashlight_vis in &mut q_flashlight {
*flashlight_vis = Visibility::Hidden; *flashlight_vis = Visibility::Hidden;
} }
} }
} }
if settings.hud_active {
let mut hud_drain = 300e3; // 300kW
hud_drain += prefs.light_amp as f32 * 200e3; // 200kW per level
battery.power -= hud_drain * d;
if battery.power <= 0.0 {
power_down = true;
ew_game.send(GameEvent::SetAR(Turn::Off));
for mut flashlight_vis in &mut q_flashlight {
*flashlight_vis = Visibility::Hidden;
}
}
}
let drain = POWER_DRAIN_THRUSTER[prefs.thruster_boost];
let boosting = !battery.overloaded_recovering
&& prefs.thruster_boost != 2
&& (prefs.thruster_boost == 1 || engine.currently_matching_velocity);
if boosting {
if battery.power > drain * d * 100.0 {
engine.current_boost_factor = THRUSTER_BOOST_FACTOR[prefs.thruster_boost];
if engine.currently_firing {
battery.power -= drain * d;
}
} else {
power_down = true;
battery.overloaded_recovering = true;
engine.current_boost_factor = 1.0;
}
} else {
engine.current_boost_factor = 1.0;
}
if power_down {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::PowerDown));
}
battery.power = (battery.power + battery.reactor * d).clamp(0.0, battery.capacity); battery.power = (battery.power + battery.reactor * d).clamp(0.0, battery.capacity);
if battery.overloaded_recovering && battery.power > battery.capacity * 0.5 {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::PowerUp));
battery.overloaded_recovering = false;
}
} }
} }

View file

@ -77,6 +77,8 @@ const PATHS: &[(SfxType, Sfx, &str)] = &[
"sounds/connect.ogg", "sounds/connect.ogg",
), ),
(SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"), (SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"),
(SfxType::OneOff, Sfx::PowerDown, "sounds/powerdown.ogg"),
(SfxType::OneOff, Sfx::PowerUp, "sounds/spark.ogg"),
(SfxType::OneOff, Sfx::Switch, "sounds/click2.ogg"), (SfxType::OneOff, Sfx::Switch, "sounds/click2.ogg"),
(SfxType::OneOff, Sfx::Refill, "sounds/refill.ogg"), (SfxType::OneOff, Sfx::Refill, "sounds/refill.ogg"),
(SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"), (SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"),
@ -103,6 +105,8 @@ pub enum Sfx {
IncomingChatMessage, IncomingChatMessage,
Ion, Ion,
Ping, Ping,
PowerDown,
PowerUp,
Refill, Refill,
Switch, Switch,
Thruster, Thruster,
@ -125,6 +129,8 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
"zoom" => Sfx::Zoom, "zoom" => Sfx::Zoom,
"chat" => Sfx::IncomingChatMessage, "chat" => Sfx::IncomingChatMessage,
"ping" => Sfx::Ping, "ping" => Sfx::Ping,
"powerdown" => Sfx::PowerDown,
"powerup" => Sfx::PowerUp,
"connect" => Sfx::Connect, "connect" => Sfx::Connect,
"refill" => Sfx::Refill, "refill" => Sfx::Refill,
"entervehicle" => Sfx::EnterVehicle, "entervehicle" => Sfx::EnterVehicle,

View file

@ -541,15 +541,22 @@ pub fn apply_input_to_player(
let right_factor = engine.thrust_sideways * engine.current_warmup; let right_factor = engine.thrust_sideways * engine.current_warmup;
let up_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); let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64);
let boost = if bike.is_none() {
engine.current_boost_factor
} else {
1.0
};
if axis_input.length_squared() > 0.003 { if axis_input.length_squared() > 0.003 {
engine.currently_firing = true;
let acceleration_global: DVec3 = let acceleration_global: DVec3 =
DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3()); DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
let mut acceleration_total: DVec3 = let mut acceleration_total: DVec3 =
(actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global; (actor::ENGINE_SPEED_FACTOR * dt) as f64 * boost * acceleration_global;
let threshold = 1e-5; let threshold = 1e-5;
if key_input.pressed(settings.key_stop) { if key_input.pressed(settings.key_stop) {
// Decelerate (or match velocity to target_v) // Decelerate (or match velocity to target_v)
engine.currently_matching_velocity = true;
let dv = v.0 - target_v; let dv = v.0 - target_v;
for i in 0..3 { for i in 0..3 {
if dv[i].abs() < threshold { if dv[i].abs() < threshold {
@ -560,6 +567,8 @@ pub fn apply_input_to_player(
acceleration_total[i] = 0.0; acceleration_total[i] = 0.0;
} }
} }
} else {
engine.currently_matching_velocity = false;
} }
// TODO: handle mass // TODO: handle mass
v.0 += acceleration_total; v.0 += acceleration_total;
@ -571,7 +580,7 @@ pub fn apply_input_to_player(
if bike.is_none() && acceleration_total.length_squared() > 1e-4 { if bike.is_none() && acceleration_total.length_squared() > 1e-4 {
let thruster_direction = acceleration_total.normalize(); let thruster_direction = acceleration_total.normalize();
let thruster_pos = pos.0 - 0.3 * thruster_direction; let thruster_pos = pos.0 - 0.3 * thruster_direction;
let thruster_v = v.0 - 5.0 * thruster_direction; let thruster_v = v.0 - boost * 5.0 * thruster_direction;
ew_effect.send(visual::SpawnEffectEvent { ew_effect.send(visual::SpawnEffectEvent {
duration: 2.0, duration: 2.0,
class: visual::Effects::ThrusterParticle( class: visual::Effects::ThrusterParticle(
@ -581,6 +590,8 @@ pub fn apply_input_to_player(
}); });
} }
} else { } else {
engine.currently_firing = false;
engine.currently_matching_velocity = false;
engine.current_warmup = engine.current_warmup =
(engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0); (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
} }
@ -676,21 +687,23 @@ pub fn apply_input_to_player(
let sinks = vec![ let sinks = vec![
( (
1.0, 1.0,
boost as f32,
actor::EngineType::Monopropellant, actor::EngineType::Monopropellant,
sinks.get(&audio::Sfx::Thruster), sinks.get(&audio::Sfx::Thruster),
), ),
(1.0, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)), (1.0, 1.0, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)),
]; ];
let seconds_to_max_vol = 0.05; let seconds_to_max_vol = 0.05;
let seconds_to_min_vol = 0.05; let seconds_to_min_vol = 0.05;
for sink_data in sinks { for sink_data in sinks {
if let (vol_boost, engine_type, Some(sink)) = sink_data { if let (vol_boost, speed, engine_type, Some(sink)) = sink_data {
if settings.mute_sfx { if settings.mute_sfx {
sink.pause(); sink.pause();
} else { } else {
let volume = sink.volume(); let volume = sink.volume();
if engine.engine_type == engine_type { if engine.engine_type == engine_type {
if play_thruster_sound { if play_thruster_sound {
sink.set_speed(speed);
sink.play(); sink.play();
if volume < 1.0 { if volume < 1.0 {
let maxvol = vol_boost; let maxvol = vol_boost;

View file

@ -816,6 +816,7 @@ fn spawn_entities(
mut achievement_tracker: ResMut<var::AchievementTracker>, mut achievement_tracker: ResMut<var::AchievementTracker>,
mut ew_updateavatar: EventWriter<hud::UpdateAvatarEvent>, mut ew_updateavatar: EventWriter<hud::UpdateAvatarEvent>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
prefs: Res<var::Preferences>,
) { ) {
for state_wrapper in er_spawn.read() { for state_wrapper in er_spawn.read() {
let state = &state_wrapper.0; let state = &state_wrapper.0;
@ -1073,7 +1074,7 @@ fn spawn_entities(
..default() ..default()
}, },
spot_light: SpotLight { spot_light: SpotLight {
intensity: 400_000_000.0, // lumens intensity: actor::FLASHLIGHT_INTENSITY[prefs.flashlight_power],
color: Color::WHITE, color: Color::WHITE,
shadows_enabled: true, shadows_enabled: true,
inner_angle: PI32 / 8.0 * 0.85, inner_angle: PI32 / 8.0 * 0.85,

View file

@ -88,6 +88,7 @@ pub enum GameEvent {
SetThirdPerson(Turn), SetThirdPerson(Turn),
SetRotationStabilizer(Turn), SetRotationStabilizer(Turn),
SetShadows(Turn), SetShadows(Turn),
UpdateFlashlight,
Achievement(String), Achievement(String),
} }
@ -141,7 +142,7 @@ impl Cycle {
} }
} }
pub fn setup(mut settings: ResMut<Settings>, prefs: Res<var::Preferences>) { pub fn setup(mut settings: ResMut<Settings>, prefs: ResMut<var::Preferences>) {
settings.hud_active = prefs.augmented_reality; settings.hud_active = prefs.augmented_reality;
settings.radio_mode = prefs.radio_station; settings.radio_mode = prefs.radio_station;
settings.set_noise_cancellation_mode(prefs.noise_cancellation_mode); settings.set_noise_cancellation_mode(prefs.noise_cancellation_mode);
@ -159,6 +160,7 @@ pub fn handle_game_event(
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>, mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut q_window: Query<&mut Window, With<PrimaryWindow>>, mut q_window: Query<&mut Window, With<PrimaryWindow>>,
mut q_light: Query<&mut DirectionalLight>, mut q_light: Query<&mut DirectionalLight>,
mut q_flashlight: Query<&mut SpotLight, With<actor::PlayersFlashLight>>,
mut mapcam: ResMut<camera::MapCam>, mut mapcam: ResMut<camera::MapCam>,
mut log: ResMut<hud::Log>, mut log: ResMut<hud::Log>,
opt: Res<var::CommandLineOptions>, opt: Res<var::CommandLineOptions>,
@ -255,6 +257,11 @@ pub fn handle_game_event(
hud::LogLevel::Achievement, hud::LogLevel::Achievement,
); );
} }
GameEvent::UpdateFlashlight => {
for mut spotlight in &mut q_flashlight {
spotlight.intensity = actor::FLASHLIGHT_INTENSITY[prefs.flashlight_power];
}
}
} }
} }
} }

View file

@ -28,8 +28,7 @@ pub const LOG_MAX: usize = LOG_MAX_ROWS;
pub const MAX_CHOICES: usize = 10; pub const MAX_CHOICES: usize = 10;
pub const SPEEDOMETER_WIDTH: f32 = 20.0; pub const SPEEDOMETER_WIDTH: f32 = 20.0;
pub const SPEEDOMETER_HEIGHT: f32 = 10.0; pub const SPEEDOMETER_HEIGHT: f32 = 10.0;
pub const AMBIENT_LIGHT: f32 = 0.0; // Space is DARK pub const AMBIENT_LIGHT: [f32; 4] = [0.0, 20.0, 60.0, 150.0];
pub const AMBIENT_LIGHT_AR: f32 = 20.0;
//pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿']; //pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿'];
//pub const REPLY_NUMBERS: [char; 10] = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩']; //pub const REPLY_NUMBERS: [char; 10] = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩'];
pub const REPLY_NUMBERS: [char; 10] = ['➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉']; pub const REPLY_NUMBERS: [char; 10] = ['➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉'];
@ -39,6 +38,7 @@ pub const DASHBOARD_DEF: &[(Dashboard, &str)] = &[
(Dashboard::Leak, "leak"), (Dashboard::Leak, "leak"),
(Dashboard::RotationStabiliser, "rotation_stabiliser"), (Dashboard::RotationStabiliser, "rotation_stabiliser"),
(Dashboard::CruiseControl, "cruise_control"), (Dashboard::CruiseControl, "cruise_control"),
(Dashboard::Battery, "battery"),
(Dashboard::Radioactivity, "radioactivity"), (Dashboard::Radioactivity, "radioactivity"),
]; ];
@ -143,6 +143,7 @@ pub enum Dashboard {
RotationStabiliser, RotationStabiliser,
CruiseControl, CruiseControl,
Radioactivity, Radioactivity,
Battery,
} }
#[derive(Component, Debug, Copy, Clone)] #[derive(Component, Debug, Copy, Clone)]
@ -682,7 +683,7 @@ fn update_dashboard(
timer: ResMut<FPSUpdateTimer>, timer: ResMut<FPSUpdateTimer>,
mut q_dashboard: Query<(&mut Visibility, &Dashboard)>, mut q_dashboard: Query<(&mut Visibility, &Dashboard)>,
id2pos: Res<game::Id2Pos>, id2pos: Res<game::Id2Pos>,
q_player: Query<(&actor::Suit, &Position), With<actor::Player>>, q_player: Query<(&actor::Suit, &actor::Battery, &Position), With<actor::Player>>,
settings: Res<Settings>, settings: Res<Settings>,
) { ) {
if !settings.hud_active || !timer.0.just_finished() { if !settings.hud_active || !timer.0.just_finished() {
@ -692,12 +693,13 @@ fn update_dashboard(
if player.is_err() { if player.is_err() {
return; return;
} }
let (suit, pos) = player.unwrap(); let (suit, battery, pos) = player.unwrap();
for (mut vis, icon) in &mut q_dashboard { for (mut vis, icon) in &mut q_dashboard {
*vis = bool2vis(match icon { *vis = bool2vis(match icon {
Dashboard::Flashlight => settings.flashlight_active, Dashboard::Flashlight => settings.flashlight_active,
Dashboard::Leak => suit.integrity < 0.5, Dashboard::Leak => suit.integrity < 0.5,
Dashboard::Battery => battery.overloaded_recovering,
Dashboard::RotationStabiliser => !settings.rotation_stabilizer_active, Dashboard::RotationStabiliser => !settings.rotation_stabilizer_active,
Dashboard::CruiseControl => settings.cruise_control_active, Dashboard::CruiseControl => settings.cruise_control_active,
Dashboard::Radioactivity => { Dashboard::Radioactivity => {
@ -1253,6 +1255,7 @@ fn update_overlay_visibility(
mut ambient_light: ResMut<AmbientLight>, mut ambient_light: ResMut<AmbientLight>,
er_target: EventReader<UpdateOverlayVisibility>, er_target: EventReader<UpdateOverlayVisibility>,
settings: Res<Settings>, settings: Res<Settings>,
prefs: Res<Preferences>,
) { ) {
if er_target.is_empty() { if er_target.is_empty() {
return; return;
@ -1280,9 +1283,9 @@ fn update_overlay_visibility(
} }
ambient_light.brightness = if settings.hud_active { ambient_light.brightness = if settings.hud_active {
AMBIENT_LIGHT_AR AMBIENT_LIGHT[prefs.light_amp]
} else { } else {
AMBIENT_LIGHT AMBIENT_LIGHT[0]
}; };
} }

View file

@ -30,7 +30,7 @@ pub mod world;
pub mod prelude { pub mod prelude {
pub use crate::common::*; pub use crate::common::*;
pub use crate::load::load_asset; pub use crate::load::load_asset;
pub use crate::var::Settings; pub use crate::var::{Preferences, Settings};
pub use crate::{ pub use crate::{
actor, audio, camera, chat, cmd, common, game, hud, load, menu, nature, var, visual, world, actor, audio, camera, chat, cmd, common, game, hud, load, menu, nature, var, visual, world,
}; };

View file

@ -66,6 +66,9 @@ pub const MENUDEF: &[(&str, MenuAction)] = &[
("", MenuAction::ToggleMap), ("", MenuAction::ToggleMap),
("", MenuAction::ToggleAR), ("", MenuAction::ToggleAR),
("", MenuAction::ChangeARAvatar), ("", MenuAction::ChangeARAvatar),
("", MenuAction::ModLightAmp),
("", MenuAction::ModFlashlightPower),
("", MenuAction::ModThrusterBoost),
("", MenuAction::ToggleSound), ("", MenuAction::ToggleSound),
("", MenuAction::ToggleMusic), ("", MenuAction::ToggleMusic),
("", MenuAction::ToggleCamera), ("", MenuAction::ToggleCamera),
@ -80,6 +83,9 @@ pub enum MenuAction {
ToggleMap, ToggleMap,
ToggleAR, ToggleAR,
ChangeARAvatar, ChangeARAvatar,
ModLightAmp,
ModFlashlightPower,
ModThrusterBoost,
ToggleSound, ToggleSound,
ToggleMusic, ToggleMusic,
ToggleCamera, ToggleCamera,
@ -428,6 +434,7 @@ pub fn update_menu(
achievement_tracker: Res<var::AchievementTracker>, achievement_tracker: Res<var::AchievementTracker>,
menustate: Res<MenuState>, menustate: Res<MenuState>,
settings: Res<Settings>, settings: Res<Settings>,
prefs: Res<Preferences>,
) { ) {
for mut vis in &mut q_vis { for mut vis in &mut q_vis {
*vis = bool2vis(settings.menu_active); *vis = bool2vis(settings.menu_active);
@ -481,6 +488,23 @@ pub fn update_menu(
let onoff = bool2string(settings.hud_active); let onoff = bool2string(settings.hud_active);
text.sections[i].value = format!("Augmented Reality: {onoff} [TAB]\n"); text.sections[i].value = format!("Augmented Reality: {onoff} [TAB]\n");
} }
MenuAction::ModLightAmp => {
let n = prefs.light_amp;
text.sections[i].value = format!("Light Amplification: {n}/3\n");
}
MenuAction::ModFlashlightPower => {
let n = prefs.flashlight_power + 1;
text.sections[i].value = format!("Flashlight Power: {n}/3\n");
}
MenuAction::ModThrusterBoost => {
let state = match prefs.thruster_boost {
0 => "Only when slowing down",
1 => "MAX POWER",
2 => "Off",
_ => "ERROR",
};
text.sections[i].value = format!("Thruster Boost: {state}\n");
}
MenuAction::ChangeARAvatar => { MenuAction::ChangeARAvatar => {
if let Some(ava) = hud::PLAYER_AR_AVATARS.get(settings.ar_avatar) { if let Some(ava) = hud::PLAYER_AR_AVATARS.get(settings.ar_avatar) {
let avatar_title = ava.3; let avatar_title = ava.3;
@ -516,6 +540,7 @@ pub fn update_menu(
pub fn handle_input( pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<Settings>, mut settings: ResMut<Settings>,
mut prefs: ResMut<Preferences>,
mut menustate: ResMut<MenuState>, mut menustate: ResMut<MenuState>,
mut app_exit_events: ResMut<Events<AppExit>>, mut app_exit_events: ResMut<Events<AppExit>>,
mut ew_game: EventWriter<game::GameEvent>, mut ew_game: EventWriter<game::GameEvent>,
@ -523,6 +548,7 @@ pub fn handle_input(
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updatemenu: EventWriter<UpdateMenuEvent>, mut ew_updatemenu: EventWriter<UpdateMenuEvent>,
mut ew_updateavatar: EventWriter<hud::UpdateAvatarEvent>, mut ew_updateavatar: EventWriter<hud::UpdateAvatarEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
) { ) {
let last_menu_entry = MENUDEF.len() - 1; let last_menu_entry = MENUDEF.len() - 1;
@ -577,6 +603,29 @@ pub fn handle_input(
ew_updateavatar.send(hud::UpdateAvatarEvent); ew_updateavatar.send(hud::UpdateAvatarEvent);
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
} }
MenuAction::ModLightAmp => {
prefs.light_amp += 1;
if prefs.light_amp > 3 {
prefs.light_amp = 0;
}
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
ew_updatemenu.send(UpdateMenuEvent);
}
MenuAction::ModFlashlightPower => {
prefs.flashlight_power += 1;
if prefs.flashlight_power > 2 {
prefs.flashlight_power = 0;
}
ew_game.send(GameEvent::UpdateFlashlight);
ew_updatemenu.send(UpdateMenuEvent);
}
MenuAction::ModThrusterBoost => {
prefs.thruster_boost += 1;
if prefs.thruster_boost > 2 {
prefs.thruster_boost = 0;
}
ew_updatemenu.send(UpdateMenuEvent);
}
MenuAction::ToggleMusic => { MenuAction::ToggleMusic => {
ew_game.send(GameEvent::SetMusic(Next)); ew_game.send(GameEvent::SetMusic(Next));
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="dashboard_battery.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/dashboard_battery.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="1.6542969"
inkscape:cx="107.59858"
inkscape:cy="110.92326"
inkscape:window-width="1440"
inkscape:window-height="820"
inkscape:window-x="0"
inkscape:window-y="60"
inkscape:window-maximized="1"
inkscape:current-layer="g227"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.13048332"
y="-0.15460972"
width="1.2609666"
height="1.3092194"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="2"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="0.99999999999999922"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g227"
style="filter:url(#filter1)"><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1-5"
transform="matrix(0.86486189,0,0,0.86486189,5.6836111,4.3940895)"><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:3.05926;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 6.7381087,16.059295 4.5e-6,40.702599 54.2571138,-2.5e-5 -3e-6,-40.702598 h -7.398697 l -14.797395,2.4e-5 14.797397,8.8e-5 -2e-6,-5.087942 H 41.265364 v 5.08783 l -14.797395,2.4e-5 -14.180835,2.4e-5 14.180837,6.4e-5 -2e-6,-5.087918 H 14.136807 v 5.08783 z"
id="path9"
sodipodi:nodetypes="ccccccccccccccccc" /><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 13.758334,30.691669 H 27.516668"
id="path10"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 20.637501,23.283335 V 38.100002"
id="path11"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 40.216669,30.691669 H 53.975003"
id="path12"
sodipodi:nodetypes="cc" /></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -439,6 +439,7 @@ impl AchievementTracker {
} }
} }
// Used for settings that are preserved across restarts
#[derive(Resource, Serialize, Deserialize, Debug, Default)] #[derive(Resource, Serialize, Deserialize, Debug, Default)]
#[serde(default)] #[serde(default)]
pub struct Preferences { pub struct Preferences {
@ -451,6 +452,9 @@ pub struct Preferences {
pub third_person: bool, pub third_person: bool,
pub shadows_sun: bool, pub shadows_sun: bool,
pub avatar: usize, pub avatar: usize,
pub light_amp: usize, // 0-3
pub flashlight_power: usize, // 0-2
pub thruster_boost: usize, // 0-2
#[serde(skip)] #[serde(skip)]
pub source_file: Option<String>, pub source_file: Option<String>,
@ -564,23 +568,26 @@ pub fn load_prefs() -> Preferences {
}; };
match toml.parse::<DocumentMut>() { match toml.parse::<DocumentMut>() {
Ok(doc) => match toml_edit::de::from_document::<Preferences>(doc) { Ok(doc) => match toml_edit::de::from_document::<Preferences>(doc) {
Ok(mut pref) => { Ok(mut prefs) => {
if let Some(path) = &path { if let Some(path) = &path {
println!("Loaded preference file from {path}"); println!("Loaded preference file from {path}");
} else { } else {
println!("Loaded preferences from internal defaults"); println!("Loaded preferences from internal defaults");
} }
pref.source_file = path; prefs.source_file = path;
return pref; prefs.flashlight_power = prefs.flashlight_power.clamp(0, 2);
prefs.light_amp = prefs.light_amp.clamp(0, 3);
prefs.thruster_boost = prefs.thruster_boost.clamp(0, 2);
prefs
} }
Err(error) => { Err(error) => {
eprintln!("Error: Failed to read preference line: {error}"); eprintln!("Error: Failed to read preference line: {error}");
return Preferences::default(); Preferences::default()
} }
}, },
Err(error) => { Err(error) => {
eprintln!("Error: Failed to open preferences: {error}"); eprintln!("Error: Failed to open preferences: {error}");
return Preferences::default(); Preferences::default()
} }
} }
} }