outfly/src/game.rs

474 lines
17 KiB
Rust
Raw Normal View History

2024-05-12 21:57:21 +00:00
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module handles player input, and coordinates interplay between other modules
use crate::prelude::*;
2024-05-12 22:10:13 +00:00
use bevy::pbr::ExtendedMaterial;
2024-05-22 02:57:20 +00:00
use bevy::prelude::*;
2024-05-12 21:57:21 +00:00
use bevy::scene::SceneInstance;
2024-05-22 02:57:20 +00:00
use bevy::window::{PrimaryWindow, Window, WindowMode};
2024-05-12 21:57:21 +00:00
use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap;
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
2024-05-13 18:21:56 +00:00
app.add_systems(Update, handle_cheats.run_if(in_control));
2024-05-12 22:10:13 +00:00
app.add_systems(Update, debug);
2024-05-13 21:50:53 +00:00
app.add_systems(PostUpdate, handle_game_event);
2024-05-12 21:57:21 +00:00
app.add_systems(PreUpdate, handle_player_death);
app.add_systems(PostUpdate, update_id2pos);
2024-05-22 02:57:20 +00:00
app.add_systems(
Update,
handle_achievement_event.run_if(on_event::<AchievementEvent>()),
);
2024-05-14 02:35:45 +00:00
app.add_systems(Update, check_achievements);
2024-05-12 21:57:21 +00:00
app.insert_resource(Id2Pos(HashMap::new()));
2024-05-13 23:24:57 +00:00
app.insert_resource(var::AchievementTracker::default());
2024-05-22 02:57:20 +00:00
app.insert_resource(AchievementCheckTimer(Timer::from_seconds(
1.0,
TimerMode::Repeating,
)));
2024-05-12 21:57:21 +00:00
app.add_event::<PlayerDiesEvent>();
2024-05-13 18:21:56 +00:00
app.add_event::<GameEvent>();
2024-05-13 23:24:57 +00:00
app.add_event::<AchievementEvent>();
2024-05-12 21:57:21 +00:00
}
}
2024-05-22 02:57:20 +00:00
#[derive(Event)]
pub struct PlayerDiesEvent(pub actor::DamageType);
#[derive(Resource)]
pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Resource)]
pub struct AchievementCheckTimer(pub Timer);
2024-05-12 21:57:21 +00:00
2024-05-13 23:24:57 +00:00
#[derive(Event)]
pub enum AchievementEvent {
RepairSuit,
TalkTo(String),
RideVehicle(String),
DrinkPizza,
InJupitersShadow,
2024-05-14 03:33:35 +00:00
FindEarth,
2024-05-13 23:24:57 +00:00
}
2024-05-13 18:21:56 +00:00
#[derive(Event)]
pub enum GameEvent {
SetAR(Turn),
SetMusic(Turn),
SetSound(Turn),
SetMap(Turn),
SetFullscreen(Turn),
2024-05-13 18:53:08 +00:00
SetMenu(Turn),
2024-05-13 19:11:27 +00:00
SetThirdPerson(Turn),
SetRotationStabilizer(Turn),
SetShadows(Turn),
2024-05-13 23:24:57 +00:00
Achievement(String),
2024-05-13 18:21:56 +00:00
}
pub enum Turn {
On,
Off,
Toggle,
}
impl Turn {
pub fn to_bool(&self, current_state: bool) -> bool {
match self {
Turn::On => true,
Turn::Off => false,
Turn::Toggle => !current_state,
}
}
}
pub fn handle_game_event(
2024-05-13 18:21:56 +00:00
mut settings: ResMut<Settings>,
mut er_game: EventReader<GameEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
2024-05-13 19:37:57 +00:00
mut ew_updatemenu: EventWriter<menu::UpdateMenuEvent>,
2024-05-13 18:21:56 +00:00
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut q_window: Query<&mut Window, With<PrimaryWindow>>,
2024-05-13 19:11:27 +00:00
mut q_light: Query<&mut DirectionalLight>,
2024-05-13 18:21:56 +00:00
mut mapcam: ResMut<camera::MapCam>,
2024-05-13 23:24:57 +00:00
mut log: ResMut<hud::Log>,
2024-05-13 18:21:56 +00:00
opt: Res<var::CommandLineOptions>,
) {
for event in er_game.read() {
match event {
GameEvent::SetAR(turn) => {
settings.hud_active = turn.to_bool(settings.hud_active);
ew_togglemusic.send(audio::ToggleMusicEvent());
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
GameEvent::SetMusic(turn) => {
// TODO invert "mute_music" to "music_active"
settings.mute_music = turn.to_bool(settings.mute_music);
ew_togglemusic.send(audio::ToggleMusicEvent());
}
GameEvent::SetSound(turn) => {
// TODO invert "mute_sfx" to "sfx_active"
settings.mute_sfx = turn.to_bool(settings.mute_sfx);
ew_togglemusic.send(audio::ToggleMusicEvent());
}
GameEvent::SetMap(turn) => {
settings.map_active = turn.to_bool(settings.map_active);
if settings.map_active {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Woosh));
}
*mapcam = camera::MapCam::default();
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
GameEvent::SetFullscreen(turn) => {
for mut window in &mut q_window {
let current_state = window.mode != WindowMode::Windowed;
window.mode = match turn.to_bool(current_state) {
true => opt.window_mode_fullscreen,
2024-05-22 02:57:20 +00:00
false => WindowMode::Windowed,
2024-05-13 18:21:56 +00:00
};
}
}
2024-05-13 18:53:08 +00:00
GameEvent::SetMenu(turn) => {
settings.menu_active = turn.to_bool(settings.menu_active);
2024-05-13 19:37:57 +00:00
ew_updatemenu.send(menu::UpdateMenuEvent);
2024-05-13 18:53:08 +00:00
}
2024-05-13 19:11:27 +00:00
GameEvent::SetThirdPerson(turn) => {
settings.third_person = turn.to_bool(settings.third_person);
}
GameEvent::SetRotationStabilizer(turn) => {
2024-05-22 02:57:20 +00:00
settings.rotation_stabilizer_active =
turn.to_bool(settings.rotation_stabilizer_active);
2024-05-13 19:11:27 +00:00
}
GameEvent::SetShadows(turn) => {
settings.shadows_sun = turn.to_bool(settings.shadows_sun);
for mut light in &mut q_light {
light.shadows_enabled = settings.shadows_sun;
}
}
2024-05-13 23:24:57 +00:00
GameEvent::Achievement(name) => {
2024-05-14 04:37:00 +00:00
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Achieve));
2024-05-22 02:57:20 +00:00
log.add(
format!("Achievement accomplished: {name}!"),
"".to_string(),
hud::LogLevel::Achievement,
);
2024-05-13 23:24:57 +00:00
}
2024-05-13 18:21:56 +00:00
}
}
}
2024-05-12 21:57:21 +00:00
fn handle_player_death(
mut cmd: Commands,
mut er_playerdies: EventReader<PlayerDiesEvent>,
q_scenes: Query<(Entity, &SceneInstance), With<world::DespawnOnPlayerDeath>>,
q_noscenes: Query<Entity, (With<world::DespawnOnPlayerDeath>, Without<SceneInstance>)>,
mut scene_spawner: ResMut<SceneSpawner>,
mut active_asteroids: ResMut<world::ActiveAsteroids>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
mut ew_deathscreen: EventWriter<menu::DeathScreenEvent>,
mut log: ResMut<hud::Log>,
mut settings: ResMut<Settings>,
) {
for death in er_playerdies.read() {
if settings.god_mode {
return;
}
settings.reset_player_settings();
active_asteroids.0.clear();
for entity in &q_noscenes {
cmd.entity(entity).despawn();
}
for (entity, sceneinstance) in &q_scenes {
cmd.entity(entity).despawn();
scene_spawner.despawn_instance(**sceneinstance);
}
log.clear();
match death.0 {
actor::DamageType::Depressurization => {
settings.death_cause = "Depressurization".to_string();
2024-05-13 18:53:08 +00:00
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
2024-05-12 21:57:21 +00:00
actor::DamageType::Mental => {
settings.death_cause = "Brain Damage".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
actor::DamageType::Asphyxiation => {
settings.death_cause = "Suffocation".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 1.0,
});
}
actor::DamageType::Trauma => {
settings.death_cause = "Trauma".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
actor::DamageType::GForce => {
settings.death_cause = "Trauma from excessive g forces".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
_ => {
settings.death_cause = "Unknown".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
}
ew_deathscreen.send(menu::DeathScreenEvent::Show);
return;
}
}
fn handle_cheats(
key_input: Res<ButtonInput<KeyCode>>,
2024-05-22 02:57:20 +00:00
mut q_player: Query<
(&Transform, &mut Position, &mut LinearVelocity),
With<actor::PlayerCamera>,
>,
2024-05-12 21:57:21 +00:00
mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With<actor::Player>>,
2024-05-22 02:57:20 +00:00
q_target: Query<
(&Transform, &Position, Option<&LinearVelocity>),
(With<hud::IsTargeted>, Without<actor::PlayerCamera>),
>,
2024-05-12 21:57:21 +00:00
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
mut settings: ResMut<Settings>,
id2pos: Res<Id2Pos>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if q_player.is_empty() || q_life.is_empty() {
return;
}
let (trans, mut pos, mut v) = q_player.get_single_mut().unwrap();
let (mut lifeform, mut gforce) = q_life.get_single_mut().unwrap();
let boost = if key_input.pressed(KeyCode::ShiftLeft) {
1e6
} else {
1e3
};
if key_input.just_pressed(settings.key_cheat_god_mode) {
settings.god_mode ^= true;
if settings.god_mode {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
}
}
if !settings.god_mode && !settings.dev_mode {
return;
}
if key_input.just_pressed(settings.key_cheat_stop) {
gforce.ignore_gforce_seconds = 1.0;
v.0 = DVec3::ZERO;
}
2024-05-22 02:57:20 +00:00
if key_input.pressed(settings.key_cheat_speed)
|| key_input.pressed(settings.key_cheat_speed_backward)
{
2024-05-12 21:57:21 +00:00
gforce.ignore_gforce_seconds = 1.0;
2024-05-22 02:57:20 +00:00
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();
let avg_speed = (current_speed + next_speed) / 2.0;
let inv_lorentz = nature::inverse_lorentz_factor(avg_speed.clamp(0.0, nature::C));
v.0 = v.0 + inv_lorentz * dv;
2024-05-12 21:57:21 +00:00
}
if key_input.just_pressed(settings.key_cheat_teleport) {
if let Ok((transform, target_pos, target_v)) = q_target.get_single() {
2024-05-22 02:57:20 +00:00
let offset: DVec3 =
4.0 * (**pos - **target_pos).normalize() * transform.scale.as_dvec3();
2024-05-12 21:57:21 +00:00
pos.0 = **target_pos + offset;
if let Some(target_v) = target_v {
*v = target_v.clone();
}
}
}
if !settings.dev_mode {
return;
}
if key_input.just_pressed(settings.key_cheat_pizza) {
if let Some(target) = id2pos.0.get(&"pizzeria".to_string()) {
pos.0 = *target + DVec3::new(-60.0, 0.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.just_pressed(settings.key_cheat_farview1) {
if let Some(target) = id2pos.0.get(&"busstopclippy2".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.just_pressed(settings.key_cheat_farview2) {
if let Some(target) = id2pos.0.get(&"busstopclippy3".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.pressed(settings.key_cheat_adrenaline_zero) {
lifeform.adrenaline = 0.0;
}
if key_input.pressed(settings.key_cheat_adrenaline_mid) {
lifeform.adrenaline = 0.5;
}
if key_input.pressed(settings.key_cheat_adrenaline_max) {
lifeform.adrenaline = 1.0;
}
if key_input.just_pressed(settings.key_cheat_die) {
settings.god_mode = false;
ew_playerdies.send(PlayerDiesEvent(actor::DamageType::Trauma));
}
}
2024-05-22 02:57:20 +00:00
fn update_id2pos(mut id2pos: ResMut<Id2Pos>, q_id: Query<(&Position, &actor::Identifier)>) {
2024-05-12 21:57:21 +00:00
id2pos.0.clear();
for (pos, id) in &q_id {
id2pos.0.insert(id.0.clone(), pos.0);
}
}
2024-05-12 22:10:13 +00:00
fn debug(
settings: Res<var::Settings>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
2024-05-22 02:57:20 +00:00
mut extended_materials: ResMut<
Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>,
>,
2024-05-14 04:28:14 +00:00
mut achievement_tracker: ResMut<var::AchievementTracker>,
2024-05-12 22:10:13 +00:00
materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
) {
2024-05-13 23:24:57 +00:00
if settings.dev_mode && keyboard_input.just_pressed(KeyCode::KeyP) {
2024-05-12 22:10:13 +00:00
for (entity, _name, mesh) in &materials {
dbg!(mesh);
let mut entity = commands.entity(entity);
entity.remove::<Handle<StandardMaterial>>();
let material = extended_materials.add(load::AsteroidSurface::material());
entity.insert(material);
}
}
2024-05-13 23:24:57 +00:00
if settings.dev_mode && keyboard_input.just_pressed(KeyCode::KeyN) {
2024-05-14 04:28:14 +00:00
achievement_tracker.achieve_all();
2024-05-13 23:24:57 +00:00
}
}
fn handle_achievement_event(
mut er_achievement: EventReader<AchievementEvent>,
mut ew_game: EventWriter<GameEvent>,
mut tracker: ResMut<var::AchievementTracker>,
) {
for event in er_achievement.read() {
match event {
AchievementEvent::RepairSuit => {
if !tracker.repair_suit {
ew_game.send(GameEvent::Achievement("Repair Your Suit".into()));
}
tracker.repair_suit = true;
}
AchievementEvent::InJupitersShadow => {
if !tracker.in_jupiters_shadow {
2024-05-22 02:57:20 +00:00
ew_game.send(GameEvent::Achievement(
"Eclipse the sun with Jupiter".into(),
));
2024-05-13 23:24:57 +00:00
}
tracker.in_jupiters_shadow = true;
}
AchievementEvent::DrinkPizza => {
if !tracker.drink_a_pizza {
2024-05-14 17:17:40 +00:00
ew_game.send(GameEvent::Achievement("Enjoy A Pizza".into()));
2024-05-13 23:24:57 +00:00
}
tracker.drink_a_pizza = true;
}
2024-05-14 03:33:35 +00:00
AchievementEvent::FindEarth => {
if !tracker.find_earth {
ew_game.send(GameEvent::Achievement("Find Earth".into()));
}
tracker.find_earth = true;
}
2024-05-13 23:24:57 +00:00
AchievementEvent::RideVehicle(name) => {
tracker.vehicles_ridden.insert(name.clone());
let len = tracker.vehicles_ridden.len();
let total = tracker.all_vehicles.len();
if !tracker.ride_every_vehicle && len == total {
tracker.ride_every_vehicle = true;
2024-05-14 17:17:40 +00:00
ew_game.send(GameEvent::Achievement("Ride Every Vehicle".into()));
2024-05-13 23:24:57 +00:00
}
}
AchievementEvent::TalkTo(name) => {
tracker.people_talked_to.insert(name.clone());
let len = tracker.people_talked_to.len();
let total = tracker.all_people.len();
if !tracker.talk_to_everyone && len == total {
tracker.talk_to_everyone = true;
ew_game.send(GameEvent::Achievement("Talk To Everyone".into()));
}
}
}
}
2024-05-12 22:10:13 +00:00
}
2024-05-14 02:35:45 +00:00
fn check_achievements(
time: Res<Time>,
q_player: Query<&Position, With<actor::PlayerCamera>>,
id2pos: Res<Id2Pos>,
mut ew_achievement: EventWriter<AchievementEvent>,
mut timer: ResMut<AchievementCheckTimer>,
) {
2024-05-22 02:57:20 +00:00
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;
};
2024-05-14 02:35:45 +00:00
2024-05-22 02:57:20 +00:00
let shadowed = in_shadow(
*pos_sun,
nature::SOL_RADIUS,
*pos_jupiter,
nature::JUPITER_RADIUS,
**pos_player,
);
2024-05-14 02:35:45 +00:00
if shadowed {
ew_achievement.send(AchievementEvent::InJupitersShadow);
}
}