//! Turns [`GcAdapterSnapshot`] port states into real Bevy `Gamepad` //! entities, the same way `bevy_gilrs` turns OS gamepad events into them. use bevy::input::gamepad::{ GamepadAxis, GamepadButton, GamepadConnection, GamepadConnectionEvent, GamepadSettings, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, }; use bevy::prelude::*; use super::GcPorts; use super::poll::GcAdapterSnapshot; /// GameCube adapters report 4 ports. pub const GC_PORT_COUNT: usize = 4; /// USB vendor/product ID of the official adapter, reported on the `Gamepad` /// component so it shows up the same way a real USB gamepad would. const GC_ADAPTER_VID: u16 = 0x057e; const GC_ADAPTER_PID: u16 = 0x0337; /// Marks a `Gamepad` entity as backed by a specific GameCube adapter port /// (0-3), and is the join key [`crate::input::gamepad::sample_gamepad`] /// uses to map GGRS handles to ports. #[derive(Component, Debug, Clone, Copy)] pub struct GcPort(pub u8); fn passthrough_gamepad_settings() -> GamepadSettings { let mut settings = GamepadSettings::default(); for axis in GamepadAxis::all() { let axis_settings = settings.axis_settings.entry(axis).or_default(); // Order matters: each setter rejects a value that would cross the // *other* bound it's paired with, so widen the live zones before // shrinking the dead zones down to zero. axis_settings.set_livezone_lowerbound(-1.0); axis_settings.set_livezone_upperbound(1.0); axis_settings.set_deadzone_lowerbound(0.0); axis_settings.set_deadzone_upperbound(0.0); axis_settings.set_threshold(0.0); } for button in GamepadButton::all() { let button_axis_settings = settings.button_axis_settings.entry(button).or_default(); button_axis_settings.low = 0.0; button_axis_settings.high = 1.0; button_axis_settings.threshold = 0.0; } settings } /// Spawns/despawns `Gamepad` entities to match which adapter ports are /// connected. Must run before `gamepad_connection_system` so the entity it /// spawns already exists by the time Bevy inserts the `Gamepad` component /// on it (mirrors what `bevy_gilrs::gilrs_event_system` does). pub fn gc_connection_system( mut commands: Commands, snapshot: Res, mut ports: ResMut, mut connection_events: MessageWriter, ) { let current = snapshot.read(); let ports = &mut *ports; for i in 0..GC_PORT_COUNT { let was_connected = ports.last[i].connected; let is_connected = current[i].connected; if is_connected && !was_connected { let entity = ports.entities[i].unwrap_or_else(|| { let entity = commands .spawn((GcPort(i as u8), passthrough_gamepad_settings())) .id(); ports.entities[i] = Some(entity); entity }); connection_events.write(GamepadConnectionEvent::new( entity, GamepadConnection::Connected { name: format!("GameCube Controller (Port {})", i + 1), vendor_id: Some(GC_ADAPTER_VID), product_id: Some(GC_ADAPTER_PID), }, )); } else if !is_connected && was_connected { if let Some(entity) = ports.entities[i] { connection_events.write(GamepadConnectionEvent::new( entity, GamepadConnection::Disconnected, )); } } } } /// Converts a GC unsigned trigger byte to the 0.0..=1.0 range /// `GamepadButton` analog values use. fn unsigned_axis_float(raw: u8) -> f32 { (raw as f32) / 255.0 } /// Diffs the current snapshot against last frame's and writes /// `RawGamepadEvent`s for anything that changed, which /// `gamepad_event_processing_system` turns into live `Gamepad` component /// state. Must run after `gc_connection_system` (so newly-connected ports /// have an entity/`GamepadSettings`) and before /// `gamepad_event_processing_system`. pub fn gc_event_system( snapshot: Res, mut ports: ResMut, mut raw_events: MessageWriter, mut axis_events: MessageWriter, mut button_events: MessageWriter, ) { let current = snapshot.read(); for i in 0..GC_PORT_COUNT { let Some(entity) = ports.entities[i] else { continue; }; let prev = &ports.last[i]; let now = ¤t[i]; if !now.connected { continue; } macro_rules! send_axis { ($axis:expr, $value:expr) => {{ let event = RawGamepadAxisChangedEvent::new(entity, $axis, $value); axis_events.write(event); raw_events.write(RawGamepadEvent::Axis(event)); }}; } macro_rules! send_button { ($button:expr, $value:expr) => {{ let event = RawGamepadButtonChangedEvent::new(entity, $button, $value); button_events.write(event); raw_events.write(RawGamepadEvent::Button(event)); }}; } if prev.left_stick != now.left_stick { send_axis!(GamepadAxis::LeftStickX, now.left_stick.0); send_axis!(GamepadAxis::LeftStickY, now.left_stick.1); } if prev.right_stick != now.right_stick { send_axis!(GamepadAxis::RightStickX, now.right_stick.0); send_axis!(GamepadAxis::RightStickY, now.right_stick.1); } if prev.triggers != now.triggers { // The analog triggers are represented as button-axis values, as we have no GamepadAxis to use. send_button!( GamepadButton::LeftTrigger2, unsigned_axis_float(now.triggers.0) ); send_button!( GamepadButton::RightTrigger2, unsigned_axis_float(now.triggers.1) ); } macro_rules! digital { ($field:ident, $button:expr) => { if prev.$field != now.$field { send_button!($button, if now.$field { 1.0 } else { 0.0 }); } }; } digital!(a, GamepadButton::East); digital!(b, GamepadButton::South); digital!(x, GamepadButton::North); digital!(y, GamepadButton::West); digital!(z, GamepadButton::Z); digital!(start, GamepadButton::Start); digital!(dpad_up, GamepadButton::DPadUp); digital!(dpad_down, GamepadButton::DPadDown); digital!(dpad_left, GamepadButton::DPadLeft); digital!(dpad_right, GamepadButton::DPadRight); digital!(left_trigger_digital, GamepadButton::LeftTrigger); digital!(right_trigger_digital, GamepadButton::RightTrigger); } ports.last = current; }