osu/osu.Game/Input/RealmKeyBindingStore.cs
Bartłomiej Dach 828cedea33
Fix bindings being cleared if multiple bindings for same action have the same combination
This actually seems to be the case in catch (dash is bound to shift
twice). This is annoying but harmless, so let's work around it for now
to avoid ruining users' configs.
2023-10-16 22:20:26 +02:00

161 lines
6.4 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Database;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using Realms;
namespace osu.Game.Input
{
public class RealmKeyBindingStore
{
private readonly RealmAccess realm;
private readonly ReadableKeyCombinationProvider keyCombinationProvider;
public RealmKeyBindingStore(RealmAccess realm, ReadableKeyCombinationProvider keyCombinationProvider)
{
this.realm = realm;
this.keyCombinationProvider = keyCombinationProvider;
}
/// <summary>
/// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action.
/// </summary>
/// <param name="globalAction">The action to lookup.</param>
/// <returns>A set of display strings for all the user's key configuration for the action.</returns>
public IReadOnlyList<string> GetReadableKeyCombinationsFor(GlobalAction globalAction)
{
List<string> combinations = new List<string>();
realm.Run(context =>
{
foreach (var action in context.All<RealmKeyBinding>().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction))
{
string str = keyCombinationProvider.GetReadableString(action.KeyCombination);
// even if found, the readable string may be empty for an unbound action.
if (str.Length > 0)
combinations.Add(str);
}
});
return combinations;
}
/// <summary>
/// Register all defaults for this store.
/// </summary>
/// <param name="container">The container to populate defaults from.</param>
/// <param name="rulesets">The rulesets to populate defaults from.</param>
public void Register(KeyBindingContainer container, IEnumerable<RulesetInfo> rulesets)
{
realm.Run(r =>
{
using (var transaction = r.BeginWrite())
{
// intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed.
// this is much faster as a result.
var existingBindings = r.All<RealmKeyBinding>().ToList();
insertDefaults(r, existingBindings, container.DefaultKeyBindings);
foreach (var ruleset in rulesets)
{
var instance = ruleset.CreateInstance();
foreach (int variant in instance.AvailableVariants)
insertDefaults(r, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant);
}
transaction.Commit();
}
});
}
private void insertDefaults(Realm realm, List<RealmKeyBinding> existingBindings, IEnumerable<IKeyBinding> defaults, string? rulesetName = null, int? variant = null)
{
// compare counts in database vs defaults for each action type.
foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
{
IEnumerable<RealmKeyBinding> existing = existingBindings.Where(k =>
k.RulesetName == rulesetName
&& k.Variant == variant
&& k.ActionInt == (int)defaultsForAction.Key);
int defaultsCount = defaultsForAction.Count();
int existingCount = existing.Count();
if (defaultsCount > existingCount)
{
// insert any defaults which are missing.
realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding(k.Action, k.KeyCombination, rulesetName, variant)));
}
else if (defaultsCount < existingCount)
{
// generally this shouldn't happen, but if the user has more key bindings for an action than we expect,
// remove the last entries until the count matches for sanity.
foreach (var k in existing.TakeLast(existingCount - defaultsCount).ToArray())
{
realm.Remove(k);
// Remove from the local flattened/cached list so future lookups don't query now deleted rows.
existingBindings.Remove(k);
}
}
}
}
/// <summary>
/// Keys which should not be allowed for gameplay input purposes.
/// </summary>
private static readonly IEnumerable<InputKey> banned_keys = new[]
{
InputKey.MouseWheelDown,
InputKey.MouseWheelLeft,
InputKey.MouseWheelUp,
InputKey.MouseWheelRight
};
public static bool CheckValidForGameplay(KeyCombination combination)
{
foreach (var key in banned_keys)
{
if (combination.Keys.Contains(key))
return false;
}
return true;
}
/// <summary>
/// Clears all <see cref="RealmKeyBinding.KeyCombination"/>s from the provided <paramref name="keyBindings"/>
/// which are assigned to more than one binding.
/// </summary>
/// <param name="keyBindings">The <see cref="RealmKeyBinding"/>s to de-duplicate.</param>
/// <returns>Number of bindings cleared.</returns>
public static int ClearDuplicateBindings(IEnumerable<IKeyBinding> keyBindings)
{
int countRemoved = 0;
var lookup = keyBindings.ToLookup(kb => kb.KeyCombination);
foreach (var group in lookup)
{
if (group.Select(kb => kb.Action).Distinct().Count() <= 1)
continue;
foreach (var binding in group)
binding.KeyCombination = new KeyCombination(InputKey.None);
countRemoved += group.Count();
}
return countRemoved;
}
}
}