mirror of
https://github.com/ppy/osu.git
synced 2026-05-26 14:30:46 +08:00
6231e06ebe
Closes https://github.com/ppy/osu/issues/37232. The actual fix is https://github.com/ppy/osu/commit/e959b20517497a093d3c00a17457c5d36bf57651; everything else is window dressing / test harness to ensure I don't try and do a wrong change like https://github.com/ppy/osu/pull/37251 did. I recommend reviewing commit-by-commit. See [this desmos](https://www.desmos.com/calculator/a5yjpacvxa) for visual explanation of change, I think it does a better job at explaining this than any words I could type here. Of note: - In the end this did only affect 14K but that should never be assumed when floating point is involved. - Test cases generated here were generated in stable manually. - Except for 11 / 13 / 15 / 17K which are not officially supported and which don't work in lazer due to orthogonal reasons (see comment added in this PR in `ManiaBeatmapConverter`), decoding in lazer was always fine. - My worry was that the old encoding method before this PR could potentially cause stable to move a note from one column to another but thankfully that is not the case. The old method of encoding columns as X positions does not cause issues wherein lazer reads them back differently than stable after encode. I checked this by checking out `master`, re-encoding all of the test stair-pattern nK beatmaps added in this PR on `master`, exporting that as compatibility, re-importing to stable, and cross-checking that the decoded beatmap is visually the same on lazer and on stable. This is important to check because if this wasn't the case, we'd potentially have cases of actual online beatmaps (remember that we have BSS now) wherein a beatmap plays differently on stable than on lazer due to notes moving between columns, and would need to screen for this being the case and potentially apply corrective / reconciliatory action.
193 lines
6.6 KiB
C#
193 lines
6.6 KiB
C#
// 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;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
using osu.Framework.Extensions.TypeExtensions;
|
|
using osu.Framework.Screens;
|
|
using osu.Framework.Utils;
|
|
using osu.Game.Beatmaps.ControlPoints;
|
|
using osu.Game.Replays;
|
|
using osu.Game.Rulesets.Judgements;
|
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
using osu.Game.Rulesets.Mania.Objects;
|
|
using osu.Game.Rulesets.Mania.Replays;
|
|
using osu.Game.Rulesets.Replays;
|
|
using osu.Game.Rulesets.Scoring;
|
|
using osu.Game.Scoring;
|
|
using osu.Game.Screens.Play;
|
|
using osu.Game.Tests.Visual;
|
|
|
|
namespace osu.Game.Rulesets.Mania.Tests
|
|
{
|
|
public partial class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene
|
|
{
|
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
|
|
|
[Test]
|
|
public void TestPreviousHitWindowDoesNotExtendPastNextObject()
|
|
{
|
|
var objects = new List<ManiaHitObject>();
|
|
var frames = new List<ReplayFrame>();
|
|
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
double time = 1000 + i * 100;
|
|
|
|
objects.Add(new Note { StartTime = time });
|
|
|
|
// don't hit the first note
|
|
if (i > 0)
|
|
{
|
|
frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1));
|
|
frames.Add(new ManiaReplayFrame(time + 11));
|
|
}
|
|
}
|
|
|
|
performTest(objects, frames);
|
|
|
|
addJudgementAssert(objects[0], HitResult.Miss);
|
|
|
|
for (int i = 1; i < 7; i++)
|
|
{
|
|
addJudgementAssert(objects[i], HitResult.Perfect);
|
|
addJudgementOffsetAssert(objects[i], 10);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TestHoldNoteMissAfterNextObjectStartTime()
|
|
{
|
|
var objects = new List<ManiaHitObject>
|
|
{
|
|
new HoldNote
|
|
{
|
|
StartTime = 1000,
|
|
EndTime = 1010,
|
|
},
|
|
new HoldNote
|
|
{
|
|
StartTime = 1020,
|
|
EndTime = 1030
|
|
}
|
|
};
|
|
|
|
performTest(objects, new List<ReplayFrame>());
|
|
|
|
addJudgementAssert(objects[0], HitResult.IgnoreMiss);
|
|
addJudgementAssert(objects[1], HitResult.IgnoreMiss);
|
|
}
|
|
|
|
[Test]
|
|
public void TestHoldNoteReleasedHitAfterNextObjectStartTime()
|
|
{
|
|
var objects = new List<ManiaHitObject>
|
|
{
|
|
new HoldNote
|
|
{
|
|
StartTime = 1000,
|
|
EndTime = 1010,
|
|
},
|
|
new HoldNote
|
|
{
|
|
StartTime = 1020,
|
|
EndTime = 1030
|
|
}
|
|
};
|
|
|
|
var frames = new List<ReplayFrame>
|
|
{
|
|
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
|
new ManiaReplayFrame(1030),
|
|
new ManiaReplayFrame(1040, ManiaAction.Key1),
|
|
new ManiaReplayFrame(1050)
|
|
};
|
|
|
|
performTest(objects, frames);
|
|
|
|
addJudgementAssert(objects[0], HitResult.IgnoreHit);
|
|
addJudgementAssert("first head", () => ((HoldNote)objects[0]).Head, HitResult.Perfect);
|
|
addJudgementAssert("first tail", () => ((HoldNote)objects[0]).Tail, HitResult.Perfect);
|
|
|
|
addJudgementAssert(objects[1], HitResult.IgnoreHit);
|
|
addJudgementAssert("second head", () => ((HoldNote)objects[1]).Head, HitResult.Great);
|
|
addJudgementAssert("second tail", () => ((HoldNote)objects[1]).Tail, HitResult.Perfect);
|
|
}
|
|
|
|
private void addJudgementAssert(ManiaHitObject hitObject, HitResult result)
|
|
{
|
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
|
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
|
|
}
|
|
|
|
private void addJudgementAssert(string name, Func<ManiaHitObject> hitObject, HitResult result)
|
|
{
|
|
AddAssert($"{name} judgement is {result}",
|
|
() => judgementResults.Single(r => r.HitObject == hitObject()).Type == result);
|
|
}
|
|
|
|
private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset)
|
|
{
|
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
|
() => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100));
|
|
}
|
|
|
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
|
private List<JudgementResult> judgementResults;
|
|
|
|
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
|
|
{
|
|
AddStep("load player", () =>
|
|
{
|
|
Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition(4))
|
|
{
|
|
HitObjects = hitObjects,
|
|
BeatmapInfo =
|
|
{
|
|
Ruleset = new ManiaRuleset().RulesetInfo
|
|
},
|
|
});
|
|
|
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
|
|
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
|
|
|
p.OnLoadComplete += _ =>
|
|
{
|
|
p.ScoreProcessor.NewJudgement += result =>
|
|
{
|
|
if (currentPlayer == p) judgementResults.Add(result);
|
|
};
|
|
};
|
|
|
|
LoadScreen(currentPlayer = p);
|
|
judgementResults = new List<JudgementResult>();
|
|
});
|
|
|
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
|
}
|
|
|
|
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
|
{
|
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
|
|
|
protected override bool PauseOnFocusLost => false;
|
|
|
|
public ScoreAccessibleReplayPlayer(Score score)
|
|
: base(score, new PlayerConfiguration
|
|
{
|
|
AllowPause = false,
|
|
ShowResults = false,
|
|
})
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|