diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
index 0ca30e00bc..9af028fd8c 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
@@ -30,23 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
});
- [Test]
- public void TestAddOverlappingControlPoints()
- {
- createVisualiser(true);
-
- addControlPointStep(new Vector2(200));
- addControlPointStep(new Vector2(300));
- addControlPointStep(new Vector2(300));
- addControlPointStep(new Vector2(500, 300));
-
- AddAssert("last connection displayed", () =>
- {
- var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position == new Vector2(300));
- return lastConnection.DrawWidth > 50;
- });
- }
-
[Test]
public void TestPerfectCurveTooManyPoints()
{
@@ -194,24 +177,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
addAssertPointPositionChanged(points, i);
}
- [Test]
- public void TestStackingUpdatesConnectionPosition()
- {
- createVisualiser(true);
-
- Vector2 connectionPosition;
- addControlPointStep(connectionPosition = new Vector2(300));
- addControlPointStep(new Vector2(600));
-
- // Apply a big number in stacking so the person running the test can clearly see if it fails
- AddStep("apply stacking", () => slider.StackHeightBindable.Value += 10);
-
- AddAssert($"Connection at {connectionPosition} changed",
- () => visualiser.Connections[0].Position,
- () => !Is.EqualTo(connectionPosition)
- );
- }
-
private void addAssertPointPositionChanged(Vector2[] points, int index)
{
AddAssert($"Point at {points.ElementAt(index)} changed",
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs
similarity index 51%
rename from osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
rename to osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs
index 9b3d8fc7a7..5706ed4baf 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs
@@ -4,10 +4,7 @@
#nullable disable
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
@@ -15,36 +12,21 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
///
- /// A visualisation of the line between two s.
+ /// A visualisation of the lines between s.
///
- /// The type of which this visualises.
- public partial class PathControlPointConnectionPiece : CompositeDrawable where T : OsuHitObject, IHasPath
+ /// The type of which this visualises.
+ public partial class PathControlPointConnection : SmoothPath where T : OsuHitObject, IHasPath
{
- public readonly PathControlPoint ControlPoint;
-
- private readonly Path path;
private readonly T hitObject;
- public int ControlPointIndex { get; set; }
private IBindable hitObjectPosition;
private IBindable pathVersion;
private IBindable stackHeight;
- public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
+ public PathControlPointConnection(T hitObject)
{
this.hitObject = hitObject;
- ControlPointIndex = controlPointIndex;
-
- Origin = Anchor.Centre;
- AutoSizeAxes = Axes.Both;
-
- ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
-
- InternalChild = path = new SmoothPath
- {
- Anchor = Anchor.Centre,
- PathRadius = 1
- };
+ PathRadius = 1;
}
protected override void LoadComplete()
@@ -68,18 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
///
private void updateConnectingPath()
{
- Position = hitObject.StackedPosition + ControlPoint.Position;
+ Position = hitObject.StackedPosition;
- path.ClearVertices();
+ ClearVertices();
- int nextIndex = ControlPointIndex + 1;
- if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
- return;
+ foreach (var controlPoint in hitObject.Path.ControlPoints)
+ AddVertex(controlPoint.Position);
- path.AddVertex(Vector2.Zero);
- path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
-
- path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
+ OriginPosition = PositionInBoundingBox(Vector2.Zero);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index b2d1709531..836d348ff4 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -37,7 +37,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
internal readonly Container> Pieces;
- internal readonly Container> Connections;
private readonly IBindableList controlPoints = new BindableList();
private readonly T hitObject;
@@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
InternalChildren = new Drawable[]
{
- Connections = new Container> { RelativeSizeAxes = Axes.Both },
+ new PathControlPointConnection(hitObject),
Pieces = new Container> { RelativeSizeAxes = Axes.Both }
};
}
@@ -78,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
controlPoints.BindTo(hitObject.Path.ControlPoints);
}
+ // Generally all the control points are within the visible area all the time.
+ public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => true;
+
///
/// Handles correction of invalid path types.
///
@@ -185,17 +187,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
case NotifyCollectionChangedAction.Add:
Debug.Assert(e.NewItems != null);
- // If inserting in the path (not appending),
- // update indices of existing connections after insert location
- if (e.NewStartingIndex < Pieces.Count)
- {
- foreach (var connection in Connections)
- {
- if (connection.ControlPointIndex >= e.NewStartingIndex)
- connection.ControlPointIndex += e.NewItems.Count;
- }
- }
-
for (int i = 0; i < e.NewItems.Count; i++)
{
var point = (PathControlPoint)e.NewItems[i];
@@ -209,8 +200,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
d.DragInProgress = DragInProgress;
d.DragEnded = DragEnded;
}));
-
- Connections.Add(new PathControlPointConnectionPiece(hitObject, e.NewStartingIndex + i));
}
break;
@@ -222,19 +211,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
piece.RemoveAndDisposeImmediately();
- foreach (var connection in Connections.Where(c => c.ControlPoint == point).ToArray())
- connection.RemoveAndDisposeImmediately();
- }
-
- // If removing before the end of the path,
- // update indices of connections after remove location
- if (e.OldStartingIndex < Pieces.Count)
- {
- foreach (var connection in Connections)
- {
- if (connection.ControlPointIndex >= e.OldStartingIndex)
- connection.ControlPointIndex -= e.OldItems.Count;
- }
}
break;
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index ebbc329b9d..eb2c098ab8 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO.Archives;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -23,6 +24,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Resources;
+using osu.Game.Users;
namespace osu.Game.Tests.Scores.IO
{
@@ -284,6 +286,196 @@ namespace osu.Game.Tests.Scores.IO
}
}
+ [Test]
+ public void TestUserLookedUpForOnlineScore()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
+ {
+ try
+ {
+ var osu = LoadOsuIntoHost(host, true);
+
+ var api = (DummyAPIAccess)osu.API;
+ api.HandleRequest = req =>
+ {
+ switch (req)
+ {
+ case GetUserRequest userRequest:
+ userRequest.TriggerSuccess(new APIUser
+ {
+ Username = "Test user",
+ CountryCode = CountryCode.JP,
+ Id = 1234
+ });
+ return true;
+
+ default:
+ return false;
+ }
+ };
+
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
+ var toImport = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ User = new APIUser { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ OnlineID = 12345,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmap.Beatmaps.First()
+ };
+
+ var imported = LoadScoreIntoOsu(osu, toImport);
+
+ Assert.AreEqual(toImport.Rank, imported.Rank);
+ Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
+ Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
+ Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
+ Assert.AreEqual(toImport.User.Username, imported.User.Username);
+ Assert.AreEqual(toImport.Date, imported.Date);
+ Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
+ Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
+ Assert.AreEqual(1234, imported.RealmUser.OnlineID);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestUserLookedUpForLegacyOnlineScore()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
+ {
+ try
+ {
+ var osu = LoadOsuIntoHost(host, true);
+
+ var api = (DummyAPIAccess)osu.API;
+ api.HandleRequest = req =>
+ {
+ switch (req)
+ {
+ case GetUserRequest userRequest:
+ userRequest.TriggerSuccess(new APIUser
+ {
+ Username = "Test user",
+ CountryCode = CountryCode.JP,
+ Id = 1234
+ });
+ return true;
+
+ default:
+ return false;
+ }
+ };
+
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
+ var toImport = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ User = new APIUser { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ LegacyOnlineID = 12345,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmap.Beatmaps.First()
+ };
+
+ var imported = LoadScoreIntoOsu(osu, toImport);
+
+ Assert.AreEqual(toImport.Rank, imported.Rank);
+ Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
+ Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
+ Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
+ Assert.AreEqual(toImport.User.Username, imported.User.Username);
+ Assert.AreEqual(toImport.Date, imported.Date);
+ Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
+ Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
+ Assert.AreEqual(1234, imported.RealmUser.OnlineID);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestUserNotLookedUpForOfflineScore()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
+ {
+ try
+ {
+ var osu = LoadOsuIntoHost(host, true);
+
+ var api = (DummyAPIAccess)osu.API;
+ api.HandleRequest = req =>
+ {
+ switch (req)
+ {
+ case GetUserRequest userRequest:
+ userRequest.TriggerSuccess(new APIUser
+ {
+ Username = "Test user",
+ CountryCode = CountryCode.JP,
+ Id = 1234
+ });
+ return true;
+
+ default:
+ return false;
+ }
+ };
+
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
+ var toImport = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ User = new APIUser { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ OnlineID = -1,
+ LegacyOnlineID = -1,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmap.Beatmaps.First()
+ };
+
+ var imported = LoadScoreIntoOsu(osu, toImport);
+
+ Assert.AreEqual(toImport.Rank, imported.Rank);
+ Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
+ Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
+ Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
+ Assert.AreEqual(toImport.User.Username, imported.User.Username);
+ Assert.AreEqual(toImport.Date, imported.Date);
+ Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
+ Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
+ Assert.That(imported.RealmUser.OnlineID, Is.LessThanOrEqualTo(1));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
// clone to avoid attaching the input score to realm.
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
index 8fa2c9922e..800857c973 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
@@ -85,8 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay
if (scaleTransformProvided)
{
- sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
- sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
+ sprite.Commands.AddScale(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
+ sprite.Commands.AddScale(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
}
layer.Elements.Clear();
@@ -211,7 +211,8 @@ namespace osu.Game.Tests.Visual.Gameplay
var layer = storyboard.GetLayer("Background");
var sprite = new StoryboardSprite(lookupName, origin, initialPosition);
- sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1);
+ var loop = sprite.AddLoopingGroup(Time.Current, 100);
+ loop.AddAlpha(Easing.None, 0, 10000, 1, 1);
layer.Elements.Clear();
layer.Add(sprite);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
index dadf3ca65f..5a71369976 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
- sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
+ sprite.Commands.AddAlpha(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
@@ -73,17 +73,17 @@ namespace osu.Game.Tests.Visual.Gameplay
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
// these should be ignored as we have an alpha visibility blocker proceeding this command.
- sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
- var loopGroup = sprite.AddLoop(loop_start_time, 50);
- loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
+ sprite.Commands.AddScale(Easing.None, loop_start_time, -18000, 0, 1);
+ var loopGroup = sprite.AddLoopingGroup(loop_start_time, 50);
+ loopGroup.AddScale(Easing.None, loop_start_time, -18000, 0, 1);
- var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
+ var target = addEventToLoop ? loopGroup : sprite.Commands;
double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0;
- target.Alpha.Add(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
+ target.AddAlpha(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
// these should be ignored due to being in the future.
- sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
- loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
+ sprite.Commands.AddAlpha(Easing.None, 18000, 20000, 0, 1);
+ loopGroup.AddAlpha(Easing.None, 38000, 40000, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs
new file mode 100644
index 0000000000..4af3d23463
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs
@@ -0,0 +1,264 @@
+// Copyright (c) ppy Pty Ltd . 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.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Storyboards;
+using osu.Game.Storyboards.Drawables;
+using osu.Game.Tests.Resources;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public partial class TestSceneStoryboardCommands : OsuTestScene
+ {
+ [Cached(typeof(Storyboard))]
+ private TestStoryboard storyboard { get; set; } = new TestStoryboard
+ {
+ UseSkinSprites = false,
+ AlwaysProvideTexture = true,
+ };
+
+ private readonly ManualClock manualClock = new ManualClock { Rate = 1, IsRunning = true };
+ private int clockDirection;
+
+ private const string lookup_name = "hitcircleoverlay";
+ private const double clock_limit = 2500;
+
+ protected override Container Content => content;
+
+ private Container content = null!;
+ private SpriteText timelineText = null!;
+ private Box timelineMarker = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ base.Content.Children = new Drawable[]
+ {
+ content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ timelineText = new OsuSpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Margin = new MarginPadding { Bottom = 60 },
+ },
+ timelineMarker = new Box
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomCentre,
+ RelativePositionAxes = Axes.X,
+ Size = new Vector2(2, 50),
+ },
+ };
+ }
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("start clock", () => clockDirection = 1);
+ AddStep("pause clock", () => clockDirection = 0);
+ AddStep("set clock = 0", () => manualClock.CurrentTime = 0);
+ }
+
+ [Test]
+ public void TestNormalCommandPlayback()
+ {
+ AddStep("create storyboard", () => Child = createStoryboard(s =>
+ {
+ s.Commands.AddY(Easing.OutBounce, 500, 900, 100, 240);
+ s.Commands.AddY(Easing.OutQuint, 1100, 1500, 240, 100);
+ }));
+
+ assert(0, 100);
+ assert(500, 100);
+ assert(1000, 240);
+ assert(1500, 100);
+ assert(clock_limit, 100);
+ assert(1500, 100);
+ assert(1000, 240);
+ assert(500, 100);
+ assert(0, 100);
+
+ void assert(double time, double y)
+ {
+ AddStep($"set clock = {time}", () => manualClock.CurrentTime = time);
+ AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType().Single().Y == y);
+ }
+ }
+
+ [Test]
+ public void TestLoopingCommandsPlayback()
+ {
+ AddStep("create storyboard", () => Child = createStoryboard(s =>
+ {
+ var loop = s.AddLoopingGroup(250, 1);
+ loop.AddY(Easing.OutBounce, 0, 400, 100, 240);
+ loop.AddY(Easing.OutQuint, 600, 1000, 240, 100);
+ }));
+
+ assert(0, 100);
+ assert(250, 100);
+ assert(850, 240);
+ assert(1250, 100);
+ assert(1850, 240);
+ assert(2250, 100);
+ assert(clock_limit, 100);
+ assert(2250, 100);
+ assert(1850, 240);
+ assert(1250, 100);
+ assert(850, 240);
+ assert(250, 100);
+ assert(0, 100);
+
+ void assert(double time, double y)
+ {
+ AddStep($"set clock = {time}", () => manualClock.CurrentTime = time);
+ AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType().Single().Y == y);
+ }
+ }
+
+ [Test]
+ public void TestLoopManyTimes()
+ {
+ AddStep("create storyboard", () => Child = createStoryboard(s =>
+ {
+ var loop = s.AddLoopingGroup(500, 10000);
+ loop.AddY(Easing.OutBounce, 0, 60, 100, 240);
+ loop.AddY(Easing.OutQuint, 80, 120, 240, 100);
+ }));
+ }
+
+ [Test]
+ public void TestParameterTemporaryEffect()
+ {
+ AddStep("create storyboard", () => Child = createStoryboard(s =>
+ {
+ s.Commands.AddFlipV(Easing.None, 1000, 1500, true, false);
+ }));
+
+ AddAssert("sprite not flipped at t = 0", () => !this.ChildrenOfType().Single().FlipV);
+
+ AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250);
+ AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType().Single().FlipV);
+
+ AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000);
+ AddAssert("sprite not flipped at t = 2000", () => !this.ChildrenOfType().Single().FlipV);
+
+ AddStep("resume clock", () => clockDirection = 1);
+ }
+
+ [Test]
+ public void TestParameterPermanentEffect()
+ {
+ AddStep("create storyboard", () => Child = createStoryboard(s =>
+ {
+ s.Commands.AddFlipV(Easing.None, 1000, 1000, true, true);
+ }));
+
+ AddAssert("sprite flipped at t = 0", () => this.ChildrenOfType().Single().FlipV);
+
+ AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250);
+ AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType().Single().FlipV);
+
+ AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000);
+ AddAssert("sprite flipped at t = 2000", () => this.ChildrenOfType().Single().FlipV);
+
+ AddStep("resume clock", () => clockDirection = 1);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (manualClock.CurrentTime > clock_limit || manualClock.CurrentTime < 0)
+ clockDirection = -clockDirection;
+
+ manualClock.CurrentTime += Time.Elapsed * clockDirection;
+ timelineText.Text = $"Time: {manualClock.CurrentTime:0}ms";
+ timelineMarker.X = (float)(manualClock.CurrentTime / clock_limit);
+ }
+
+ private DrawableStoryboard createStoryboard(Action? addCommands = null)
+ {
+ var layer = storyboard.GetLayer("Background");
+
+ var sprite = new StoryboardSprite(lookup_name, Anchor.Centre, new Vector2(320, 240));
+ sprite.Commands.AddScale(Easing.None, 0, clock_limit, 0.5f, 0.5f);
+ sprite.Commands.AddAlpha(Easing.None, 0, clock_limit, 1, 1);
+ addCommands?.Invoke(sprite);
+
+ layer.Elements.Clear();
+ layer.Add(sprite);
+
+ return storyboard.CreateDrawable().With(c => c.Clock = new FramedClock(manualClock));
+ }
+
+ private partial class TestStoryboard : Storyboard
+ {
+ public override DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null)
+ {
+ return new TestDrawableStoryboard(this, mods);
+ }
+
+ public bool AlwaysProvideTexture { get; set; }
+
+ public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty;
+
+ private partial class TestDrawableStoryboard : DrawableStoryboard
+ {
+ private readonly bool alwaysProvideTexture;
+
+ public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList? mods)
+ : base(storyboard, mods)
+ {
+ alwaysProvideTexture = storyboard.AlwaysProvideTexture;
+ }
+
+ protected override IResourceStore CreateResourceLookupStore() => alwaysProvideTexture
+ ? new AlwaysReturnsTextureStore()
+ : new ResourceStore();
+
+ internal class AlwaysReturnsTextureStore : IResourceStore
+ {
+ private const string test_image = "Resources/Textures/test-image.png";
+
+ private readonly DllResourceStore store;
+
+ public AlwaysReturnsTextureStore()
+ {
+ store = TestResources.GetStore();
+ }
+
+ public void Dispose() => store.Dispose();
+
+ public byte[] Get(string name) => store.Get(test_image);
+
+ public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken);
+
+ public Stream GetStream(string name) => store.GetStream(test_image);
+
+ public IEnumerable GetAvailableResources() => store.GetAvailableResources();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs
index 502a0de616..ee6a6938a9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
- sprite.TimelineGroup.Alpha.Add(Easing.None, startTime, 0, 0, 1);
+ sprite.Commands.AddAlpha(Easing.None, startTime, 0, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
return storyboard;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index f532921d63..aff6139c08 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
- sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0);
+ sprite.Commands.AddAlpha(Easing.None, 0, duration, 1, 0);
storyboard.GetLayer("Background").Add(sprite);
return storyboard;
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index cebc75f90c..2b17f91e68 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestIntroStoryboardElement() => testLeadIn(b =>
{
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
- sprite.TimelineGroup.Alpha.Add(Easing.None, -2000, 0, 0, 1);
+ sprite.Commands.AddAlpha(Easing.None, -2000, 0, 0, 1);
b.Storyboard.GetLayer("Background").Add(sprite);
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
index 1670741cbd..d84089fb6f 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
@@ -5,6 +5,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
@@ -21,8 +22,10 @@ namespace osu.Game.Tests.Visual.UserInterface
private NowPlayingOverlay nowPlayingOverlay;
[BackgroundDependencyLoader]
- private void load()
+ private void load(FrameworkConfigManager frameworkConfig)
{
+ AddToggleStep("toggle unicode", v => frameworkConfig.SetValue(FrameworkSetting.ShowUnicode, v));
+
nowPlayingOverlay = new NowPlayingOverlay
{
Origin = Anchor.Centre,
@@ -50,9 +53,9 @@ namespace osu.Game.Tests.Visual.UserInterface
Metadata =
{
Artist = "very very very very very very very very very very verry long artist",
- ArtistUnicode = "very very very very very very very very very very verry long artist",
+ ArtistUnicode = "very very very very very very very very very very verry long artist unicode",
Title = "very very very very very verry long title",
- TitleUnicode = "very very very very very verry long title",
+ TitleUnicode = "very very very very very verry long title unicode",
}
}));
@@ -61,9 +64,9 @@ namespace osu.Game.Tests.Visual.UserInterface
Metadata =
{
Artist = "very very very very very very very very very very verrry long artist",
- ArtistUnicode = "very very very very very very very very very very verrry long artist",
+ ArtistUnicode = "not very long artist unicode",
Title = "very very very very very verrry long title",
- TitleUnicode = "very very very very very verrry long title",
+ TitleUnicode = "not very long title unicode",
}
}));
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index b5d9ad1194..2f9a256d31 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps.Legacy;
using osu.Game.IO;
using osu.Game.Storyboards;
+using osu.Game.Storyboards.Commands;
using osuTK;
using osuTK.Graphics;
@@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps.Formats
public class LegacyStoryboardDecoder : LegacyDecoder
{
private StoryboardSprite? storyboardSprite;
- private CommandTimelineGroup? timelineGroup;
+ private StoryboardCommandGroup? currentCommandsGroup;
private Storyboard storyboard = null!;
@@ -164,7 +165,7 @@ namespace osu.Game.Beatmaps.Formats
else
{
if (depth < 2)
- timelineGroup = storyboardSprite?.TimelineGroup;
+ currentCommandsGroup = storyboardSprite?.Commands;
string commandType = split[0];
@@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps.Formats
double startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue;
double endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue;
int groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0;
- timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
+ currentCommandsGroup = storyboardSprite?.AddTriggerGroup(triggerName, startTime, endTime, groupNumber);
break;
}
@@ -184,7 +185,7 @@ namespace osu.Game.Beatmaps.Formats
{
double startTime = Parsing.ParseDouble(split[1]);
int repeatCount = Parsing.ParseInt(split[2]);
- timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1));
+ currentCommandsGroup = storyboardSprite?.AddLoopingGroup(startTime, Math.Max(0, repeatCount - 1));
break;
}
@@ -203,7 +204,7 @@ namespace osu.Game.Beatmaps.Formats
{
float startValue = Parsing.ParseFloat(split[4]);
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
- timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
+ currentCommandsGroup?.AddAlpha(easing, startTime, endTime, startValue, endValue);
break;
}
@@ -211,7 +212,7 @@ namespace osu.Game.Beatmaps.Formats
{
float startValue = Parsing.ParseFloat(split[4]);
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
- timelineGroup?.Scale.Add(easing, startTime, endTime, startValue, endValue);
+ currentCommandsGroup?.AddScale(easing, startTime, endTime, startValue, endValue);
break;
}
@@ -221,7 +222,7 @@ namespace osu.Game.Beatmaps.Formats
float startY = Parsing.ParseFloat(split[5]);
float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX;
float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY;
- timelineGroup?.VectorScale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
+ currentCommandsGroup?.AddVectorScale(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
break;
}
@@ -229,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats
{
float startValue = Parsing.ParseFloat(split[4]);
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
- timelineGroup?.Rotation.Add(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
+ currentCommandsGroup?.AddRotation(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
break;
}
@@ -239,8 +240,8 @@ namespace osu.Game.Beatmaps.Formats
float startY = Parsing.ParseFloat(split[5]);
float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX;
float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY;
- timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
- timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
+ currentCommandsGroup?.AddX(easing, startTime, endTime, startX, endX);
+ currentCommandsGroup?.AddY(easing, startTime, endTime, startY, endY);
break;
}
@@ -248,7 +249,7 @@ namespace osu.Game.Beatmaps.Formats
{
float startValue = Parsing.ParseFloat(split[4]);
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
- timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
+ currentCommandsGroup?.AddX(easing, startTime, endTime, startValue, endValue);
break;
}
@@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats
{
float startValue = Parsing.ParseFloat(split[4]);
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
- timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
+ currentCommandsGroup?.AddY(easing, startTime, endTime, startValue, endValue);
break;
}
@@ -268,7 +269,7 @@ namespace osu.Game.Beatmaps.Formats
float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed;
float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen;
float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue;
- timelineGroup?.Colour.Add(easing, startTime, endTime,
+ currentCommandsGroup?.AddColour(easing, startTime, endTime,
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
break;
@@ -281,16 +282,16 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
case "A":
- timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive,
+ currentCommandsGroup?.AddBlendingParameters(easing, startTime, endTime, BlendingParameters.Additive,
startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
break;
case "H":
- timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
+ currentCommandsGroup?.AddFlipH(easing, startTime, endTime, true, startTime == endTime);
break;
case "V":
- timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
+ currentCommandsGroup?.AddFlipV(easing, startTime, endTime, true, startTime == endTime);
break;
}
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index 29052ace8e..76c8c237d5 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -5,6 +5,7 @@ using System;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
@@ -479,6 +480,11 @@ namespace osu.Game.Overlays
private OsuSpriteText mainSpriteText = null!;
private OsuSpriteText fillerSpriteText = null!;
+ private Bindable showUnicode = null!;
+
+ [Resolved]
+ private FrameworkConfigManager frameworkConfig { get; set; } = null!;
+
private LocalisableString text;
public LocalisableString Text
@@ -531,6 +537,9 @@ namespace osu.Game.Overlays
{
base.LoadComplete();
+ showUnicode = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode);
+ showUnicode.BindValueChanged(_ => updateText());
+
updateFontAndText();
}
diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs
index 768c28cc38..4ae8e51f6d 100644
--- a/osu.Game/Scoring/ScoreImporter.cs
+++ b/osu.Game/Scoring/ScoreImporter.cs
@@ -127,6 +127,9 @@ namespace osu.Game.Scoring
if (model.RealmUser.OnlineID == APIUser.SYSTEM_USER_ID)
return;
+ if (model.OnlineID < 0 && model.LegacyOnlineID <= 0)
+ return;
+
string username = model.RealmUser.Username;
if (usernameLookupCache.TryGetValue(username, out var existing))
diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs
index 2db751402c..95eb2d4376 100644
--- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs
+++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs
@@ -16,6 +16,7 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@@ -138,7 +139,7 @@ namespace osu.Game.Screens.Play
},
automaticDownload = new SettingsCheckbox
{
- LabelText = "Automatically download beatmaps",
+ LabelText = OnlineSettingsStrings.AutomaticallyDownloadMissingBeatmaps,
Current = config.GetBindable(OsuSetting.AutomaticallyDownloadMissingBeatmaps),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs
deleted file mode 100644
index 480d69c12f..0000000000
--- a/osu.Game/Storyboards/CommandLoop.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-
-namespace osu.Game.Storyboards
-{
- public class CommandLoop : CommandTimelineGroup
- {
- public double LoopStartTime;
-
- ///
- /// The total number of times this loop is played back. Always greater than zero.
- ///
- public readonly int TotalIterations;
-
- public override double StartTime => LoopStartTime + CommandsStartTime;
-
- public override double EndTime =>
- // In an ideal world, we would multiply the command duration by TotalIterations here.
- // Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro
- // sequences for minutes or hours.
- StartTime + CommandsDuration;
-
- ///
- /// Construct a new command loop.
- ///
- /// The start time of the loop.
- /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback.
- public CommandLoop(double startTime, int repeatCount)
- {
- if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount));
-
- LoopStartTime = startTime;
- TotalIterations = repeatCount + 1;
- }
-
- public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0)
- {
- for (int loop = 0; loop < TotalIterations; loop++)
- {
- double loopOffset = LoopStartTime + loop * CommandsDuration;
- foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset))
- yield return command;
- }
- }
-
- public override string ToString()
- => $"{LoopStartTime} x{TotalIterations}";
- }
-}
diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs
deleted file mode 100644
index 0650c97165..0000000000
--- a/osu.Game/Storyboards/CommandTimeline.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using osu.Framework.Graphics;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace osu.Game.Storyboards
-{
- public class CommandTimeline : ICommandTimeline
- {
- private readonly List commands = new List();
-
- public IEnumerable Commands => commands.OrderBy(c => c.StartTime);
-
- public bool HasCommands => commands.Count > 0;
-
- public double StartTime { get; private set; } = double.MaxValue;
- public double EndTime { get; private set; } = double.MinValue;
-
- public T StartValue { get; private set; }
- public T EndValue { get; private set; }
-
- public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
- {
- if (endTime < startTime)
- {
- endTime = startTime;
- }
-
- commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue });
-
- if (startTime < StartTime)
- {
- StartValue = startValue;
- StartTime = startTime;
- }
-
- if (endTime > EndTime)
- {
- EndValue = endValue;
- EndTime = endTime;
- }
- }
-
- public override string ToString()
- => $"{commands.Count} command(s)";
-
- public class TypedCommand : ICommand
- {
- public Easing Easing { get; set; }
- public double StartTime { get; set; }
- public double EndTime { get; set; }
- public double Duration => EndTime - StartTime;
-
- public T StartValue;
- public T EndValue;
-
- public int CompareTo(ICommand other)
- {
- int result = StartTime.CompareTo(other.StartTime);
- if (result != 0) return result;
-
- return EndTime.CompareTo(other.EndTime);
- }
-
- public override string ToString()
- => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}";
- }
- }
-
- public interface ICommandTimeline
- {
- double StartTime { get; }
- double EndTime { get; }
- bool HasCommands { get; }
- }
-
- public interface ICommand : IComparable
- {
- Easing Easing { get; set; }
- double StartTime { get; set; }
- double EndTime { get; set; }
- double Duration { get; }
- }
-}
diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs
deleted file mode 100644
index 0b96db6861..0000000000
--- a/osu.Game/Storyboards/CommandTimelineGroup.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osuTK;
-using osuTK.Graphics;
-using osu.Framework.Graphics;
-using System.Collections.Generic;
-using System.Linq;
-using Newtonsoft.Json;
-
-namespace osu.Game.Storyboards
-{
- public delegate CommandTimeline CommandTimelineSelector(CommandTimelineGroup commandTimelineGroup);
-
- public class CommandTimelineGroup
- {
- public CommandTimeline X = new CommandTimeline();
- public CommandTimeline Y = new CommandTimeline();
- public CommandTimeline Scale = new CommandTimeline();
- public CommandTimeline VectorScale = new CommandTimeline();
- public CommandTimeline Rotation = new CommandTimeline();
- public CommandTimeline Colour = new CommandTimeline();
- public CommandTimeline Alpha = new CommandTimeline();
- public CommandTimeline BlendingParameters = new CommandTimeline();
- public CommandTimeline FlipH = new CommandTimeline();
- public CommandTimeline FlipV = new CommandTimeline();
-
- private readonly ICommandTimeline[] timelines;
-
- public CommandTimelineGroup()
- {
- timelines = new ICommandTimeline[]
- {
- X,
- Y,
- Scale,
- VectorScale,
- Rotation,
- Colour,
- Alpha,
- BlendingParameters,
- FlipH,
- FlipV
- };
- }
-
- [JsonIgnore]
- public double CommandsStartTime
- {
- get
- {
- double min = double.MaxValue;
-
- for (int i = 0; i < timelines.Length; i++)
- min = Math.Min(min, timelines[i].StartTime);
-
- return min;
- }
- }
-
- [JsonIgnore]
- public double CommandsEndTime
- {
- get
- {
- double max = double.MinValue;
-
- for (int i = 0; i < timelines.Length; i++)
- max = Math.Max(max, timelines[i].EndTime);
-
- return max;
- }
- }
-
- [JsonIgnore]
- public double CommandsDuration => CommandsEndTime - CommandsStartTime;
-
- [JsonIgnore]
- public virtual double StartTime => CommandsStartTime;
-
- [JsonIgnore]
- public virtual double EndTime => CommandsEndTime;
-
- [JsonIgnore]
- public bool HasCommands
- {
- get
- {
- for (int i = 0; i < timelines.Length; i++)
- {
- if (timelines[i].HasCommands)
- return true;
- }
-
- return false;
- }
- }
-
- public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0)
- {
- if (offset != 0)
- {
- return timelineSelector(this).Commands.Select(command =>
- new CommandTimeline.TypedCommand
- {
- Easing = command.Easing,
- StartTime = offset + command.StartTime,
- EndTime = offset + command.EndTime,
- StartValue = command.StartValue,
- EndValue = command.EndValue,
- });
- }
-
- return timelineSelector(this).Commands;
- }
- }
-}
diff --git a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs
new file mode 100644
index 0000000000..ea14f5fa40
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osu.Game.Storyboards.Drawables;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public interface IStoryboardCommand
+ {
+ ///
+ /// The start time of the storyboard command.
+ ///
+ double StartTime { get; }
+
+ ///
+ /// The end time of the storyboard command.
+ ///
+ double EndTime { get; }
+
+ ///
+ /// The name of the property affected by this storyboard command.
+ /// Used to apply initial property values based on the list of commands given in .
+ ///
+ string PropertyName { get; }
+
+ ///
+ /// Sets the value of the corresponding property in to the start value of this command.
+ ///
+ ///
+ /// Parameter commands (e.g. / / ) only apply the start value if they have zero duration, i.e. take "permanent" effect regardless of time.
+ ///
+ /// The target drawable.
+ void ApplyInitialValue(TDrawable d)
+ where TDrawable : Drawable, IFlippable, IVectorScalable;
+
+ ///
+ /// Applies the transforms described by this storyboard command to the target drawable.
+ ///
+ /// The target drawable.
+ /// The sequence of transforms applied to the target drawable.
+ TransformSequence ApplyTransforms(TDrawable d)
+ where TDrawable : Drawable, IFlippable, IVectorScalable;
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs
new file mode 100644
index 0000000000..1c17da7592
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardAlphaCommand : StoryboardCommand
+ {
+ public StoryboardAlphaCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.Alpha);
+
+ public override void ApplyInitialValue(TDrawable d) => d.Alpha = StartValue;
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs
new file mode 100644
index 0000000000..cf9cadf1a7
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardBlendingParametersCommand : StoryboardCommand
+ {
+ public StoryboardBlendingParametersCommand(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.Blending);
+
+ public override void ApplyInitialValue(TDrawable d)
+ {
+ if (StartTime == EndTime)
+ d.Blending = StartValue;
+ }
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration)
+ .TransformTo(nameof(d.Blending), EndValue);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs
new file mode 100644
index 0000000000..da8a20647c
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osuTK.Graphics;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardColourCommand : StoryboardCommand
+ {
+ public StoryboardColourCommand(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.Colour);
+
+ public override void ApplyInitialValue(TDrawable d) => d.Colour = StartValue;
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.FadeColour(StartValue).Then().FadeColour(EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardCommand.cs b/osu.Game/Storyboards/Commands/StoryboardCommand.cs
new file mode 100644
index 0000000000..60c28e7833
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardCommand.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osu.Game.Storyboards.Drawables;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public abstract class StoryboardCommand : IStoryboardCommand, IComparable>
+ {
+ public double StartTime { get; }
+ public double EndTime { get; }
+
+ public T StartValue { get; }
+ public T EndValue { get; }
+ public Easing Easing { get; }
+
+ public double Duration => EndTime - StartTime;
+
+ protected StoryboardCommand(Easing easing, double startTime, double endTime, T startValue, T endValue)
+ {
+ if (endTime < startTime)
+ endTime = startTime;
+
+ StartTime = startTime;
+ StartValue = startValue;
+ EndTime = endTime;
+ EndValue = endValue;
+ Easing = easing;
+ }
+
+ public abstract string PropertyName { get; }
+
+ public abstract void ApplyInitialValue(TDrawable d)
+ where TDrawable : Drawable, IFlippable, IVectorScalable;
+
+ public abstract TransformSequence ApplyTransforms(TDrawable d)
+ where TDrawable : Drawable, IFlippable, IVectorScalable;
+
+ public int CompareTo(StoryboardCommand? other)
+ {
+ if (other == null)
+ return 1;
+
+ int result = StartTime.CompareTo(other.StartTime);
+ if (result != 0)
+ return result;
+
+ return EndTime.CompareTo(other.EndTime);
+ }
+
+ public override string ToString() => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}";
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs
new file mode 100644
index 0000000000..0925231412
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs
@@ -0,0 +1,130 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using osu.Framework.Graphics;
+using osu.Framework.Lists;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardCommandGroup
+ {
+ private readonly SortedList> x = new SortedList>();
+
+ public IReadOnlyList> X => x;
+
+ private readonly SortedList> y = new SortedList>();
+
+ public IReadOnlyList> Y => y;
+
+ private readonly SortedList> scale = new SortedList>();
+
+ public IReadOnlyList> Scale => scale;
+
+ private readonly SortedList> vectorScale = new SortedList>();
+
+ public IReadOnlyList> VectorScale => vectorScale;
+
+ private readonly SortedList> rotation = new SortedList>();
+
+ public IReadOnlyList> Rotation => rotation;
+
+ private readonly SortedList> colour = new SortedList>();
+
+ public IReadOnlyList> Colour => colour;
+
+ private readonly SortedList> alpha = new SortedList>();
+
+ public IReadOnlyList> Alpha => alpha;
+
+ private readonly SortedList> blendingParameters = new SortedList>();
+
+ public IReadOnlyList> BlendingParameters => blendingParameters;
+
+ private readonly SortedList> flipH = new SortedList>();
+
+ public IReadOnlyList> FlipH => flipH;
+
+ private readonly SortedList> flipV = new SortedList>();
+
+ public IReadOnlyList> FlipV => flipV;
+
+ ///
+ /// Returns the earliest start time of the commands added to this group.
+ ///
+ [JsonIgnore]
+ public double StartTime { get; private set; } = double.MaxValue;
+
+ ///
+ /// Returns the latest end time of the commands added to this group.
+ ///
+ [JsonIgnore]
+ public double EndTime { get; private set; } = double.MinValue;
+
+ [JsonIgnore]
+ public double Duration => EndTime - StartTime;
+
+ [JsonIgnore]
+ public bool HasCommands { get; private set; }
+
+ private readonly IReadOnlyList[] lists;
+
+ public IEnumerable AllCommands => lists.SelectMany(g => g);
+
+ public StoryboardCommandGroup()
+ {
+ lists = new IReadOnlyList[] { X, Y, Scale, VectorScale, Rotation, Colour, Alpha, BlendingParameters, FlipH, FlipV };
+ }
+
+ public void AddX(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ => AddCommand(x, new StoryboardXCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddY(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ => AddCommand(y, new StoryboardYCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddScale(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ => AddCommand(scale, new StoryboardScaleCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddVectorScale(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue)
+ => AddCommand(vectorScale, new StoryboardVectorScaleCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddRotation(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ => AddCommand(rotation, new StoryboardRotationCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddColour(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue)
+ => AddCommand(colour, new StoryboardColourCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddAlpha(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ => AddCommand(alpha, new StoryboardAlphaCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddBlendingParameters(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue)
+ => AddCommand(blendingParameters, new StoryboardBlendingParametersCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddFlipH(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
+ => AddCommand(flipH, new StoryboardFlipHCommand(easing, startTime, endTime, startValue, endValue));
+
+ public void AddFlipV(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
+ => AddCommand(flipV, new StoryboardFlipVCommand(easing, startTime, endTime, startValue, endValue));
+
+ ///
+ /// Adds the given storyboard to the target .
+ /// Can be overriden to apply custom effects to the given command before adding it to the list (e.g. looping or time offsets).
+ ///
+ /// The value type of the target property affected by this storyboard command.
+ protected virtual void AddCommand(ICollection> list, StoryboardCommand command)
+ {
+ list.Add(command);
+ HasCommands = true;
+
+ if (command.StartTime < StartTime)
+ StartTime = command.StartTime;
+
+ if (command.EndTime > EndTime)
+ EndTime = command.EndTime;
+ }
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs
new file mode 100644
index 0000000000..fbf7295f15
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osu.Game.Storyboards.Drawables;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardFlipHCommand : StoryboardCommand
+ {
+ public StoryboardFlipHCommand(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(IFlippable.FlipH);
+
+ public override void ApplyInitialValue(TDrawable d)
+ {
+ if (StartTime == EndTime)
+ d.FlipH = StartValue;
+ }
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.TransformTo(nameof(IFlippable.FlipH), StartValue).Delay(Duration)
+ .TransformTo(nameof(IFlippable.FlipH), EndValue);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs
new file mode 100644
index 0000000000..136bd52f1f
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osu.Game.Storyboards.Drawables;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardFlipVCommand : StoryboardCommand
+ {
+ public StoryboardFlipVCommand(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(IFlippable.FlipV);
+
+ public override void ApplyInitialValue(TDrawable d)
+ {
+ if (StartTime == EndTime)
+ d.FlipV = StartValue;
+ }
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.TransformTo(nameof(IFlippable.FlipV), StartValue).Delay(Duration)
+ .TransformTo(nameof(IFlippable.FlipV), EndValue);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs
new file mode 100644
index 0000000000..fe334ad608
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs
@@ -0,0 +1,66 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardLoopingGroup : StoryboardCommandGroup
+ {
+ private readonly double loopStartTime;
+
+ ///
+ /// The total number of times this loop is played back. Always greater than zero.
+ ///
+ public readonly int TotalIterations;
+
+ ///
+ /// Construct a new command loop.
+ ///
+ /// The start time of the loop.
+ /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback.
+ public StoryboardLoopingGroup(double startTime, int repeatCount)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(repeatCount);
+
+ loopStartTime = startTime;
+ TotalIterations = repeatCount + 1;
+ }
+
+ protected override void AddCommand(ICollection> list, StoryboardCommand command)
+ => base.AddCommand(list, new StoryboardLoopingCommand(command, this));
+
+ public override string ToString() => $"{loopStartTime} x{TotalIterations}";
+
+ private class StoryboardLoopingCommand : StoryboardCommand
+ {
+ private readonly StoryboardCommand command;
+ private readonly StoryboardLoopingGroup loopingGroup;
+
+ public StoryboardLoopingCommand(StoryboardCommand command, StoryboardLoopingGroup loopingGroup)
+ // In an ideal world, we would multiply the command duration by TotalIterations in command end time.
+ // Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro
+ // sequences for minutes or hours.
+ : base(command.Easing, loopingGroup.loopStartTime + command.StartTime, loopingGroup.loopStartTime + command.EndTime, command.StartValue, command.EndValue)
+ {
+ this.command = command;
+ this.loopingGroup = loopingGroup;
+ }
+
+ public override string PropertyName => command.PropertyName;
+
+ public override void ApplyInitialValue(TDrawable d) => command.ApplyInitialValue(d);
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ {
+ if (loopingGroup.TotalIterations == 0)
+ return command.ApplyTransforms(d);
+
+ double loopingGroupDuration = loopingGroup.Duration;
+ return command.ApplyTransforms(d).Loop(loopingGroupDuration - Duration, loopingGroup.TotalIterations);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs
new file mode 100644
index 0000000000..7e097fce25
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardRotationCommand : StoryboardCommand
+ {
+ public StoryboardRotationCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.Rotation);
+
+ public override void ApplyInitialValue(TDrawable d) => d.Rotation = StartValue;
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.RotateTo(StartValue).Then().RotateTo(EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs
new file mode 100644
index 0000000000..832533af5e
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osuTK;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardScaleCommand : StoryboardCommand
+ {
+ public StoryboardScaleCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.Scale);
+
+ public override void ApplyInitialValue(TDrawable d) => d.Scale = new Vector2(StartValue);
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.ScaleTo(StartValue).Then().ScaleTo(EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/CommandTrigger.cs b/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs
similarity index 74%
rename from osu.Game/Storyboards/CommandTrigger.cs
rename to osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs
index 011f345df2..89a68e9ec0 100644
--- a/osu.Game/Storyboards/CommandTrigger.cs
+++ b/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs
@@ -1,16 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-namespace osu.Game.Storyboards
+namespace osu.Game.Storyboards.Commands
{
- public class CommandTrigger : CommandTimelineGroup
+ public class StoryboardTriggerGroup : StoryboardCommandGroup
{
public string TriggerName;
public double TriggerStartTime;
public double TriggerEndTime;
public int GroupNumber;
- public CommandTrigger(string triggerName, double startTime, double endTime, int groupNumber)
+ public StoryboardTriggerGroup(string triggerName, double startTime, double endTime, int groupNumber)
{
TriggerName = triggerName;
TriggerStartTime = startTime;
diff --git a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs
new file mode 100644
index 0000000000..06983a1590
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+using osu.Game.Storyboards.Drawables;
+using osuTK;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardVectorScaleCommand : StoryboardCommand
+ {
+ public StoryboardVectorScaleCommand(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(IVectorScalable.VectorScale);
+
+ public override void ApplyInitialValue(TDrawable d) => d.VectorScale = StartValue;
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.TransformTo(nameof(d.VectorScale), StartValue).Then()
+ .TransformTo(nameof(d.VectorScale), EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs
new file mode 100644
index 0000000000..d52e9c8a05
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardXCommand : StoryboardCommand
+ {
+ public StoryboardXCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.X);
+
+ public override void ApplyInitialValue(TDrawable d) => d.X = StartValue;
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.MoveToX(StartValue).Then().MoveToX(EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs
new file mode 100644
index 0000000000..90dfe4d995
--- /dev/null
+++ b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Storyboards.Commands
+{
+ public class StoryboardYCommand : StoryboardCommand
+ {
+ public StoryboardYCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
+ : base(easing, startTime, endTime, startValue, endValue)
+ {
+ }
+
+ public override string PropertyName => nameof(Drawable.Y);
+
+ public override void ApplyInitialValue(TDrawable d) => d.Y = StartValue;
+
+ public override TransformSequence ApplyTransforms(TDrawable d)
+ => d.MoveToY(StartValue).Then().MoveToY(EndValue, Duration, Easing);
+ }
+}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
index f2454be190..329564a345 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
@@ -1,11 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.IO;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
+using osu.Framework.Utils;
+using osuTK;
namespace osu.Game.Storyboards.Drawables
{
@@ -13,7 +18,7 @@ namespace osu.Game.Storyboards.Drawables
{
public readonly StoryboardVideo Video;
- private Video? drawableVideo;
+ private DrawableVideo? drawableVideo;
public override bool RemoveWhenNotAlive => false;
@@ -25,7 +30,7 @@ namespace osu.Game.Storyboards.Drawables
// This allows scaling based on the video's absolute size.
//
// If not specified we take up the full available space.
- bool useRelative = !video.TimelineGroup.Scale.HasCommands;
+ bool useRelative = !video.Commands.Scale.Any();
RelativeSizeAxes = useRelative ? Axes.Both : Axes.None;
AutoSizeAxes = useRelative ? Axes.None : Axes.Both;
@@ -42,7 +47,7 @@ namespace osu.Game.Storyboards.Drawables
if (stream == null)
return;
- InternalChild = drawableVideo = new Video(stream, false)
+ InternalChild = drawableVideo = new DrawableVideo(stream, false)
{
RelativeSizeAxes = RelativeSizeAxes,
FillMode = FillMode.Fill,
@@ -70,5 +75,65 @@ namespace osu.Game.Storyboards.Drawables
drawableVideo.FadeOut(500);
}
}
+
+ private partial class DrawableVideo : Video, IFlippable, IVectorScalable
+ {
+ private bool flipH;
+
+ public bool FlipH
+ {
+ get => flipH;
+ set
+ {
+ if (flipH == value)
+ return;
+
+ flipH = value;
+ Invalidate(Invalidation.MiscGeometry);
+ }
+ }
+
+ private bool flipV;
+
+ public bool FlipV
+ {
+ get => flipV;
+ set
+ {
+ if (flipV == value)
+ return;
+
+ flipV = value;
+ Invalidate(Invalidation.MiscGeometry);
+ }
+ }
+
+ private Vector2 vectorScale = Vector2.One;
+
+ public Vector2 VectorScale
+ {
+ get => vectorScale;
+ set
+ {
+ if (vectorScale == value)
+ return;
+
+ if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(VectorScale)} must be finite, but is {value}.");
+
+ vectorScale = value;
+ Invalidate(Invalidation.MiscGeometry);
+ }
+ }
+
+ protected override Vector2 DrawScale
+ => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale;
+
+ public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV);
+
+ public DrawableVideo(Stream stream, bool startAtCurrentTime = true)
+ : base(stream, startAtCurrentTime)
+ {
+ }
+ }
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs
deleted file mode 100644
index bbc55a336d..0000000000
--- a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
-
-namespace osu.Game.Storyboards.Drawables
-{
- public static class DrawablesExtensions
- {
- ///
- /// Adjusts after a delay.
- ///
- /// A to which further transforms can be added.
- public static TransformSequence TransformBlendingMode(this T drawable, BlendingParameters newValue, double delay = 0)
- where T : Drawable
- => drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingParameters(), newValue, delay));
- }
-
- public class TransformBlendingParameters : Transform
- {
- private BlendingParameters valueAt(double time)
- => time < EndTime ? StartValue : EndValue;
-
- public override string TargetMember => nameof(Drawable.Blending);
-
- protected override void Apply(Drawable d, double time) => d.Blending = valueAt(time);
- protected override void ReadIntoStartValue(Drawable d) => StartValue = d.Blending;
- }
-}
diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs
index 165b3d97cc..79f98ea6ef 100644
--- a/osu.Game/Storyboards/Drawables/IFlippable.cs
+++ b/osu.Game/Storyboards/Drawables/IFlippable.cs
@@ -1,55 +1,13 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
namespace osu.Game.Storyboards.Drawables
{
- internal interface IFlippable : ITransformable
+ public interface IFlippable : IDrawable
{
bool FlipH { get; set; }
bool FlipV { get; set; }
}
-
- internal class TransformFlipH : Transform
- {
- private bool valueAt(double time)
- => time < EndTime ? StartValue : EndValue;
-
- public override string TargetMember => nameof(IFlippable.FlipH);
-
- protected override void Apply(IFlippable d, double time) => d.FlipH = valueAt(time);
- protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipH;
- }
-
- internal class TransformFlipV : Transform
- {
- private bool valueAt(double time)
- => time < EndTime ? StartValue : EndValue;
-
- public override string TargetMember => nameof(IFlippable.FlipV);
-
- protected override void Apply(IFlippable d, double time) => d.FlipV = valueAt(time);
- protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipV;
- }
-
- internal static class FlippableExtensions
- {
- ///
- /// Adjusts after a delay.
- ///
- /// A to which further transforms can be added.
- public static TransformSequence TransformFlipH(this T flippable, bool newValue, double delay = 0)
- where T : class, IFlippable
- => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay));
-
- ///
- /// Adjusts after a delay.
- ///
- /// A to which further transforms can be added.
- public static TransformSequence TransformFlipV(this T flippable, bool newValue, double delay = 0)
- where T : class, IFlippable
- => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay));
- }
}
diff --git a/osu.Game/Storyboards/Drawables/IVectorScalable.cs b/osu.Game/Storyboards/Drawables/IVectorScalable.cs
index 60a297e126..ce6047c8f6 100644
--- a/osu.Game/Storyboards/Drawables/IVectorScalable.cs
+++ b/osu.Game/Storyboards/Drawables/IVectorScalable.cs
@@ -1,21 +1,13 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
using osuTK;
namespace osu.Game.Storyboards.Drawables
{
- internal interface IVectorScalable : ITransformable
+ public interface IVectorScalable : IDrawable
{
Vector2 VectorScale { get; set; }
}
-
- internal static class VectorScalableExtensions
- {
- public static TransformSequence VectorScaleTo(this T target, Vector2 newVectorScale, double duration = 0, Easing easing = Easing.None)
- where T : class, IVectorScalable
- => target.TransformTo(nameof(IVectorScalable.VectorScale), newVectorScale, duration, easing);
- }
}
diff --git a/osu.Game/Storyboards/StoryboardAnimation.cs b/osu.Game/Storyboards/StoryboardAnimation.cs
index 1a4b6bb923..0b714633c9 100644
--- a/osu.Game/Storyboards/StoryboardAnimation.cs
+++ b/osu.Game/Storyboards/StoryboardAnimation.cs
@@ -21,8 +21,7 @@ namespace osu.Game.Storyboards
LoopType = loopType;
}
- public override Drawable CreateDrawable()
- => new DrawableStoryboardAnimation(this);
+ public override Drawable CreateDrawable() => new DrawableStoryboardAnimation(this);
}
public enum AnimationLoopType
diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs
index 982185d51b..42426c8c85 100644
--- a/osu.Game/Storyboards/StoryboardSprite.cs
+++ b/osu.Game/Storyboards/StoryboardSprite.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
+using osu.Game.Storyboards.Commands;
using osu.Game.Storyboards.Drawables;
using osuTK;
@@ -12,8 +13,8 @@ namespace osu.Game.Storyboards
{
public class StoryboardSprite : IStoryboardElementWithDuration
{
- private readonly List loops = new List();
- private readonly List triggers = new List();
+ private readonly List loopingGroups = new List();
+ private readonly List triggerGroups = new List();
public string Path { get; }
public bool IsDrawable => HasCommands;
@@ -21,7 +22,7 @@ namespace osu.Game.Storyboards
public Anchor Origin;
public Vector2 InitialPosition;
- public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup();
+ public readonly StoryboardCommandGroup Commands = new StoryboardCommandGroup();
public double StartTime
{
@@ -34,13 +35,13 @@ namespace osu.Game.Storyboards
// anything before that point can be ignored (the sprite is not visible after all).
var alphaCommands = new List<(double startTime, bool isZeroStartValue)>();
- var command = TimelineGroup.Alpha.Commands.FirstOrDefault();
+ var command = Commands.Alpha.FirstOrDefault();
if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0));
- foreach (var loop in loops)
+ foreach (var loop in loopingGroups)
{
- command = loop.Alpha.Commands.FirstOrDefault();
- if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0));
+ command = loop.Alpha.FirstOrDefault();
+ if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0));
}
if (alphaCommands.Count > 0)
@@ -61,8 +62,8 @@ namespace osu.Game.Storyboards
{
// If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value.
// The sprite's StartTime will be determined by the earliest command, regardless of type.
- double earliestStartTime = TimelineGroup.StartTime;
- foreach (var l in loops)
+ double earliestStartTime = Commands.StartTime;
+ foreach (var l in loopingGroups)
earliestStartTime = Math.Min(earliestStartTime, l.StartTime);
return earliestStartTime;
}
@@ -72,9 +73,9 @@ namespace osu.Game.Storyboards
{
get
{
- double latestEndTime = TimelineGroup.EndTime;
+ double latestEndTime = Commands.EndTime;
- foreach (var l in loops)
+ foreach (var l in loopingGroups)
latestEndTime = Math.Max(latestEndTime, l.EndTime);
return latestEndTime;
@@ -85,20 +86,16 @@ namespace osu.Game.Storyboards
{
get
{
- double latestEndTime = TimelineGroup.EndTime;
+ double latestEndTime = Commands.EndTime;
- foreach (var l in loops)
- latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations);
+ foreach (var l in loopingGroups)
+ latestEndTime = Math.Max(latestEndTime, l.StartTime + l.Duration * l.TotalIterations);
return latestEndTime;
}
}
- public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);
-
- private delegate void DrawablePropertyInitializer(Drawable drawable, T value);
-
- private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing);
+ public bool HasCommands => Commands.HasCommands || loopingGroups.Any(l => l.HasCommands);
public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition)
{
@@ -107,127 +104,39 @@ namespace osu.Game.Storyboards
InitialPosition = initialPosition;
}
- public CommandLoop AddLoop(double startTime, int repeatCount)
+ public virtual Drawable CreateDrawable() => new DrawableStoryboardSprite(this);
+
+ public StoryboardLoopingGroup AddLoopingGroup(double loopStartTime, int repeatCount)
{
- var loop = new CommandLoop(startTime, repeatCount);
- loops.Add(loop);
+ var loop = new StoryboardLoopingGroup(loopStartTime, repeatCount);
+ loopingGroups.Add(loop);
return loop;
}
- public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber)
+ public StoryboardTriggerGroup AddTriggerGroup(string triggerName, double startTime, double endTime, int groupNumber)
{
- var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber);
- triggers.Add(trigger);
+ var trigger = new StoryboardTriggerGroup(triggerName, startTime, endTime, groupNumber);
+ triggerGroups.Add(trigger);
return trigger;
}
- public virtual Drawable CreateDrawable()
- => new DrawableStoryboardSprite(this);
-
- public void ApplyTransforms(Drawable drawable, IEnumerable>? triggeredGroups = null)
+ public void ApplyTransforms(TDrawable drawable)
+ where TDrawable : Drawable, IFlippable, IVectorScalable
{
- // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
- // To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
- // The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially.
+ HashSet appliedProperties = new HashSet();
- List generated = new List();
+ // For performance reasons, we need to apply the commands in chronological order.
+ // Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
+ IEnumerable commands = Commands.AllCommands;
+ commands = commands.Concat(loopingGroups.SelectMany(l => l.AllCommands));
- generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing));
- generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing));
- generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing));
- generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing));
- generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing));
- generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing));
- generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, _) => d.TransformBlendingMode(value, duration),
- false);
-
- if (drawable is IVectorScalable vectorScalable)
+ foreach (var command in commands.OrderBy(c => c.StartTime))
{
- generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value,
- (_, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing));
- }
-
- if (drawable is IFlippable flippable)
- {
- generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, (_, value, duration, _) => flippable.TransformFlipH(value, duration),
- false);
- generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, (_, value, duration, _) => flippable.TransformFlipV(value, duration),
- false);
- }
-
- foreach (var command in generated.OrderBy(g => g.StartTime))
- command.ApplyTo(drawable);
- }
-
- private void generateCommands(List resultList, IEnumerable.TypedCommand> commands,
- DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true)
- {
- bool initialized = false;
-
- foreach (var command in commands)
- {
- DrawablePropertyInitializer? initFunc = null;
-
- if (!initialized)
- {
- if (alwaysInitialize || command.StartTime == command.EndTime)
- initFunc = initializeProperty;
- initialized = true;
- }
-
- resultList.Add(new GeneratedCommand(command, initFunc, transform));
- }
- }
-
- private IEnumerable.TypedCommand> getCommands(CommandTimelineSelector timelineSelector, IEnumerable>? triggeredGroups)
- {
- var commands = TimelineGroup.GetCommands(timelineSelector);
- foreach (var loop in loops)
- commands = commands.Concat(loop.GetCommands(timelineSelector));
-
- if (triggeredGroups != null)
- {
- foreach (var pair in triggeredGroups)
- commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2));
- }
-
- return commands;
- }
-
- public override string ToString()
- => $"{Path}, {Origin}, {InitialPosition}";
-
- private interface IGeneratedCommand
- {
- double StartTime { get; }
-
- void ApplyTo(Drawable drawable);
- }
-
- private readonly struct GeneratedCommand : IGeneratedCommand
- {
- public double StartTime => command.StartTime;
-
- private readonly DrawablePropertyInitializer? initializeProperty;
- private readonly DrawableTransformer transform;
- private readonly CommandTimeline.TypedCommand command;
-
- public GeneratedCommand(CommandTimeline.TypedCommand command, DrawablePropertyInitializer? initializeProperty, DrawableTransformer transform)
- {
- this.command = command;
- this.initializeProperty = initializeProperty;
- this.transform = transform;
- }
-
- public void ApplyTo(Drawable drawable)
- {
- initializeProperty?.Invoke(drawable, command.StartValue);
+ if (appliedProperties.Add(command.PropertyName))
+ command.ApplyInitialValue(drawable);
using (drawable.BeginAbsoluteSequence(command.StartTime))
- {
- transform(drawable, command.StartValue, 0, Easing.None);
- transform(drawable, command.EndValue, command.Duration, command.Easing);
- }
+ command.ApplyTransforms(drawable);
}
}
}
diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs
index 5573162d26..14189a1a6c 100644
--- a/osu.Game/Storyboards/StoryboardVideo.cs
+++ b/osu.Game/Storyboards/StoryboardVideo.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Storyboards
{
// This is just required to get a valid StartTime based on the incoming offset.
// Actual fades are handled inside DrawableStoryboardVideo for now.
- TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 0);
+ Commands.AddAlpha(Easing.None, offset, offset, 0, 0);
}
public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this);
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 51af281ac6..08eb264aab 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -347,6 +347,7 @@
GL
GLSL
HID
+ HP
HSL
HSPA
HSV