mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 07:22:38 +08:00
Merge pull request #33491 from bdach/fix-replays
Fix replays being misrecorded if an action is pressed and released in one update frame
This commit is contained in:
+4
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
@@ -17,5 +18,8 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
if (button.HasValue)
|
||||
Actions.Add(button.Value);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is EmptyFreeformReplayFrame freeformFrame && Time == freeformFrame.Time && Position == freeformFrame.Position && Actions.SequenceEqual(freeformFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -9,5 +9,8 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
public class PippidonReplayFrame : ReplayFrame
|
||||
{
|
||||
public Vector2 Position;
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is PippidonReplayFrame pippidonFrame && Time == pippidonFrame.Time && Position == pippidonFrame.Position;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
@@ -15,5 +16,8 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
if (button.HasValue)
|
||||
Actions.Add(button.Value);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is EmptyScrollingReplayFrame scrollingFrame && Time == scrollingFrame.Time && Actions.SequenceEqual(scrollingFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
@@ -15,5 +16,8 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
if (button.HasValue)
|
||||
Actions.Add(button.Value);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is PippidonReplayFrame pippidonFrame && Time == pippidonFrame.Time && Actions.SequenceEqual(pippidonFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -64,5 +65,12 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, Position, null, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is CatchReplayFrame catchFrame
|
||||
&& Time == catchFrame.Time
|
||||
&& Position == catchFrame.Position
|
||||
&& Dashing == catchFrame.Dashing
|
||||
&& Actions.SequenceEqual(catchFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -47,5 +48,8 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is ManiaReplayFrame maniaFrame && Time == maniaFrame.Time && Actions.SequenceEqual(maniaFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddAssert("smoke button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.Smoke])));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPressAndReleaseOnSameFrame()
|
||||
{
|
||||
seekTo(0);
|
||||
AddStep("move cursor to circle", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.Single()));
|
||||
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
AddAssert("right button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.RightButton])));
|
||||
}
|
||||
|
||||
private void seekTo(double time)
|
||||
{
|
||||
AddStep($"seek to {time}ms", () => Player.GameplayClockContainer.Seek(time));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -47,5 +48,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is OsuReplayFrame osuFrame && Time == osuFrame.Time && Position == osuFrame.Position && Actions.SequenceEqual(osuFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -42,5 +43,8 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, null, null, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TaikoReplayFrame taikoFrame && Time == taikoFrame.Time && Actions.SequenceEqual(taikoFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +383,9 @@ namespace osu.Game.Tests.NonVisual
|
||||
IsImportant = isImportant;
|
||||
FrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TestReplayFrame testFrame && Time == testFrame.Time && IsImportant == testFrame.IsImportant && FrameIndex == testFrame.FrameIndex;
|
||||
}
|
||||
|
||||
private class TestInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
|
||||
@@ -317,6 +317,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Position = position;
|
||||
Actions.AddRange(actions);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TestReplayFrame testFrame && Time == testFrame.Time && Position == testFrame.Position && Actions.SequenceEqual(testFrame.Actions);
|
||||
}
|
||||
|
||||
public enum TestAction
|
||||
|
||||
@@ -353,6 +353,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TestReplayFrame testFrame && Time == testFrame.Time && Position == testFrame.Position && Actions.SequenceEqual(testFrame.Actions);
|
||||
}
|
||||
|
||||
public enum TestAction
|
||||
|
||||
@@ -247,12 +247,10 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
var convertedFrame = convertible.ToLegacy(currentBeatmap);
|
||||
|
||||
// only keep the last recorded frame for a given timestamp.
|
||||
// this reduces redundancy of frames in the resulting replay.
|
||||
//
|
||||
// this is also done at `ReplayRecorded`, but needs to be done here as well
|
||||
// it is also done at `ReplayRecorder`, but needs to be done here as well
|
||||
// due to the flow being handled differently.
|
||||
if (pendingFrames.LastOrDefault()?.Time == convertedFrame.Time)
|
||||
if (pendingFrames.LastOrDefault()?.IsEquivalentTo(convertedFrame) == true)
|
||||
pendingFrames[^1] = convertedFrame;
|
||||
else
|
||||
pendingFrames.Add(convertedFrame);
|
||||
|
||||
@@ -64,5 +64,12 @@ namespace osu.Game.Replays.Legacy
|
||||
{
|
||||
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is LegacyReplayFrame legacyFrame
|
||||
&& Time == legacyFrame.Time
|
||||
&& MouseX == legacyFrame.MouseX
|
||||
&& MouseY == legacyFrame.MouseY
|
||||
&& ButtonState == legacyFrame.ButtonState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,5 +30,10 @@ namespace osu.Game.Rulesets.Replays
|
||||
{
|
||||
Time = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this frame is equivalent to <paramref name="other"/> with respect to replay recording.
|
||||
/// </summary>
|
||||
public virtual bool IsEquivalentTo(ReplayFrame other) => Time == other.Time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,9 +86,8 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
if (frame != null)
|
||||
{
|
||||
// only keep the last recorded frame for a given timestamp.
|
||||
// this reduces redundancy of frames in the resulting replay.
|
||||
if (last?.Time == frame.Time)
|
||||
if (last?.IsEquivalentTo(frame) == true)
|
||||
target.Replay.Frames[^1] = frame;
|
||||
else
|
||||
target.Replay.Frames.Add(frame);
|
||||
|
||||
Reference in New Issue
Block a user