use std::error; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::Resource; use bevy_input::{keyboard::KeyCode, mouse::MouseButton}; #[cfg(feature = "inspector-egui")] use bevy_inspector_egui::prelude::*; #[cfg(all(feature = "reflect", feature = "serialize"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_utils::HashMap; use log::warn; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; use std::mem::discriminant; use strum::IntoEnumIterator; use strum_macros::EnumIter; #[cfg(feature = "reflect")] use {bevy_ecs::prelude::ReflectResource, bevy_reflect::Reflect}; use crate::{common_traits_conditions, hashmap}; /// Validate that all enum variants are in the hash map pub fn validate_hash_map(hash_map: &HashMap) -> bool where K: Eq + std::hash::Hash + Copy + IntoEnumIterator, K::Iterator: Iterator, { let all_keys = K::iter().collect::>(); if hash_map.len() != all_keys.len() { return false; } for key in all_keys { if !hash_map.contains_key(&key) { return false; } } true } common_traits_conditions! { /// Struct that contains user's inputs corresponding to actions #[derive(Debug, PartialEq, Clone, Deref, DerefMut)] pub struct Inputs(HashMap); /// Resource that contains current user inputs #[derive(Debug, PartialEq, Clone)] pub struct PlayerInputs { current: Inputs, previous: Inputs, } } impl Default for PlayerInputs { fn default() -> Self { PlayerInputs { current: Inputs( Action::iter() .map(|action| (action, InputValue::Empty)) .collect(), ), previous: Inputs( Action::iter() .map(|action| (action, InputValue::Empty)) .collect(), ), } } } impl PlayerInputs { /// Returns current input value for action /// /// in any case faster that `get_many` method pub fn get(&self, action: Action) -> InputValue { // SAFETY: action is always valid // because we iterate over all actions in `default` method *self.current.get(&action).unwrap() } /// Returns current input values for actions pub fn get_many(&self, actions: Vec) -> Vec { // SAFETY: action is always valid // because we iterate over all actions in `default` method actions .iter() .map(|action| *self.current.get(action).unwrap()) .collect() } /// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame /// If input has not same type as `InputValue` on previous frame call `panic` pub fn just_pressed(&self, action: Action) -> bool { // SAFETY: action is always valid // because we iterate over all actions in `default` method if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() { if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() { return current && !previous; } panic!("Previous input is not boolean"); } panic!("This input is not boolean") } /// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame /// If input has not same type as `InputValue` on previous frame return `Error()` pub fn get_just_pressed(&self, action: Action) -> Result> { // SAFETY: action is always valid // because we iterate over all actions in `default` method if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() { if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() { return Ok(current && !previous); } return Err("Previous input is not boolean".into()); } Err("This input is not boolean".into()) } /// Set input value to new pub fn forced_set(&mut self, action: Action, value: impl Into) { // SAFETY: action is always valid // because we iterate over all actions in `default` method let current_input = self.current.get_mut(&action).unwrap(); *self.previous.get_mut(&action).unwrap() = *current_input; *current_input = value.into(); } /// Set input value to new /// if it is not the same InputValue type return `Error()` pub fn set( &mut self, action: Action, value: impl Into, ) -> Result<(), Box> { // SAFETY: action is always valid // because we iterate over all actions in `default` method let current_input = self.current.get_mut(&action).unwrap(); let previos_input = self.previous.get_mut(&action).unwrap(); // check if enum type is the same ignoring value if discriminant(current_input) == discriminant(previos_input) { *previos_input = *current_input; *current_input = value.into(); Ok(()) } else { Err("Input type is not the same".into()) } } /// Update current inputs pub fn forced_update(&mut self, inputs: Inputs) { *self.previous = self.current.clone().0; *self.current = inputs.0; } /// Update current inputs /// if any input is not the same InputValue type return `Error()` /// ! remember that it work safely but slow pub fn update(&mut self, inputs: Inputs) -> Result<(), Box> { // SAFETY: action is always valid // because we iterate over all actions in `default` method for (action, value) in inputs.0.iter() { let current_input = self.current.get_mut(action).unwrap(); let previos_input = self.previous.get_mut(action).unwrap(); // check if enum type is not the same ignoring value if discriminant(current_input) != discriminant(previos_input) { return Err("Input type is not the same".into()); } *previos_input = *current_input; *current_input = *value; } Ok(()) } } common_traits_conditions! { // TODO: it must be a trait #[derive(Debug, PartialEq, Clone)] pub enum GameState { LevelEditor, InGame, } #[derive(Debug, PartialEq, Clone)] pub enum InputType { Keyboard(KeyCode), Mouse(MouseInput), // TODO: Gamepad(GamepadButtonType), // TODO: Touch screen } #[derive(Debug, PartialEq, Clone)] pub enum MouseInput { Button(MouseButton), Axis(AxisName), Wheel(AxisName) // TODO: CursorPosition // TODO: TouchPad inputs } /// Name of axis #[derive(Debug, PartialEq, Clone)] pub enum AxisName { Horizontal, Vertical, } /// Represents a binding that can be changed by the player #[derive(Debug, PartialEq, Clone)] pub enum OptionsMode { /// Represents a binding that cannot be changed as it is crucial to the game logic Immutable, /// Represents a binding that the player can customize Customizable, } /// Represents the manner in which input is registered #[derive(Debug, PartialEq, Clone)] pub enum ActivationMode { /// Action is triggered when the button is pressed Hold, /// Action is triggered when the button is pressed /// and cannot be triggered again until the button is released /// you don't need to release all buttons in chord /// you can release only last pressed button Tap, } pub struct ActivationOptions { /// Manner in which input is registered pub mode: ActivationMode, /// Possibility to change this activation mode pub option: OptionsMode, /// Delay between activation pub delay: f32, } } impl Default for ActivationOptions { fn default() -> Self { Self { mode: ActivationMode::Tap, // Because it is more safe option: OptionsMode::Immutable, delay: 0.05, // 20 times per second; 14 - world record? } } } impl ActivationOptions { pub fn new(mode: ActivationMode, option: OptionsMode) -> Self { Self { mode, option, ..Default::default() } } pub fn with_delay(mut self, delay: f32) -> Self { self.delay = delay; self } } common_traits_conditions! { /// Represents a combination of buttons that triggers an action #[derive(Debug, PartialEq, Clone)] pub enum ButtonCombination { /// Single button press Single(InputType), /// Chord is a combination of buttons that must be pressed at the same time Chord(Vec), } #[derive(Debug, PartialEq, Clone)] pub enum BindingCondition { /// During specific game state InGameState(GameState), /// Binding is active only if player is in pause menu DuringPauseMenu(bool), /// Binding is active only if player types text ListeningForText(bool), } // TODO: it is just plug, it must be a trait /// Actions that can be performed by player #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, EnumIter)] pub enum Action { /// Move forward LeverEditorForward, /// Move backward LevelEditorBackward, /// Move left LevelEditorLeft, /// Move right LevelEditorRight, LevelEditorFly, } /// Binding is a combination of buttons that triggers action #[derive(Debug, PartialEq, Clone)] pub struct Binding { pub input: ButtonCombination, pub conditions: Vec, } } impl From for Binding { fn from(input: ButtonCombination) -> Self { Binding { input, conditions: Vec::new(), } } } impl Binding { pub fn new(input: ButtonCombination) -> Self { Self { input, conditions: Vec::new(), } } pub fn with_condition(mut self, condition: BindingCondition) -> Self { self.conditions.push(condition); self } } common_traits_conditions! { /// Input value that can be used in game logic #[derive(Default, Clone, Copy, PartialEq, Debug)] pub enum InputValue { #[default] Empty, Boolean(bool), Float(f32), } } impl From for InputValue { fn from(value: bool) -> Self { InputValue::Boolean(value) } } impl From for InputValue { fn from(value: f32) -> Self { InputValue::Float(value) } } impl From for bool { fn from(value: InputValue) -> Self { match value { InputValue::Boolean(value) => value, _ => panic!("InputValue is not boolean"), } } } impl From for f32 { fn from(value: InputValue) -> Self { match value { InputValue::Float(value) => value, _ => panic!("InputValue is not float"), } } } impl InputValue { pub fn is_empty(&self) -> bool { matches!(self, InputValue::Empty) } pub fn is_boolean(&self) -> bool { matches!(self, InputValue::Boolean(_)) } pub fn is_float(&self) -> bool { matches!(self, InputValue::Float(_)) } pub fn as_boolean(&self) -> bool { match self { InputValue::Boolean(value) => *value, InputValue::Float(value) => *value > 0.0, InputValue::Empty => false, } } } common_traits_conditions! { pub struct Bindings { /// List of bindings for action list: Vec, /// Possibility to change this binding options: OptionsMode, } } impl Default for Bindings { fn default() -> Self { Self { list: Vec::new(), options: OptionsMode::Immutable, } } } impl Bindings { pub fn new(bindings: Vec, options: OptionsMode) -> Self { Self { list: bindings, options, } } pub fn push(&mut self, binding: Binding) { if self.options == OptionsMode::Immutable { warn!("You try to push binding to immutable bindings"); return; } self.list.push(binding); } pub fn clear(&mut self) { if self.options == OptionsMode::Immutable { warn!("You try to clear immutable bindings"); return; } self.list.clear(); } pub fn retain(&mut self, f: F) where F: FnMut(&Binding) -> bool, { if self.options == OptionsMode::Immutable { warn!("You try to retain immutable bindings"); return; } self.list.retain(f); } pub fn iter(&self) -> impl Iterator { self.list.iter() } pub fn iter_mut( &mut self, ) -> Result, Box> { if self.options == OptionsMode::Immutable { warn!("You try to iterate over immutable bindings"); return Err("You try to iterate over immutable bindings".into()); } Ok(self.list.iter_mut()) } pub fn is_customizable(&self) -> bool { self.options == OptionsMode::Customizable } pub fn is_immutable(&self) -> bool { self.options == OptionsMode::Immutable } } common_traits_conditions! { /// Contains all bindings for action /// and different activation options #[derive(Default)] pub struct BindingConfig { pub bindings: Bindings, #[cfg_attr(feature = "serialize", serde(skip))] pub activation: ActivationOptions, } } impl BindingConfig { pub fn new(bindings: Bindings, activation: ActivationOptions) -> Self { Self { bindings, activation, } } } common_traits_conditions! { /// Contains all bindings for actions #[derive(Resource)] #[cfg_attr(feature = "reflect", reflect(Resource))] // TODO: Add delay for input pub struct Controls(HashMap); // TODO: change HashMap to Vec for faster iteration } impl Default for Controls { fn default() -> Self { // TODO: default must create empty controls or fill empty / default BindingConfig on any actions let controls = Self(hashmap! { Action::LeverEditorForward => BindingConfig::new( Bindings::new( vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::W))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))], OptionsMode::Customizable, ), ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable), ), Action::LevelEditorBackward => BindingConfig::new( Bindings::new( vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::S))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))], OptionsMode::Customizable, ), ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable), ), Action::LevelEditorLeft => BindingConfig::new( Bindings::new( vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::A))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))], OptionsMode::Customizable, ), ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable), ), Action::LevelEditorRight => BindingConfig::new( Bindings::new( vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::D))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))], OptionsMode::Customizable, ), ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable), ), Action::LevelEditorFly => BindingConfig::new( Bindings::new( vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::Space))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))], OptionsMode::Customizable, ), ActivationOptions::new(ActivationMode::Hold, OptionsMode::Customizable), ) }); // If you see this error, you may add new action in menu_actions // or make sure that you have only one MenuAction with the same name in the MenuActions assert!(validate_hash_map(&controls.0)); controls } } impl Controls { /// Returns bindings for action pub fn get(&self, action: Action) -> Option<&BindingConfig> { self.0.get(&action) } /// Push new binding for action pub fn push(&mut self, action: Action, binding: Binding) { // TODO: warning if config is not exist yet self.0 .entry(action) .or_insert_with(BindingConfig::default) .bindings .push(binding); } /// Remove all bindings for action pub fn remove(&mut self, action: Action) { // TODO: warning if config is not exist yet self.0 .entry(action) .or_insert_with(BindingConfig::default) .bindings .clear(); } pub fn remove_binding(&mut self, action: Action, binding: Binding) { if let Some(config) = self.0.get_mut(&action) { config.bindings.retain(|b| *b == binding); } } pub fn iter(&self) -> impl Iterator { self.0.iter() } }