1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 02:42:54 +08:00
osu-lazer/osu.Game/Rulesets/Objects/HitWindows.cs

174 lines
7.1 KiB
C#
Raw Normal View History

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
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;
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
{
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>
/// 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>
public double Perfect { get; protected set; }
2017-05-03 11:53:45 +08:00
/// <summary>
/// Hit window for a <see cref="HitResult.Great"/> result.
2017-05-03 11:53:45 +08:00
/// </summary>
public double Great { get; protected set; }
2017-05-03 11:53:45 +08:00
/// <summary>
/// Hit window for a <see cref="HitResult.Good"/> result.
2017-05-03 11:53:45 +08:00
/// </summary>
public double Good { get; protected set; }
2017-05-03 11:53:45 +08:00
/// <summary>
/// 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 11:53:45 +08:00
/// </summary>
public double Ok { get; protected set; }
2017-05-03 11:53:45 +08:00
/// <summary>
/// Hit window for a <see cref="HitResult.Meh"/> result.
2017-05-03 11:53:45 +08:00
/// </summary>
public double Meh { get; protected set; }
2017-05-03 11:53:45 +08:00
2017-05-10 16:29:54 +08:00
/// <summary>
/// Hit window for a <see cref="HitResult.Miss"/> result.
2017-05-10 16:29:54 +08:00
/// </summary>
public double Miss { get; protected set; }
2017-05-03 11:53:45 +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
}
/// <summary>
2018-02-02 19:29:50 +08:00
/// Retrieves the <see cref="HitResult"/> for a time offset.
/// </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)
{
timeOffset = Math.Abs(timeOffset);
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;
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))
return HitResult.Miss;
2018-02-08 13:25:59 +08:00
return HitResult.None;
}
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.
/// This happens if <paramref name="timeOffset"/> is less than what is required for a <see cref="Meh"/> result.
/// </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);
/// <summary>
/// Multiplies all hit windows by a value.
2017-05-10 16:29:54 +08:00
/// </summary>
/// <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)
{
windows.Perfect *= value;
windows.Great *= value;
windows.Good *= value;
windows.Ok *= value;
2018-02-02 17:56:44 +08:00
windows.Meh *= value;
windows.Miss *= value;
return windows;
2017-05-03 11:53:45 +08:00
}
2017-05-10 16:29:54 +08:00
/// <summary>
/// Divides all hit windows by a value.
2017-05-10 16:29:54 +08:00
/// </summary>
/// <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)
{
windows.Perfect /= value;
windows.Great /= value;
windows.Good /= value;
windows.Ok /= value;
2018-02-02 17:56:44 +08:00
windows.Meh /= value;
windows.Miss /= value;
return windows;
2017-05-03 11:53:45 +08:00
}
}
}