2018-01-05 19:21:19 +08:00
|
|
|
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
2017-05-03 11:53:45 +08:00
|
|
|
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
|
|
|
|
2018-02-02 17:47:54 +08:00
|
|
|
|
using System;
|
2018-02-02 18:35:44 +08:00
|
|
|
|
using System.Collections.Generic;
|
2017-07-26 12:22:46 +08:00
|
|
|
|
using osu.Game.Beatmaps;
|
2017-12-31 04:23:18 +08:00
|
|
|
|
using osu.Game.Rulesets.Scoring;
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
2018-02-02 16:52:55 +08:00
|
|
|
|
namespace osu.Game.Rulesets.Objects
|
2017-05-03 11:53:45 +08:00
|
|
|
|
{
|
|
|
|
|
public class HitWindows
|
|
|
|
|
{
|
2018-02-08 12:38:31 +08:00
|
|
|
|
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
|
2018-02-02 18:35:44 +08:00
|
|
|
|
{
|
|
|
|
|
{ HitResult.Perfect, (44.8, 38.8, 27.8) },
|
|
|
|
|
{ HitResult.Great, (128, 98, 68 ) },
|
|
|
|
|
{ HitResult.Good, (194, 164, 134) },
|
|
|
|
|
{ HitResult.Ok, (254, 224, 194) },
|
2018-02-08 13:16:31 +08:00
|
|
|
|
{ HitResult.Meh, (302, 272, 242) },
|
2018-02-02 18:35:44 +08:00
|
|
|
|
{ HitResult.Miss, (376, 346, 316) },
|
|
|
|
|
};
|
2017-05-09 19:55:20 +08:00
|
|
|
|
|
2017-05-03 11:53:45 +08:00
|
|
|
|
/// <summary>
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Perfect"/> result.
|
|
|
|
|
/// The user can only achieve receive this result if <see cref="AllowsPerfect"/> is true.
|
2017-05-03 11:53:45 +08:00
|
|
|
|
/// </summary>
|
2018-02-08 12:38:31 +08:00
|
|
|
|
public double Perfect { get; protected set; }
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Great"/> result.
|
2017-05-03 11:53:45 +08:00
|
|
|
|
/// </summary>
|
2018-02-08 12:38:31 +08:00
|
|
|
|
public double Great { get; protected set; }
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Good"/> result.
|
2017-05-03 11:53:45 +08:00
|
|
|
|
/// </summary>
|
2018-02-08 12:38:31 +08:00
|
|
|
|
public double Good { get; protected set; }
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 22:47:03 +08:00
|
|
|
|
/// Hit window for an <see cref="HitResult.Ok"/> result.
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// The user can only achieve this result if <see cref="AllowsOk"/> is true.
|
2017-05-03 11:53:45 +08:00
|
|
|
|
/// </summary>
|
2018-02-08 12:38:31 +08:00
|
|
|
|
public double Ok { get; protected set; }
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Meh"/> result.
|
2017-05-03 11:53:45 +08:00
|
|
|
|
/// </summary>
|
2018-02-08 12:38:31 +08:00
|
|
|
|
public double Meh { get; protected set; }
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// <summary>
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Miss"/> result.
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// </summary>
|
2018-02-08 12:38:31 +08:00
|
|
|
|
public double Miss { get; protected set; }
|
2017-05-03 11:53:45 +08:00
|
|
|
|
|
2018-02-08 12:54:08 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether it's possible to achieve a <see cref="HitResult.Perfect"/> result.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool AllowsPerfect;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether it's possible to achieve a <see cref="HitResult.Ok"/> result.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool AllowsOk;
|
|
|
|
|
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="difficulty">The parameter.</param>
|
2017-05-03 11:53:45 +08:00
|
|
|
|
public HitWindows(double difficulty)
|
|
|
|
|
{
|
2018-02-02 18:35:44 +08:00
|
|
|
|
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]);
|
2017-05-03 11:53:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-22 15:28:44 +08:00
|
|
|
|
/// <summary>
|
2018-02-02 19:29:50 +08:00
|
|
|
|
/// Retrieves the <see cref="HitResult"/> for a time offset.
|
2017-05-22 15:28:44 +08:00
|
|
|
|
/// </summary>
|
2018-02-02 19:29:50 +08:00
|
|
|
|
/// <param name="timeOffset">The time offset.</param>
|
2018-02-08 13:25:59 +08:00
|
|
|
|
/// <returns>The hit result, or <see cref="HitResult.None"/> if <paramref name="timeOffset"/> doesn't result in a judgement.</returns>
|
|
|
|
|
public HitResult ResultFor(double timeOffset)
|
2017-05-22 15:28:44 +08:00
|
|
|
|
{
|
2018-02-02 17:47:54 +08:00
|
|
|
|
timeOffset = Math.Abs(timeOffset);
|
|
|
|
|
|
2018-02-08 12:54:08 +08:00
|
|
|
|
if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect))
|
2017-09-05 18:44:59 +08:00
|
|
|
|
return HitResult.Perfect;
|
2018-02-02 19:29:50 +08:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Great))
|
2017-09-05 18:44:59 +08:00
|
|
|
|
return HitResult.Great;
|
2018-02-02 19:29:50 +08:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Good))
|
2017-09-05 18:44:59 +08:00
|
|
|
|
return HitResult.Good;
|
2018-02-08 12:54:08 +08:00
|
|
|
|
if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok))
|
2017-09-05 18:44:59 +08:00
|
|
|
|
return HitResult.Ok;
|
2018-02-02 19:29:50 +08:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Meh))
|
2017-09-05 18:44:59 +08:00
|
|
|
|
return HitResult.Meh;
|
2018-02-02 19:29:50 +08:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Miss))
|
2018-02-02 17:47:54 +08:00
|
|
|
|
return HitResult.Miss;
|
|
|
|
|
|
2018-02-08 13:25:59 +08:00
|
|
|
|
return HitResult.None;
|
2017-05-22 15:28:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-02 19:29:50 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves half the hit window for a <see cref="HitResult"/>.
|
2018-02-08 13:25:44 +08:00
|
|
|
|
/// This is useful if the hit window for one half of the hittable range of a <see cref="HitObject"/> is required.
|
2018-02-02 19:29:50 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="result">The expected <see cref="HitResult"/>.</param>
|
|
|
|
|
/// <returns>One half of the hit window for <paramref name="result"/>.</returns>
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// <summary>
|
2018-02-08 13:25:44 +08:00
|
|
|
|
/// Given a time offset, whether the <see cref="HitObject"/> can ever be hit in the future with a non-<see cref="HitResult.Miss"/> result.
|
2018-02-08 22:47:03 +08:00
|
|
|
|
/// This happens if <paramref name="timeOffset"/> is less than what is required for a <see cref="Meh"/> result.
|
2018-02-02 17:47:54 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="timeOffset">The time offset.</param>
|
|
|
|
|
/// <returns>Whether the <see cref="HitObject"/> can be hit at any point in the future from this time offset.</returns>
|
2018-02-02 19:29:50 +08:00
|
|
|
|
public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh);
|
2018-02-02 17:47:54 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Multiplies all hit windows by a value.
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// </summary>
|
2018-02-02 17:47:54 +08:00
|
|
|
|
/// <param name="windows">The hit windows to multiply.</param>
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// <param name="value">The value to multiply each hit window by.</param>
|
2017-05-03 11:53:45 +08:00
|
|
|
|
public static HitWindows operator *(HitWindows windows, double value)
|
|
|
|
|
{
|
2018-02-02 17:47:54 +08:00
|
|
|
|
windows.Perfect *= value;
|
|
|
|
|
windows.Great *= value;
|
|
|
|
|
windows.Good *= value;
|
|
|
|
|
windows.Ok *= value;
|
2018-02-02 17:56:44 +08:00
|
|
|
|
windows.Meh *= value;
|
2018-02-02 17:47:54 +08:00
|
|
|
|
windows.Miss *= value;
|
|
|
|
|
|
|
|
|
|
return windows;
|
2017-05-03 11:53:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// <summary>
|
2018-02-02 17:47:54 +08:00
|
|
|
|
/// Divides all hit windows by a value.
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// </summary>
|
2018-02-02 17:47:54 +08:00
|
|
|
|
/// <param name="windows">The hit windows to divide.</param>
|
2017-05-10 16:29:54 +08:00
|
|
|
|
/// <param name="value">The value to divide each hit window by.</param>
|
2017-05-03 11:53:45 +08:00
|
|
|
|
public static HitWindows operator /(HitWindows windows, double value)
|
|
|
|
|
{
|
2018-02-02 17:47:54 +08:00
|
|
|
|
windows.Perfect /= value;
|
|
|
|
|
windows.Great /= value;
|
|
|
|
|
windows.Good /= value;
|
|
|
|
|
windows.Ok /= value;
|
2018-02-02 17:56:44 +08:00
|
|
|
|
windows.Meh /= value;
|
2018-02-02 17:47:54 +08:00
|
|
|
|
windows.Miss /= value;
|
|
|
|
|
|
|
|
|
|
return windows;
|
2017-05-03 11:53:45 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|