// ▄████████▄ + ███ + ▄█████████ ███ + // ███▀ ▀███ + + ███ ███▀ + ███ + + // ███ + ███ ███ ███ █████████ ███ ███ ███ ███ // ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███ // ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███ // ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███ // ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████ // + + + ███ // + ▀████████████████████████████████████████████████████▀ // // This module handles player input, and coordinates interplay between other modules use crate::prelude::*; use bevy::prelude::*; use bevy::pbr::ExtendedMaterial; use bevy::scene::SceneInstance; use bevy::window::{Window, WindowMode, PrimaryWindow}; use bevy_xpbd_3d::prelude::*; use std::collections::HashMap; pub struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_systems(Update, handle_cheats.run_if(in_control)); app.add_systems(Update, debug); app.add_systems(Update, handle_game_event); app.add_systems(PreUpdate, handle_player_death); app.add_systems(PostUpdate, update_id2pos); app.insert_resource(Id2Pos(HashMap::new())); app.add_event::(); app.add_event::(); } } #[derive(Event)] pub struct PlayerDiesEvent(pub actor::DamageType); #[derive(Resource)] pub struct Id2Pos(pub HashMap); #[derive(Event)] pub enum GameEvent { SetAR(Turn), SetMusic(Turn), SetSound(Turn), SetMap(Turn), SetFullscreen(Turn), SetMenu(Turn), } 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, } } } fn handle_game_event( mut settings: ResMut, mut er_game: EventReader, mut ew_sfx: EventWriter, mut ew_updateoverlays: EventWriter, mut ew_togglemusic: EventWriter, mut q_window: Query<&mut Window, With>, mut q_menu_vis: Query<&mut Visibility, With>, mut mapcam: ResMut, opt: Res, ) { 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, false => WindowMode::Windowed }; } } GameEvent::SetMenu(turn) => { settings.menu_active = turn.to_bool(settings.menu_active); for mut vis in &mut q_menu_vis { *vis = bool2vis(settings.menu_active); } } } } } fn handle_player_death( mut cmd: Commands, mut er_playerdies: EventReader, q_scenes: Query<(Entity, &SceneInstance), With>, q_noscenes: Query, Without)>, mut scene_spawner: ResMut, mut active_asteroids: ResMut, mut ew_effect: EventWriter, mut ew_deathscreen: EventWriter, mut log: ResMut, mut settings: ResMut, ) { 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::DivineIntervention => { settings.death_cause = "Divine Intervention".to_string(); ew_effect.send(visual::SpawnEffectEvent { class: visual::Effects::FadeIn(Color::BLACK), duration: 4.0, }); } 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>, 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)>, mut ew_playerdies: EventWriter, mut settings: ResMut, id2pos: Res, mut ew_sfx: EventWriter, ) { 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; } 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 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; } 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(); 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)); } } 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); } } fn debug( settings: Res, keyboard_input: Res>, mut commands: Commands, mut extended_materials: ResMut>>, materials: Query<(Entity, Option<&Name>, &Handle)>, ) { if settings.dev_mode && keyboard_input.pressed(KeyCode::KeyP) { for (entity, _name, mesh) in &materials { dbg!(mesh); let mut entity = commands.entity(entity); entity.remove::>(); let material = extended_materials.add(load::AsteroidSurface::material()); entity.insert(material); } } }