1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 12:42:54 +08:00
This commit is contained in:
jvyden 2021-04-16 05:02:40 -04:00
commit 471b8e3c74
No known key found for this signature in database
GPG Key ID: 18BCF2BE0262B278
21 changed files with 180 additions and 149 deletions

View File

@ -2,13 +2,11 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.EmptyFreeform.Replays namespace osu.Game.Rulesets.EmptyFreeform.Replays
{ {
@ -21,26 +19,13 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
protected Vector2 Position
{
get
{
var frame = CurrentFrame;
if (frame == null)
return Vector2.Zero;
Debug.Assert(CurrentTime != null);
return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time);
}
}
public override void CollectPendingInputs(List<IInput> inputs) public override void CollectPendingInputs(List<IInput> inputs)
{ {
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
inputs.Add(new MousePositionAbsoluteInput inputs.Add(new MousePositionAbsoluteInput
{ {
Position = GamefieldToScreenSpace(Position), Position = GamefieldToScreenSpace(position),
}); });
inputs.Add(new ReplayState<EmptyFreeformAction> inputs.Add(new ReplayState<EmptyFreeformAction>
{ {

View File

@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.Pippidon.Replays namespace osu.Game.Rulesets.Pippidon.Replays
{ {
@ -20,26 +18,13 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => true; protected override bool IsImportant(PippidonReplayFrame frame) => true;
protected Vector2 Position
{
get
{
var frame = CurrentFrame;
if (frame == null)
return Vector2.Zero;
Debug.Assert(CurrentTime != null);
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
}
}
public override void CollectPendingInputs(List<IInput> inputs) public override void CollectPendingInputs(List<IInput> inputs)
{ {
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
inputs.Add(new MousePositionAbsoluteInput inputs.Add(new MousePositionAbsoluteInput
{ {
Position = GamefieldToScreenSpace(Position) Position = GamefieldToScreenSpace(position)
}); });
} }
} }

View File

@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Catch.Replays
private void addFrame(double time, float? position = null, bool dashing = false) private void addFrame(double time, float? position = null, bool dashing = false)
{ {
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
if (Replay.Frames.Count == 0)
Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
var last = currentFrame; var last = currentFrame;
currentFrame = new CatchReplayFrame(time, position, dashing, last); currentFrame = new CatchReplayFrame(time, position, dashing, last);
Replay.Frames.Add(currentFrame); Replay.Frames.Add(currentFrame);

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -20,29 +19,14 @@ namespace osu.Game.Rulesets.Catch.Replays
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
protected float? Position
{
get
{
var frame = CurrentFrame;
if (frame == null)
return null;
Debug.Assert(CurrentTime != null);
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
}
}
public override void CollectPendingInputs(List<IInput> inputs) public override void CollectPendingInputs(List<IInput> inputs)
{ {
if (!Position.HasValue) return; var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
inputs.Add(new CatchReplayState inputs.Add(new CatchReplayState
{ {
PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(), PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(),
CatcherX = Position.Value CatcherX = position
}); });
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests
/// <summary> /// <summary>
/// The number of frames which are generated at the start of a replay regardless of hitobject content. /// The number of frames which are generated at the start of a replay regardless of hitobject content.
/// </summary> /// </summary>
private const int frame_offset = 1; private const int frame_offset = 0;
[Test] [Test]
public void TestSingleNote() public void TestSingleNote()
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames"); Assert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");

View File

@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
base.UpdateTimeAndPosition(result); base.UpdateTimeAndPosition(result);
if (PlacementActive) if (PlacementActive == PlacementState.Active)
{ {
if (result.Time is double endTime) if (result.Time is double endTime)
{ {

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
base.UpdateTimeAndPosition(result); base.UpdateTimeAndPosition(result);
if (!PlacementActive) if (PlacementActive == PlacementState.Waiting)
Column = result.Playfield as Column; Column = result.Playfield as Column;
} }
} }

View File

@ -70,10 +70,6 @@ namespace osu.Game.Rulesets.Mania.Replays
} }
} }
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
if (Replay.Frames.Count == 0)
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
} }

View 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.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Editor
{
[TestFixture]
public class TestSliderScaling : TestSceneOsuEditor
{
private OsuPlayfield playfield;
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
}
[Test]
public void TestScalingLinearSlider()
{
Slider slider = null;
AddStep("Add slider", () =>
{
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
PathControlPoint[] points =
{
new PathControlPoint(new Vector2(0), PathType.Linear),
new PathControlPoint(new Vector2(100, 0)),
};
slider.Path = new SliderPath(points);
EditorBeatmap.Add(slider);
});
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
moveMouse(new Vector2(300));
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
double distanceBefore = 0;
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<SelectionBoxDragHandle>().Skip(1).First()));
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
moveMouse(new Vector2(300, 300));
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
}
private void moveMouse(Vector2 pos) =>
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
}
}

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private InputManager inputManager; private InputManager inputManager;
private PlacementState state; private SliderPlacementState state;
private PathControlPoint segmentStart; private PathControlPoint segmentStart;
private PathControlPoint cursor; private PathControlPoint cursor;
private int currentSegmentLength; private int currentSegmentLength;
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
}; };
setState(PlacementState.Initial); setState(SliderPlacementState.Initial);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -73,12 +73,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
switch (state) switch (state)
{ {
case PlacementState.Initial: case SliderPlacementState.Initial:
BeginPlacement(); BeginPlacement();
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
break; break;
case PlacementState.Body: case SliderPlacementState.Body:
updateCursor(); updateCursor();
break; break;
} }
@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
switch (state) switch (state)
{ {
case PlacementState.Initial: case SliderPlacementState.Initial:
beginCurve(); beginCurve();
break; break;
case PlacementState.Body: case SliderPlacementState.Body:
if (canPlaceNewControlPoint(out var lastPoint)) if (canPlaceNewControlPoint(out var lastPoint))
{ {
// Place a new point by detatching the current cursor. // Place a new point by detatching the current cursor.
@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
if (state == PlacementState.Body && e.Button == MouseButton.Right) if (state == SliderPlacementState.Body && e.Button == MouseButton.Right)
endCurve(); endCurve();
base.OnMouseUp(e); base.OnMouseUp(e);
} }
@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve() private void beginCurve()
{ {
BeginPlacement(commitStart: true); BeginPlacement(commitStart: true);
setState(PlacementState.Body); setState(SliderPlacementState.Body);
} }
private void endCurve() private void endCurve()
@ -219,12 +219,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
tailCirclePiece.UpdateFrom(HitObject.TailCircle); tailCirclePiece.UpdateFrom(HitObject.TailCircle);
} }
private void setState(PlacementState newState) private void setState(SliderPlacementState newState)
{ {
state = newState; state = newState;
} }
private enum PlacementState private enum SliderPlacementState
{ {
Initial, Initial,
Body, Body,

View File

@ -208,7 +208,9 @@ namespace osu.Game.Rulesets.Osu.Edit
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height); Vector2 pathRelativeDeltaScale = new Vector2(
sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width,
sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height);
Queue<Vector2> oldControlPoints = new Queue<Vector2>(); Queue<Vector2> oldControlPoints = new Queue<Vector2>();

View File

@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Replays
buttonIndex = 0; buttonIndex = 0;
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
for (int i = 0; i < Beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)

View File

@ -2,13 +2,11 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {
@ -21,24 +19,11 @@ namespace osu.Game.Rulesets.Osu.Replays
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
protected Vector2? Position
{
get
{
var frame = CurrentFrame;
if (frame == null)
return null;
Debug.Assert(CurrentTime != null);
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
}
}
public override void CollectPendingInputs(List<IInput> inputs) public override void CollectPendingInputs(List<IInput> inputs)
{ {
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) }); var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(position) });
inputs.Add(new ReplayState<OsuAction> { PressedActions = CurrentFrame?.Actions ?? new List<OsuAction>() }); inputs.Add(new ReplayState<OsuAction> { PressedActions = CurrentFrame?.Actions ?? new List<OsuAction>() });
} }
} }

View File

@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{ {
base.UpdateTimeAndPosition(result); base.UpdateTimeAndPosition(result);
if (PlacementActive) if (PlacementActive == PlacementState.Active)
{ {
if (result.Time is double dragTime) if (result.Time is double dragTime)
{ {

View File

@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
bool hitButton = true; bool hitButton = true;
Frames.Add(new TaikoReplayFrame(-100000));
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000)); Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
for (int i = 0; i < Beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)

View File

@ -13,6 +13,7 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
@ -128,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
AddStep("click ready button", () => AddStep("click ready button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single()); InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed. /// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
/// </summary> /// </summary>
public bool PlacementActive { get; private set; } public PlacementState PlacementActive { get; private set; }
/// <summary> /// <summary>
/// The <see cref="HitObject"/> that is being placed. /// The <see cref="HitObject"/> that is being placed.
@ -72,7 +72,8 @@ namespace osu.Game.Rulesets.Edit
protected void BeginPlacement(bool commitStart = false) protected void BeginPlacement(bool commitStart = false)
{ {
placementHandler.BeginPlacement(HitObject); placementHandler.BeginPlacement(HitObject);
PlacementActive |= commitStart; if (commitStart)
PlacementActive = PlacementState.Active;
} }
/// <summary> /// <summary>
@ -82,10 +83,19 @@ namespace osu.Game.Rulesets.Edit
/// <param name="commit">Whether the object should be committed.</param> /// <param name="commit">Whether the object should be committed.</param>
public void EndPlacement(bool commit) public void EndPlacement(bool commit)
{ {
if (!PlacementActive) switch (PlacementActive)
{
case PlacementState.Finished:
return;
case PlacementState.Waiting:
// ensure placement was started before ending to make state handling simpler.
BeginPlacement(); BeginPlacement();
break;
}
placementHandler.EndPlacement(HitObject, commit); placementHandler.EndPlacement(HitObject, commit);
PlacementActive = false; PlacementActive = PlacementState.Finished;
} }
/// <summary> /// <summary>
@ -94,7 +104,7 @@ namespace osu.Game.Rulesets.Edit
/// <param name="result">The snap result information.</param> /// <param name="result">The snap result information.</param>
public virtual void UpdateTimeAndPosition(SnapResult result) public virtual void UpdateTimeAndPosition(SnapResult result)
{ {
if (!PlacementActive) if (PlacementActive == PlacementState.Waiting)
HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
} }
@ -125,5 +135,12 @@ namespace osu.Game.Rulesets.Edit
return false; return false;
} }
} }
public enum PlacementState
{
Waiting,
Active,
Finished
}
} }
} }

View File

@ -1,6 +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 enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -31,32 +33,42 @@ namespace osu.Game.Rulesets.Replays
/// The current time is always between the start and the end time of the current frame. /// The current time is always between the start and the end time of the current frame.
/// </summary> /// </summary>
/// <remarks>Returns null if the current time is strictly before the first frame.</remarks> /// <remarks>Returns null if the current time is strictly before the first frame.</remarks>
public TFrame? CurrentFrame => currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex];
/// <summary>
/// The next frame of the replay.
/// The start time of <see cref="NextFrame"/> is always greater or equal to the start time of <see cref="CurrentFrame"/> regardless of the seeking direction.
/// </summary>
/// <remarks>Returns null if the current frame is the last frame.</remarks>
public TFrame? NextFrame => currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1];
/// <summary>
/// The frame for the start value of the interpolation of the replay movement.
/// </summary>
/// <exception cref="InvalidOperationException">The replay is empty.</exception> /// <exception cref="InvalidOperationException">The replay is empty.</exception>
public TFrame CurrentFrame public TFrame StartFrame
{ {
get get
{ {
if (!HasFrames) if (!HasFrames)
throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay"); throw new InvalidOperationException($"Attempted to get {nameof(StartFrame)} of an empty replay");
return currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex]; return (TFrame)Frames[Math.Max(0, currentFrameIndex)];
} }
} }
/// <summary> /// <summary>
/// The next frame of the replay. /// The frame for the end value of the interpolation of the replay movement.
/// The start time is always greater or equal to the start time of <see cref="CurrentFrame"/> regardless of the seeking direction.
/// </summary> /// </summary>
/// <remarks>Returns null if the current frame is the last frame.</remarks>
/// <exception cref="InvalidOperationException">The replay is empty.</exception> /// <exception cref="InvalidOperationException">The replay is empty.</exception>
public TFrame NextFrame public TFrame EndFrame
{ {
get get
{ {
if (!HasFrames) if (!HasFrames)
throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay"); throw new InvalidOperationException($"Attempted to get {nameof(EndFrame)} of an empty replay");
return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1]; return (TFrame)Frames[Math.Min(currentFrameIndex + 1, Frames.Count - 1)];
} }
} }
@ -69,8 +81,7 @@ namespace osu.Game.Rulesets.Replays
// This input handler should be enabled only if there is at least one replay frame. // This input handler should be enabled only if there is at least one replay frame.
public override bool IsActive => HasFrames; public override bool IsActive => HasFrames;
// Can make it non-null but that is a breaking change. protected double CurrentTime { get; private set; }
protected double? CurrentTime { get; private set; }
protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2;
@ -97,11 +108,11 @@ namespace osu.Game.Rulesets.Replays
{ {
get get
{ {
if (!HasFrames || !FrameAccuratePlayback || CurrentFrame == null) if (!HasFrames || !FrameAccuratePlayback || currentFrameIndex == -1)
return false; return false;
return IsImportant(CurrentFrame) && // a button is in a pressed state return IsImportant(StartFrame) && // a button is in a pressed state
Math.Abs(CurrentTime - NextFrame.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span Math.Abs(CurrentTime - EndFrame.Time) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span
} }
} }
@ -151,7 +162,7 @@ namespace osu.Game.Rulesets.Replays
CurrentTime = Math.Clamp(time, frameStart, frameEnd); CurrentTime = Math.Clamp(time, frameStart, frameEnd);
// In an important section, a mid-frame time cannot be used and a null is returned instead. // In an important section, a mid-frame time cannot be used and a null is returned instead.
return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime; return inImportantSection && frameStart < time && time < frameEnd ? null : (double?)CurrentTime;
} }
private double getFrameTime(int index) private double getFrameTime(int index)

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -11,7 +10,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.StateChanges.Events;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -102,17 +100,6 @@ namespace osu.Game.Rulesets.UI
#endregion #endregion
// to avoid allocation
private readonly List<IInput> emptyInputList = new List<IInput>();
protected override List<IInput> GetPendingInputs()
{
if (replayInputHandler?.IsActive == false)
return emptyInputList;
return base.GetPendingInputs();
}
#region Setting application (disables etc.) #region Setting application (disables etc.)
private Bindable<bool> mouseDisabled; private Bindable<bool> mouseDisabled;

View File

@ -438,8 +438,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void onBlueprintDeselected(SelectionBlueprint blueprint) private void onBlueprintDeselected(SelectionBlueprint blueprint)
{ {
SelectionHandler.HandleDeselected(blueprint);
SelectionBlueprints.ChangeChildDepth(blueprint, 0); SelectionBlueprints.ChangeChildDepth(blueprint, 0);
SelectionHandler.HandleDeselected(blueprint);
Composer.Playfield.SetKeepAlive(blueprint.HitObject, false); Composer.Playfield.SetKeepAlive(blueprint.HitObject, false);
} }

View File

@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void refreshTool() private void refreshTool()
{ {
removePlacement(); removePlacement();
createPlacement(); ensurePlacementCreated();
} }
private void updatePlacementPosition() private void updatePlacementPosition()
@ -215,17 +215,28 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
base.Update(); base.Update();
if (Composer.CursorInPlacementArea)
createPlacement();
else if (currentPlacement?.PlacementActive == false)
removePlacement();
if (currentPlacement != null) if (currentPlacement != null)
{ {
updatePlacementPosition(); switch (currentPlacement.PlacementActive)
{
case PlacementBlueprint.PlacementState.Waiting:
if (!Composer.CursorInPlacementArea)
removePlacement();
break;
case PlacementBlueprint.PlacementState.Finished:
removePlacement();
break;
} }
} }
if (Composer.CursorInPlacementArea)
ensurePlacementCreated();
if (currentPlacement != null)
updatePlacementPosition();
}
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
{ {
var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject);
@ -249,7 +260,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
NewCombo.Value = TernaryState.False; NewCombo.Value = TernaryState.False;
} }
private void createPlacement() private void ensurePlacementCreated()
{ {
if (currentPlacement != null) return; if (currentPlacement != null) return;