mirror of
https://github.com/ppy/osu.git
synced 2024-11-13 18:47:27 +08:00
Merge pull request #24280 from tybug/stable-notelock
Correctly implement stable notelock in Classic mod
This commit is contained in:
commit
8d0f6df329
@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false)
|
if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current, HitResult.Great) == ClickAction.Hit)
|
||||||
{
|
{
|
||||||
// force success
|
// force success
|
||||||
ApplyResult(r => r.Type = HitResult.Great);
|
ApplyResult(r => r.Type = HitResult.Great);
|
||||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (shouldHit && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false)
|
if (shouldHit && !userTriggered && timeOffset >= 0)
|
||||||
{
|
{
|
||||||
// force success
|
// force success
|
||||||
ApplyResult(r => r.Type = HitResult.Great);
|
ApplyResult(r => r.Type = HitResult.Great);
|
||||||
|
@ -11,17 +11,21 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -32,7 +36,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
public partial class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestScene
|
public partial class TestSceneLegacyHitPolicy : RateAdjustedBeatmapTestScene
|
||||||
{
|
{
|
||||||
private readonly OsuHitWindows referenceHitWindows;
|
private readonly OsuHitWindows referenceHitWindows;
|
||||||
|
|
||||||
@ -43,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly string? exportLocation = null;
|
private readonly string? exportLocation = null;
|
||||||
|
|
||||||
public TestSceneObjectOrderedHitPolicy()
|
public TestSceneLegacyHitPolicy()
|
||||||
{
|
{
|
||||||
referenceHitWindows = new OsuHitWindows();
|
referenceHitWindows = new OsuHitWindows();
|
||||||
referenceHitWindows.SetDifficulty(0);
|
referenceHitWindows.SetDifficulty(0);
|
||||||
@ -83,6 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
||||||
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
||||||
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
||||||
|
addClickActionAssert(0, ClickAction.Shake);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -119,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
||||||
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
||||||
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
||||||
|
addClickActionAssert(0, ClickAction.Shake);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -155,6 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
||||||
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
||||||
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
||||||
|
addClickActionAssert(0, ClickAction.Shake);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -191,7 +198,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[0], HitResult.Meh);
|
addJudgementAssert(hitObjects[0], HitResult.Meh);
|
||||||
addJudgementAssert(hitObjects[1], HitResult.Meh);
|
addJudgementAssert(hitObjects[1], HitResult.Meh);
|
||||||
addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190
|
addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190
|
||||||
addJudgementOffsetAssert(hitObjects[0], -90); // time_second_circle - first_circle_time - 90
|
addJudgementOffsetAssert(hitObjects[1], -190); // time_second_circle - first_circle_time - 90
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -229,13 +238,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[1], HitResult.Ok);
|
addJudgementAssert(hitObjects[1], HitResult.Ok);
|
||||||
addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190
|
addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190
|
||||||
addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time
|
addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests clicking a future circle after a slider's start time, but hitting all slider ticks.
|
/// Tests clicking a future circle after a slider's start time, but hitting the slider head and all slider ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMissSliderHeadAndHitAllSliderTicks()
|
public void TestHitCircleBeforeSliderHead()
|
||||||
{
|
{
|
||||||
const double time_slider = 1500;
|
const double time_slider = 1500;
|
||||||
const double time_circle = 1510;
|
const double time_circle = 1510;
|
||||||
@ -267,10 +278,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } }
|
new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } }
|
||||||
});
|
});
|
||||||
|
|
||||||
addJudgementAssert(hitObjects[0], HitResult.Miss);
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
addJudgementAssert(hitObjects[1], HitResult.Great);
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit);
|
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit);
|
||||||
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit);
|
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit);
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -314,6 +327,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[1], HitResult.Great);
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit);
|
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit);
|
||||||
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit);
|
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit);
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -353,6 +368,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
addJudgementAssert(hitObjects[0], HitResult.Great);
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
addJudgementAssert(hitObjects[1], HitResult.Meh);
|
addJudgementAssert(hitObjects[1], HitResult.Meh);
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -391,6 +407,102 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
addJudgementAssert(hitObjects[0], HitResult.Great);
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
addJudgementAssert(hitObjects[1], HitResult.Great);
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addClickActionAssert(0, ClickAction.Shake);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
|
addClickActionAssert(2, ClickAction.Hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlappingSliders()
|
||||||
|
{
|
||||||
|
const double time_first_slider = 1000;
|
||||||
|
const double time_second_slider = 1200;
|
||||||
|
Vector2 positionFirstSlider = new Vector2(100, 50);
|
||||||
|
Vector2 positionSecondSlider = new Vector2(100, 80);
|
||||||
|
var midpoint = (positionFirstSlider + positionSecondSlider) / 2;
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = time_first_slider,
|
||||||
|
Position = positionFirstSlider,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(25, 0),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = time_second_slider,
|
||||||
|
Position = positionSecondSlider,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(25, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_slider, Position = midpoint, Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_first_slider + 25, Position = midpoint, Actions = { OsuAction.LeftButton, OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_first_slider + 50, Position = midpoint },
|
||||||
|
new OsuReplayFrame { Time = time_second_slider, Position = positionSecondSlider + new Vector2(0, 10), Actions = { OsuAction.LeftButton } },
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Ok);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStacksDoNotShake()
|
||||||
|
{
|
||||||
|
const double time_stack_start = 1000;
|
||||||
|
Vector2 position = new Vector2(80);
|
||||||
|
|
||||||
|
var hitObjects = Enumerable.Range(0, 20).Select(i => new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_stack_start + i * 100,
|
||||||
|
Position = position
|
||||||
|
}).Cast<OsuHitObject>().ToList();
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_stack_start - 450, Position = new Vector2(55), Actions = { OsuAction.LeftButton } },
|
||||||
|
});
|
||||||
|
|
||||||
|
addClickActionAssert(0, ClickAction.Ignore);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutopilotReducesHittableRange()
|
||||||
|
{
|
||||||
|
const double time_circle = 1500;
|
||||||
|
Vector2 positionCircle = Vector2.Zero;
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_circle,
|
||||||
|
Position = positionCircle
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_circle - 250, Position = positionCircle, Actions = { OsuAction.LeftButton } }
|
||||||
|
}, new Mod[] { new OsuModAutopilot() });
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Miss);
|
||||||
|
// note lock prevented the object from being hit, so the judgement offset should be very late.
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh));
|
||||||
|
addClickActionAssert(0, ClickAction.Shake);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
|
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
|
||||||
@ -408,17 +520,30 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset)
|
private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset)
|
||||||
{
|
{
|
||||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
||||||
() => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(100));
|
() => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addClickActionAssert(int inputIndex, ClickAction action)
|
||||||
|
=> AddAssert($"input #{inputIndex} resulted in {action}", () => testPolicy.ClickActions[inputIndex], () => Is.EqualTo(action));
|
||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||||
private List<JudgementResult> judgementResults = null!;
|
private List<JudgementResult> judgementResults = null!;
|
||||||
|
private TestLegacyHitPolicy testPolicy = null!;
|
||||||
|
|
||||||
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames, [CallerMemberName] string testCaseName = "")
|
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames, IEnumerable<Mod>? extraMods = null, [CallerMemberName] string testCaseName = "")
|
||||||
{
|
{
|
||||||
|
List<Mod> mods = null!;
|
||||||
IBeatmap playableBeatmap = null!;
|
IBeatmap playableBeatmap = null!;
|
||||||
Score score = null!;
|
Score score = null!;
|
||||||
|
|
||||||
|
AddStep("set up mods", () =>
|
||||||
|
{
|
||||||
|
mods = new List<Mod> { new OsuModClassic() };
|
||||||
|
|
||||||
|
if (extraMods != null)
|
||||||
|
mods.AddRange(extraMods);
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("create beatmap", () =>
|
AddStep("create beatmap", () =>
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
@ -461,7 +586,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
ScoreInfo =
|
ScoreInfo =
|
||||||
{
|
{
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
BeatmapInfo = playableBeatmap.BeatmapInfo
|
BeatmapInfo = playableBeatmap.BeatmapInfo,
|
||||||
|
Mods = mods.ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -495,7 +621,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddStep("load player", () =>
|
AddStep("load player", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.Value = new[] { new OsuModClassic() };
|
SelectedMods.Value = mods.ToArray();
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(score);
|
var p = new ScoreAccessibleReplayPlayer(score);
|
||||||
|
|
||||||
@ -513,6 +639,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
AddStep("Substitute hit policy", () =>
|
||||||
|
{
|
||||||
|
var playfield = currentPlayer.ChildrenOfType<OsuPlayfield>().Single();
|
||||||
|
var currentPolicy = playfield.HitPolicy;
|
||||||
|
playfield.HitPolicy = testPolicy = new TestLegacyHitPolicy(currentPolicy);
|
||||||
|
});
|
||||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,5 +672,24 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestLegacyHitPolicy : LegacyHitPolicy
|
||||||
|
{
|
||||||
|
private readonly IHitPolicy currentPolicy;
|
||||||
|
|
||||||
|
public TestLegacyHitPolicy(IHitPolicy currentPolicy)
|
||||||
|
{
|
||||||
|
this.currentPolicy = currentPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClickAction> ClickActions { get; } = new List<ClickAction>();
|
||||||
|
|
||||||
|
public override ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result)
|
||||||
|
{
|
||||||
|
var action = currentPolicy.CheckHittable(hitObject, time, result);
|
||||||
|
ClickActions.Add(action);
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -57,7 +58,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var osuRuleset = (DrawableOsuRuleset)drawableRuleset;
|
var osuRuleset = (DrawableOsuRuleset)drawableRuleset;
|
||||||
|
|
||||||
if (ClassicNoteLock.Value)
|
if (ClassicNoteLock.Value)
|
||||||
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
|
{
|
||||||
|
double hittableRange = OsuHitWindows.MISS_WINDOW - (drawableRuleset.Mods.OfType<OsuModAutopilot>().Any() ? 200 : 0);
|
||||||
|
osuRuleset.Playfield.HitPolicy = new LegacyHitPolicy(hittableRange);
|
||||||
|
}
|
||||||
|
|
||||||
usingHiddenFading = drawableRuleset.Mods.OfType<OsuModHidden>().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false;
|
usingHiddenFading = drawableRuleset.Mods.OfType<OsuModHidden>().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -154,12 +155,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = ResultFor(timeOffset);
|
var result = ResultFor(timeOffset);
|
||||||
|
var clickAction = CheckHittable?.Invoke(this, Time.Current, result);
|
||||||
|
|
||||||
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
if (clickAction == ClickAction.Shake)
|
||||||
{
|
|
||||||
Shake();
|
Shake();
|
||||||
|
|
||||||
|
if (result == HitResult.None || clickAction != ClickAction.Hit)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,8 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -30,10 +32,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
protected override float SamplePlaybackPosition => CalculateDrawableRelativePosition(this);
|
protected override float SamplePlaybackPosition => CalculateDrawableRelativePosition(this);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit, given a time value.
|
/// What action this <see cref="DrawableOsuHitObject"/> should take in response to a
|
||||||
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
|
/// click at the given time value.
|
||||||
|
/// If non-null, judgements will be ignored for return values of <see cref="ClickAction.Ignore"/>
|
||||||
|
/// and <see cref="ClickAction.Shake"/>, and this hit object will be shaken for return values of
|
||||||
|
/// <see cref="ClickAction.Shake"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
public Func<DrawableHitObject, double, HitResult, ClickAction> CheckHittable;
|
||||||
|
|
||||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
|
@ -8,6 +8,7 @@ using System.Diagnostics;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
@ -60,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
pathVersion.BindTo(DrawableSlider.PathVersion);
|
pathVersion.BindTo(DrawableSlider.PathVersion);
|
||||||
|
|
||||||
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
CheckHittable = (d, t, r) => DrawableSlider.CheckHittable?.Invoke(d, t, r) ?? ClickAction.Hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
@ -13,9 +12,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AnyOrderHitPolicy : IHitPolicy
|
public class AnyOrderHitPolicy : IHitPolicy
|
||||||
{
|
{
|
||||||
public IHitObjectContainer HitObjectContainer { get; set; }
|
public IHitObjectContainer HitObjectContainer { get; set; } = null!;
|
||||||
|
|
||||||
public bool IsHittable(DrawableHitObject hitObject, double time) => true;
|
public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) => ClickAction.Hit;
|
||||||
|
|
||||||
public void HandleHit(DrawableHitObject hitObject)
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
|
18
osu.Game.Rulesets.Osu/UI/ClickAction.cs
Normal file
18
osu.Game.Rulesets.Osu/UI/ClickAction.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An action that an <see cref="IHitPolicy"/> recommends be taken in response to a click
|
||||||
|
/// on a <see cref="DrawableOsuHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public enum ClickAction
|
||||||
|
{
|
||||||
|
Ignore,
|
||||||
|
Shake,
|
||||||
|
Hit
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
@ -19,8 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
||||||
/// <param name="time">The time to check.</param>
|
/// <param name="time">The time to check.</param>
|
||||||
|
/// <param name="result">The result that the object would be judged with if hit.</param>
|
||||||
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
||||||
bool IsHittable(DrawableHitObject hitObject, double time);
|
ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles a <see cref="HitObject"/> being hit.
|
/// Handles a <see cref="HitObject"/> being hit.
|
||||||
|
72
osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs
Normal file
72
osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that <see cref="HitObject"/>s are hit in order of appearance. The classic note lock.
|
||||||
|
/// <remarks>
|
||||||
|
/// Hits will be blocked until the previous <see cref="HitObject"/>s have been judged.
|
||||||
|
/// </remarks>
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyHitPolicy : IHitPolicy
|
||||||
|
{
|
||||||
|
public IHitObjectContainer? HitObjectContainer { get; set; }
|
||||||
|
|
||||||
|
private readonly double hittableRange;
|
||||||
|
|
||||||
|
public LegacyHitPolicy(double hittableRange = OsuHitWindows.MISS_WINDOW)
|
||||||
|
{
|
||||||
|
this.hittableRange = hittableRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result)
|
||||||
|
{
|
||||||
|
if (HitObjectContainer == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(CheckHittable)} is called.");
|
||||||
|
|
||||||
|
var aliveObjects = HitObjectContainer.AliveObjects.ToList();
|
||||||
|
int index = aliveObjects.IndexOf(hitObject);
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
var previousHitObject = (DrawableOsuHitObject)aliveObjects[index - 1];
|
||||||
|
if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged)
|
||||||
|
return ClickAction.Ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == HitResult.None)
|
||||||
|
return ClickAction.Shake;
|
||||||
|
|
||||||
|
foreach (DrawableHitObject testObject in aliveObjects)
|
||||||
|
{
|
||||||
|
if (testObject.AllJudged)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if we found the object being checked, we can move on to the final timing test.
|
||||||
|
if (testObject == hitObject)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// for all other objects, we check for validity and block the hit if any are still valid.
|
||||||
|
// 3ms of extra leniency to account for slightly unsnapped objects.
|
||||||
|
if (testObject.HitObject.GetEndTime() + 3 < hitObject.HitObject.StartTime)
|
||||||
|
return ClickAction.Shake;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Abs(hitObject.HitObject.StartTime - time) < hittableRange ? ClickAction.Hit : ClickAction.Shake;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures that <see cref="HitObject"/>s are hit in order of appearance. The classic note lock.
|
|
||||||
/// <remarks>
|
|
||||||
/// Hits will be blocked until the previous <see cref="HitObject"/>s have been judged.
|
|
||||||
/// </remarks>
|
|
||||||
/// </summary>
|
|
||||||
public class ObjectOrderedHitPolicy : IHitPolicy
|
|
||||||
{
|
|
||||||
public IHitObjectContainer HitObjectContainer { get; set; }
|
|
||||||
|
|
||||||
public bool IsHittable(DrawableHitObject hitObject, double time) => enumerateHitObjectsUpTo(hitObject.HitObject.StartTime).All(obj => obj.AllJudged);
|
|
||||||
|
|
||||||
public void HandleHit(DrawableHitObject hitObject)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
|
||||||
{
|
|
||||||
foreach (var obj in HitObjectContainer.AliveObjects)
|
|
||||||
{
|
|
||||||
if (obj.HitObject.StartTime >= targetTime)
|
|
||||||
yield break;
|
|
||||||
|
|
||||||
switch (obj)
|
|
||||||
{
|
|
||||||
case DrawableSpinner:
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case DrawableSlider slider:
|
|
||||||
yield return slider.HeadCircle;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
yield return obj;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
|
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable;
|
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.CheckHittable;
|
||||||
|
|
||||||
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
|
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
|
||||||
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
|
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
@ -22,11 +21,14 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class StartTimeOrderedHitPolicy : IHitPolicy
|
public class StartTimeOrderedHitPolicy : IHitPolicy
|
||||||
{
|
{
|
||||||
public IHitObjectContainer HitObjectContainer { get; set; }
|
public IHitObjectContainer? HitObjectContainer { get; set; }
|
||||||
|
|
||||||
public bool IsHittable(DrawableHitObject hitObject, double time)
|
public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult _)
|
||||||
{
|
{
|
||||||
DrawableHitObject blockingObject = null;
|
if (HitObjectContainer == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(CheckHittable)} is called.");
|
||||||
|
|
||||||
|
DrawableHitObject? blockingObject = null;
|
||||||
|
|
||||||
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
{
|
{
|
||||||
@ -36,22 +38,25 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
// If there is no previous hitobject, allow the hit.
|
// If there is no previous hitobject, allow the hit.
|
||||||
if (blockingObject == null)
|
if (blockingObject == null)
|
||||||
return true;
|
return ClickAction.Hit;
|
||||||
|
|
||||||
// A hit is allowed if:
|
// A hit is allowed if:
|
||||||
// 1. The last blocking hitobject has been judged.
|
// 1. The last blocking hitobject has been judged.
|
||||||
// 2. The current time is after the last hitobject's start time.
|
// 2. The current time is after the last hitobject's start time.
|
||||||
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
|
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
|
||||||
return blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
|
return (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) ? ClickAction.Hit : ClickAction.Shake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleHit(DrawableHitObject hitObject)
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
|
if (HitObjectContainer == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(HandleHit)} is called.");
|
||||||
|
|
||||||
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
|
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
|
||||||
if (!hitObjectCanBlockFutureHits(hitObject))
|
if (!hitObjectCanBlockFutureHits(hitObject))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset, hitObject.Result.Type) != ClickAction.Hit)
|
||||||
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||||
|
|
||||||
// Miss all hitobjects prior to the hit one.
|
// Miss all hitobjects prior to the hit one.
|
||||||
@ -74,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||||
{
|
{
|
||||||
foreach (var obj in HitObjectContainer.AliveObjects)
|
foreach (var obj in HitObjectContainer!.AliveObjects)
|
||||||
{
|
{
|
||||||
if (obj.HitObject.StartTime >= targetTime)
|
if (obj.HitObject.StartTime >= targetTime)
|
||||||
yield break;
|
yield break;
|
||||||
|
Loading…
Reference in New Issue
Block a user