308 lines
9.2 KiB
Rust
308 lines
9.2 KiB
Rust
// ▄████████▄ + ███ + ▄█████████ ███ +
|
|
// ███▀ ▀███ + + ███ ███▀ + ███ + +
|
|
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
|
|
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
|
|
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
|
|
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
|
|
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
|
|
// + + + ███
|
|
// + ▀████████████████████████████████████████████████████▀
|
|
//
|
|
// This module manages sound effects and music.
|
|
|
|
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,
|
|
(
|
|
play_zoom_sfx,
|
|
play_gasp_sfx,
|
|
respawn_sinks.run_if(on_event::<RespawnSinksEvent>()),
|
|
pause_all.run_if(on_event::<PauseAllSfxEvent>()),
|
|
),
|
|
);
|
|
app.add_systems(
|
|
PostUpdate,
|
|
(
|
|
play_sfx,
|
|
toggle_music.run_if(on_event::<ToggleMusicEvent>()),
|
|
),
|
|
);
|
|
app.add_event::<PlaySfxEvent>();
|
|
app.add_event::<PauseAllSfxEvent>();
|
|
app.add_event::<ToggleMusicEvent>();
|
|
app.add_event::<RespawnSinksEvent>();
|
|
app.insert_resource(ZoomTimer(Timer::from_seconds(0.09, TimerMode::Repeating)));
|
|
}
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
pub struct ZoomTimer(Timer);
|
|
|
|
const PATHS: &[(SfxType, Sfx, &str)] = &[
|
|
(
|
|
SfxType::BGM,
|
|
Sfx::BGM,
|
|
"music/Aleksey Chistilin - Cinematic Cello.ogg",
|
|
),
|
|
(
|
|
SfxType::BGMNoAR,
|
|
Sfx::BGMActualJupiterRecording,
|
|
"music/JupiterRecording.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::LoopSfx, Sfx::Gasp, "sounds/gasp.ogg"),
|
|
(SfxType::OneOff, Sfx::GaspRelief, "sounds/gasprelief.ogg"),
|
|
(SfxType::OneOff, Sfx::Achieve, "sounds/achieve.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::Ping, "sounds/connect.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"),
|
|
];
|
|
|
|
#[derive(Component, PartialEq, Hash, Eq, Copy, Clone)]
|
|
pub enum Sfx {
|
|
Achieve,
|
|
BGM,
|
|
BGMActualJupiterRecording,
|
|
Click,
|
|
Connect,
|
|
Crash,
|
|
ElectricMotor,
|
|
EnterVehicle,
|
|
Gasp,
|
|
GaspRelief,
|
|
IncomingChatMessage,
|
|
Ion,
|
|
Ping,
|
|
Rocket,
|
|
Switch,
|
|
Thruster,
|
|
WakeUp,
|
|
Woosh,
|
|
Zoom,
|
|
}
|
|
|
|
pub fn str2sfx(sfx_label: &str) -> Sfx {
|
|
return match sfx_label {
|
|
"achieve" => Sfx::Achieve,
|
|
"switch" => Sfx::Switch,
|
|
"click" => Sfx::Click,
|
|
"woosh" => Sfx::Woosh,
|
|
"zoom" => Sfx::Zoom,
|
|
"chat" => Sfx::IncomingChatMessage,
|
|
"ping" => Sfx::Ping,
|
|
"connect" => Sfx::Connect,
|
|
"entervehicle" => Sfx::EnterVehicle,
|
|
"crash" => Sfx::Ping,
|
|
_ => Sfx::Click,
|
|
};
|
|
}
|
|
|
|
pub enum SfxType {
|
|
BGM,
|
|
BGMNoAR,
|
|
LoopSfx,
|
|
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<Sfx, Handle<AudioSource>>);
|
|
|
|
pub fn setup(
|
|
mut commands: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
mut ew_respawnsinks: EventWriter<RespawnSinksEvent>,
|
|
) {
|
|
let mut map = HashMap::new();
|
|
for (_, sfx, path) in PATHS {
|
|
let source = asset_server.load(*path);
|
|
map.insert(*sfx, source.clone());
|
|
}
|
|
commands.insert_resource(Sounds(map));
|
|
ew_respawnsinks.send(RespawnSinksEvent);
|
|
}
|
|
|
|
pub fn respawn_sinks(
|
|
mut commands: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
settings: Res<var::Settings>,
|
|
q_audiosinks: Query<Entity, (With<AudioSink>, With<Sfx>)>,
|
|
) {
|
|
for sink in &q_audiosinks {
|
|
commands.entity(sink).despawn();
|
|
}
|
|
for (sfxtype, sfx, path) in PATHS {
|
|
let source = asset_server.load(*path);
|
|
match sfxtype {
|
|
SfxType::BGM => {
|
|
commands.spawn((
|
|
*sfx,
|
|
AudioBundle {
|
|
source,
|
|
settings: PlaybackSettings {
|
|
mode: PlaybackMode::Loop,
|
|
paused: settings.mute_music || !settings.hud_active,
|
|
..default()
|
|
},
|
|
},
|
|
));
|
|
}
|
|
SfxType::BGMNoAR => {
|
|
commands.spawn((
|
|
*sfx,
|
|
AudioBundle {
|
|
source,
|
|
settings: PlaybackSettings {
|
|
mode: PlaybackMode::Loop,
|
|
paused: settings.mute_music || settings.hud_active,
|
|
..default()
|
|
},
|
|
},
|
|
));
|
|
}
|
|
SfxType::LoopSfx => {
|
|
commands.spawn((
|
|
*sfx,
|
|
AudioBundle {
|
|
source,
|
|
settings: PlaybackSettings {
|
|
mode: PlaybackMode::Loop,
|
|
volume: Volume::new(0.0),
|
|
paused: true,
|
|
..default()
|
|
},
|
|
},
|
|
));
|
|
}
|
|
SfxType::OneOff => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn play_sfx(
|
|
mut commands: Commands,
|
|
settings: Res<var::Settings>,
|
|
mut events_sfx: EventReader<PlaySfxEvent>,
|
|
sounds: Res<Sounds>,
|
|
) {
|
|
if settings.mute_sfx && !events_sfx.is_empty() {
|
|
events_sfx.clear();
|
|
}
|
|
for sfx in events_sfx.read() {
|
|
if let Some(source) = sounds.0.get(&sfx.0) {
|
|
commands.spawn(AudioBundle {
|
|
source: source.clone(),
|
|
settings: PlaybackSettings::DESPAWN,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn toggle_music(q_audiosinks: Query<(&AudioSink, &Sfx)>, settings: Res<var::Settings>) {
|
|
for (bgm_sink, sfx) in &q_audiosinks {
|
|
let play = match *sfx {
|
|
Sfx::BGM => settings.hud_active,
|
|
Sfx::BGMActualJupiterRecording => !settings.hud_active,
|
|
_ => {
|
|
continue;
|
|
}
|
|
};
|
|
if settings.mute_music || !play {
|
|
bgm_sink.pause();
|
|
} else {
|
|
bgm_sink.play();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn play_zoom_sfx(
|
|
time: Res<Time>,
|
|
mut timer: ResMut<ZoomTimer>,
|
|
mut last_zoom_level: Local<f64>,
|
|
mapcam: Res<camera::MapCam>,
|
|
mut ew_sfx: EventWriter<PlaySfxEvent>,
|
|
settings: Res<var::Settings>,
|
|
) {
|
|
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 *last_zoom_level != 0.0 && mapcam.target_zoom_level != camera::INITIAL_ZOOM_LEVEL {
|
|
ew_sfx.send(PlaySfxEvent(Sfx::Zoom));
|
|
}
|
|
*last_zoom_level = mapcam.target_zoom_level;
|
|
}
|
|
}
|
|
|
|
pub fn play_gasp_sfx(
|
|
player: Query<&actor::Suit, With<actor::Player>>,
|
|
mut ew_sfx: EventWriter<PlaySfxEvent>,
|
|
q_audiosinks: Query<(&audio::Sfx, &AudioSink)>,
|
|
) {
|
|
if let Ok(suit) = player.get_single() {
|
|
for (sfxtype, sink) in &q_audiosinks {
|
|
if *sfxtype != Sfx::Gasp {
|
|
continue;
|
|
}
|
|
if suit.oxygen <= 0.0 {
|
|
sink.set_volume(0.6);
|
|
sink.play();
|
|
} else {
|
|
if !sink.is_paused() {
|
|
ew_sfx.send(PlaySfxEvent(Sfx::GaspRelief));
|
|
sink.pause();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn pause_all(q_audiosinks: Query<&AudioSink, With<Sfx>>) {
|
|
for sink in &q_audiosinks {
|
|
sink.pause();
|
|
}
|
|
}
|