2022-06-21 19:10:22 +08:00
|
|
|
// 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.
|
|
|
|
|
2022-06-21 20:24:30 +08:00
|
|
|
using System;
|
2022-06-21 19:10:22 +08:00
|
|
|
using System.Collections.Generic;
|
2022-06-21 20:24:30 +08:00
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Linq;
|
2022-06-21 19:37:17 +08:00
|
|
|
using osu.Framework.Input.Events;
|
2022-06-21 20:24:30 +08:00
|
|
|
using osu.Game.Rulesets.Mods;
|
|
|
|
using osuTK.Input;
|
2022-06-21 19:10:22 +08:00
|
|
|
|
|
|
|
namespace osu.Game.Overlays.Mods.Input
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Uses bindings from stable 1:1.
|
|
|
|
/// </summary>
|
|
|
|
public class ClassicModHotkeyHandler : IModHotkeyHandler
|
|
|
|
{
|
2022-06-21 20:24:30 +08:00
|
|
|
private static readonly Dictionary<Key, Type[]> mod_type_lookup = new Dictionary<Key, Type[]>
|
|
|
|
{
|
|
|
|
[Key.Q] = new[] { typeof(ModEasy) },
|
|
|
|
[Key.W] = new[] { typeof(ModNoFail) },
|
2023-12-27 21:35:19 +08:00
|
|
|
[Key.E] = new[] { typeof(ModHalfTime), typeof(ModDaycore) },
|
2022-06-21 20:24:30 +08:00
|
|
|
[Key.A] = new[] { typeof(ModHardRock) },
|
|
|
|
[Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) },
|
|
|
|
[Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) },
|
|
|
|
[Key.F] = new[] { typeof(ModHidden) },
|
|
|
|
[Key.G] = new[] { typeof(ModFlashlight) },
|
|
|
|
[Key.Z] = new[] { typeof(ModRelax) },
|
|
|
|
[Key.V] = new[] { typeof(ModAutoplay), typeof(ModCinema) }
|
|
|
|
};
|
|
|
|
|
2022-06-21 20:48:41 +08:00
|
|
|
private readonly bool allowIncompatibleSelection;
|
|
|
|
|
|
|
|
public ClassicModHotkeyHandler(bool allowIncompatibleSelection)
|
|
|
|
{
|
|
|
|
this.allowIncompatibleSelection = allowIncompatibleSelection;
|
|
|
|
}
|
|
|
|
|
2022-06-21 20:24:30 +08:00
|
|
|
public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable<ModState> availableMods)
|
|
|
|
{
|
|
|
|
if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch))
|
|
|
|
return false;
|
|
|
|
|
2023-06-02 16:33:38 +08:00
|
|
|
var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.Visible).ToArray();
|
2022-06-21 20:24:30 +08:00
|
|
|
|
|
|
|
if (matchingMods.Length == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (matchingMods.Length == 1)
|
|
|
|
{
|
|
|
|
matchingMods.Single().Active.Toggle();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-21 20:48:41 +08:00
|
|
|
if (allowIncompatibleSelection)
|
|
|
|
{
|
|
|
|
// easier path - multiple incompatible mods can be active at a time.
|
|
|
|
// this is used in the free mod select overlay.
|
|
|
|
// in this case, just toggle everything.
|
|
|
|
bool anyActive = matchingMods.Any(mod => mod.Active.Value);
|
|
|
|
foreach (var mod in matchingMods)
|
|
|
|
mod.Active.Value = !anyActive;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we now know there are multiple possible mods to handle, and only one of them can be active at a time.
|
|
|
|
// let's make sure of this just in case.
|
2022-06-21 20:24:30 +08:00
|
|
|
Debug.Assert(matchingMods.Count(modState => modState.Active.Value) <= 1);
|
|
|
|
int currentSelectedIndex = Array.FindIndex(matchingMods, modState => modState.Active.Value);
|
|
|
|
|
|
|
|
// `FindIndex` will return -1 if it doesn't find the item.
|
|
|
|
// this is convenient in the forward direction, since if we add 1 then we'll end up at the first item,
|
|
|
|
// but less so in the backwards direction.
|
|
|
|
// for convenience, detect this situation and set the index to one index past the last item.
|
|
|
|
// this makes it so that if we subtract 1 then we'll end up at the last item again.
|
|
|
|
if (currentSelectedIndex < 0 && e.ShiftPressed)
|
|
|
|
currentSelectedIndex = matchingMods.Length;
|
|
|
|
|
|
|
|
int indexToSelect = e.ShiftPressed ? currentSelectedIndex - 1 : currentSelectedIndex + 1;
|
|
|
|
|
|
|
|
// `currentSelectedIndex` and `indexToSelect` can both be equal to -1 or `matchingMods.Length`.
|
|
|
|
// if the former is beyond array range, it means nothing was previously selected and so there's nothing to deselect.
|
|
|
|
// if the latter is beyond array range, it means that either the previous selection was first and we're going backwards,
|
|
|
|
// or it was last and we're going forwards.
|
|
|
|
// in either case there is nothing to select.
|
|
|
|
if (currentSelectedIndex >= 0 && currentSelectedIndex <= matchingMods.Length - 1)
|
|
|
|
matchingMods[currentSelectedIndex].Active.Value = false;
|
|
|
|
if (indexToSelect >= 0 && indexToSelect <= matchingMods.Length - 1)
|
|
|
|
matchingMods[indexToSelect].Active.Value = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static bool matches(ModState modState, Type[] typesToMatch)
|
|
|
|
=> typesToMatch.Any(typeToMatch => typeToMatch.IsInstanceOfType(modState.Mod));
|
2022-06-21 19:10:22 +08:00
|
|
|
}
|
|
|
|
}
|