//! Input, from physical device to the buffered per-fighter state that the //! state machine reads. use bevy::platform::collections::HashMap; use bevy::prelude::*; use bevy_ggrs::prelude::*; use bevy_ggrs::{LocalInputs, LocalPlayers}; pub mod buffer; pub mod control; pub mod frame; pub mod gamecube; pub mod gamepad; pub mod keyboard; pub mod map; pub use buffer::{FighterCommands, FighterInput}; pub use frame::{ActionBindingMask, DigitalAction, DigitalActionBindings, FighterInputFrame}; pub use map::{ BaseInputMap, GamepadBinding, GamepadInputMapElement, InputActionState, InputMapAction, KeyboardInputMapElement, MenuGamepadInputMapElement, MenuInputMap, MenuInputMapAction, MenuKeyboardInputMapElement, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum LocalInputSource { Keyboard, Gamepad(Entity), } /// Persistent mapping from physical/menu controller slots to input sources. #[derive(Resource, Clone, Debug, Default, Deref, DerefMut)] pub struct LocalInputAssignments(pub Vec<(usize, LocalInputSource)>); impl LocalInputAssignments { pub fn get_free_player_index(&self) -> Option { for i in 0..16 { if let None = self.iter().find(|(idx, _)| *idx == i) { return Some(i); } } None } pub fn get_from_slot(&self, slot: usize) -> Option<&LocalInputSource> { self.0 .iter() .find(|(c_slot, _)| slot == *c_slot) .map(|(_, source)| source) } } /// Match-local mapping from dense GGRS handles to physical input sources. #[derive(Resource, Clone, Debug, Default, Deref, DerefMut)] pub struct MatchInputAssignments(pub Vec>); impl MatchInputAssignments { pub fn get_for_handle(&self, handle: usize) -> Option { self.0.get(handle).copied().flatten() } } use crate::game_settings::GameSettings; use crate::math::int::FGi32; use crate::netcode::GGRSCfg; use crate::schedule::GameplaySet; /// Owns the input map, the local-input collection that feeds GGRS, and the /// per-fighter input state derived from the rolled-back inputs. #[derive(Default)] pub struct FighterInputPlugin; impl Plugin for FighterInputPlugin { fn build(&self, app: &mut App) { app.insert_resource(BaseInputMap { keyboard: vec![ // right KeyboardInputMapElement { key: KeyCode::KeyD, action: InputMapAction::MovementXDir(1), binding_slot: 0, }, // left KeyboardInputMapElement { key: KeyCode::KeyA, action: InputMapAction::MovementXDir(-1), binding_slot: 0, }, // up KeyboardInputMapElement { key: KeyCode::KeyW, action: InputMapAction::MovementYDir(1), binding_slot: 0, }, // down KeyboardInputMapElement { key: KeyCode::KeyS, action: InputMapAction::MovementYDir(-1), binding_slot: 0, }, // jump KeyboardInputMapElement { key: KeyCode::Space, action: InputMapAction::Jump, binding_slot: 0, }, // shield/airdodge KeyboardInputMapElement { key: KeyCode::ShiftLeft, action: InputMapAction::Shield, binding_slot: 0, }, // attack KeyboardInputMapElement { key: KeyCode::KeyJ, action: InputMapAction::Attack, binding_slot: 0, }, // Menu ], gamepad: vec![ // left stick GamepadInputMapElement { binding: GamepadBinding::Axis(GamepadAxis::LeftStickX, 1), action: InputMapAction::MovementXDir(1), binding_slot: 0, }, GamepadInputMapElement { binding: GamepadBinding::Axis(GamepadAxis::LeftStickY, 1), action: InputMapAction::MovementYDir(1), binding_slot: 0, }, // jump GamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::North), action: InputMapAction::Jump, binding_slot: 0, }, GamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::LeftTrigger2), action: InputMapAction::Shield, binding_slot: 0, }, GamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::RightTrigger2), action: InputMapAction::Shield, binding_slot: 1, }, // attack GamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::East), action: InputMapAction::Attack, binding_slot: 0, }, ], }) .insert_resource(MenuInputMap { keyboard: vec![ MenuKeyboardInputMapElement { key: KeyCode::ArrowUp, action: MenuInputMapAction::MovementYDir(1), }, MenuKeyboardInputMapElement { key: KeyCode::ArrowDown, action: MenuInputMapAction::MovementYDir(-1), }, MenuKeyboardInputMapElement { key: KeyCode::ArrowLeft, action: MenuInputMapAction::MovementXDir(-1), }, MenuKeyboardInputMapElement { key: KeyCode::ArrowRight, action: MenuInputMapAction::MovementXDir(1), }, MenuKeyboardInputMapElement { key: KeyCode::Enter, action: MenuInputMapAction::Accept, }, MenuKeyboardInputMapElement { key: KeyCode::Escape, action: MenuInputMapAction::Back, }, MenuKeyboardInputMapElement { key: KeyCode::Space, action: MenuInputMapAction::Start, }, MenuKeyboardInputMapElement { key: KeyCode::KeyW, action: MenuInputMapAction::MovementYDir(1), }, MenuKeyboardInputMapElement { key: KeyCode::KeyS, action: MenuInputMapAction::MovementYDir(-1), }, MenuKeyboardInputMapElement { key: KeyCode::KeyA, action: MenuInputMapAction::MovementXDir(-1), }, MenuKeyboardInputMapElement { key: KeyCode::KeyD, action: MenuInputMapAction::MovementXDir(1), }, ], gamepad: vec![ MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::DPadUp), action: MenuInputMapAction::MovementYDir(1), }, MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::DPadDown), action: MenuInputMapAction::MovementYDir(-1), }, MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::DPadLeft), action: MenuInputMapAction::MovementXDir(-1), }, MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::DPadRight), action: MenuInputMapAction::MovementXDir(1), }, MenuGamepadInputMapElement { binding: GamepadBinding::Axis(GamepadAxis::LeftStickX, 1), action: MenuInputMapAction::MovementXDir(1), }, MenuGamepadInputMapElement { binding: GamepadBinding::Axis(GamepadAxis::LeftStickY, 1), action: MenuInputMapAction::MovementYDir(1), }, MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::South), action: MenuInputMapAction::Accept, }, MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::West), action: MenuInputMapAction::Back, }, MenuGamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::Start), action: MenuInputMapAction::Start, }, ], }) .init_resource::() .add_plugins(gamecube::GamecubeAdapterPlugin) .add_systems(ReadInputs, read_local_inputs) .add_systems( GgrsSchedule, buffer::postprocess_input.in_set(GameplaySet::Input), ) .rollback_component_with_clone::() .checksum_component_with_hash::(); } } fn preprocess_input_frame(frame: &mut FighterInputFrame, game_settings: &GameSettings) { if frame.movement.x.abs() < game_settings.input_common.stick_deadzone { frame.movement.x = FGi32::ZERO; } if frame.movement.y.abs() < game_settings.input_common.stick_deadzone { frame.movement.y = FGi32::ZERO; } } fn read_local_inputs( mut commands: Commands, key: Res>, input_map: Res, gc_ports: Res, gamepads: Query<&Gamepad>, assignments: Res, local_players: Res, game_settings: Res, ) { let mut local_inputs = HashMap::new(); for handle in &local_players.0 { let assigned = assignments.get_for_handle(*handle); let gamepad = match assigned { Some(LocalInputSource::Gamepad(entity)) => gamepads.get(entity).ok(), Some(LocalInputSource::Keyboard) => None, None => gc_ports .entities .get(*handle) .copied() .flatten() .and_then(|entity| gamepads.get(entity).ok()), }; let mut input_frame = match assigned { Some(LocalInputSource::Keyboard) => keyboard::sample_keyboard(&key, &input_map), Some(LocalInputSource::Gamepad(_)) => gamepad .map(|pad| gamepad::sample_gamepad(pad, &input_map)) .unwrap_or_default(), None => FighterInputFrame::default(), }; // preprocess frame preprocess_input_frame(&mut input_frame, &game_settings); local_inputs.insert(*handle, input_frame); } commands.insert_resource(LocalInputs::(local_inputs)); }