mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 12:33:01 +08:00
Merge remote-tracking branch 'origin/master' into editor-pattern-adjustments
This commit is contained in:
commit
87baa6febd
@ -1,200 +0,0 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Judgements
|
||||
{
|
||||
public class HitWindows
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// PERFECT hit window at OD = 10.
|
||||
/// </summary>
|
||||
private const double perfect_min = 27.8;
|
||||
/// <summary>
|
||||
/// PERFECT hit window at OD = 5.
|
||||
/// </summary>
|
||||
private const double perfect_mid = 38.8;
|
||||
/// <summary>
|
||||
/// PERFECT hit window at OD = 0.
|
||||
/// </summary>
|
||||
private const double perfect_max = 44.8;
|
||||
|
||||
/// <summary>
|
||||
/// GREAT hit window at OD = 10.
|
||||
/// </summary>
|
||||
private const double great_min = 68;
|
||||
/// <summary>
|
||||
/// GREAT hit window at OD = 5.
|
||||
/// </summary>
|
||||
private const double great_mid = 98;
|
||||
/// <summary>
|
||||
/// GREAT hit window at OD = 0.
|
||||
/// </summary>
|
||||
private const double great_max = 128;
|
||||
|
||||
/// <summary>
|
||||
/// GOOD hit window at OD = 10.
|
||||
/// </summary>
|
||||
private const double good_min = 134;
|
||||
/// <summary>
|
||||
/// GOOD hit window at OD = 5.
|
||||
/// </summary>
|
||||
private const double good_mid = 164;
|
||||
/// <summary>
|
||||
/// GOOD hit window at OD = 0.
|
||||
/// </summary>
|
||||
private const double good_max = 194;
|
||||
|
||||
/// <summary>
|
||||
/// OK hit window at OD = 10.
|
||||
/// </summary>
|
||||
private const double ok_min = 194;
|
||||
/// <summary>
|
||||
/// OK hit window at OD = 5.
|
||||
/// </summary>
|
||||
private const double ok_mid = 224;
|
||||
/// <summary>
|
||||
/// OK hit window at OD = 0.
|
||||
/// </summary>
|
||||
private const double ok_max = 254;
|
||||
|
||||
/// <summary>
|
||||
/// BAD hit window at OD = 10.
|
||||
/// </summary>
|
||||
private const double bad_min = 242;
|
||||
/// <summary>
|
||||
/// BAD hit window at OD = 5.
|
||||
/// </summary>
|
||||
private const double bad_mid = 272;
|
||||
/// <summary>
|
||||
/// BAD hit window at OD = 0.
|
||||
/// </summary>
|
||||
private const double bad_max = 302;
|
||||
|
||||
/// <summary>
|
||||
/// MISS hit window at OD = 10.
|
||||
/// </summary>
|
||||
private const double miss_min = 316;
|
||||
/// <summary>
|
||||
/// MISS hit window at OD = 5.
|
||||
/// </summary>
|
||||
private const double miss_mid = 346;
|
||||
/// <summary>
|
||||
/// MISS hit window at OD = 0.
|
||||
/// </summary>
|
||||
private const double miss_max = 376;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a PERFECT hit.
|
||||
/// </summary>
|
||||
public double Perfect = perfect_mid;
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a GREAT hit.
|
||||
/// </summary>
|
||||
public double Great = great_mid;
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a GOOD hit.
|
||||
/// </summary>
|
||||
public double Good = good_mid;
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for an OK hit.
|
||||
/// </summary>
|
||||
public double Ok = ok_mid;
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a BAD hit.
|
||||
/// </summary>
|
||||
public double Bad = bad_mid;
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a MISS hit.
|
||||
/// </summary>
|
||||
public double Miss = miss_mid;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs default hit windows.
|
||||
/// </summary>
|
||||
public HitWindows()
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public HitWindows(double difficulty)
|
||||
{
|
||||
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, perfect_max, perfect_mid, perfect_min);
|
||||
Great = BeatmapDifficulty.DifficultyRange(difficulty, great_max, great_mid, great_min);
|
||||
Good = BeatmapDifficulty.DifficultyRange(difficulty, good_max, good_mid, good_min);
|
||||
Ok = BeatmapDifficulty.DifficultyRange(difficulty, ok_max, ok_mid, ok_min);
|
||||
Bad = BeatmapDifficulty.DifficultyRange(difficulty, bad_max, bad_mid, bad_min);
|
||||
Miss = BeatmapDifficulty.DifficultyRange(difficulty, miss_max, miss_mid, miss_min);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the hit result for a time offset.
|
||||
/// </summary>
|
||||
/// <param name="hitOffset">The time offset.</param>
|
||||
/// <returns>The hit result, or null if the time offset results in a miss.</returns>
|
||||
public HitResult? ResultFor(double hitOffset)
|
||||
{
|
||||
if (hitOffset <= Perfect / 2)
|
||||
return HitResult.Perfect;
|
||||
if (hitOffset <= Great / 2)
|
||||
return HitResult.Great;
|
||||
if (hitOffset <= Good / 2)
|
||||
return HitResult.Good;
|
||||
if (hitOffset <= Ok / 2)
|
||||
return HitResult.Ok;
|
||||
if (hitOffset <= Bad / 2)
|
||||
return HitResult.Meh;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs new hit windows which have been multiplied by a value.
|
||||
/// </summary>
|
||||
/// <param name="windows">The original hit windows.</param>
|
||||
/// <param name="value">The value to multiply each hit window by.</param>
|
||||
public static HitWindows operator *(HitWindows windows, double value)
|
||||
{
|
||||
return new HitWindows
|
||||
{
|
||||
Perfect = windows.Perfect * value,
|
||||
Great = windows.Great * value,
|
||||
Good = windows.Good * value,
|
||||
Ok = windows.Ok * value,
|
||||
Bad = windows.Bad * value,
|
||||
Miss = windows.Miss * value
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs new hit windows which have been divided by a value.
|
||||
/// </summary>
|
||||
/// <param name="windows">The original hit windows.</param>
|
||||
/// <param name="value">The value to divide each hit window by.</param>
|
||||
public static HitWindows operator /(HitWindows windows, double value)
|
||||
{
|
||||
return new HitWindows
|
||||
{
|
||||
Perfect = windows.Perfect / value,
|
||||
Great = windows.Great / value,
|
||||
Good = windows.Good / value,
|
||||
Ok = windows.Ok / value,
|
||||
Bad = windows.Bad / value,
|
||||
Miss = windows.Miss / value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -212,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset > HitObject.HitWindows.Bad / 2)
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
{
|
||||
AddJudgement(new HoldNoteTailJudgement
|
||||
{
|
||||
@ -224,14 +223,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
return;
|
||||
}
|
||||
|
||||
double offset = Math.Abs(timeOffset);
|
||||
|
||||
if (offset > HitObject.HitWindows.Miss / 2)
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
if (result == HitResult.None)
|
||||
return;
|
||||
|
||||
AddJudgement(new HoldNoteTailJudgement
|
||||
{
|
||||
Result = HitObject.HitWindows.ResultFor(offset) ?? HitResult.Miss,
|
||||
Result = result,
|
||||
HasBroken = holdNote.hasBroken
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@ -63,17 +62,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset > HitObject.HitWindows.Bad / 2)
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
AddJudgement(new ManiaJudgement { Result = HitResult.Miss });
|
||||
return;
|
||||
}
|
||||
|
||||
double offset = Math.Abs(timeOffset);
|
||||
|
||||
if (offset > HitObject.HitWindows.Miss / 2)
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
if (result == HitResult.None)
|
||||
return;
|
||||
|
||||
AddJudgement(new ManiaJudgement { Result = HitObject.HitWindows.ResultFor(offset) ?? HitResult.Miss });
|
||||
AddJudgement(new ManiaJudgement { Result = result });
|
||||
}
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Objects.Types;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
@ -9,5 +11,13 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
public abstract class ManiaHitObject : HitObject, IHasColumn
|
||||
{
|
||||
public virtual int Column { get; set; }
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
HitWindows.AllowsPerfect = true;
|
||||
HitWindows.AllowsOk = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
/// <summary>
|
||||
@ -13,17 +8,5 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// </summary>
|
||||
public class Note : ManiaHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The key-press hit window for this note.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public HitWindows HitWindows { get; protected set; } = new HitWindows();
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
HitWindows = new HitWindows(difficulty.OverallDifficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,6 @@
|
||||
<Compile Include="Beatmaps\Patterns\Pattern.cs" />
|
||||
<Compile Include="Configuration\ManiaConfigManager.cs" />
|
||||
<Compile Include="MathUtils\FastRandom.cs" />
|
||||
<Compile Include="Judgements\HitWindows.cs" />
|
||||
<Compile Include="Judgements\HoldNoteTailJudgement.cs" />
|
||||
<Compile Include="Judgements\HoldNoteTickJudgement.cs" />
|
||||
<Compile Include="Judgements\ManiaJudgement.cs" />
|
||||
|
@ -72,14 +72,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset > HitObject.HitWindowFor(HitResult.Meh))
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
AddJudgement(new OsuJudgement { Result = HitResult.Miss });
|
||||
return;
|
||||
}
|
||||
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
if (result == HitResult.None)
|
||||
return;
|
||||
|
||||
AddJudgement(new OsuJudgement
|
||||
{
|
||||
Result = HitObject.ScoreResultForOffset(Math.Abs(timeOffset)),
|
||||
Result = result,
|
||||
PositionOffset = Vector2.Zero //todo: set to correct value
|
||||
});
|
||||
}
|
||||
@ -104,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Expire(true);
|
||||
|
||||
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
|
||||
LifetimeEnd = HitObject.StartTime + HitObject.HitWindowFor(HitResult.Miss);
|
||||
LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss);
|
||||
break;
|
||||
case ArmedState.Miss:
|
||||
ApproachCircle.FadeOut(50);
|
||||
|
@ -7,7 +7,6 @@ using OpenTK;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
@ -15,11 +14,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public const double OBJECT_RADIUS = 64;
|
||||
|
||||
private const double hittable_range = 300;
|
||||
public double HitWindow50 = 150;
|
||||
public double HitWindow100 = 80;
|
||||
public double HitWindow300 = 30;
|
||||
|
||||
public double TimePreempt = 600;
|
||||
public double TimeFadein = 400;
|
||||
|
||||
@ -45,32 +39,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
public virtual bool NewCombo { get; set; }
|
||||
public int IndexInCurrentCombo { get; set; }
|
||||
|
||||
public double HitWindowFor(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
default:
|
||||
return hittable_range;
|
||||
case HitResult.Meh:
|
||||
return HitWindow50;
|
||||
case HitResult.Good:
|
||||
return HitWindow100;
|
||||
case HitResult.Great:
|
||||
return HitWindow300;
|
||||
}
|
||||
}
|
||||
|
||||
public HitResult ScoreResultForOffset(double offset)
|
||||
{
|
||||
if (offset < HitWindowFor(HitResult.Great))
|
||||
return HitResult.Great;
|
||||
if (offset < HitWindowFor(HitResult.Good))
|
||||
return HitResult.Good;
|
||||
if (offset < HitWindowFor(HitResult.Meh))
|
||||
return HitResult.Meh;
|
||||
return HitResult.Miss;
|
||||
}
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
@ -78,10 +46,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
|
||||
TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
|
||||
|
||||
HitWindow50 = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 200, 150, 100);
|
||||
HitWindow100 = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 140, 100, 60);
|
||||
HitWindow300 = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 80, 50, 20);
|
||||
|
||||
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
|
||||
}
|
||||
}
|
||||
|
@ -89,20 +89,20 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
|
||||
|
||||
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
|
||||
if (h.StartTime - h.HitWindowFor(HitResult.Miss) > endTime + h.HitWindowFor(HitResult.Meh) + 50)
|
||||
if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
|
||||
{
|
||||
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(HitResult.Miss), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
|
||||
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
|
||||
}
|
||||
else if (h.StartTime - h.HitWindowFor(HitResult.Meh) > endTime + h.HitWindowFor(HitResult.Meh) + 50)
|
||||
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
|
||||
{
|
||||
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
|
||||
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
|
||||
}
|
||||
else if (h.StartTime - h.HitWindowFor(HitResult.Good) > endTime + h.HitWindowFor(HitResult.Good) + 50)
|
||||
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
|
||||
{
|
||||
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(HitResult.Good), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(HitResult.Good), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
|
||||
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -38,30 +37,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset > HitObject.HitWindowGood)
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
AddJudgement(new TaikoJudgement { Result = HitResult.Miss });
|
||||
return;
|
||||
}
|
||||
|
||||
double hitOffset = Math.Abs(timeOffset);
|
||||
|
||||
if (hitOffset > HitObject.HitWindowMiss)
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
if (result == HitResult.None)
|
||||
return;
|
||||
|
||||
if (!validKeyPressed)
|
||||
if (!validKeyPressed || result == HitResult.Miss)
|
||||
AddJudgement(new TaikoJudgement { Result = HitResult.Miss });
|
||||
else if (hitOffset < HitObject.HitWindowGood)
|
||||
else
|
||||
{
|
||||
AddJudgement(new TaikoJudgement
|
||||
{
|
||||
Result = hitOffset < HitObject.HitWindowGreat ? HitResult.Great : HitResult.Good,
|
||||
Result = result,
|
||||
Final = !HitObject.IsStrong
|
||||
});
|
||||
|
||||
SecondHitAllowed = true;
|
||||
}
|
||||
else
|
||||
AddJudgement(new TaikoJudgement { Result = HitResult.Miss });
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action)
|
||||
@ -90,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
switch (State.Value)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
this.Delay(HitObject.HitWindowMiss).Expire();
|
||||
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
||||
break;
|
||||
case ArmedState.Miss:
|
||||
this.FadeOut(100)
|
||||
|
@ -1,35 +1,9 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class Hit : TaikoHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The hit window that results in a "GREAT" hit.
|
||||
/// </summary>
|
||||
public double HitWindowGreat = 35;
|
||||
|
||||
/// <summary>
|
||||
/// The hit window that results in a "GOOD" hit.
|
||||
/// </summary>
|
||||
public double HitWindowGood = 80;
|
||||
|
||||
/// <summary>
|
||||
/// The hit window that results in a "MISS".
|
||||
/// </summary>
|
||||
public double HitWindowMiss = 95;
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
|
||||
HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);
|
||||
HitWindowMiss = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 135, 95, 70);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,5 +40,17 @@ namespace osu.Game.Beatmaps
|
||||
return mid - (mid - min) * (5 - difficulty) / 5;
|
||||
return mid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
|
||||
/// </summary>
|
||||
/// <param name="difficulty">The difficulty value to be mapped.</param>
|
||||
/// <param name="range">The values that define the two linear ranges.</param>
|
||||
/// <param name="range.od0">Minimum of the resulting range which will be achieved by a difficulty value of 0.</param>
|
||||
/// <param name="range.od5">Midpoint of the resulting range which will be achieved by a difficulty value of 5.</param>
|
||||
/// <param name="range.od10">Maximum of the resulting range which will be achieved by a difficulty value of 10.</param>
|
||||
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
|
||||
public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
|
||||
=> DifficultyRange(difficulty, range.od0, range.od5, range.od10);
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,19 @@ namespace osu.Game.Rulesets.Objects
|
||||
[JsonIgnore]
|
||||
public bool Kiai { get; private set; }
|
||||
|
||||
private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
|
||||
|
||||
private HitWindows hitWindows;
|
||||
|
||||
/// <summary>
|
||||
/// The hit windows for this <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
public HitWindows HitWindows
|
||||
{
|
||||
get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty));
|
||||
protected set => hitWindows = value;
|
||||
}
|
||||
|
||||
private readonly SortedList<HitObject> nestedHitObjects = new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
|
||||
[JsonIgnore]
|
||||
@ -75,6 +88,9 @@ namespace osu.Game.Rulesets.Objects
|
||||
|
||||
Kiai = effectPoint.KiaiMode;
|
||||
SampleControlPoint = samplePoint;
|
||||
|
||||
overallDifficulty = difficulty.OverallDifficulty;
|
||||
hitWindows = null;
|
||||
}
|
||||
|
||||
protected virtual void CreateNestedHitObjects()
|
||||
|
173
osu.Game/Rulesets/Objects/HitWindows.cs
Normal file
173
osu.Game/Rulesets/Objects/HitWindows.cs
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// 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<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
|
||||
{
|
||||
{ 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) },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a <see cref="HitResult.Perfect"/> result.
|
||||
/// The user can only achieve receive this result if <see cref="AllowsPerfect"/> is true.
|
||||
/// </summary>
|
||||
public double Perfect { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a <see cref="HitResult.Great"/> result.
|
||||
/// </summary>
|
||||
public double Great { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a <see cref="HitResult.Good"/> result.
|
||||
/// </summary>
|
||||
public double Good { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for an <see cref="HitResult.Ok"/> result.
|
||||
/// The user can only achieve this result if <see cref="AllowsOk"/> is true.
|
||||
/// </summary>
|
||||
public double Ok { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a <see cref="HitResult.Meh"/> result.
|
||||
/// </summary>
|
||||
public double Meh { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hit window for a <see cref="HitResult.Miss"/> result.
|
||||
/// </summary>
|
||||
public double Miss { get; protected set; }
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <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>
|
||||
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]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the <see cref="HitResult"/> for a time offset.
|
||||
/// </summary>
|
||||
/// <param name="timeOffset">The time offset.</param>
|
||||
/// <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))
|
||||
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 HitResult.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves half the hit window for a <see cref="HitResult"/>.
|
||||
/// This is useful if the hit window for one half of the hittable range of a <see cref="HitObject"/> is required.
|
||||
/// </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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh);
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies all hit windows by a value.
|
||||
/// </summary>
|
||||
/// <param name="windows">The hit windows to multiply.</param>
|
||||
/// <param name="value">The value to multiply each hit window by.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides all hit windows by a value.
|
||||
/// </summary>
|
||||
/// <param name="windows">The hit windows to divide.</param>
|
||||
/// <param name="value">The value to divide each hit window by.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// Ranges from [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
|
||||
public static Vector2 PositionAt(this IHasCurve obj, double progress)
|
||||
=> obj.Curve.PositionAt(obj.ProgressAt(progress));
|
||||
@ -42,6 +43,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// Finds the progress along the curve, accounting for repeat logic.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
|
||||
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
|
||||
public static double ProgressAt(this IHasCurve obj, double progress)
|
||||
@ -55,6 +57,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// Determines which span of the curve the progress point is on.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
|
||||
/// <returns>[0, SpanCount) where 0 is the first run.</returns>
|
||||
public static int SpanAt(this IHasCurve obj, double progress)
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
protected virtual bool UserScrollSpeedAdjustment => true;
|
||||
|
||||
/// <summary>
|
||||
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
|
||||
/// The container that contains the <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects;
|
||||
|
||||
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScrollingPlayfield"/>.
|
||||
/// </summary>
|
||||
/// <param name="scrollingAxes">The axes on which <see cref="DrawableHitObject"/>s in this container should scroll.</param>
|
||||
/// <param name="direction">The direction in which <see cref="DrawableHitObject"/>s in this container should scroll.</param>
|
||||
/// <param name="customWidth">Whether we want our internal coordinate system to be scaled to a specified width</param>
|
||||
protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null)
|
||||
: base(customWidth)
|
||||
|
@ -56,19 +56,19 @@ namespace osu.Game.Users
|
||||
public int? Id;
|
||||
}
|
||||
|
||||
[JsonProperty(@"isAdmin")]
|
||||
[JsonProperty(@"is_admin")]
|
||||
public bool IsAdmin;
|
||||
|
||||
[JsonProperty(@"isSupporter")]
|
||||
[JsonProperty(@"is_supporter")]
|
||||
public bool IsSupporter;
|
||||
|
||||
[JsonProperty(@"isGMT")]
|
||||
[JsonProperty(@"is_gmt")]
|
||||
public bool IsGMT;
|
||||
|
||||
[JsonProperty(@"isQAT")]
|
||||
[JsonProperty(@"is_qat")]
|
||||
public bool IsQAT;
|
||||
|
||||
[JsonProperty(@"isBNG")]
|
||||
[JsonProperty(@"is_bng")]
|
||||
public bool IsBNG;
|
||||
|
||||
[JsonProperty(@"is_active")]
|
||||
@ -107,7 +107,7 @@ namespace osu.Game.Users
|
||||
[JsonProperty(@"playmode")]
|
||||
public string PlayMode;
|
||||
|
||||
[JsonProperty(@"profileOrder")]
|
||||
[JsonProperty(@"profile_order")]
|
||||
public string[] ProfileOrder;
|
||||
|
||||
[JsonProperty(@"kudosu")]
|
||||
|
@ -340,6 +340,7 @@
|
||||
<Compile Include="Overlays\Social\SocialListPanel.cs" />
|
||||
<Compile Include="Overlays\Social\SocialPanel.cs" />
|
||||
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
|
||||
<Compile Include="Rulesets\Objects\HitWindows.cs" />
|
||||
<Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" />
|
||||
<Compile Include="Rulesets\Objects\CatmullApproximator.cs" />
|
||||
<Compile Include="Rulesets\UI\HitObjectContainer.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user