From 50bcd4538a4f1c750a157baa3c3a60391cdc79c4 Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 6 Jun 2025 00:00:51 +0200 Subject: [PATCH] initial 1-10k --- Quaver.API/Enums/GameMode.cs | 11 +- Quaver.API/Helpers/ModeHelper.cs | 88 +++++++++-- Quaver.API/Maps/Parsers/Malody/MalodyFile.cs | 13 +- Quaver.API/Maps/Parsers/OsuBeatmap.cs | 23 +-- .../Rulesets/Keys/DifficultyProcessorKeys.cs | 148 +++++++++--------- Quaver.API/Maps/Qua.cs | 19 +-- Quaver.API/Replays/Replay.cs | 51 +----- Quaver.API/Replays/ReplayKeyPressState.cs | 6 +- .../Replays/Virtual/VirtualReplayPlayer.cs | 41 ++--- 9 files changed, 196 insertions(+), 204 deletions(-) diff --git a/Quaver.API/Enums/GameMode.cs b/Quaver.API/Enums/GameMode.cs index 9a8a976b5..327d02076 100644 --- a/Quaver.API/Enums/GameMode.cs +++ b/Quaver.API/Enums/GameMode.cs @@ -16,6 +16,15 @@ namespace Quaver.API.Enums public enum GameMode { Keys4 = 1, - Keys7 = 2 + Keys7 = 2, + + Keys1 = 3, + Keys2 = 4, + Keys3 = 5, + Keys5 = 6, + Keys6 = 7, + Keys8 = 8, + Keys9 = 9, + Keys10 = 10, } } diff --git a/Quaver.API/Helpers/ModeHelper.cs b/Quaver.API/Helpers/ModeHelper.cs index 1f73fefef..22ceebce9 100644 --- a/Quaver.API/Helpers/ModeHelper.cs +++ b/Quaver.API/Helpers/ModeHelper.cs @@ -12,6 +12,8 @@ namespace Quaver.API.Helpers { public static class ModeHelper { + public static int MaxKeyCount => Enum.GetValues(typeof(GameMode)).Length; + /// /// Converts game mode to short hand version. /// @@ -21,19 +23,25 @@ public static class ModeHelper /// public static string ToShortHand(GameMode mode, bool hasScratch = false) { + string res; switch (mode) { - case GameMode.Keys4: - if (hasScratch) - return "4K+1"; - return "4K"; - case GameMode.Keys7: - if (hasScratch) - return "7K+1"; - return "7K"; + case GameMode.Keys4: res = "4K"; break; + case GameMode.Keys7: res = "7K"; break; + + case GameMode.Keys1: res = "1K"; break; + case GameMode.Keys2: res = "2K"; break; + case GameMode.Keys3: res = "3K"; break; + case GameMode.Keys5: res = "5K"; break; + case GameMode.Keys6: res = "6K"; break; + case GameMode.Keys8: res = "8K"; break; + case GameMode.Keys9: res = "9K"; break; + case GameMode.Keys10: res = "10K"; break; default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null); } + + return res + (hasScratch ? "+1" : ""); } /// @@ -46,13 +54,69 @@ public static string ToLongHand(GameMode mode) { switch (mode) { - case GameMode.Keys4: - return "4 Keys"; - case GameMode.Keys7: - return "7 Keys"; + case GameMode.Keys4: return "4 Keys"; + case GameMode.Keys7: return "7 Keys"; + + case GameMode.Keys1: return "1 Keys"; + case GameMode.Keys2: return "2 Keys"; + case GameMode.Keys3: return "3 Keys"; + case GameMode.Keys5: return "5 Keys"; + case GameMode.Keys6: return "6 Keys"; + case GameMode.Keys8: return "8 Keys"; + case GameMode.Keys9: return "9 Keys"; + case GameMode.Keys10: return "10 Keys"; + default: + throw new ArgumentOutOfRangeException(nameof(mode), mode, null); + } + } + + public static int ToKeyCount(GameMode mode, bool hasScratch = false) + { + int res; + switch (mode) + { + + case GameMode.Keys4: res = 4; break; + case GameMode.Keys7: res = 7; break; + + case GameMode.Keys1: res = 1; break; + case GameMode.Keys2: res = 2; break; + case GameMode.Keys3: res = 3; break; + case GameMode.Keys5: res = 5; break; + case GameMode.Keys6: res = 6; break; + case GameMode.Keys8: res = 8; break; + case GameMode.Keys9: res = 9; break; + case GameMode.Keys10: res = 10; break; default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null); } + + return res + (hasScratch ? 1 : 0); + } + + public static GameMode FromKeyCount(int keyCount) + { + switch (keyCount) + { + case 4: return GameMode.Keys4; + case 7: return GameMode.Keys7; + + case 1: return GameMode.Keys1; + case 2: return GameMode.Keys2; + case 3: return GameMode.Keys3; + case 5: return GameMode.Keys5; + case 6: return GameMode.Keys6; + case 8: return GameMode.Keys8; + case 9: return GameMode.Keys9; + case 10: return GameMode.Keys10; + default: throw new ArgumentOutOfRangeException(nameof(keyCount), keyCount, null); + } + } + + public static bool IsKeyMode(GameMode mode) + { + // we only have keys gamemode for now... + return true; } } } diff --git a/Quaver.API/Maps/Parsers/Malody/MalodyFile.cs b/Quaver.API/Maps/Parsers/Malody/MalodyFile.cs index 1944e96ce..1fd54100e 100644 --- a/Quaver.API/Maps/Parsers/Malody/MalodyFile.cs +++ b/Quaver.API/Maps/Parsers/Malody/MalodyFile.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using Quaver.API.Enums; +using Quaver.API.Helpers; using Quaver.API.Maps.Structures; using System; using System.Collections.Generic; @@ -57,17 +58,7 @@ public Qua ToQua() if (Meta.Mode != 0) throw new ArgumentException("Only the 'Key' Malody game mode can be converted to Qua"); - switch (Meta.Keymode.Keymode) - { - case 4: - qua.Mode = GameMode.Keys4; - break; - case 7: - qua.Mode = GameMode.Keys7; - break; - default: - throw new InvalidEnumArgumentException(); - } + qua.Mode = ModeHelper.FromKeyCount(Meta.Keymode.Keymode); foreach (var tp in TimingPoints) { diff --git a/Quaver.API/Maps/Parsers/OsuBeatmap.cs b/Quaver.API/Maps/Parsers/OsuBeatmap.cs index 4cc6201bc..a864afdc9 100644 --- a/Quaver.API/Maps/Parsers/OsuBeatmap.cs +++ b/Quaver.API/Maps/Parsers/OsuBeatmap.cs @@ -293,7 +293,7 @@ public OsuBeatmap(string filePath) case "CircleSize": KeyCount = int.Parse(value, CultureInfo.InvariantCulture); - if (KeyCount != 4 && KeyCount != 7 && KeyCount != 5 && KeyCount != 8) + if (KeyCount < 1 || KeyCount > ModeHelper.MaxKeyCount) IsValid = false; break; case "OverallDifficulty": @@ -476,27 +476,10 @@ public Qua ToQua(bool checkValidity = true) Tags = Tags, Creator = Creator, DifficultyName = Version, - Description = $"This is a Quaver converted version of {Creator}'s map." + Description = $"This is a Quaver converted version of {Creator}'s map.", + Mode = ModeHelper.FromKeyCount(KeyCount) }; - // Get the correct game mode based on the amount of keys the map has. - switch (KeyCount) - { - case 4: - qua.Mode = GameMode.Keys4; - break; - case 7: - qua.Mode = GameMode.Keys7; - break; - case 8: - qua.Mode = GameMode.Keys7; - qua.HasScratchKey = true; - break; - default: - qua.Mode = (GameMode)(-1); - break; - } - foreach (var path in CustomAudioSamples) { qua.CustomAudioSamples.Add(new CustomAudioSampleInfo() diff --git a/Quaver.API/Maps/Processors/Difficulty/Rulesets/Keys/DifficultyProcessorKeys.cs b/Quaver.API/Maps/Processors/Difficulty/Rulesets/Keys/DifficultyProcessorKeys.cs index 349ef6488..021e4918a 100644 --- a/Quaver.API/Maps/Processors/Difficulty/Rulesets/Keys/DifficultyProcessorKeys.cs +++ b/Quaver.API/Maps/Processors/Difficulty/Rulesets/Keys/DifficultyProcessorKeys.cs @@ -46,52 +46,72 @@ public class DifficultyProcessorKeys : DifficultyProcessor /// /// Assumes that the assigned hand will be the one to press that key /// - private Dictionary LaneToHand4K { get; set; } = new Dictionary() + private static Hand LaneToHand(int lane, int keyCount) { - {1, Hand.Left}, - {2, Hand.Left}, - {3, Hand.Right}, - {4, Hand.Right} - }; + if (lane < 1 || lane > keyCount) + throw new ArgumentOutOfRangeException(nameof(lane), "Lane must be between 1 and keyCount"); - /// - /// Assumes that the assigned hand will be the one to press that key - /// - private Dictionary LaneToHand7K { get; set; } = new Dictionary() - { - {1, Hand.Left}, - {2, Hand.Left}, - {3, Hand.Left}, - {4, Hand.Ambiguous}, - {5, Hand.Right}, - {6, Hand.Right}, - {7, Hand.Right} - }; + var half = keyCount / 2; - /// - /// Assumes that the assigned finger will be the one to press that key. - /// - private Dictionary LaneToFinger4K { get; set; } = new Dictionary() - { - {1, FingerState.Middle}, - {2, FingerState.Index}, - {3, FingerState.Index}, - {4, FingerState.Middle} - }; + if (keyCount % 2 == 0) + { + if (lane <= half) + return Hand.Left; + else + return Hand.Right; + } + else + { + if (lane <= half) + return Hand.Left; + + if (lane == half + 1) + return Hand.Ambiguous; + else + return Hand.Right; + } + } /// /// Assumes that the assigned finger will be the one to press that key. /// - private Dictionary LaneToFinger7K { get; set; } = new Dictionary() + private static FingerState LaneToFinger(int lane, int keyCount) { - {1, FingerState.Ring}, - {2, FingerState.Middle}, - {3, FingerState.Index}, - {4, FingerState.Thumb}, - {5, FingerState.Index}, - {6, FingerState.Middle}, - {7, FingerState.Ring} - }; + if (lane < 1 || lane > keyCount) + throw new ArgumentOutOfRangeException(nameof(lane), "Lane must be between 1 and keyCount"); + + var half = keyCount / 2; + + if (keyCount <= 9) + { + // even key count + if (keyCount % 2 == 0) + { + if (lane <= half) + return (FingerState)(1 << (half - lane)); + else + return (FingerState)(1 << (lane - (half + 1))); + } + + // odd key count + if (lane <= half) + return (FingerState)(1 << (half - lane)); + if (lane == half + 1) + return FingerState.Thumb; + return (FingerState)(1 << (lane - (half + 2))); + } + + if (keyCount == 10) + { + if (lane <= half - 1) + return (FingerState)(1 << (half - 1 - lane)); + if (lane == half || lane == half + 1) + return FingerState.Thumb; + return (FingerState)(1 << (lane - (half + 2))); + } + + throw new ArgumentOutOfRangeException(nameof(keyCount), "Key count must be between 1 and 10"); + } /// /// Value of confidence that there's vibro manipulation in the calculated map. @@ -114,7 +134,7 @@ public DifficultyProcessorKeys(Qua map, StrainConstants constants, ModIdentifier bool detailedSolve = false) : base(map, constants, mods) { // Cast the current Strain Constants Property to the correct type. - StrainConstants = (StrainConstantsKeys) constants; + StrainConstants = (StrainConstantsKeys)constants; // Don't bother calculating map difficulty if there's less than 2 hit objects if (map.HitObjects.Count < 2) @@ -144,15 +164,10 @@ public void CalculateDifficulty(ModIdentifier mods) var rate = ModHelper.GetRateFromMods(mods); // Compute for overall difficulty - switch (Map.Mode) - { - case GameMode.Keys4: - OverallDifficulty = ComputeForOverallDifficulty(rate); - break; - case GameMode.Keys7: - OverallDifficulty = (ComputeForOverallDifficulty(rate, Hand.Left) + ComputeForOverallDifficulty(rate, Hand.Right) ) / 2; - break; - } + if (ModeHelper.ToKeyCount(Map.Mode) % 2 == 0) + OverallDifficulty = ComputeForOverallDifficulty(rate); + else + OverallDifficulty = (ComputeForOverallDifficulty(rate, Hand.Left) + ComputeForOverallDifficulty(rate, Hand.Right)) / 2; } /// @@ -193,19 +208,12 @@ private void ComputeBaseStrainStates(float rate, Hand assumeHand) var curStrainData = new StrainSolverData(curHitOb, rate); // Assign Finger and Hand States - switch (Map.Mode) - { - case GameMode.Keys4: - curHitOb.FingerState = LaneToFinger4K[Map.HitObjects[i].Lane]; - curStrainData.Hand = LaneToHand4K[Map.HitObjects[i].Lane]; - break; - case GameMode.Keys7: - curHitOb.FingerState = LaneToFinger7K[Map.HitObjects[i].Lane]; - curStrainData.Hand = LaneToHand7K[Map.HitObjects[i].Lane] == Hand.Ambiguous - ? assumeHand - : LaneToHand7K[Map.HitObjects[i].Lane]; - break; - } + var keyCount = ModeHelper.ToKeyCount(Map.Mode); + + curHitOb.FingerState = LaneToFinger(Map.HitObjects[i].Lane, keyCount); + + var hand = LaneToHand(Map.HitObjects[i].Lane, keyCount); + curStrainData.Hand = hand == Hand.Ambiguous ? assumeHand : hand; // Add Strain Solver Data to list StrainSolverData.Add(curStrainData); @@ -283,7 +291,7 @@ private void ComputeForFingerActions() if (curHitOb.Hand == nextHitOb.Hand && nextHitOb.StartTime > curHitOb.StartTime) { // Determined by if there's a minijack within 2 set of chords/single notes - var actionJackFound = ( curHitOb.FingerState & nextHitOb.FingerState ) != 0; + var actionJackFound = (curHitOb.FingerState & nextHitOb.FingerState) != 0; // Determined by if a chord is found in either finger state var actionChordFound = curHitOb.HandChord || nextHitOb.HandChord; @@ -437,7 +445,7 @@ private void ComputeForJackManipulation() // 93.7ms = 160bpm 1/4 vibro // 35f = 35ms tolerance before hitting vibro point (88.2ms, 170bpm vibro) - var durationValue = Math.Min(1, Math.Max(0, (StrainConstants.VibroActionDurationMs + StrainConstants.VibroActionToleranceMs - data.FingerActionDurationMs ) / StrainConstants.VibroActionToleranceMs)); + var durationValue = Math.Min(1, Math.Max(0, (StrainConstants.VibroActionDurationMs + StrainConstants.VibroActionToleranceMs - data.FingerActionDurationMs) / StrainConstants.VibroActionToleranceMs)); var durationMultiplier = 1 - durationValue * (1 - StrainConstants.VibroMultiplier); var manipulationFoundRatio = 1 - longJackSize / StrainConstants.VibroMaxLength * (1 - StrainConstants.VibroLengthMultiplier); @@ -479,7 +487,7 @@ private void ComputeForLnMultiplier() if (next != null) { - // Check to see if the target hitobject is layered inside the current LN + // Check to see if the target hitobject is layered inside the current LN if (next.StartTime < data.EndTime - StrainConstants.LnEndThresholdMs) { if (next.StartTime >= data.StartTime + StrainConstants.LnEndThresholdMs) @@ -614,7 +622,7 @@ private float CalculateOverallDifficulty() // We do not consider sections without notes, since there are no "easy notes". Those sections have barely // affected the rating in the old difficulty calculator. - var continuity = (float) bins.Where(strain => strain > 0) + var continuity = (float)bins.Where(strain => strain > 0) .Average(strain => Math.Sqrt(strain / easyRatingCutoff)); // The average continuity of maps in our dataset has been observed to be around 0.85, so we take that @@ -631,13 +639,13 @@ private float CalculateOverallDifficulty() if (continuity > avgContinuity) { - var continuityFactor = 1 - (continuity - avgContinuity)/(maxContinuity - avgContinuity); - continuityAdjustment = Math.Min(avgAdjustment, Math.Max(minAdjustment, continuityFactor * ( avgAdjustment - minAdjustment ) + minAdjustment + var continuityFactor = 1 - (continuity - avgContinuity) / (maxContinuity - avgContinuity); + continuityAdjustment = Math.Min(avgAdjustment, Math.Max(minAdjustment, continuityFactor * (avgAdjustment - minAdjustment) + minAdjustment )); } else { - var continuityFactor = 1 - (continuity - minContinuity)/(avgContinuity - minContinuity); + var continuityFactor = 1 - (continuity - minContinuity) / (avgContinuity - minContinuity); continuityAdjustment = Math.Min(maxAdjustment, Math.Max(avgAdjustment, continuityFactor * (maxAdjustment - avgAdjustment) + avgAdjustment)); } @@ -667,7 +675,7 @@ private float CalculateOverallDifficulty() private void ComputeNoteDensityData(float rate) { //MapLength = Qua.Length; - AverageNoteDensity = SECONDS_TO_MILLISECONDS * Map.HitObjects.Count / ( Map.Length * ( -.5f * rate + 1.5f ) ); + AverageNoteDensity = SECONDS_TO_MILLISECONDS * Map.HitObjects.Count / (Map.Length * (-.5f * rate + 1.5f)); } /// @@ -695,7 +703,7 @@ private float GetCoefficientValue(float duration, float xMin, float xMax, float } // compute for difficulty - return lowestDifficulty + (strainMax - lowestDifficulty) * (float) Math.Pow(ratio, exp); + return lowestDifficulty + (strainMax - lowestDifficulty) * (float)Math.Pow(ratio, exp); } } } \ No newline at end of file diff --git a/Quaver.API/Maps/Qua.cs b/Quaver.API/Maps/Qua.cs index e6e955fae..e51b41e68 100644 --- a/Quaver.API/Maps/Qua.cs +++ b/Quaver.API/Maps/Qua.cs @@ -571,14 +571,7 @@ public float GetActionsPerSecond(float rate = 1.0f) /// This translates mode to key count. /// /// - public int GetKeyCount(bool includeScratch = true) => - Mode switch - { - GameMode.Keys4 => 4, - GameMode.Keys7 => 7, - _ => throw new InvalidEnumArgumentException(), - } + - (HasScratchKey && includeScratch ? 1 : 0); + public int GetKeyCount(bool includeScratch = true) => ModeHelper.ToKeyCount(Mode, HasScratchKey && includeScratch); /// /// Finds the most common BPM in a Qua object. @@ -725,12 +718,10 @@ public DifficultyProcessor SolveDifficulty(ModIdentifier mods = ModIdentifier.No qua.ApplyMods(mods); } - return Mode switch - { - GameMode.Keys4 => new DifficultyProcessorKeys(qua, new StrainConstantsKeys(), mods), - GameMode.Keys7 => new DifficultyProcessorKeys(qua, new StrainConstantsKeys(), mods), - _ => throw new InvalidEnumArgumentException(), - }; + if (ModeHelper.IsKeyMode(qua.Mode)) + return new DifficultyProcessorKeys(qua, new StrainConstantsKeys(), mods); + else + throw new InvalidEnumArgumentException(); } /// diff --git a/Quaver.API/Replays/Replay.cs b/Quaver.API/Replays/Replay.cs index d0af0977c..3458e0d39 100644 --- a/Quaver.API/Replays/Replay.cs +++ b/Quaver.API/Replays/Replay.cs @@ -383,32 +383,7 @@ public static Replay GeneratePerfectReplayKeys(Replay replay, Qua map) /// /// /// - public static ReplayKeyPressState KeyLaneToPressState(int lane) - { - switch (lane) - { - case 1: - return ReplayKeyPressState.K1; - case 2: - return ReplayKeyPressState.K2; - case 3: - return ReplayKeyPressState.K3; - case 4: - return ReplayKeyPressState.K4; - case 5: - return ReplayKeyPressState.K5; - case 6: - return ReplayKeyPressState.K6; - case 7: - return ReplayKeyPressState.K7; - case 8: - return ReplayKeyPressState.K8; - case 9: - return ReplayKeyPressState.K9; - default: - throw new ArgumentException("Lane specified must be between 1 and 7"); - } - } + public static ReplayKeyPressState KeyLaneToPressState(int lane) => (ReplayKeyPressState)(1 << (lane - 1)); /// /// Converts a key press state to a list of lanes that are active. @@ -419,24 +394,12 @@ public static List KeyPressStateToLanes(ReplayKeyPressState keys) { var lanes = new List(); - if (keys.HasFlag(ReplayKeyPressState.K1)) - lanes.Add(0); - if (keys.HasFlag(ReplayKeyPressState.K2)) - lanes.Add(1); - if (keys.HasFlag(ReplayKeyPressState.K3)) - lanes.Add(2); - if (keys.HasFlag(ReplayKeyPressState.K4)) - lanes.Add(3); - if (keys.HasFlag(ReplayKeyPressState.K5)) - lanes.Add(4); - if (keys.HasFlag(ReplayKeyPressState.K6)) - lanes.Add(5); - if (keys.HasFlag(ReplayKeyPressState.K7)) - lanes.Add(6); - if (keys.HasFlag(ReplayKeyPressState.K8)) - lanes.Add(7); - if (keys.HasFlag(ReplayKeyPressState.K9)) - lanes.Add(8); + // MaxKeyCount + 2 to include the 2 keys used for scratch + for (var i = 0; i < ModeHelper.MaxKeyCount + 2; i++) + { + if (keys.HasFlag((ReplayKeyPressState)(1 << i))) + lanes.Add(i); + } return lanes; } diff --git a/Quaver.API/Replays/ReplayKeyPressState.cs b/Quaver.API/Replays/ReplayKeyPressState.cs index b11ebf5bd..ed7fa1a0b 100644 --- a/Quaver.API/Replays/ReplayKeyPressState.cs +++ b/Quaver.API/Replays/ReplayKeyPressState.cs @@ -23,6 +23,10 @@ public enum ReplayKeyPressState K6 = 1 << 5, K7 = 1 << 6, K8 = 1 << 7, - K9 = 1 << 8 // Scratch Lane Second Key on 7K+1 + K9 = 1 << 8, + K10 = 1 << 9, + K11 = 1 << 10, + K12 = 1 << 11, + } } diff --git a/Quaver.API/Replays/Virtual/VirtualReplayPlayer.cs b/Quaver.API/Replays/Virtual/VirtualReplayPlayer.cs index ee055a6d9..8b86668ca 100644 --- a/Quaver.API/Replays/Virtual/VirtualReplayPlayer.cs +++ b/Quaver.API/Replays/Virtual/VirtualReplayPlayer.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using Quaver.API.Enums; +using Quaver.API.Helpers; using Quaver.API.Maps; using Quaver.API.Maps.Processors.Scoring; using Quaver.API.Maps.Processors.Scoring.Data; @@ -99,35 +100,13 @@ public VirtualReplayPlayer(Replay replay, Qua map, JudgementWindows windows = nu map.HitObjects.ForEach(x => ActiveHitObjects.Add(x)); // Add virtual key bindings based on the game mode of the replay. - switch (Map.Mode) - { - case GameMode.Keys4: - InputKeyStore = new List() - { - new VirtualReplayKeyBinding(ReplayKeyPressState.K1), - new VirtualReplayKeyBinding(ReplayKeyPressState.K2), - new VirtualReplayKeyBinding(ReplayKeyPressState.K3), - new VirtualReplayKeyBinding(ReplayKeyPressState.K4), - new VirtualReplayKeyBinding(ReplayKeyPressState.K5), - }; - break; - case GameMode.Keys7: - InputKeyStore = new List() - { - new VirtualReplayKeyBinding(ReplayKeyPressState.K1), - new VirtualReplayKeyBinding(ReplayKeyPressState.K2), - new VirtualReplayKeyBinding(ReplayKeyPressState.K3), - new VirtualReplayKeyBinding(ReplayKeyPressState.K4), - new VirtualReplayKeyBinding(ReplayKeyPressState.K5), - new VirtualReplayKeyBinding(ReplayKeyPressState.K6), - new VirtualReplayKeyBinding(ReplayKeyPressState.K7), - new VirtualReplayKeyBinding(ReplayKeyPressState.K8), - new VirtualReplayKeyBinding(ReplayKeyPressState.K9), - }; - break; - default: - throw new ArgumentOutOfRangeException(); - } + InputKeyStore = new List(); + var keyCount = ModeHelper.ToKeyCount(map.Mode); + if (map.HasScratchKey) + keyCount += 2; // Scratch can be pressed by 2 keys + + for (var i = 0; i < keyCount; i++) + InputKeyStore.Add(new VirtualReplayKeyBinding((ReplayKeyPressState)(1 << i))); } /// @@ -222,8 +201,8 @@ private void HandleKeyPressesInFrame() { var inputLane = key; - // Allow scratch key to be dual-binded to lane 8 - if (Map.HasScratchKey && Map.Mode == GameMode.Keys7 && key + 1 == 9) + // Allow scratch key to be dual-binded + if (Map.HasScratchKey && key + 1 == ModeHelper.ToKeyCount(Map.Mode) + 2) inputLane--; // This key was uniquely pressed during this frame.