// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { /// /// Detects special hit object patterns which are easier to hit using special techniques /// than normally assumed in the fully-alternating play style. /// /// /// This component detects two basic types of patterns, leveraged by the following techniques: /// /// Rolling allows hitting patterns with quickly and regularly alternating notes with a single hand. /// TL tapping makes hitting longer sequences of consecutive same-colour notes with little to no colour changes in-between. /// /// public class StaminaCheeseDetector { /// /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a roll. /// private const int roll_min_repetitions = 12; /// /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a TL tap. /// private const int tl_min_repetitions = 16; /// /// The list of all s in the map. /// private readonly List hitObjects; public StaminaCheeseDetector(List hitObjects) { this.hitObjects = hitObjects; } /// /// Finds and marks all objects in that special difficulty-reducing techiques apply to /// with the flag. /// public void FindCheese() { findRolls(3); findRolls(4); findTlTap(0, HitType.Rim); findTlTap(1, HitType.Rim); findTlTap(0, HitType.Centre); findTlTap(1, HitType.Centre); } /// /// Finds and marks all sequences hittable using a roll. /// /// The length of a single repeating pattern to consider (triplets/quadruplets). private void findRolls(int patternLength) { var history = new LimitedCapacityQueue(2 * patternLength); // for convenience, we're tracking the index of the item *before* our suspected repeat's start, // as that index can be simply subtracted from the current index to get the number of elements in between // without off-by-one errors int indexBeforeLastRepeat = -1; int lastMarkEnd = 0; for (int i = 0; i < hitObjects.Count; i++) { history.Enqueue(hitObjects[i]); if (!history.Full) continue; if (!containsPatternRepeat(history, patternLength)) { // we're setting this up for the next iteration, hence the +1. // right here this index will point at the queue's front (oldest item), // but that item is about to be popped next loop with an enqueue. indexBeforeLastRepeat = i - history.Count + 1; continue; } int repeatedLength = i - indexBeforeLastRepeat; if (repeatedLength < roll_min_repetitions) continue; markObjectsAsCheese(Math.Max(lastMarkEnd, i - repeatedLength + 1), i); lastMarkEnd = i; } } /// /// Determines whether the objects stored in contain a repetition of a pattern of length . /// private static bool containsPatternRepeat(LimitedCapacityQueue history, int patternLength) { for (int j = 0; j < patternLength; j++) { if (history[j].HitType != history[j + patternLength].HitType) return false; } return true; } /// /// Finds and marks all sequences hittable using a TL tap. /// /// Whether sequences starting with an odd- (1) or even-indexed (0) hit object should be checked. /// The type of hit to check for TL taps. private void findTlTap(int parity, HitType type) { int tlLength = -2; int lastMarkEnd = 0; for (int i = parity; i < hitObjects.Count; i += 2) { if (hitObjects[i].HitType == type) tlLength += 2; else tlLength = -2; if (tlLength < tl_min_repetitions) continue; markObjectsAsCheese(Math.Max(lastMarkEnd, i - tlLength + 1), i); lastMarkEnd = i; } } /// /// Marks all objects from to (inclusive) as . /// private void markObjectsAsCheese(int start, int end) { for (int i = start; i <= end; i++) hitObjects[i].StaminaCheese = true; } } }