diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index 8e973a70c7..2344b41d95 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchInputManager : ActionMappingInputManager { - public CatchInputManager(RulesetInfo ruleset) : base(ruleset, allowConcurrentActions: true) + public CatchInputManager(RulesetInfo ruleset) : base(ruleset, concurrencyMode: ConcurrentActionMode.UniqueActions) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 27cc05c47a..8469be24dd 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(); - protected override PassThroughInputManager CreateActionMappingInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateActionMappingInputManager() => new CatchInputManager(Ruleset?.RulesetInfo); protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h) { diff --git a/osu.Game/Input/ActionMappingInputManager.cs b/osu.Game/Input/ActionMappingInputManager.cs index c4489b644c..8c8d274264 100644 --- a/osu.Game/Input/ActionMappingInputManager.cs +++ b/osu.Game/Input/ActionMappingInputManager.cs @@ -10,6 +10,25 @@ using osu.Game.Rulesets; namespace osu.Game.Input { + public enum ConcurrentActionMode + { + /// + /// One action can be actuated at once. The first action matching a chord will take precedence and no other action will be actuated until it has been released. + /// + None, + /// + /// Unique actions are allowed to be fired at the same time. There may therefore be more than one action in an actuated state at once. + /// If one action has multiple bindings, only the first will add actuation data, and the last to be released will add de-actuation data. + /// + UniqueActions, + /// + /// Both unique actions and the same action can be concurrently actuated. + /// Same as , but multiple bindings for the same action will individually add actuation and de-actuation data to events. + /// + UniqueAndSameActions, + } + + /// /// Maps custom action data of type and stores to . /// @@ -21,7 +40,7 @@ namespace osu.Game.Input private readonly int? variant; - private readonly bool allowConcurrentActions; + private readonly ConcurrentActionMode concurrencyMode; private readonly List mappings = new List(); @@ -30,12 +49,12 @@ namespace osu.Game.Input /// /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. - /// Allow concurrent actions to be actuated at once. Note that this disables chord bindings. - protected ActionMappingInputManager(RulesetInfo ruleset = null, int? variant = null, bool allowConcurrentActions = false) + /// Specify how to deal with multiple matches of combinations and actions. + protected ActionMappingInputManager(RulesetInfo ruleset = null, int? variant = null, ConcurrentActionMode concurrencyMode = ConcurrentActionMode.None) { this.ruleset = ruleset; this.variant = variant; - this.allowConcurrentActions = allowConcurrentActions; + this.concurrencyMode = concurrencyMode; } protected abstract IDictionary CreateDefaultMappings(); @@ -64,7 +83,7 @@ namespace osu.Game.Input mappings.Add(b); } - if (allowConcurrentActions) + if (concurrencyMode > ConcurrentActionMode.None) { // ensure we have no overlapping bindings. foreach (var m in mappings) @@ -77,15 +96,17 @@ namespace osu.Game.Input protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (!args.Repeat && (allowConcurrentActions || pressedBindings.Count == 0)) + if (!args.Repeat && (concurrencyMode > ConcurrentActionMode.None || pressedBindings.Count == 0)) { Binding validBinding; - if ((validBinding = mappings.Except(pressedBindings).LastOrDefault(m => m.Keys.CheckValid(state.Keyboard.Keys, !allowConcurrentActions))) != null) + if ((validBinding = mappings.Except(pressedBindings).LastOrDefault(m => m.Keys.CheckValid(state.Keyboard.Keys, concurrencyMode == ConcurrentActionMode.None))) != null) { + if (concurrencyMode == ConcurrentActionMode.UniqueAndSameActions || pressedBindings.All(p => p.Action != validBinding.Action)) + state.Data = validBinding.GetAction(); + // store both the pressed combination and the resulting action, just in case the assignments change while we are actuated. pressedBindings.Add(validBinding); - state.Data = validBinding.GetAction(); } } @@ -96,13 +117,14 @@ namespace osu.Game.Input { foreach (var binding in pressedBindings.ToList()) { - if (!binding.Keys.CheckValid(state.Keyboard.Keys, !allowConcurrentActions)) + if (!binding.Keys.CheckValid(state.Keyboard.Keys, concurrencyMode == ConcurrentActionMode.None)) { - // set data as KeyUp. - state.Data = binding.GetAction(); - - // and clear the no-longer-valid combination/action. + // clear the no-longer-valid combination/action. pressedBindings.Remove(binding); + + if (concurrencyMode == ConcurrentActionMode.UniqueAndSameActions || pressedBindings.All(p => p.Action != binding.Action)) + // set data as KeyUp if we're all done with this action. + state.Data = binding.GetAction(); } }