-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix editor test play not marking hit objects before its start time as judged #26465
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
osu!catch has automagically been working prior to this PR due to the nature of its hit object judgement logic (judging all object prior to current time as miss, instead of ignoring).
With this PR, now the judged hits count goes over the maximum value, as all previous objects increase the judged hits counter with "miss" judgements after the score processor has already been set to the calculated judged hits in this PR. This results in the osu!catch editor test player not being able to exit at finish (whereas on master it works).
An alternative method I have in mind would be to give maximum results to the actual hit objects of the test session themselves (which are assigned as entries to the drawable hit objects), so that they're not judged with a "miss" later on. I tried doing that, but I cannot seem to get it work at first attempt...something along the lines of the following diff:
diff
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index e2b2b067e0..f8fdb1f809 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -1,6 +1,7 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -8,6 +9,7 @@
using osu.Game.Beatmaps;
using osu.Game.Online.Spectator;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
@@ -21,19 +23,17 @@ public partial class EditorPlayer : Player
{
private readonly Editor editor;
private readonly EditorState editorState;
- private readonly IBeatmap playableBeatmap;
protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo, Ruleset.Value);
[Resolved]
private MusicController musicController { get; set; } = null!;
- public EditorPlayer(Editor editor, IBeatmap playableBeatmap)
+ public EditorPlayer(Editor editor)
: base(new PlayerConfiguration { ShowResults = false })
{
this.editor = editor;
editorState = editor.GetState();
- this.playableBeatmap = playableBeatmap;
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
@@ -52,21 +52,16 @@ protected override void LoadComplete()
{
base.LoadComplete();
- var frame = new ReplayFrame { Header = new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()) };
-
- foreach (var hitObject in enumerateHitObjects(playableBeatmap.HitObjects.Where(h => h.StartTime < editorState.Time)))
+ foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects))
{
- var judgement = hitObject.CreateJudgement();
-
- if (!frame.Header.Statistics.ContainsKey(judgement.MaxResult))
- frame.Header.Statistics.Add(judgement.MaxResult, 0);
+ if (hitObject.StartTime >= editorState.Time)
+ continue;
- frame.Header.Statistics[judgement.MaxResult]++;
+ var judgement = hitObject.CreateJudgement();
+ HealthProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult });
+ ScoreProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult });
}
- HealthProcessor.ResetFromReplayFrame(frame);
- ScoreProcessor.ResetFromReplayFrame(frame);
-
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
{
if (completed.NewValue)
In addition, there also seem to be specific scenarios at which editor test play still fails to exit on finish: CleanShot.2024-01-11.at.06.53.57.mp4At this point it may seem more ideal to add local code somewhere to mark gameplay as "completed" when being 1000ms ahead of the last hit object end time. Something to keep in mind as a better alternative. |
disagree. i would like an explanation as to why this method is failing before resorting to such hacks |
Hence why I mentioned it as an alternative. |
i don't consider it a valid alternative unless significant extenuating circumstances are provided. |
I will wait for a response to the issues I pointed above to get the current method in a fully working state (specifically #26465), and then discuss it from there. |
It was caused by not accounting for hit windows and hit objects with a duration causing hit objects to be judged twice making
Other modes were working as well. It's because in other modes other than standard, they mark all hit objects after a certain time as missed causing After a bit more digging its likely that |
…ement count mismatch
I've fixed multiple edge cases and applied @frenzibyte's proposed changes above (applying results instead of using replay frame) as it solves the issue of DHOs currently on screen duplicating judgements (this could not be resolved with the replay frame approach). Not sure if we want to try and craft tests for this but before expending the effort in doing so I'd like to see if anyone can break this. |
Can you elaborate on this any further? I guess I'll have to have a look myself at the end of the day at this point but I'm really loath to be inventing another way to do this. Especially so that I don't see how the replay frame mechanism can work for multi but not for editor. I will need to find out (myself or not) why that is the case before passing any judgement (whether there are some special editor circumstances at play, or whether the multi spectator mechanism is broken e.g. for catch and needs to be either fixed or replaced). |
This is correctly touched on by frenzi above, but I'll attempt. Basically, assume there are multiple DHOs on screen, some before We would need to use some concoction of In a simple scenario where past objects are not mapped to DHOs – ie. only objects after Which is to say, I honestly don't know how this works in replays (it probably doesn't always). |
foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time)) | ||
{ | ||
var judgement = hitObject.Judgement; | ||
|
||
HealthProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult }); | ||
ScoreProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the problem with the replay frame-based approach is flawed because it relied on using hitobjects and hitobject-based cutoffs, then how is this fine? This is still hitobject-based logic no? There are no DHOs here? Why is there no attempt to compensate for preempt or transforms or whatever here either?
That explanation isn't making much sense to me in light of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hookup flow with ScoreProcessor
via Player
means that applying result here will have the results end up on the DHOs, seemingly. It's a long chain of events that bugs me every time i have to traverse it.
Started https://osu.ppy.sh/beatmapsets/310607#mania/710980 on 01:37:623 and it kicked me out of test play before I actually completed the map: 2024-05-30.09-37-38.mp4Another mania diff on the same set I tried, gameplay still stuck after end. So as above, I don't see how this solution is any better than the previous one. It seems to be subject to the same exact issues. |
👍 safe to say this is all doomed then, and go back to the drawing board. |
Coming back to this: As far as I can tell, the assertions from this comment that going through However, how do you feel about the following? diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index 2028094964..9a7c1822a3 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -48,6 +48,7 @@ protected override void LoadComplete()
base.LoadComplete();
markPreviousObjectsHit();
+ markVisibleDrawableObjectsHit();
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
{
@@ -67,9 +68,10 @@ private void markPreviousObjectsHit()
foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time))
{
var judgement = hitObject.Judgement;
+ var result = new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult };
- HealthProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult });
- ScoreProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult });
+ HealthProcessor.ApplyResult(result);
+ ScoreProcessor.ApplyResult(result);
}
static IEnumerable<HitObject> enumerateHitObjects(IEnumerable<HitObject> hitObjects, double cutoffTime)
@@ -88,6 +90,40 @@ static IEnumerable<HitObject> enumerateHitObjects(IEnumerable<HitObject> hitObje
}
}
+ private void markVisibleDrawableObjectsHit()
+ {
+ if (!DrawableRuleset.Playfield.IsLoaded)
+ {
+ Schedule(markVisibleDrawableObjectsHit);
+ return;
+ }
+
+ foreach (var drawableObjectEntry in enumerateDrawableEntries(
+ DrawableRuleset.Playfield.AllHitObjects
+ .Select(ho => ho.Entry)
+ .Where(e => e != null)
+ .Cast<HitObjectLifetimeEntry>(), editorState.Time))
+ {
+ drawableObjectEntry.Result = new JudgementResult(drawableObjectEntry.HitObject, drawableObjectEntry.HitObject.Judgement)
+ { Type = drawableObjectEntry.HitObject.Judgement.MaxResult };
+ }
+
+ static IEnumerable<HitObjectLifetimeEntry> enumerateDrawableEntries(IEnumerable<HitObjectLifetimeEntry> entries, double cutoffTime)
+ {
+ foreach (var entry in entries)
+ {
+ foreach (var nested in enumerateDrawableEntries(entry.NestedEntries, cutoffTime))
+ {
+ if (nested.HitObject.GetEndTime() < cutoffTime)
+ yield return nested;
+ }
+
+ if (entry.HitObject.GetEndTime() < cutoffTime)
+ yield return entry;
+ }
+ }
+ }
+
protected override void PrepareReplay()
{
// don't record replays.
Is it pretty cursed? Yeah. Does it appear to work? In my (somewhat brief) testing, also yeah. It should also scale to non-pooled rulesets, thankfully, because of that whole As far as I can tell the issue happens because the DHOs do not get the |
I think the diff looks okay. |
- Covers fail case that wasn't covered before - Removes arbitrary wait step that was inevitably going to cause intermittent test failures
Fixes #17395 and #23029 using the suggestion from #17395 (comment).
osu._2024.01.10-15.24.mp4