1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

ActionMapping doesn't support concurrent actions by default

But can when required. Also supports key combination bindings now.
This commit is contained in:
Dean Herbert 2017-08-10 16:08:43 +09:00
parent 720bd38d8e
commit 30bd1d70b5
7 changed files with 167 additions and 34 deletions

View File

@ -9,11 +9,11 @@ namespace osu.Game.Rulesets.Catch
{
public class CatchInputManager : ActionMappingInputManager<CatchAction>
{
public CatchInputManager(RulesetInfo ruleset) : base(ruleset)
public CatchInputManager(RulesetInfo ruleset) : base(ruleset, allowConcurrentActions: true)
{
}
protected override IDictionary<Key, CatchAction> CreateDefaultMappings() => new Dictionary<Key, CatchAction>
protected override IDictionary<KeyCombination, CatchAction> CreateDefaultMappings() => new Dictionary<KeyCombination, CatchAction>
{
{ Key.Z, CatchAction.MoveLeft },
{ Key.Left, CatchAction.MoveLeft },

View File

@ -3,10 +3,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Game.Rulesets;
using OpenTK.Input;
namespace osu.Game.Input
{
@ -21,58 +21,92 @@ namespace osu.Game.Input
private readonly int? variant;
private readonly bool allowConcurrentActions;
private readonly List<Binding> mappings = new List<Binding>();
/// <summary>
/// Create a new instance.
/// </summary>
/// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param>
/// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param>
protected ActionMappingInputManager(RulesetInfo ruleset = null, int? variant = null)
/// <param name="allowConcurrentActions">Allow concurrent actions to be actuated at once. Note that this disables chord bindings.</param>
protected ActionMappingInputManager(RulesetInfo ruleset = null, int? variant = null, bool allowConcurrentActions = false)
{
this.ruleset = ruleset;
this.variant = variant;
Mappings = CreateDefaultMappings();
this.allowConcurrentActions = allowConcurrentActions;
}
protected IDictionary<Key, T> Mappings { get; private set; }
protected abstract IDictionary<KeyCombination, T> CreateDefaultMappings();
protected abstract IDictionary<Key, T> CreateDefaultMappings();
private BindingStore store;
[BackgroundDependencyLoader]
private void load(BindingStore bindings)
{
var rulesetId = ruleset?.ID;
foreach (var b in bindings.Query<Binding>(b => b.RulesetID == rulesetId && b.Variant == variant))
Mappings[b.Key] = (T)(object)b.Action;
store = bindings;
ReloadMappings();
}
protected void ReloadMappings()
{
var rulesetId = ruleset?.ID;
mappings.Clear();
foreach (var kvp in CreateDefaultMappings())
mappings.Add(new Binding(kvp.Key, kvp.Value));
if (store != null)
{
foreach (var b in store.Query<Binding>(b => b.RulesetID == rulesetId && b.Variant == variant))
mappings.Add(b);
}
if (allowConcurrentActions)
{
// ensure we have no overlapping bindings.
foreach (var m in mappings)
foreach (var colliding in mappings.Where(k => !k.Keys.Equals(m.Keys) && k.Keys.CheckValid(m.Keys.Keys)))
throw new InvalidOperationException($"Multiple partially overlapping bindings are not supported ({m} and {colliding} are colliding)!");
}
}
private readonly List<Binding> pressedBindings = new List<Binding>();
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
mapKey(state, args.Key);
if (!args.Repeat && (allowConcurrentActions || pressedBindings.Count == 0))
{
Binding validBinding;
if ((validBinding = mappings.Except(pressedBindings).LastOrDefault(m => m.Keys.CheckValid(state.Keyboard.Keys))) != null)
{
// 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<T>();
}
}
return base.OnKeyDown(state, args);
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
mapKey(state, args.Key);
foreach (var binding in pressedBindings.ToList())
{
if (!binding.Keys.CheckValid(state.Keyboard.Keys))
{
// set data as KeyUp.
state.Data = binding.GetAction<T>();
// and clear the no-longer-valid combination/action.
pressedBindings.Remove(binding);
}
}
return base.OnKeyUp(state, args);
}
private void mapKey(InputState state, Key key)
{
T mappedData;
if (Mappings.TryGetValue(key, out mappedData))
state.Data = mappedData;
}
private T parseStringRepresentation(string str)
{
T res;
if (Enum.TryParse(str, out res))
return res;
return default(T);
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets;
using OpenTK.Input;
using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes;
@ -16,8 +15,31 @@ namespace osu.Game.Input
[Indexed]
public int? Variant { get; set; }
public Key Key { get; set; }
[Column("Keys")]
public string KeysString
{
get { return Keys.ToString(); }
set { Keys = value; }
}
public int Action { get; set; }
[Ignore]
public KeyCombination Keys { get; private set; }
public int Action { get; private set; }
public Binding()
{
}
public Binding(KeyCombination keys, object action)
{
Keys = keys;
Action = (int)action;
}
public virtual T GetAction<T>() => (T)(object)Action;
public override string ToString() => $"{KeysString}=>{Action}";
}
}

View File

@ -15,6 +15,24 @@ namespace osu.Game.Input
{
}
protected override int StoreVersion => 2;
protected override void PerformMigration(int currentVersion, int targetVersion)
{
base.PerformMigration(currentVersion, targetVersion);
while (currentVersion++ < targetVersion)
{
switch (currentVersion)
{
case 1:
// cannot migrate; breaking underlying changes.
Reset();
break;
}
}
}
protected override void Prepare(bool reset = false)
{
Connection.CreateTable<Binding>();

View File

@ -8,7 +8,7 @@ namespace osu.Game.Input
{
public class GlobalActionMappingInputManager : ActionMappingInputManager<OsuAction>
{
protected override IDictionary<Key, OsuAction> CreateDefaultMappings() => new Dictionary<Key, OsuAction>
protected override IDictionary<KeyCombination, OsuAction> CreateDefaultMappings() => new Dictionary<KeyCombination, OsuAction>
{
{ Key.F8, OsuAction.ToggleChat },
{ Key.F9, OsuAction.ToggleSocial },

View File

@ -0,0 +1,58 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK.Input;
namespace osu.Game.Input
{
/// <summary>
/// Represent a combination of more than one <see cref="Key"/>s.
/// </summary>
public class KeyCombination : IEquatable<KeyCombination>
{
public readonly IEnumerable<Key> Keys;
public KeyCombination(params Key[] keys)
{
Keys = keys;
}
public KeyCombination(IEnumerable<Key> keys)
{
Keys = keys;
}
public KeyCombination(string stringRepresentation)
{
Keys = stringRepresentation.Split(',').Select(s => (Key)int.Parse(s));
}
public bool CheckValid(IEnumerable<Key> keys) => !Keys.Except(keys).Any();
public bool Equals(KeyCombination other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Keys.SequenceEqual(other.Keys);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((KeyCombination)obj);
}
public override int GetHashCode() => Keys != null ? Keys.Select(k => k.GetHashCode()).Aggregate((h1, h2) => h1 + h2) : 0;
public static implicit operator KeyCombination(Key singleKey) => new KeyCombination(singleKey);
public static implicit operator KeyCombination(string stringRepresentation) => new KeyCombination(stringRepresentation);
public override string ToString() => Keys.Select(k => ((int)k).ToString()).Aggregate((s1, s2) => $"{s1},{s2}");
}
}

View File

@ -94,6 +94,7 @@
<Compile Include="Graphics\UserInterface\OsuContextMenuItem.cs" />
<Compile Include="Input\Binding.cs" />
<Compile Include="Input\BindingStore.cs" />
<Compile Include="Input\KeyCombination.cs" />
<Compile Include="Input\OsuAction.cs" />
<Compile Include="Input\GlobalActionMappingInputManager.cs" />
<Compile Include="IO\FileStore.cs" />