// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects { public class HitWindows { private static readonly IReadOnlyDictionary base_ranges = new Dictionary { { HitResult.Perfect, (44.8, 38.8, 27.8) }, { HitResult.Great, (128, 98, 68 ) }, { HitResult.Good, (194, 164, 134) }, { HitResult.Ok, (254, 224, 194) }, { HitResult.Meh, (302, 272, 242) }, { HitResult.Miss, (376, 346, 316) }, }; /// /// Hit window for a result. /// The user can only achieve receive this result if is true. /// public double Perfect { get; protected set; } /// /// Hit window for a result. /// public double Great { get; protected set; } /// /// Hit window for a result. /// public double Good { get; protected set; } /// /// Hit window for an result. /// The user can only achieve this result if is true. /// public double Ok { get; protected set; } /// /// Hit window for a result. /// public double Meh { get; protected set; } /// /// Hit window for a result. /// public double Miss { get; protected set; } /// /// Whether it's possible to achieve a result. /// public bool AllowsPerfect; /// /// Whether it's possible to achieve a result. /// public bool AllowsOk; /// /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window. /// /// The parameter. public HitWindows(double difficulty) { Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]); Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); } /// /// Retrieves the for a time offset. /// /// The time offset. /// The hit result, or null if doesn't result in a judgement. public HitResult? ResultFor(double timeOffset) { timeOffset = Math.Abs(timeOffset); if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect)) return HitResult.Perfect; if (timeOffset <= HalfWindowFor(HitResult.Great)) return HitResult.Great; if (timeOffset <= HalfWindowFor(HitResult.Good)) return HitResult.Good; if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok)) return HitResult.Ok; if (timeOffset <= HalfWindowFor(HitResult.Meh)) return HitResult.Meh; if (timeOffset <= HalfWindowFor(HitResult.Miss)) return HitResult.Miss; return null; } /// /// Retrieves half the hit window for a . /// This is useful if the hit window for one half of the hittable range of a is required. /// /// The expected . /// One half of the hit window for . public double HalfWindowFor(HitResult result) { switch (result) { case HitResult.Perfect: return Perfect / 2; case HitResult.Great: return Great / 2; case HitResult.Good: return Good / 2; case HitResult.Ok: return Ok / 2; case HitResult.Meh: return Meh / 2; case HitResult.Miss: return Miss / 2; default: throw new ArgumentException(nameof(result)); } } /// /// Given a time offset, whether the can ever be hit in the future with a non- result. /// This happens if . /// /// The time offset. /// Whether the can be hit at any point in the future from this time offset. public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh); /// /// Multiplies all hit windows by a value. /// /// The hit windows to multiply. /// The value to multiply each hit window by. public static HitWindows operator *(HitWindows windows, double value) { windows.Perfect *= value; windows.Great *= value; windows.Good *= value; windows.Ok *= value; windows.Meh *= value; windows.Miss *= value; return windows; } /// /// Divides all hit windows by a value. /// /// The hit windows to divide. /// The value to divide each hit window by. public static HitWindows operator /(HitWindows windows, double value) { windows.Perfect /= value; windows.Great /= value; windows.Good /= value; windows.Ok /= value; windows.Meh /= value; windows.Miss /= value; return windows; } } }