Compare commits

..

8 commits

7 changed files with 90 additions and 40 deletions

View file

@ -1,13 +1,14 @@
# v0.12.0-dev # v0.12.0-dev
- Add power-hungry optional thruster boost - Implement space suit thruster particle effects
- Implement space suit modding
- Add power-hungry thruster booster
- Add different flashlight power settings - Add different flashlight power settings
- Add different light amplification settings - Add different light amplification settings
- Add reactor overload and shutdown
# v0.11.1 - Add battery damage when overusing thruster booster
- Add radiation damage
- Added space suit thruster particle effects - Fix radio stations
- Fixed radio stations
# v0.11.0 # v0.11.0

View file

@ -115,7 +115,7 @@ A variety of relatively simple game systems should interact with each other to c
- [X] Augmented Reality - [X] Augmented Reality
- [X] Flashlight intensity - [X] Flashlight intensity
- [ ] AI assistance systems - [ ] AI assistance systems
- [ ] Thruster boost - [X] Thruster boost
- [ ] G-force dampeners - [ ] G-force dampeners
- [ ] Life support, material recyclers (air, water, etc) - [ ] Life support, material recyclers (air, water, etc)
- [ ] High energy particle shield? - [ ] High energy particle shield?

View file

@ -21,10 +21,13 @@ 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]; pub const POWER_DRAIN_THRUSTER: [f32; 3] = [3e6, 3e6, 0.0];
const THRUSTER_BOOST_FACTOR: [f64; 3] = [3.0, 3.0, 0.0]; pub const THRUSTER_BOOST_FACTOR: [f64; 3] = [3.0, 3.0, 0.0];
const POWER_DRAIN_FLASHLIGHT: [f32; 3] = [200e3, 1500e3, 2500e3]; pub const POWER_DRAIN_FLASHLIGHT: [f32; 4] = [200e3, 1500e3, 2500e3, 10000e3];
pub const FLASHLIGHT_INTENSITY: [f32; 3] = [10e6, 400e6, 2e9]; // in lumens pub const FLASHLIGHT_INTENSITY: [f32; 4] = [10e6, 400e6, 2e9, 100e9]; // in lumens
pub const POWER_DRAIN_LIGHTAMP: [f32; 4] = [0.0, 200e3, 400e3, 1400e3];
pub const POWER_DRAIN_AR: f32 = 300e3;
pub const POWER_GAIN_REACTOR: [f32; 3] = [0.0, 2000e3, 10000e3];
pub struct ActorPlugin; pub struct ActorPlugin;
impl Plugin for ActorPlugin { impl Plugin for ActorPlugin {
@ -68,7 +71,7 @@ pub enum DamageType {
Asphyxiation, Asphyxiation,
Depressurization, Depressurization,
//Poison, //Poison,
//Radiation, Radiation,
//Freeze, //Freeze,
//Burn, //Burn,
} }
@ -172,6 +175,7 @@ pub struct OrbitsJupiter;
#[derive(Component)] #[derive(Component)]
pub struct LifeForm { pub struct LifeForm {
pub is_alive: bool, pub is_alive: bool,
pub is_radioactively_damaged: bool,
pub adrenaline: f32, pub adrenaline: f32,
pub adrenaline_baseline: f32, pub adrenaline_baseline: f32,
pub adrenaline_jolt: f32, pub adrenaline_jolt: f32,
@ -180,6 +184,7 @@ impl Default for LifeForm {
fn default() -> Self { fn default() -> Self {
Self { Self {
is_alive: true, is_alive: true,
is_radioactively_damaged: false,
adrenaline: 0.3, adrenaline: 0.3,
adrenaline_baseline: 0.3, adrenaline_baseline: 0.3,
adrenaline_jolt: 0.0, adrenaline_jolt: 0.0,
@ -257,7 +262,6 @@ const SUIT_SIMPLE: Suit = Suit {
pub struct Battery { 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 overloaded_recovering: bool, pub overloaded_recovering: bool,
} }
@ -266,7 +270,6 @@ impl Default for Battery {
Self { Self {
power: 10e3 * 3600.0, power: 10e3 * 3600.0,
capacity: 10e3 * 3600.0, // 10kWh capacity: 10e3 * 3600.0, // 10kWh
reactor: 2000e3, // 2MW
overloaded_recovering: false, overloaded_recovering: false,
} }
} }
@ -295,8 +298,8 @@ pub fn update_power(
} }
} }
if settings.hud_active { if settings.hud_active {
let mut hud_drain = 300e3; // 300kW let mut hud_drain = POWER_DRAIN_AR;
hud_drain += prefs.light_amp as f32 * 200e3; // 200kW per level hud_drain += POWER_DRAIN_LIGHTAMP[prefs.light_amp];
battery.power -= hud_drain * d; battery.power -= hud_drain * d;
if battery.power <= 0.0 { if battery.power <= 0.0 {
power_down = true; power_down = true;
@ -327,7 +330,8 @@ pub fn update_power(
if power_down { if power_down {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::PowerDown)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::PowerDown));
} }
battery.power = (battery.power + battery.reactor * d).clamp(0.0, battery.capacity); let reactor = POWER_GAIN_REACTOR[settings.reactor_state];
battery.power = (battery.power + reactor * d).clamp(0.0, battery.capacity);
if battery.overloaded_recovering && battery.power > battery.capacity * 0.5 { if battery.overloaded_recovering && battery.power > battery.capacity * 0.5 {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::PowerUp)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::PowerUp));
battery.overloaded_recovering = false; battery.overloaded_recovering = false;
@ -337,10 +341,12 @@ pub fn update_power(
pub fn update_physics_lifeforms( pub fn update_physics_lifeforms(
time: Res<Time>, time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity)>, settings: Res<Settings>,
id2pos: Res<game::Id2Pos>,
mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity, &Position, Option<&Player>)>,
) { ) {
let d = time.delta_seconds(); let d = time.delta_seconds();
for (mut lifeform, mut hp, mut suit, velocity) in query.iter_mut() { for (mut lifeform, mut hp, mut suit, velocity, pos, player) in query.iter_mut() {
if lifeform.adrenaline_jolt.abs() > 1e-3 { if lifeform.adrenaline_jolt.abs() > 1e-3 {
lifeform.adrenaline_jolt *= 0.99; lifeform.adrenaline_jolt *= 0.99;
} else { } else {
@ -353,6 +359,20 @@ pub fn update_physics_lifeforms(
lifeform.adrenaline = lifeform.adrenaline =
(lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0); (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0);
if player.is_some() {
lifeform.is_radioactively_damaged = if settings.reactor_state == 2 {
true
} else if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) {
pos_jupiter.distance(pos.0) < 140_000_000.0
} else {
false
};
if lifeform.is_radioactively_damaged {
hp.damage += 0.3 * d;
hp.damagetype = DamageType::Radiation;
}
}
let mut oxygen_drain = nature::OXY_S; let mut oxygen_drain = nature::OXY_S;
let integr_threshold = 0.5; let integr_threshold = 0.5;
if suit.integrity < integr_threshold { if suit.integrity < integr_threshold {

View file

@ -331,7 +331,14 @@ fn handle_player_death(
duration: 1.0, duration: 1.0,
}); });
} }
_ => { actor::DamageType::Radiation => {
settings.death_cause = "Acute radiation poisoning".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
actor::DamageType::Unknown => {
settings.death_cause = "Unknown".to_string(); settings.death_cause = "Unknown".to_string();
ew_effect.send(visual::SpawnEffectEvent { ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(css::MAROON.into()), class: visual::Effects::FadeIn(css::MAROON.into()),

View file

@ -682,8 +682,7 @@ pub fn setup(
fn update_dashboard( 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>, q_player: Query<(&actor::Suit, &actor::Battery, &actor::LifeForm), 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() {
@ -693,7 +692,7 @@ fn update_dashboard(
if player.is_err() { if player.is_err() {
return; return;
} }
let (suit, battery, pos) = player.unwrap(); let (suit, battery, lifeform) = 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 {
@ -702,13 +701,7 @@ fn update_dashboard(
Dashboard::Battery => battery.overloaded_recovering, 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 => lifeform.is_radioactively_damaged,
if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) {
pos_jupiter.distance(pos.0) < 140_000_000.0
} else {
false
}
}
}); });
} }
} }

View file

@ -69,6 +69,7 @@ pub const MENUDEF: &[(&str, MenuAction)] = &[
("", MenuAction::ModLightAmp), ("", MenuAction::ModLightAmp),
("", MenuAction::ModFlashlightPower), ("", MenuAction::ModFlashlightPower),
("", MenuAction::ModThrusterBoost), ("", MenuAction::ModThrusterBoost),
("", MenuAction::ModReactor),
("", MenuAction::ToggleSound), ("", MenuAction::ToggleSound),
("", MenuAction::ToggleMusic), ("", MenuAction::ToggleMusic),
("", MenuAction::ToggleCamera), ("", MenuAction::ToggleCamera),
@ -86,6 +87,7 @@ pub enum MenuAction {
ModLightAmp, ModLightAmp,
ModFlashlightPower, ModFlashlightPower,
ModThrusterBoost, ModThrusterBoost,
ModReactor,
ToggleSound, ToggleSound,
ToggleMusic, ToggleMusic,
ToggleCamera, ToggleCamera,
@ -486,24 +488,39 @@ pub fn update_menu(
} }
MenuAction::ToggleAR => { MenuAction::ToggleAR => {
let onoff = bool2string(settings.hud_active); let onoff = bool2string(settings.hud_active);
text.sections[i].value = format!("Augmented Reality: {onoff} [TAB]\n"); let p = if settings.hud_active { actor::POWER_DRAIN_AR / 1e3 } else { 0.0 };
let kw = if p > 0.0 { format!(" ({p}kW)") } else { String::from("") };
text.sections[i].value = format!("Augmented Reality: {onoff}{kw} [TAB]\n");
} }
MenuAction::ModLightAmp => { MenuAction::ModLightAmp => {
let n = prefs.light_amp; let p = actor::POWER_DRAIN_LIGHTAMP[prefs.light_amp] / 1e3;
text.sections[i].value = format!("Light Amplification: {n}/3\n"); text.sections[i].value = format!("Light Amplification: {p}kW\n");
} }
MenuAction::ModFlashlightPower => { MenuAction::ModFlashlightPower => {
let n = prefs.flashlight_power + 1; let p = actor::POWER_DRAIN_FLASHLIGHT[prefs.flashlight_power] / 1e3;
text.sections[i].value = format!("Flashlight Power: {n}/3\n"); text.sections[i].value = format!("Flashlight Power: {p}kW\n");
} }
MenuAction::ModThrusterBoost => { MenuAction::ModThrusterBoost => {
let state = match prefs.thruster_boost { let state = match prefs.thruster_boost {
0 => "Only when slowing down", 0 => "For braking",
1 => "MAX POWER", 1 => "Always",
2 => "Off", 2 => "Off",
_ => "ERROR", _ => "ERROR",
}; };
text.sections[i].value = format!("Thruster Boost: {state}\n"); let p = actor::POWER_DRAIN_THRUSTER[prefs.thruster_boost] / 1e3;
let kw = if p > 0.0 { format!(" ({p}kW)") } else { String::from("") };
text.sections[i].value = format!("Thruster Boost: {state}{kw}\n");
}
MenuAction::ModReactor => {
let state = match settings.reactor_state {
0 => "Off",
1 => "On",
2 => "OVERLOAD",
_ => "ERROR",
};
let p = actor::POWER_GAIN_REACTOR[settings.reactor_state] / 1e3;
let kw = if p > 0.0 { format!(" (+{p}kW)") } else { String::from("") };
text.sections[i].value = format!("Reactor: {state}{kw}\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) {
@ -608,14 +625,16 @@ pub fn handle_input(
if prefs.light_amp > 3 { if prefs.light_amp > 3 {
prefs.light_amp = 0; prefs.light_amp = 0;
} }
prefs.save();
ew_updateoverlays.send(hud::UpdateOverlayVisibility); ew_updateoverlays.send(hud::UpdateOverlayVisibility);
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
} }
MenuAction::ModFlashlightPower => { MenuAction::ModFlashlightPower => {
prefs.flashlight_power += 1; prefs.flashlight_power += 1;
if prefs.flashlight_power > 2 { if prefs.flashlight_power > 3 {
prefs.flashlight_power = 0; prefs.flashlight_power = 0;
} }
prefs.save();
ew_game.send(GameEvent::UpdateFlashlight); ew_game.send(GameEvent::UpdateFlashlight);
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
} }
@ -624,6 +643,14 @@ pub fn handle_input(
if prefs.thruster_boost > 2 { if prefs.thruster_boost > 2 {
prefs.thruster_boost = 0; prefs.thruster_boost = 0;
} }
prefs.save();
ew_updatemenu.send(UpdateMenuEvent);
}
MenuAction::ModReactor => {
settings.reactor_state += 1;
if settings.reactor_state > 2 {
settings.reactor_state = 0;
}
ew_updatemenu.send(UpdateMenuEvent); ew_updatemenu.send(UpdateMenuEvent);
} }
MenuAction::ToggleMusic => { MenuAction::ToggleMusic => {

View file

@ -86,6 +86,7 @@ pub struct Settings {
pub chat_speed: f32, pub chat_speed: f32,
pub ar_avatar: usize, pub ar_avatar: usize,
pub flashlight_active: bool, pub flashlight_active: bool,
pub reactor_state: usize,
pub hud_active: bool, pub hud_active: bool,
pub map_active: bool, pub map_active: bool,
pub deathscreen_active: bool, pub deathscreen_active: bool,
@ -222,6 +223,7 @@ impl Default for Settings {
chat_speed: DEFAULT_CHAT_SPEED, chat_speed: DEFAULT_CHAT_SPEED,
ar_avatar: 0, ar_avatar: 0,
flashlight_active: false, flashlight_active: false,
reactor_state: 1,
hud_active: true, hud_active: true,
map_active: false, map_active: false,
deathscreen_active: false, deathscreen_active: false,
@ -575,7 +577,7 @@ pub fn load_prefs() -> Preferences {
println!("Loaded preferences from internal defaults"); println!("Loaded preferences from internal defaults");
} }
prefs.source_file = path; prefs.source_file = path;
prefs.flashlight_power = prefs.flashlight_power.clamp(0, 2); prefs.flashlight_power = prefs.flashlight_power.clamp(0, 3);
prefs.light_amp = prefs.light_amp.clamp(0, 3); prefs.light_amp = prefs.light_amp.clamp(0, 3);
prefs.thruster_boost = prefs.thruster_boost.clamp(0, 2); prefs.thruster_boost = prefs.thruster_boost.clamp(0, 2);
prefs prefs