diff --git a/data/netplay_desync.wav b/data/netplay_desync.wav new file mode 100644 index 0000000..dd461d3 Binary files /dev/null and b/data/netplay_desync.wav differ diff --git a/data/netplay_lost_connection.wav b/data/netplay_lost_connection.wav new file mode 100644 index 0000000..903219a Binary files /dev/null and b/data/netplay_lost_connection.wav differ diff --git a/data/netplay_p1_disconnected.wav b/data/netplay_p1_disconnected.wav new file mode 100644 index 0000000..96f512d Binary files /dev/null and b/data/netplay_p1_disconnected.wav differ diff --git a/data/netplay_p2_disconnected.wav b/data/netplay_p2_disconnected.wav new file mode 100644 index 0000000..0a9690e Binary files /dev/null and b/data/netplay_p2_disconnected.wav differ diff --git a/data/netplay_p3_disconnected.wav b/data/netplay_p3_disconnected.wav new file mode 100644 index 0000000..6ef2b3b Binary files /dev/null and b/data/netplay_p3_disconnected.wav differ diff --git a/data/netplay_p4_disconnected.wav b/data/netplay_p4_disconnected.wav new file mode 100644 index 0000000..cccd18c Binary files /dev/null and b/data/netplay_p4_disconnected.wav differ diff --git a/src/netplay.rs b/src/netplay.rs index 4272e99..c40514a 100644 --- a/src/netplay.rs +++ b/src/netplay.rs @@ -1,5 +1,4 @@ use crate::{device, ui}; -use eframe::egui; use std::io::{Read, Write}; //UDP packet formats const UDP_SEND_KEY_INFO: u8 = 0; @@ -29,8 +28,6 @@ pub struct Netplay { status: u8, buffer_target: u8, pub fast_forward: bool, - pub error_notifier: tokio::sync::mpsc::Sender, - pub gui_ctx: egui::Context, } pub struct PlayerData { @@ -45,11 +42,13 @@ struct InputEvent { plugin: u8, } -fn send_error(netplay: &mut Netplay, error: String) { - netplay.error_notifier.try_send(error).unwrap(); - - netplay.gui_ctx.request_repaint(); // this is so the window pops up right away -} +pub const NETPLAY_ERROR_DESYNC: u32 = 0; +pub const NETPLAY_ERROR_LOST_CONNECTION: u32 = 1; +pub const NETPLAY_ERROR_PLAYER_DISCONNECTED: u32 = 2; +pub const NETPLAY_ERROR_PLAYER_1_DISCONNECTED: u32 = 3; +pub const NETPLAY_ERROR_PLAYER_2_DISCONNECTED: u32 = 4; +pub const NETPLAY_ERROR_PLAYER_3_DISCONNECTED: u32 = 5; +pub const NETPLAY_ERROR_PLAYER_4_DISCONNECTED: u32 = 6; pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size: usize) { let mut request: Vec = [TCP_SEND_SAVE].to_vec(); @@ -109,7 +108,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) { let timeout = std::time::Instant::now() + std::time::Duration::from_secs(10); let mut request_timer = std::time::Instant::now() - std::time::Duration::from_millis(5); while input.is_none() { - process_incoming(netplay); // we execute process_incoming before request_input so that we send an accurate buffer count + process_incoming(netplay, &mut device.ui); // we execute process_incoming before request_input so that we send an accurate buffer count if std::time::Instant::now() > request_timer { // sends a request packet every 5ms request_input(netplay, channel); @@ -120,10 +119,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) { .remove(&netplay.player_data[channel].count); if std::time::Instant::now() > timeout { - send_error( - netplay, - "Timed out waiting for input. Lost connection to server".to_string(), - ); + ui::audio::play_netplay_audio(&mut device.ui, NETPLAY_ERROR_LOST_CONNECTION); input = Some(InputEvent { input: 0, plugin: 0, @@ -154,7 +150,7 @@ fn request_input(netplay: &Netplay, channel: usize) { netplay.udp_socket.send(&request).unwrap(); } -fn process_incoming(netplay: &mut Netplay) { +fn process_incoming(netplay: &mut Netplay, ui: &mut ui::Ui) { let mut buf: [u8; 1024] = [0; 1024]; while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) { match buf[0] { @@ -168,15 +164,15 @@ fn process_incoming(netplay: &mut Netplay) { } if current_status != netplay.status { if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 { - send_error( - netplay, - format!("Players have desynced at VI {}", netplay.vi_counter), - ); + ui::audio::play_netplay_audio(ui, NETPLAY_ERROR_DESYNC); } for dis in 1..5 { if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0 { - send_error(netplay, format!("Player {} has disconnected", dis)); + ui::audio::play_netplay_audio( + ui, + NETPLAY_ERROR_PLAYER_DISCONNECTED + dis, + ); } } netplay.status = current_status; @@ -218,8 +214,6 @@ pub fn init( mut peer_addr: std::net::SocketAddr, session: ui::gui::gui_netplay::NetplayRoom, player_number: u8, - error_notifier: tokio::sync::mpsc::Sender, - gui_ctx: egui::Context, ) -> Netplay { peer_addr.set_port(session.port.unwrap() as u16); let udp_socket; @@ -283,8 +277,6 @@ pub fn init( status: 0, buffer_target, fast_forward: false, - error_notifier, - gui_ctx, player_data: [ PlayerData { lag: 0, diff --git a/src/ui.rs b/src/ui.rs index 95d0d0d..74c52f5 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -22,10 +22,10 @@ pub struct Ui { pub game_hash: String, pub paths: storage::Paths, pub saves: storage::Saves, - pub pak_audio: audio::PakAudio, + pub event_audio: audio::EventAudio, pub window: *mut sdl3_sys::video::SDL_Window, pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, - pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, + pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, pub audio_device: u32, pub num_joysticks: i32, pub fullscreen: bool, @@ -129,14 +129,23 @@ impl Ui { sdcard: (Vec::new(), false), romsave: (std::collections::HashMap::new(), false), }, - pak_audio: audio::PakAudio { + event_audio: audio::EventAudio { mempak: include_bytes!("../data/mempak.wav").to_vec(), rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(), transferpak: include_bytes!("../data/transferpak.wav").to_vec(), + netplay_desync: include_bytes!("../data/netplay_desync.wav").to_vec(), + netplay_lost_connection: include_bytes!("../data/netplay_lost_connection.wav") + .to_vec(), + netplay_disconnected: [ + include_bytes!("../data/netplay_p1_disconnected.wav").to_vec(), + include_bytes!("../data/netplay_p2_disconnected.wav").to_vec(), + include_bytes!("../data/netplay_p3_disconnected.wav").to_vec(), + include_bytes!("../data/netplay_p4_disconnected.wav").to_vec(), + ], }, window: std::ptr::null_mut(), audio_stream: std::ptr::null_mut(), - pak_audio_stream: std::ptr::null_mut(), + event_audio_stream: std::ptr::null_mut(), audio_device: 0, fullscreen: false, num_joysticks, diff --git a/src/ui/audio.rs b/src/ui/audio.rs index 41d966f..2b8ecd9 100644 --- a/src/ui/audio.rs +++ b/src/ui/audio.rs @@ -1,10 +1,14 @@ use crate::device; +use crate::netplay; use crate::ui; -pub struct PakAudio { +pub struct EventAudio { pub mempak: Vec, pub rumblepak: Vec, pub transferpak: Vec, + pub netplay_desync: Vec, + pub netplay_lost_connection: Vec, + pub netplay_disconnected: [Vec; 4], } pub fn init(ui: &mut ui::Ui, frequency: u64) { @@ -46,8 +50,9 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) { channels: 1, }; - ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) }; - if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } { + ui.event_audio_stream = + unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) }; + if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.event_audio_stream) } { panic!("Could not bind audio stream"); } } @@ -58,33 +63,53 @@ pub fn close(ui: &mut ui::Ui) { sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream); ui.audio_stream = std::ptr::null_mut(); } - if !ui.pak_audio_stream.is_null() { - sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream); - ui.pak_audio_stream = std::ptr::null_mut(); + if !ui.event_audio_stream.is_null() { + sdl3_sys::audio::SDL_DestroyAudioStream(ui.event_audio_stream); + ui.event_audio_stream = std::ptr::null_mut(); } sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device); ui.audio_device = 0; } } +pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) { + if ui.event_audio_stream.is_null() { + return; + } + let sound = match error { + netplay::NETPLAY_ERROR_DESYNC => &ui.event_audio.netplay_desync, + netplay::NETPLAY_ERROR_LOST_CONNECTION => &ui.event_audio.netplay_lost_connection, + netplay::NETPLAY_ERROR_PLAYER_1_DISCONNECTED => &ui.event_audio.netplay_disconnected[0], + netplay::NETPLAY_ERROR_PLAYER_2_DISCONNECTED => &ui.event_audio.netplay_disconnected[1], + netplay::NETPLAY_ERROR_PLAYER_3_DISCONNECTED => &ui.event_audio.netplay_disconnected[2], + netplay::NETPLAY_ERROR_PLAYER_4_DISCONNECTED => &ui.event_audio.netplay_disconnected[3], + _ => panic!("Invalid netplay error"), + }; + if !unsafe { + sdl3_sys::audio::SDL_PutAudioStreamData( + ui.event_audio_stream, + sound.as_ptr() as *const std::ffi::c_void, + sound.len() as i32, + ) + } { + panic!("Could not play audio"); + } +} + pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) { - if ui.pak_audio_stream.is_null() { + if ui.event_audio_stream.is_null() { return; } - let sound; - if pak == device::controller::PakType::RumblePak { - sound = &ui.pak_audio.rumblepak; - } else if pak == device::controller::PakType::MemPak { - sound = &ui.pak_audio.mempak; - } else if pak == device::controller::PakType::TransferPak { - sound = &ui.pak_audio.transferpak; - } else { - return; - } + let sound = match pak { + device::controller::PakType::RumblePak => &ui.event_audio.rumblepak, + device::controller::PakType::MemPak => &ui.event_audio.mempak, + device::controller::PakType::TransferPak => &ui.event_audio.transferpak, + _ => panic!("Invalid pak type"), + }; if !unsafe { sdl3_sys::audio::SDL_PutAudioStreamData( - ui.pak_audio_stream, + ui.event_audio_stream, sound.as_ptr() as *const std::ffi::c_void, sound.len() as i32, ) diff --git a/src/ui/gui.rs b/src/ui/gui.rs index daf6571..4f0af7e 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -20,7 +20,6 @@ pub struct GopherEguiApp { dinput: bool, show_vru_dialog: bool, vru_window_receiver: Option>>, - netplay_error_receiver: Option>, vru_word_notifier: Option>, vru_word_list: Vec, pub netplay: gui_netplay::GuiNetplay, @@ -111,7 +110,6 @@ impl GopherEguiApp { show_vru_dialog: false, dinput: false, controller_paths, - netplay_error_receiver: None, vru_window_receiver: None, vru_word_notifier: None, vru_word_list: Vec::new(), @@ -267,11 +265,6 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { player_number = Some(app.netplay.player_number); } - let (netplay_error_notifier, netplay_error_receiver): ( - tokio::sync::mpsc::Sender, - tokio::sync::mpsc::Receiver, - ) = tokio::sync::mpsc::channel(1); - let (vru_window_notifier, vru_window_receiver): ( tokio::sync::mpsc::Sender>, tokio::sync::mpsc::Receiver>, @@ -282,9 +275,6 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { tokio::sync::mpsc::Receiver, ) = tokio::sync::mpsc::channel(1); - if netplay { - app.netplay_error_receiver = Some(netplay_error_receiver); - } if emulate_vru && !netplay { app.vru_window_receiver = Some(vru_window_receiver); app.vru_word_notifier = Some(vru_word_notifier); @@ -336,8 +326,6 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { peer_addr.unwrap(), session.unwrap(), player_number.unwrap(), - netplay_error_notifier, - gui_ctx, )); device.ui.fullscreen = fullscreen; device::run_game(rom_contents, &mut device); @@ -381,13 +369,6 @@ impl eframe::App for GopherEguiApp { gui_netplay::netplay_wait(self, ctx); } - if self.netplay_error_receiver.is_some() { - let result = self.netplay_error_receiver.as_mut().unwrap().try_recv(); - if result.is_ok() { - self.netplay.error = result.unwrap(); - } - } - if !self.netplay.error.is_empty() { gui_netplay::netplay_error(self, ctx, self.netplay.error.clone()); }