2018-01-05 11:21:19 +00:00
|
|
|
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
2017-05-03 03:53:45 +00:00
|
|
|
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
|
|
|
|
2018-02-02 09:47:54 +00:00
|
|
|
|
using System;
|
2018-02-02 10:35:44 +00:00
|
|
|
|
using System.Collections.Generic;
|
2017-07-26 04:22:46 +00:00
|
|
|
|
using osu.Game.Beatmaps;
|
2017-12-30 20:23:18 +00:00
|
|
|
|
using osu.Game.Rulesets.Scoring;
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
2018-02-02 08:52:55 +00:00
|
|
|
|
namespace osu.Game.Rulesets.Objects
|
2017-05-03 03:53:45 +00:00
|
|
|
|
{
|
|
|
|
|
public class HitWindows
|
|
|
|
|
{
|
2018-02-08 04:38:31 +00:00
|
|
|
|
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
|
2018-02-02 10:35:44 +00: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 05:16:31 +00:00
|
|
|
|
{ HitResult.Meh, (302, 272, 242) },
|
2018-02-02 10:35:44 +00:00
|
|
|
|
{ HitResult.Miss, (376, 346, 316) },
|
|
|
|
|
};
|
2017-05-09 11:55:20 +00:00
|
|
|
|
|
2017-05-03 03:53:45 +00:00
|
|
|
|
/// <summary>
|
2018-02-08 04:54:08 +00: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 03:53:45 +00:00
|
|
|
|
/// </summary>
|
2018-02-08 04:38:31 +00:00
|
|
|
|
public double Perfect { get; protected set; }
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 04:54:08 +00:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Great"/> result.
|
2017-05-03 03:53:45 +00:00
|
|
|
|
/// </summary>
|
2018-02-08 04:38:31 +00:00
|
|
|
|
public double Great { get; protected set; }
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 04:54:08 +00:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Good"/> result.
|
2017-05-03 03:53:45 +00:00
|
|
|
|
/// </summary>
|
2018-02-08 04:38:31 +00:00
|
|
|
|
public double Good { get; protected set; }
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 04:54:08 +00:00
|
|
|
|
/// Hit window for an <see cref="HitResult.OK"/> result.
|
|
|
|
|
/// The user can only achieve this result if <see cref="AllowsOk"/> is true.
|
2017-05-03 03:53:45 +00:00
|
|
|
|
/// </summary>
|
2018-02-08 04:38:31 +00:00
|
|
|
|
public double Ok { get; protected set; }
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-02-08 04:54:08 +00:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Meh"/> result.
|
2017-05-03 03:53:45 +00:00
|
|
|
|
/// </summary>
|
2018-02-08 04:38:31 +00:00
|
|
|
|
public double Meh { get; protected set; }
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// <summary>
|
2018-02-08 04:54:08 +00:00
|
|
|
|
/// Hit window for a <see cref="HitResult.Miss"/> result.
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// </summary>
|
2018-02-08 04:38:31 +00:00
|
|
|
|
public double Miss { get; protected set; }
|
2017-05-03 03:53:45 +00:00
|
|
|
|
|
2018-02-08 04:54:08 +00: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 08:29:54 +00: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 03:53:45 +00:00
|
|
|
|
public HitWindows(double difficulty)
|
|
|
|
|
{
|
2018-02-02 10:35:44 +00: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 03:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-22 07:28:44 +00:00
|
|
|
|
/// <summary>
|
2018-02-02 11:29:50 +00:00
|
|
|
|
/// Retrieves the <see cref="HitResult"/> for a time offset.
|
2017-05-22 07:28:44 +00:00
|
|
|
|
/// </summary>
|
2018-02-02 11:29:50 +00:00
|
|
|
|
/// <param name="timeOffset">The time offset.</param>
|
2018-02-02 09:47:54 +00:00
|
|
|
|
/// <returns>The hit result, or null if <paramref name="timeOffset"/> doesn't result in a judgement.</returns>
|
|
|
|
|
public HitResult? ResultFor(double timeOffset)
|
2017-05-22 07:28:44 +00:00
|
|
|
|
{
|
2018-02-02 09:47:54 +00:00
|
|
|
|
timeOffset = Math.Abs(timeOffset);
|
|
|
|
|
|
2018-02-08 04:54:08 +00:00
|
|
|
|
if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect))
|
2017-09-05 10:44:59 +00:00
|
|
|
|
return HitResult.Perfect;
|
2018-02-02 11:29:50 +00:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Great))
|
2017-09-05 10:44:59 +00:00
|
|
|
|
return HitResult.Great;
|
2018-02-02 11:29:50 +00:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Good))
|
2017-09-05 10:44:59 +00:00
|
|
|
|
return HitResult.Good;
|
2018-02-08 04:54:08 +00:00
|
|
|
|
if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok))
|
2017-09-05 10:44:59 +00:00
|
|
|
|
return HitResult.Ok;
|
2018-02-02 11:29:50 +00:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Meh))
|
2017-09-05 10:44:59 +00:00
|
|
|
|
return HitResult.Meh;
|
2018-02-02 11:29:50 +00:00
|
|
|
|
if (timeOffset <= HalfWindowFor(HitResult.Miss))
|
2018-02-02 09:47:54 +00:00
|
|
|
|
return HitResult.Miss;
|
|
|
|
|
|
2017-05-22 07:28:44 +00:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-02 11:29:50 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves half the hit window for a <see cref="HitResult"/>.
|
2018-02-08 05:25:44 +00: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 11:29:50 +00: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 08:29:54 +00:00
|
|
|
|
/// <summary>
|
2018-02-08 05:25:44 +00: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.
|
|
|
|
|
/// This happens if <paramref name="timeOffset"/> ≤ <see cref="Meh"/>.
|
2018-02-02 09:47:54 +00: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 11:29:50 +00:00
|
|
|
|
public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh);
|
2018-02-02 09:47:54 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Multiplies all hit windows by a value.
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// </summary>
|
2018-02-02 09:47:54 +00:00
|
|
|
|
/// <param name="windows">The hit windows to multiply.</param>
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// <param name="value">The value to multiply each hit window by.</param>
|
2017-05-03 03:53:45 +00:00
|
|
|
|
public static HitWindows operator *(HitWindows windows, double value)
|
|
|
|
|
{
|
2018-02-02 09:47:54 +00:00
|
|
|
|
windows.Perfect *= value;
|
|
|
|
|
windows.Great *= value;
|
|
|
|
|
windows.Good *= value;
|
|
|
|
|
windows.Ok *= value;
|
2018-02-02 09:56:44 +00:00
|
|
|
|
windows.Meh *= value;
|
2018-02-02 09:47:54 +00:00
|
|
|
|
windows.Miss *= value;
|
|
|
|
|
|
|
|
|
|
return windows;
|
2017-05-03 03:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// <summary>
|
2018-02-02 09:47:54 +00:00
|
|
|
|
/// Divides all hit windows by a value.
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// </summary>
|
2018-02-02 09:47:54 +00:00
|
|
|
|
/// <param name="windows">The hit windows to divide.</param>
|
2017-05-10 08:29:54 +00:00
|
|
|
|
/// <param name="value">The value to divide each hit window by.</param>
|
2017-05-03 03:53:45 +00:00
|
|
|
|
public static HitWindows operator /(HitWindows windows, double value)
|
|
|
|
|
{
|
2018-02-02 09:47:54 +00:00
|
|
|
|
windows.Perfect /= value;
|
|
|
|
|
windows.Great /= value;
|
|
|
|
|
windows.Good /= value;
|
|
|
|
|
windows.Ok /= value;
|
2018-02-02 09:56:44 +00:00
|
|
|
|
windows.Meh /= value;
|
2018-02-02 09:47:54 +00:00
|
|
|
|
windows.Miss /= value;
|
|
|
|
|
|
|
|
|
|
return windows;
|
2017-05-03 03:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|