use bevy_app::{App, Plugin, PreUpdate}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::{In, IntoSystem}; use bevy_ecs::{ event::EventReader, system::{Res, ResMut, Resource}, }; use bevy_input::{ keyboard::KeyCode, mouse::{MouseButton, MouseMotion, MouseScrollUnit, MouseWheel}, ButtonInput, }; use bevy_utils::HashMap; use crate::{ contract::{Action, GameState, InputsContainer}, resource::*, }; pub struct ControlsPlugin, Gs: GameState> { _action: std::marker::PhantomData, _inputs_container: std::marker::PhantomData, _game_state: std::marker::PhantomData, controls: Controls, } impl, Gs: GameState> Default for ControlsPlugin { fn default() -> Self { Self { _action: std::marker::PhantomData, _inputs_container: std::marker::PhantomData, _game_state: std::marker::PhantomData, controls: Controls::::default(), } } } impl, Gs: GameState> ControlsPlugin { pub fn new(controls: Controls) -> Self { Self { _action: std::marker::PhantomData, _inputs_container: std::marker::PhantomData, _game_state: std::marker::PhantomData, controls, } } } #[derive(Resource, Default, Debug, Deref, DerefMut)] struct TrigeredInputs(HashMap); impl, Gs: GameState> Plugin for ControlsPlugin { fn build(&self, app: &mut App) { app .insert_resource(self.controls.clone()) .init_resource::() .init_resource::() .insert_state::(Gs::default()) .add_systems( PreUpdate, Self::collect_inputs.pipe(Self::fill_players_inputs), ); #[cfg(feature = "reflect")] app.register_type::>(); } } impl, Gs: GameState> ControlsPlugin { // Separate collecting and filling is necessary becouse order of complicate imputs in future fn collect_inputs( keyboard_button: Res>, mouse_buttons: Res>, mut scroll_evr: EventReader, mut motion_evr: EventReader, // mut collected_inputs: ResMut, ) -> TrigeredInputs { let mut collected_inputs = TrigeredInputs(HashMap::new()); for key in keyboard_button.get_pressed() { collected_inputs.insert(InputType::Keyboard(*key), InputValue::Boolean(true)); } // buttons.get_just_released() for button in mouse_buttons.get_pressed() { collected_inputs.insert( InputType::Mouse(MouseInput::Button(*button)), InputValue::Boolean(true), ); } for ev in motion_evr.read() { collected_inputs.insert( InputType::Mouse(MouseInput::Axis(AxisName::Horizontal)), InputValue::Float(ev.delta.x), ); collected_inputs.insert( InputType::Mouse(MouseInput::Axis(AxisName::Vertical)), InputValue::Float(ev.delta.y), ); } for ev in scroll_evr.read() { match ev.unit { MouseScrollUnit::Line => { collected_inputs.insert( InputType::Mouse(MouseInput::Wheel(AxisName::Horizontal)), InputValue::Float(ev.x), ); collected_inputs.insert( InputType::Mouse(MouseInput::Wheel(AxisName::Vertical)), InputValue::Float(ev.y), ); } MouseScrollUnit::Pixel => { panic!("I'm do not accept you to use touch pad in my game"); } } } collected_inputs } // TODO: ! Zero out values that did not arrive fn fill_players_inputs( In(collected_inputs): In, mut inputs_container: ResMut, controls: Res>, game_state: Res>, ) { if let Some(inputs) = inputs_container.me_mut() { // TODO: only on controls change let mut action_binding_pairs: Vec<(&A, &BindingConfig, &Binding)> = Vec::new(); for (action, config) in controls.iter() { for binding in config.bindings.iter() { action_binding_pairs.push((action, config, binding)); } } action_binding_pairs.sort_by(|a, b| b.2.input.len().cmp(&a.2.input.len())); 'bindings_loop: for (action, _config, binding) in action_binding_pairs.iter() { // check binding conditions for condition in &binding.conditions { match condition { BindingCondition::InGameState(state) => { if *state != *game_state.get() { continue 'bindings_loop; } } } } //log::info!("{:#?}", collected_inputs); //log::info!("{:#?}", action_binding_pairs[0]); match &binding.input { ButtonCombination::Single(input_type) => { // TODO: exclude if used if let Some(value) = collected_inputs.get(&*input_type) { inputs.forced_set(**action, *value); } else { inputs.forced_set(**action, InputValue::Empty); } } } } } else { // TODO: print onece log::warn!("cannot find me in inputs container"); } } }