diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
index 03ee7c9204..63c481a623 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index 2baa7ee0e0..5babdb47ff 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index 55c0cf6a3b..c44cbb845b 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index a2308e6dfc..5d64ca832a 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
index b45505678c..5beb6616a7 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index e839d2657c..6796a8962b 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index 55c0cf6a3b..c44cbb845b 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index a2308e6dfc..5d64ca832a 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf
index 503e5935f5..606988ccdf 100644
--- a/osu.Desktop.slnf
+++ b/osu.Desktop.slnf
@@ -16,15 +16,14 @@
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
"osu.Game\\osu.Game.csproj",
-
- "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
- "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
+ "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
- "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
+ "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
- "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
- "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj"
+ "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
+ "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
+ "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj"
]
}
-}
+}
\ No newline at end of file
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 6b95a82703..a7453dc0e0 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -102,7 +102,7 @@ namespace osu.Desktop
}
}
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = !tournamentClient }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null }))
{
if (!host.IsPrimaryInstance)
{
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index f37cfdc5f1..d6a11fa924 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -23,9 +23,9 @@
-
+
-
+
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 5de21a68d0..47c93fbd02 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index c45c85833c..0a77845343 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 161a59c5fd..12a4182bf1 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
}
- public override ScoreRank RankFromAccuracy(double accuracy)
+ public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results)
{
if (accuracy == accuracy_cutoff_x)
return ScoreRank.X;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
index ab9f57ecc3..a5c18babe2 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
@@ -1,10 +1,8 @@
// 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 NUnit.Framework;
-using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
@@ -16,37 +14,35 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Test]
public void TestMinor()
{
- AddStep("Create barlines", () => recreate());
+ AddStep("Create barlines", recreate);
}
- private void recreate(Func>? createBarLines = null)
+ private void recreate()
{
var stageDefinitions = new List
{
new StageDefinition(4),
};
- SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
+ SetContents(_ =>
{
- if (createBarLines != null)
+ var maniaPlayfield = new ManiaPlayfield(stageDefinitions);
+
+ // Must be scheduled so the pool is loaded before we try and retrieve from it.
+ Schedule(() =>
{
- var barLines = createBarLines();
-
- foreach (var b in barLines)
- s.Add(b);
-
- return;
- }
-
- for (int i = 0; i < 64; i++)
- {
- s.Add(new BarLine
+ for (int i = 0; i < 64; i++)
{
- StartTime = Time.Current + i * 500,
- Major = i % 4 == 0,
- });
- }
- }));
+ maniaPlayfield.Add(new BarLine
+ {
+ StartTime = Time.Current + i * 500,
+ Major = i % 4 == 0,
+ });
+ }
+ });
+
+ return maniaPlayfield;
+ });
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index b991db408c..877b9c3ea4 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index 874130233a..5f5596cbb3 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -25,16 +25,16 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
private OsuConfigManager config { get; set; } = null!;
- private readonly List> pools;
+ private readonly List> pools = new List>();
- public TestSceneDrawableJudgement()
+ [TestCaseSource(nameof(validResults))]
+ public void Test(HitResult result)
{
- pools = new List>();
-
- foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
- showResult(result);
+ showResult(result);
}
+ private static IEnumerable validResults => Enum.GetValues().Skip(1);
+
[Test]
public void TestHitLightingDisabled()
{
@@ -72,32 +72,33 @@ namespace osu.Game.Rulesets.Osu.Tests
pools.Add(pool = new DrawablePool(1));
else
{
- pool = pools[poolIndex];
-
// We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent.
+ pool = pools[poolIndex];
((Container)pool.Parent!).Clear(false);
}
var container = new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- pool,
- pool.Get(j => j.Apply(new JudgementResult(new HitObject
- {
- StartTime = Time.Current
- }, new Judgement())
- {
- Type = result,
- }, null)).With(j =>
- {
- j.Anchor = Anchor.Centre;
- j.Origin = Anchor.Centre;
- })
- }
+ Child = pool,
};
+ // Must be scheduled so the pool is loaded before we try and retrieve from it.
+ Schedule(() =>
+ {
+ container.Add(pool.Get(j => j.Apply(new JudgementResult(new HitObject
+ {
+ StartTime = Time.Current
+ }, new Judgement())
+ {
+ Type = result,
+ }, null)).With(j =>
+ {
+ j.Anchor = Anchor.Centre;
+ j.Origin = Anchor.Centre;
+ }));
+ });
+
poolIndex++;
return container;
});
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
index f41dd913ab..380a2087ac 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
DrawableSlider dho = null;
- AddStep("create slider", () => Child = dho = new DrawableSlider(prepareObject(new Slider
+ AddStep("create slider", () => Child = dho = new DrawableSlider(applyDefaults(new Slider
{
Position = new Vector2(256, 192),
IndexInCurrentCombo = 0,
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddWaitStep("wait for progression", 1);
- AddStep("apply new slider", () => dho.Apply(prepareObject(new Slider
+ AddStep("apply new slider", () => dho.Apply(applyDefaults(new Slider
{
Position = new Vector2(256, 192),
ComboIndex = 1,
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Child = new SkinProvidingContainer(provider)
{
RelativeSizeAxes = Axes.Both,
- Child = dho = new DrawableSlider(prepareObject(new Slider
+ Child = dho = new DrawableSlider(applyDefaults(new Slider
{
Position = new Vector2(256, 192),
IndexInCurrentCombo = 0,
@@ -97,7 +97,38 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("ball is red", () => dho.ChildrenOfType().Single().BallColour == Color4.Red);
}
- private Slider prepareObject(Slider slider)
+ [Test]
+ public void TestIncreaseRepeatCount()
+ {
+ DrawableSlider dho = null;
+
+ AddStep("create slider", () =>
+ {
+ Child = dho = new DrawableSlider(applyDefaults(new Slider
+ {
+ Position = new Vector2(256, 192),
+ IndexInCurrentCombo = 0,
+ StartTime = Time.Current,
+ Path = new SliderPath(PathType.LINEAR, new[]
+ {
+ Vector2.Zero,
+ new Vector2(150, 100),
+ new Vector2(300, 0),
+ })
+ }));
+ });
+
+ AddStep("increase repeat count", () =>
+ {
+ dho.HitObject.RepeatCount++;
+ applyDefaults(dho.HitObject);
+ });
+
+ AddAssert("repeat got custom anchor", () =>
+ dho.ChildrenOfType().Single().RelativeAnchorPosition == Vector2.Divide(dho.SliderBody!.PathOffset, dho.DrawSize));
+ }
+
+ private Slider applyDefaults(Slider slider)
{
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return slider;
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index ea033cda45..9c248abd66 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index ed4c02a4a9..baec200107 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Container repeatContainer;
private PausableSkinnableSound slidingSample;
- private readonly LayoutValue drawSizeLayout;
+ private readonly LayoutValue relativeAnchorPositionLayout;
public DrawableSlider()
: this(null)
@@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true,
Alpha = 0
};
- AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry));
+ AddLayout(relativeAnchorPositionLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry));
}
[BackgroundDependencyLoader]
@@ -190,6 +190,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
repeatContainer.Add(repeat);
break;
}
+
+ relativeAnchorPositionLayout.Invalidate();
}
protected override void ClearNestedHitObjects()
@@ -265,14 +267,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = SliderBody?.Size ?? Vector2.Zero;
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
- if (!drawSizeLayout.IsValid)
+ if (!relativeAnchorPositionLayout.IsValid)
{
Vector2 pos = Vector2.Divide(OriginPosition, DrawSize);
foreach (var obj in NestedHitObjects)
obj.RelativeAnchorPosition = pos;
Ball.RelativeAnchorPosition = pos;
- drawSizeLayout.Validate();
+ relativeAnchorPositionLayout.Validate();
}
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 97980c6d18..4d8381cf42 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -1,9 +1,11 @@
// 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 osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
@@ -14,6 +16,22 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
}
+ public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results)
+ {
+ ScoreRank rank = base.RankFromScore(accuracy, results);
+
+ switch (rank)
+ {
+ case ScoreRank.S:
+ case ScoreRank.X:
+ if (results.GetValueOrDefault(HitResult.Miss) > 0)
+ rank = ScoreRank.A;
+ break;
+ }
+
+ return rank;
+ }
+
protected override HitEvent CreateHitEvent(JudgementResult result)
=> base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
index bb61bd37c1..9a5abba4fb 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
@@ -65,14 +65,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
{
this.RotateTo(-45);
- this.ScaleTo(1.8f);
+ this.ScaleTo(1.6f);
this.ScaleTo(1.2f, 100, Easing.In);
- this.MoveTo(Vector2.Zero);
- this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint);
+ this.FadeOutFromOne(400);
}
else if (Result.IsMiss())
{
+ this.FadeOutFromOne(800);
+
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
@@ -84,14 +85,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
}
else
{
+ this.FadeOutFromOne(800);
+
JudgementText
.FadeInFromZero(300, Easing.OutQuint)
.ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
}
- this.FadeOutFromOne(800);
-
ringExplosion?.PlayAnimation();
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs
index b65f46c414..272f4b5658 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs
@@ -3,47 +3,39 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
- public partial class DefaultApproachCircle : SkinnableSprite
+ public partial class DefaultApproachCircle : Sprite
{
- private readonly IBindable accentColour = new Bindable();
-
[Resolved]
private DrawableHitObject drawableObject { get; set; } = null!;
- public DefaultApproachCircle()
- : base("Gameplay/osu/approachcircle")
- {
- }
+ private IBindable accentColour = null!;
[BackgroundDependencyLoader]
- private void load()
+ private void load(TextureStore textures)
{
- accentColour.BindTo(drawableObject.AccentColour);
+ Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
+
+ // account for the sprite being used for the default approach circle being taken from stable,
+ // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
+ Scale = new Vector2(128 / 118f);
}
protected override void LoadComplete()
{
base.LoadComplete();
- accentColour.BindValueChanged(colour => Colour = colour.NewValue, true);
- }
- protected override Drawable CreateDefault(ISkinComponentLookup lookup)
- {
- var drawable = base.CreateDefault(lookup);
-
- // Although this is a non-legacy component, osu-resources currently stores approach circle as a legacy-like texture.
- // See LegacyApproachCircle for documentation as to why this is required.
- drawable.Scale = new Vector2(128 / 118f);
-
- return drawable;
+ accentColour = drawableObject.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs
index f1143cf14d..512ac8ee3e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs
@@ -1,6 +1,7 @@
// 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.Game.Graphics.Backgrounds;
namespace osu.Game.Rulesets.Osu.Skinning.Default
@@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
{
TriangleScale = 1.2f;
HideAlphaDiscrepancies = false;
+ ClampAxes = Axes.None;
}
protected override void Update()
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs
index eea6606233..0bdea0cab1 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs
@@ -1,9 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Skinning;
@@ -12,40 +13,31 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
- // todo: this should probably not be a SkinnableSprite, as this is always created for legacy skins and is recreated on skin change.
- public partial class LegacyApproachCircle : SkinnableSprite
+ public partial class LegacyApproachCircle : Sprite
{
- private readonly IBindable accentColour = new Bindable();
-
[Resolved]
private DrawableHitObject drawableObject { get; set; } = null!;
- public LegacyApproachCircle()
- : base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS * 2)
- {
- }
+ private IBindable accentColour = null!;
[BackgroundDependencyLoader]
- private void load()
+ private void load(ISkinSource skin)
{
- accentColour.BindTo(drawableObject.AccentColour);
+ var texture = skin.GetTexture(@"approachcircle");
+ Debug.Assert(texture != null);
+ Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
+
+ // account for the sprite being used for the default approach circle being taken from stable,
+ // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
+ Scale = new Vector2(128 / 118f);
}
protected override void LoadComplete()
{
base.LoadComplete();
+
+ accentColour = drawableObject.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
}
-
- protected override Drawable CreateDefault(ISkinComponentLookup lookup)
- {
- var drawable = base.CreateDefault(lookup);
-
- // account for the sprite being used for the default approach circle being taken from stable,
- // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
- drawable.Scale = new Vector2(128 / 118f);
-
- return drawable;
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs
index 780084115d..ad1fb98aef 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs
@@ -34,19 +34,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableObject, ISkinSource skinSource)
{
+ const string lookup_name = @"reversearrow";
+
drawableRepeat = (DrawableSliderRepeat)drawableObject;
AutoSizeAxes = Axes.Both;
- string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName;
-
- var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
+ var skin = skinSource.FindProvider(s => s.GetTexture(lookup_name) != null);
InternalChild = arrow = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Texture = skin?.GetTexture(lookupName)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2),
+ Texture = skin?.GetTexture(lookup_name)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2),
};
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index c01d28c8e1..d2ebc68c52 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -163,7 +163,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
case OsuSkinComponents.ApproachCircle:
- return new LegacyApproachCircle();
+ if (GetTexture(@"approachcircle") != null)
+ return new LegacyApproachCircle();
+
+ return null;
default:
throw new UnsupportedSkinComponentException(lookup);
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
index c89e2b727b..d1a8a048ed 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
@@ -1,17 +1,19 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
+using osuTK;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
@@ -37,11 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Beatmap.Value.Track.Start();
});
- AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield
+ AddStep("Load playfield", () => SetContents(_ => new Container
{
- Height = 0.2f,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(2f, 1f),
+ Scale = new Vector2(0.5f),
+ Child = new TaikoPlayfieldAdjustmentContainer { Child = new TaikoPlayfield() },
}));
}
@@ -54,7 +59,20 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[Test]
public void TestHeightChanges()
{
- AddRepeatStep("change height", () => this.ChildrenOfType().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50);
+ int value = 0;
+
+ AddRepeatStep("change height", () =>
+ {
+ value = (value + 1) % 5;
+
+ this.ChildrenOfType().ForEach(p =>
+ {
+ var parent = (Container)p.Parent.AsNonNull();
+ parent.Scale = new Vector2(0.5f + 0.1f * value);
+ parent.Width = 1f / parent.Scale.X;
+ parent.Height = 0.5f / parent.Scale.Y;
+ });
+ }, 50);
}
[Test]
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 48465bb119..4eb6b0ab3d 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index 2fd9f070ec..7e40d575bc 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Scoring;
namespace osu.Game.Rulesets.Taiko.Scoring
{
@@ -33,6 +35,22 @@ namespace osu.Game.Rulesets.Taiko.Scoring
* strongScaleValue(result);
}
+ public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results)
+ {
+ ScoreRank rank = base.RankFromScore(accuracy, results);
+
+ switch (rank)
+ {
+ case ScoreRank.S:
+ case ScoreRank.X:
+ if (results.GetValueOrDefault(HitResult.Miss) > 0)
+ rank = ScoreRank.A;
+ break;
+ }
+
+ return rank;
+ }
+
public override int GetBaseScoreForResult(HitResult result)
{
switch (result)
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
index 07a7f237ba..838f172186 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
@@ -1,7 +1,6 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -143,16 +142,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
if (target != null)
{
- const float alpha_amount = 1;
-
const float down_time = 80;
const float up_time = 50;
- target.Animate(
- t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time)
- ).Then(
- t => t.FadeOut(up_time)
- );
+ target
+ .FadeTo(1, down_time * (1 - target.Alpha), Easing.Out)
+ .Delay(100).FadeOut(up_time);
}
return false;
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs
index cf9d8dd52e..0b43f1c845 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs
@@ -17,30 +17,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
RelativeSizeAxes = Axes.Both;
- InternalChild = new Container
+ InternalChildren = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Children = new Drawable[]
+ new Sprite
{
- new Sprite
- {
- Texture = skin.GetTexture("approachcircle"),
- Scale = new Vector2(0.83f),
- Alpha = 0.47f, // eyeballed to match stable
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new Sprite
- {
- Texture = skin.GetTexture("taikobigcircle"),
- Scale = new Vector2(0.8f),
- Alpha = 0.22f, // eyeballed to match stable
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- }
+ Texture = skin.GetTexture("approachcircle"),
+ Scale = new Vector2(0.83f),
+ Alpha = 0.47f, // eyeballed to match stable
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new Sprite
+ {
+ Texture = skin.GetTexture("taikobigcircle"),
+ Scale = new Vector2(0.8f),
+ Alpha = 0.22f, // eyeballed to match stable
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
};
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 6e7c8c3631..7e3967dc95 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -10,7 +10,6 @@ using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions;
-using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Legacy;
@@ -23,6 +22,7 @@ using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
@@ -59,14 +59,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
- Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
+ Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore);
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
- Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
+ Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
Assert.That(score.Replay.Frames, Is.Not.Empty);
@@ -252,7 +252,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
[Test]
- public void AccuracyAndRankOfStableScorePreserved()
+ public void AccuracyOfStableScoreRecomputed()
{
var memoryStream = new MemoryStream();
@@ -261,15 +261,16 @@ namespace osu.Game.Tests.Beatmaps.Formats
// and we want to emulate a stable score here
using (var sw = new SerializationWriter(memoryStream, true))
{
- sw.Write((byte)0); // ruleset id (osu!)
+ sw.Write((byte)3); // ruleset id (mania).
+ // mania is used intentionally as it is the only ruleset wherein default accuracy calculation is changed in lazer
sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable)
sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
sw.Write("username"); // irrelevant to this test
sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
- sw.Write((ushort)198); // count300
- sw.Write((ushort)1); // count100
+ sw.Write((ushort)1); // count300
+ sw.Write((ushort)0); // count100
sw.Write((ushort)0); // count50
- sw.Write((ushort)0); // countGeki
+ sw.Write((ushort)198); // countGeki (perfects / "rainbow 300s" in mania)
sw.Write((ushort)0); // countKatu
sw.Write((ushort)1); // countMiss
sw.Write(12345678); // total score, irrelevant to this test
@@ -287,13 +288,54 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.Multiple(() =>
{
- Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 300 + 100) / (200 * 300)));
- Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A));
+ Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 305 + 300) / (200 * 305)));
+ Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
});
}
[Test]
- public void AccuracyAndRankOfLazerScorePreserved()
+ public void RankOfStableScoreUsesLazerDefinitions()
+ {
+ var memoryStream = new MemoryStream();
+
+ // local partial implementation of legacy score encoder
+ // this is done half for readability, half because `LegacyScoreEncoder` forces `LATEST_VERSION`
+ // and we want to emulate a stable score here
+ using (var sw = new SerializationWriter(memoryStream, true))
+ {
+ sw.Write((byte)0); // ruleset id (osu!)
+ sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable)
+ sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
+ sw.Write("username"); // irrelevant to this test
+ sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
+ sw.Write((ushort)195); // count300
+ sw.Write((ushort)1); // count100
+ sw.Write((ushort)4); // count50
+ sw.Write((ushort)0); // countGeki
+ sw.Write((ushort)0); // countKatu
+ sw.Write((ushort)0); // countMiss
+ sw.Write(12345678); // total score, irrelevant to this test
+ sw.Write((ushort)1000); // max combo, irrelevant to this test
+ sw.Write(false); // full combo, irrelevant to this test
+ sw.Write((int)LegacyMods.Hidden); // mods
+ sw.Write(string.Empty); // hp graph, irrelevant
+ sw.Write(DateTime.Now); // date, irrelevant
+ sw.Write(Array.Empty()); // replay data, irrelevant
+ sw.Write((long)1234); // legacy online ID, irrelevant
+ }
+
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ var decoded = new TestLegacyScoreDecoder().Parse(memoryStream);
+
+ Assert.Multiple(() =>
+ {
+ // In stable this would be an A because there are over 1% 50s. But that's not a thing in lazer.
+ Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
+ });
+ }
+
+ [Test]
+ public void AccuracyRankAndTotalScoreOfLazerScorePreserved()
{
var ruleset = new OsuRuleset().RulesetInfo;
@@ -321,8 +363,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.Multiple(() =>
{
+ Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(284_537));
+ Assert.That(decodedAfterEncode.ScoreInfo.LegacyTotalScore, Is.Null);
Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30)));
- Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
+ Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A));
});
}
@@ -415,6 +459,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
Ruleset = new OsuRuleset().RulesetInfo,
Difficulty = new BeatmapDifficulty(),
BeatmapVersion = beatmapVersion,
+ },
+ // needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die
+ // when trying to recompute total score.
+ HitObjects =
+ {
+ new HitCircle()
}
});
}
diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
index 8b066f860f..e960995c45 100644
--- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
+++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
@@ -210,31 +211,6 @@ namespace osu.Game.Tests.Database
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001));
}
- [Test]
- public void TestNonLegacyScoreNotSubjectToUpgrades()
- {
- ScoreInfo scoreInfo = null!;
- TestBackgroundDataStoreProcessor processor = null!;
-
- AddStep("Add score which requires upgrade (and has beatmap)", () =>
- {
- Realm.Write(r =>
- {
- r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: r.All().First())
- {
- TotalScoreVersion = 30000005,
- LegacyTotalScore = 123456,
- });
- });
- });
-
- AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
- AddUntilStep("Wait for completion", () => processor.Completed);
-
- AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
- AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000005));
- }
-
public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor
{
protected override int TimeToSleepDuringGameplay => 10;
diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs
index ff954211eb..fbe5a0e4d7 100644
--- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs
+++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs
@@ -56,24 +56,6 @@ namespace osu.Game.Tests.NonVisual.Skinning
"Gameplay/osu/followpoint", 1
},
new object[]
- {
- new[] { "followpoint@2x", "followpoint" },
- "Gameplay/osu/followpoint",
- "followpoint@2x", 2
- },
- new object[]
- {
- new[] { "followpoint@2x" },
- "Gameplay/osu/followpoint",
- "followpoint@2x", 2
- },
- new object[]
- {
- new[] { "followpoint" },
- "Gameplay/osu/followpoint",
- "followpoint", 1
- },
- new object[]
{
// Looking up a filename with extension specified should work.
new[] { "followpoint.png" },
diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs
index 378dd99664..dd4c372193 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs
@@ -29,7 +29,8 @@ namespace osu.Game.Tests.Visual.Background
ColourDark = Color4.Gray,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(0.9f)
+ Size = new Vector2(0.9f),
+ ClampAxes = Axes.None
}
};
}
@@ -40,7 +41,10 @@ namespace osu.Game.Tests.Visual.Background
AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s);
AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s));
- AddToggleStep("Masking", m => triangles.Masking = m);
+ AddStep("ClampAxes X", () => triangles.ClampAxes = Axes.X);
+ AddStep("ClampAxes Y", () => triangles.ClampAxes = Axes.Y);
+ AddStep("ClampAxes Both", () => triangles.ClampAxes = Axes.Both);
+ AddStep("ClampAxes None", () => triangles.ClampAxes = Axes.None);
}
}
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs
index 01a2464b8e..4713852c0b 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs
@@ -86,7 +86,8 @@ namespace osu.Game.Tests.Visual.Background
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
+ ClampAxes = Axes.None
}
}
},
@@ -128,7 +129,10 @@ namespace osu.Game.Tests.Visual.Background
AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
- AddToggleStep("Masking", m => maskedTriangles.Masking = m);
+ AddStep("ClampAxes X", () => maskedTriangles.ClampAxes = Axes.X);
+ AddStep("ClampAxes Y", () => maskedTriangles.ClampAxes = Axes.Y);
+ AddStep("ClampAxes Both", () => maskedTriangles.ClampAxes = Axes.Both);
+ AddStep("ClampAxes None", () => maskedTriangles.ClampAxes = Axes.None);
}
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs
index f2b3351533..6c36e6729e 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs
@@ -210,6 +210,13 @@ namespace osu.Game.Tests.Visual.Editing
switchPresets(-1);
assertPreset(BeatDivisorType.Custom, 15);
assertBeatSnap(15);
+
+ setDivisorViaInput(24);
+ assertPreset(BeatDivisorType.Custom, 24);
+ switchPresets(1);
+ assertPreset(BeatDivisorType.Common);
+ switchPresets(-2);
+ assertPreset(BeatDivisorType.Triplets);
}
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
index 534348bed3..98a97e1d23 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
@@ -129,10 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
checkRate(1);
}
- private const int max_frames_catchup = 50;
-
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
- mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup }
+ mainContainer.Child = new FrameStabilityContainer(gameplayStartTime)
.WithChild(consumer = new ClockConsumingChild()));
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index ec3b3e0822..73aa3be73d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -93,15 +93,12 @@ namespace osu.Game.Tests.Visual.Gameplay
double currentTime = masterClock.CurrentTime;
- bool goingForward = currentTime >= (masterClock.LastStopTime ?? lastStopTime);
+ bool goingForward = currentTime >= lastStopTime;
alwaysGoingForward &= goingForward;
if (!goingForward)
Logger.Log($"Went too far backwards (last stop: {lastStopTime:N1} current: {currentTime:N1})");
-
- if (masterClock.LastStopTime != null)
- lastStopTime = masterClock.LastStopTime.Value;
};
});
@@ -125,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay
resumeAndConfirm();
- AddAssert("Resumed without seeking forward", () => Player.LastResumeTime, () => Is.LessThanOrEqualTo(Player.LastPauseTime));
+ AddAssert("continued playing forward", () => Player.LastResumeTime, () => Is.GreaterThanOrEqualTo(Player.LastPauseTime));
AddUntilStep("player playing", () => Player.LocalUserPlaying.Value);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index e2ce3a014c..5e22e47572 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -23,6 +23,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Scoring;
+using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Beatmaps;
@@ -383,6 +384,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AllowImportCompletion = new SemaphoreSlim(1);
}
+ protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart)
+ {
+ ShouldValidatePlaybackRate = false,
+ };
+
protected override async Task ImportScore(Score score)
{
ScoreImportStarted = true;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
index e975a85401..99f0ffb9d0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Child = frameStabilityContainer = new FrameStabilityContainer
{
- MaxCatchUpFrames = 1
+ Child = new FakeLoad()
}
}
});
@@ -56,6 +57,15 @@ namespace osu.Game.Tests.Visual.Gameplay
Dependencies.CacheAs(frameStabilityContainer);
}
+ private partial class FakeLoad : Drawable
+ {
+ protected override void Update()
+ {
+ base.Update();
+ Thread.Sleep(1);
+ }
+ }
+
[SetUpSteps]
public void SetupSteps()
{
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
index 1ecd38e1d3..83430b5665 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation
});
AddStep("create IPC sender channels", () =>
{
- ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true });
+ ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPort = OsuGame.IPC_PORT });
osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost);
});
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
index 435dd77120..41a5603060 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -45,6 +46,16 @@ namespace osu.Game.Tests.Visual.Ranking
addCircleStep(createScore(1, new OsuRuleset()));
}
+ [Test]
+ public void TestOsuRankHidden()
+ {
+ addCircleStep(createScore(0, new OsuRuleset(), 20, true));
+ addCircleStep(createScore(0.8, new OsuRuleset(), 5, true));
+ addCircleStep(createScore(0.95, new OsuRuleset(), 0, true));
+ addCircleStep(createScore(0.97, new OsuRuleset(), 1, true));
+ addCircleStep(createScore(1, new OsuRuleset(), 0, true));
+ }
+
[Test]
public void TestCatchRank()
{
@@ -65,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking
addCircleStep(createScore(1, new CatchRuleset()));
}
- private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy})", () =>
+ private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy}, {score.Statistics.GetValueOrDefault(HitResult.Miss)} miss)", () =>
{
Children = new Drawable[]
{
@@ -92,10 +103,22 @@ namespace osu.Game.Tests.Visual.Ranking
};
});
- private ScoreInfo createScore(double accuracy, Ruleset ruleset)
+ private ScoreInfo createScore(double accuracy, Ruleset ruleset, int missCount = 0, bool hidden = false)
{
var scoreProcessor = ruleset.CreateScoreProcessor();
+ var statistics = new Dictionary
+ {
+ { HitResult.Miss, missCount },
+ { HitResult.Meh, 50 },
+ { HitResult.Good, 100 },
+ { HitResult.Great, 300 },
+ };
+
+ var mods = hidden
+ ? new[] { new OsuModHidden() }
+ : new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() };
+
return new ScoreInfo
{
User = new APIUser
@@ -105,19 +128,13 @@ namespace osu.Game.Tests.Visual.Ranking
},
BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
Ruleset = ruleset.RulesetInfo,
- Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
+ Mods = mods,
TotalScore = 2845370,
Accuracy = accuracy,
MaxCombo = 999,
- Rank = scoreProcessor.RankFromAccuracy(accuracy),
+ Rank = scoreProcessor.RankFromScore(accuracy, statistics),
Date = DateTimeOffset.Now,
- Statistics =
- {
- { HitResult.Miss, 1 },
- { HitResult.Meh, 50 },
- { HitResult.Good, 100 },
- { HitResult.Great, 300 },
- }
+ Statistics = statistics,
};
}
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index ab2e867255..866e20d063 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -21,6 +21,7 @@ using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
@@ -71,15 +72,16 @@ namespace osu.Game.Tests.Visual.Ranking
private int onlineScoreID = 1;
- [TestCase(1, ScoreRank.X)]
- [TestCase(0.9999, ScoreRank.S)]
- [TestCase(0.975, ScoreRank.S)]
- [TestCase(0.925, ScoreRank.A)]
- [TestCase(0.85, ScoreRank.B)]
- [TestCase(0.75, ScoreRank.C)]
- [TestCase(0.5, ScoreRank.D)]
- [TestCase(0.2, ScoreRank.D)]
- public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
+ [TestCase(1, ScoreRank.X, 0)]
+ [TestCase(0.9999, ScoreRank.S, 0)]
+ [TestCase(0.975, ScoreRank.S, 0)]
+ [TestCase(0.975, ScoreRank.A, 1)]
+ [TestCase(0.925, ScoreRank.A, 5)]
+ [TestCase(0.85, ScoreRank.B, 9)]
+ [TestCase(0.75, ScoreRank.C, 11)]
+ [TestCase(0.5, ScoreRank.D, 21)]
+ [TestCase(0.2, ScoreRank.D, 51)]
+ public void TestResultsWithPlayer(double accuracy, ScoreRank rank, int missCount)
{
TestResultsScreen screen = null;
@@ -91,6 +93,7 @@ namespace osu.Game.Tests.Visual.Ranking
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
score.Accuracy = accuracy;
score.Rank = rank;
+ score.Statistics[HitResult.Miss] = missCount;
return screen = createResultsScreen(score);
});
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index ef6c16f2c4..7b08524240 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs
index 5f642b14f5..e09d1be22c 100644
--- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs
+++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development", new HostOptions { BindIPC = true }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development"))
{
host.Run(new TournamentTestBrowser());
return 0;
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 2cc07dd9ed..3b00f103c4 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs
index 587e6bbeed..d0ffbdd459 100644
--- a/osu.Game/Beatmaps/FramedBeatmapClock.cs
+++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs
@@ -27,11 +27,6 @@ namespace osu.Game.Beatmaps
{
private readonly bool applyOffsets;
- ///
- /// The total frequency adjustment from pause transforms. Should eventually be handled in a better way.
- ///
- public readonly BindableDouble ExternalPauseFrequencyAdjust = new BindableDouble(1);
-
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
private readonly OffsetCorrectionClock? platformOffsetClock;
private readonly OffsetCorrectionClock? userBeatmapOffsetClock;
@@ -69,13 +64,13 @@ namespace osu.Game.Beatmaps
{
// Audio timings in general with newer BASS versions don't match stable.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
- platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
+ platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
// User global offset (set in settings) should also be applied.
- userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock, ExternalPauseFrequencyAdjust);
+ userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
// User per-beatmap offset will be applied to this final clock.
- finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock, ExternalPauseFrequencyAdjust);
+ finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock);
}
else
{
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index c05831d043..6b2cb4ee74 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -142,6 +142,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false);
SetDefault(OsuSetting.ReplaySettingsOverlay, true);
+ SetDefault(OsuSetting.ReplayPlaybackControlsExpanded, true);
SetDefault(OsuSetting.GameplayLeaderboard, true);
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
@@ -421,6 +422,7 @@ namespace osu.Game.Configuration
ProfileCoverExpanded,
EditorLimitedDistanceSnap,
ReplaySettingsOverlay,
+ ReplayPlaybackControlsExpanded,
AutomaticallyDownloadMissingBeatmaps,
EditorShowSpeedChanges,
TouchDisableGameplayTaps,
diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs
similarity index 87%
rename from osu.Game/BackgroundDataStoreProcessor.cs
rename to osu.Game/Database/BackgroundDataStoreProcessor.cs
index fc7db13d41..be0c83bdb3 100644
--- a/osu.Game/BackgroundDataStoreProcessor.cs
+++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs
@@ -12,7 +12,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
-using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Overlays;
@@ -22,7 +21,7 @@ using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Play;
-namespace osu.Game
+namespace osu.Game.Database
{
///
/// Performs background updating of data stores at startup.
@@ -74,6 +73,7 @@ namespace osu.Game
processBeatmapsWithMissingObjectCounts();
processScoresWithMissingStatistics();
convertLegacyTotalScoreToStandardised();
+ upgradeScoreRanks();
}, TaskCreationOptions.LongRunning).ContinueWith(t =>
{
if (t.Exception?.InnerException is ObjectDisposedException)
@@ -355,7 +355,7 @@ namespace osu.Game
realmAccess.Write(r =>
{
ScoreInfo s = r.Find(id)!;
- StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager);
+ StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager.GetWorkingBeatmap(s.BeatmapInfo));
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
});
@@ -376,6 +376,66 @@ namespace osu.Game
completeNotification(notification, processedCount, scoreIds.Count, failedCount);
}
+ private void upgradeScoreRanks()
+ {
+ Logger.Log("Querying for scores that need rank upgrades...");
+
+ HashSet scoreIds = realmAccess.Run(r => new HashSet(
+ r.All()
+ .Where(s => s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION)
+ .AsEnumerable()
+ // must be done after materialisation, as realm doesn't support
+ // filtering on nested property predicates or projection via `.Select()`
+ .Where(s => s.Ruleset.IsLegacyRuleset())
+ .Select(s => s.ID)));
+
+ Logger.Log($"Found {scoreIds.Count} scores which require rank upgrades.");
+
+ if (scoreIds.Count == 0)
+ return;
+
+ var notification = showProgressNotification(scoreIds.Count, "Adjusting ranks of scores", "scores now have more correct ranks");
+
+ int processedCount = 0;
+ int failedCount = 0;
+
+ foreach (var id in scoreIds)
+ {
+ if (notification?.State == ProgressNotificationState.Cancelled)
+ break;
+
+ updateNotificationProgress(notification, processedCount, scoreIds.Count);
+
+ sleepIfRequired();
+
+ try
+ {
+ // Can't use async overload because we're not on the update thread.
+ // ReSharper disable once MethodHasAsyncOverload
+ realmAccess.Write(r =>
+ {
+ ScoreInfo s = r.Find(id)!;
+ s.Rank = StandardisedScoreMigrationTools.ComputeRank(s);
+ s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
+ });
+
+ ++processedCount;
+ }
+ catch (ObjectDisposedException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.Log($"Failed to update rank score {id}: {e}");
+ realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true);
+ ++failedCount;
+ }
+ }
+
+ completeNotification(notification, processedCount, scoreIds.Count, failedCount);
+ }
+
private void updateNotificationProgress(ProgressNotification? notification, int processedCount, int totalCount)
{
if (notification == null)
diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs
index 8c73806cb5..403e73ab77 100644
--- a/osu.Game/Database/StandardisedScoreMigrationTools.cs
+++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs
@@ -232,42 +232,60 @@ namespace osu.Game.Database
}
///
- /// Updates a legacy to standardised scoring.
+ /// Updates a to standardised scoring.
+ /// This will recompite the score's (always), (always),
+ /// and (if the score comes from stable).
+ /// The total score from stable - if any applicable - will be stored to .
///
/// The score to update.
- /// A used for lookups.
- public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps)
+ /// The applicable for this score.
+ public static void UpdateFromLegacy(ScoreInfo score, WorkingBeatmap beatmap)
{
- score.TotalScore = convertFromLegacyTotalScore(score, beatmaps);
- score.Accuracy = ComputeAccuracy(score);
+ var ruleset = score.Ruleset.CreateInstance();
+ var scoreProcessor = ruleset.CreateScoreProcessor();
+
+ // warning: ordering is important here - both total score and ranks are dependent on accuracy!
+ score.Accuracy = computeAccuracy(score, scoreProcessor);
+ score.Rank = computeRank(score, scoreProcessor);
+ score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap);
}
///
- /// Updates a legacy to standardised scoring.
+ /// Updates a to standardised scoring.
+ /// This will recompute the score's (always), (always),
+ /// and (if the score comes from stable).
+ /// The total score from stable - if any applicable - will be stored to .
///
+ ///
+ /// This overload is intended for server-side flows.
+ /// See: https://github.com/ppy/osu-queue-score-statistics/blob/3681e92ac91c6c61922094bdbc7e92e6217dd0fc/osu.Server.Queues.ScoreStatisticsProcessor/Commands/Queue/BatchInserter.cs
+ ///
/// The score to update.
+ /// The in which the score was set.
/// The beatmap difficulty.
/// The legacy scoring attributes for the beatmap which the score was set on.
- public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
+ public static void UpdateFromLegacy(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
- score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes);
- score.Accuracy = ComputeAccuracy(score);
+ var scoreProcessor = ruleset.CreateScoreProcessor();
+
+ // warning: ordering is important here - both total score and ranks are dependent on accuracy!
+ score.Accuracy = computeAccuracy(score, scoreProcessor);
+ score.Rank = computeRank(score, scoreProcessor);
+ score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
}
///
/// Converts from to the new standardised scoring of .
///
/// The score to convert the total score of.
- /// A used for lookups.
+ /// The in which the score was set.
+ /// The applicable for this score.
/// The standardised total score.
- private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
+ private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap)
{
if (!score.IsLegacyScore)
return score.TotalScore;
- WorkingBeatmap beatmap = beatmaps.GetWorkingBeatmap(score.BeatmapInfo);
- Ruleset ruleset = score.Ruleset.CreateInstance();
-
if (ruleset is not ILegacyRuleset legacyRuleset)
return score.TotalScore;
@@ -283,24 +301,24 @@ namespace osu.Game.Database
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
- return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
+ return convertFromLegacyTotalScore(score, ruleset, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
}
///
/// Converts from to the new standardised scoring of .
///
/// The score to convert the total score of.
+ /// The in which the score was set.
/// The beatmap difficulty.
/// The legacy scoring attributes for the beatmap which the score was set on.
/// The standardised total score.
- private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
+ private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
if (!score.IsLegacyScore)
return score.TotalScore;
Debug.Assert(score.LegacyTotalScore != null);
- Ruleset ruleset = score.Ruleset.CreateInstance();
if (ruleset is not ILegacyRuleset legacyRuleset)
return score.TotalScore;
@@ -474,14 +492,9 @@ namespace osu.Game.Database
break;
case 3:
- // in the mania case accuracy actually changes between score V1 and score V2 / standardised
- // (PERFECT weighting changes from 300 to 305),
- // so for better accuracy recompute accuracy locally based on hit statistics and use that instead,
- double scoreV2Accuracy = ComputeAccuracy(score);
-
convertedTotalScore = (long)Math.Round((
850000 * comboProportion
- + 150000 * Math.Pow(scoreV2Accuracy, 2 + 2 * scoreV2Accuracy)
+ + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
+ bonusProportion) * modMultiplier);
break;
@@ -584,11 +597,8 @@ namespace osu.Game.Database
}
}
- public static double ComputeAccuracy(ScoreInfo scoreInfo)
+ private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
{
- Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
- ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
-
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
@@ -597,6 +607,18 @@ namespace osu.Game.Database
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
}
+ public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor());
+
+ private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
+ {
+ var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics);
+
+ foreach (var mod in scoreInfo.Mods.OfType())
+ rank = mod.AdjustRank(rank, scoreInfo.Accuracy);
+
+ return rank;
+ }
+
///
/// Used to populate the model using data parsed from its corresponding replay file.
///
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 1a78c1ec5e..e877915fac 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -15,7 +15,6 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Allocation;
using System.Collections.Generic;
using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Lists;
using osu.Framework.Bindables;
@@ -78,10 +77,10 @@ namespace osu.Game.Graphics.Backgrounds
}
///
- /// If enabled, only the portion of triangles that falls within this 's
- /// shape is drawn to the screen.
+ /// Controls on which the portion of triangles that falls within this 's
+ /// shape is drawn to the screen. Default is Axes.Both.
///
- public bool Masking { get; set; }
+ public Axes ClampAxes { get; set; } = Axes.Both;
///
/// Whether we should drop-off alpha values of triangles more quickly to improve
@@ -258,13 +257,12 @@ namespace osu.Game.Graphics.Backgrounds
private IShader shader;
private Texture texture;
- private bool masking;
+ private Axes clampAxes;
private readonly List parts = new List();
private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size;
private Vector2 size;
- private IVertexBatch vertexBatch;
public TrianglesDrawNode(Triangles source)
: base(source)
@@ -278,7 +276,7 @@ namespace osu.Game.Graphics.Backgrounds
shader = Source.shader;
texture = Source.texture;
size = Source.DrawSize;
- masking = Source.Masking;
+ clampAxes = Source.ClampAxes;
parts.Clear();
parts.AddRange(Source.parts);
@@ -290,12 +288,6 @@ namespace osu.Game.Graphics.Backgrounds
{
base.Draw(renderer);
- if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount))
- {
- vertexBatch?.Dispose();
- vertexBatch = renderer.CreateQuadBatch(Source.AimCount, 1);
- }
-
borderDataBuffer ??= renderer.CreateUniformBuffer();
borderDataBuffer.Data = borderDataBuffer.Data with
{
@@ -314,7 +306,7 @@ namespace osu.Game.Graphics.Backgrounds
Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
- Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y);
+ Quad triangleQuad = getClampedQuad(clampAxes, topLeft, relativeSize);
var drawQuad = new Quad(
Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
@@ -333,30 +325,35 @@ namespace osu.Game.Graphics.Backgrounds
triangleQuad.Height
) / relativeSize;
- renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
+ renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), textureCoords: textureCoords);
}
shader.Unbind();
}
- private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
+ private static Quad getClampedQuad(Axes clampAxes, Vector2 topLeft, Vector2 size)
{
- float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
- float topClamped = Math.Clamp(topLeft.Y, 0f, 1f);
+ Vector2 clampedTopLeft = topLeft;
- return new Quad(
- leftClamped,
- topClamped,
- Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped,
- Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped
- );
+ if (clampAxes == Axes.X || clampAxes == Axes.Both)
+ {
+ clampedTopLeft.X = Math.Clamp(topLeft.X, 0f, 1f);
+ size.X = Math.Clamp(topLeft.X + size.X, 0f, 1f) - clampedTopLeft.X;
+ }
+
+ if (clampAxes == Axes.Y || clampAxes == Axes.Both)
+ {
+ clampedTopLeft.Y = Math.Clamp(topLeft.Y, 0f, 1f);
+ size.Y = Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - clampedTopLeft.Y;
+ }
+
+ return new Quad(clampedTopLeft.X, clampedTopLeft.Y, size.X, size.Y);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- vertexBatch?.Dispose();
borderDataBuffer?.Dispose();
}
}
diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs
index a20fd78569..706b05f5ad 100644
--- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs
+++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Allocation;
using System.Collections.Generic;
using osu.Framework.Graphics.Rendering;
-using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -34,10 +33,10 @@ namespace osu.Game.Graphics.Backgrounds
protected virtual bool CreateNewTriangles => true;
///
- /// If enabled, only the portion of triangles that falls within this 's
- /// shape is drawn to the screen.
+ /// Controls on which the portion of triangles that falls within this 's
+ /// shape is drawn to the screen. Default is Axes.Both.
///
- public bool Masking { get; set; }
+ public Axes ClampAxes { get; set; } = Axes.Both;
private readonly BindableFloat spawnRatio = new BindableFloat(1f);
@@ -194,9 +193,7 @@ namespace osu.Game.Graphics.Backgrounds
private Vector2 size;
private float thickness;
private float texelSize;
- private bool masking;
-
- private IVertexBatch? vertexBatch;
+ private Axes clampAxes;
public TrianglesDrawNode(TrianglesV2 source)
: base(source)
@@ -211,7 +208,7 @@ namespace osu.Game.Graphics.Backgrounds
texture = Source.texture;
size = Source.DrawSize;
thickness = Source.Thickness;
- masking = Source.Masking;
+ clampAxes = Source.ClampAxes;
Quad triangleQuad = new Quad(
Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix),
@@ -235,12 +232,6 @@ namespace osu.Game.Graphics.Backgrounds
if (Source.AimCount == 0 || thickness == 0)
return;
- if (vertexBatch == null || vertexBatch.Size != Source.AimCount)
- {
- vertexBatch?.Dispose();
- vertexBatch = renderer.CreateQuadBatch(Source.AimCount, 1);
- }
-
borderDataBuffer ??= renderer.CreateUniformBuffer();
borderDataBuffer.Data = borderDataBuffer.Data with
{
@@ -257,7 +248,7 @@ namespace osu.Game.Graphics.Backgrounds
{
Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
- Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y);
+ Quad triangleQuad = getClampedQuad(clampAxes, topLeft, relativeSize);
var drawQuad = new Quad(
Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
@@ -273,30 +264,35 @@ namespace osu.Game.Graphics.Backgrounds
triangleQuad.Height
) / relativeSize;
- renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
+ renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), textureCoords: textureCoords);
}
shader.Unbind();
}
- private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
+ private static Quad getClampedQuad(Axes clampAxes, Vector2 topLeft, Vector2 size)
{
- float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
- float topClamped = Math.Clamp(topLeft.Y, 0f, 1f);
+ Vector2 clampedTopLeft = topLeft;
- return new Quad(
- leftClamped,
- topClamped,
- Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped,
- Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped
- );
+ if (clampAxes == Axes.X || clampAxes == Axes.Both)
+ {
+ clampedTopLeft.X = Math.Clamp(topLeft.X, 0f, 1f);
+ size.X = Math.Clamp(topLeft.X + size.X, 0f, 1f) - clampedTopLeft.X;
+ }
+
+ if (clampAxes == Axes.Y || clampAxes == Axes.Both)
+ {
+ clampedTopLeft.Y = Math.Clamp(topLeft.Y, 0f, 1f);
+ size.Y = Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - clampedTopLeft.Y;
+ }
+
+ return new Quad(clampedTopLeft.X, clampedTopLeft.Y, size.X, size.Y);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- vertexBatch?.Dispose();
borderDataBuffer?.Dispose();
}
}
diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs
index 5abc66d2ac..2ae4dc5a76 100644
--- a/osu.Game/Graphics/Containers/WaveContainer.cs
+++ b/osu.Game/Graphics/Containers/WaveContainer.cs
@@ -122,7 +122,7 @@ namespace osu.Game.Graphics.Containers
protected override void PopIn()
{
- foreach (var w in wavesContainer.Children)
+ foreach (var w in wavesContainer)
w.Show();
contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint);
@@ -132,7 +132,7 @@ namespace osu.Game.Graphics.Containers
protected override void PopOut()
{
- foreach (var w in wavesContainer.Children)
+ foreach (var w in wavesContainer)
w.Hide();
contentContainer.MoveToY(2, DISAPPEAR_DURATION, Easing.In);
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index 26e499ae9a..a085558b3a 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -1,8 +1,6 @@
// 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 System;
using System.IO;
using System.Threading;
@@ -24,6 +22,8 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
namespace osu.Game.Graphics
{
@@ -37,30 +37,26 @@ namespace osu.Game.Graphics
///
public IBindable CursorVisibility => cursorVisibility;
- private Bindable screenshotFormat;
- private Bindable captureMenuCursor;
+ [Resolved]
+ private GameHost host { get; set; } = null!;
[Resolved]
- private GameHost host { get; set; }
+ private Clipboard clipboard { get; set; } = null!;
[Resolved]
- private Clipboard clipboard { get; set; }
-
- private Storage storage;
+ private INotificationOverlay notificationOverlay { get; set; } = null!;
[Resolved]
- private INotificationOverlay notificationOverlay { get; set; }
+ private OsuConfigManager config { get; set; } = null!;
- private Sample shutter;
+ private Storage storage = null!;
+
+ private Sample? shutter;
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config, Storage storage, AudioManager audio)
+ private void load(Storage storage, AudioManager audio)
{
this.storage = storage.GetStorageForDirectory(@"screenshots");
-
- screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat);
- captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor);
-
shutter = audio.Samples.Get("UI/shutter");
}
@@ -72,7 +68,7 @@ namespace osu.Game.Graphics
switch (e.Action)
{
case GlobalAction.TakeScreenshot:
- shutter.Play();
+ shutter?.Play();
TakeScreenshotAsync().FireAndForget();
return true;
}
@@ -90,9 +86,12 @@ namespace osu.Game.Graphics
{
Interlocked.Increment(ref screenShotTasks);
+ ScreenshotFormat screenshotFormat = config.Get(OsuSetting.ScreenshotFormat);
+ bool captureMenuCursor = config.Get(OsuSetting.ScreenshotCaptureMenuCursor);
+
try
{
- if (!captureMenuCursor.Value)
+ if (!captureMenuCursor)
{
cursorVisibility.Value = false;
@@ -101,7 +100,7 @@ namespace osu.Game.Graphics
int framesWaited = 0;
- using (var framesWaitedEvent = new ManualResetEventSlim(false))
+ using (ManualResetEventSlim framesWaitedEvent = new ManualResetEventSlim(false))
{
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
{
@@ -117,17 +116,41 @@ namespace osu.Game.Graphics
}
}
- using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
+ using (Image? image = await host.TakeScreenshotAsync().ConfigureAwait(false))
{
+ if (config.Get(OsuSetting.Scaling) == ScalingMode.Everything)
+ {
+ float posX = config.Get(OsuSetting.ScalingPositionX);
+ float posY = config.Get(OsuSetting.ScalingPositionY);
+ float sizeX = config.Get(OsuSetting.ScalingSizeX);
+ float sizeY = config.Get(OsuSetting.ScalingSizeY);
+
+ image.Mutate(m =>
+ {
+ Rectangle rect = new Rectangle(Point.Empty, m.GetCurrentSize());
+
+ // Reduce size by user scale settings...
+ int sx = (rect.Width - (int)(rect.Width * sizeX)) / 2;
+ int sy = (rect.Height - (int)(rect.Height * sizeY)) / 2;
+ rect.Inflate(-sx, -sy);
+
+ // ...then adjust the region based on their positional offset.
+ rect.X = (int)(rect.X * posX) * 2;
+ rect.Y = (int)(rect.Y * posY) * 2;
+
+ m.Crop(rect);
+ });
+ }
+
clipboard.SetImage(image);
- (string filename, var stream) = getWritableStream();
+ (string? filename, Stream? stream) = getWritableStream(screenshotFormat);
if (filename == null) return;
using (stream)
{
- switch (screenshotFormat.Value)
+ switch (screenshotFormat)
{
case ScreenshotFormat.Png:
await image.SaveAsPngAsync(stream).ConfigureAwait(false);
@@ -140,7 +163,7 @@ namespace osu.Game.Graphics
break;
default:
- throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
+ throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat}.");
}
}
@@ -164,12 +187,12 @@ namespace osu.Game.Graphics
private static readonly object filename_reservation_lock = new object();
- private (string filename, Stream stream) getWritableStream()
+ private (string? filename, Stream? stream) getWritableStream(ScreenshotFormat format)
{
lock (filename_reservation_lock)
{
- var dt = DateTime.Now;
- string fileExt = screenshotFormat.ToString().ToLowerInvariant();
+ DateTime dt = DateTime.Now;
+ string fileExt = format.ToString().ToLowerInvariant();
string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}";
if (!storage.Exists(withoutIndex))
diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
index fc0770d896..af4b3849af 100644
--- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface
Current.ValueChanged += index =>
{
- foreach (var t in TabContainer.Children.OfType())
+ foreach (var t in TabContainer.OfType())
{
int tIndex = TabContainer.IndexOf(t);
int tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]);
diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs
index c920597a95..c39f41bf72 100644
--- a/osu.Game/Graphics/UserInterface/DialogButton.cs
+++ b/osu.Game/Graphics/UserInterface/DialogButton.cs
@@ -150,6 +150,7 @@ namespace osu.Game.Graphics.UserInterface
TriangleScale = 4,
ColourDark = OsuColour.Gray(0.88f),
Shear = new Vector2(-0.2f, 0),
+ ClampAxes = Axes.Y
},
},
},
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index 05309760e7..c260c92b43 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Graphics.UserInterface
if (Dropdown is IHasAccentColour dropdown)
dropdown.AccentColour = value;
- foreach (var i in TabContainer.Children.OfType())
+ foreach (var i in TabContainer.OfType())
i.AccentColour = value;
}
}
@@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface
protected override TabItem CreateTabItem(T value) => new OsuTabItem(value);
- protected virtual float StripWidth => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
+ protected virtual float StripWidth => TabContainer.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
///
/// Whether entries should be automatically populated if is an type.
diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs
index 255b2149f0..62cdefda43 100644
--- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs
+++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs
@@ -4,7 +4,6 @@
#nullable disable
using osu.Framework.Bindables;
-using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
@@ -39,7 +38,7 @@ namespace osu.Game.Graphics.UserInterface
protected override double GetProportionalDuration(long currentValue, long newValue) =>
currentValue > newValue ? currentValue - newValue : newValue - currentValue;
- protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(formatString);
+ protected override LocalisableString FormatCount(long count) => count.ToString(formatString);
protected override OsuSpriteText CreateSpriteText()
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs
index fe986b275e..720f479216 100644
--- a/osu.Game/Graphics/UserInterface/StarCounter.cs
+++ b/osu.Game/Graphics/UserInterface/StarCounter.cs
@@ -101,7 +101,7 @@ namespace osu.Game.Graphics.UserInterface
public void StopAnimation()
{
animate(current);
- foreach (var star in stars.Children)
+ foreach (var star in stars)
star.FinishTransforms(true);
}
diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs
index 14a3c5a43c..d03d259f71 100644
--- a/osu.Game/IO/MigratableStorage.cs
+++ b/osu.Game/IO/MigratableStorage.cs
@@ -6,8 +6,8 @@
using System;
using System.IO;
using System.Linq;
-using System.Threading;
using osu.Framework.Platform;
+using osu.Game.Utils;
namespace osu.Game.IO
{
@@ -81,7 +81,7 @@ namespace osu.Game.IO
if (IgnoreSuffixes.Any(suffix => fi.Name.EndsWith(suffix, StringComparison.Ordinal)))
continue;
- allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false);
+ allFilesDeleted &= FileUtils.AttemptOperation(() => fi.Delete(), throwOnFailure: false);
}
foreach (DirectoryInfo dir in target.GetDirectories())
@@ -92,11 +92,11 @@ namespace osu.Game.IO
if (IgnoreSuffixes.Any(suffix => dir.Name.EndsWith(suffix, StringComparison.Ordinal)))
continue;
- allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false);
+ allFilesDeleted &= FileUtils.AttemptOperation(() => dir.Delete(true), throwOnFailure: false);
}
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
- allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false);
+ allFilesDeleted &= FileUtils.AttemptOperation(target.Delete, throwOnFailure: false);
return allFilesDeleted;
}
@@ -115,7 +115,7 @@ namespace osu.Game.IO
if (IgnoreSuffixes.Any(suffix => fileInfo.Name.EndsWith(suffix, StringComparison.Ordinal)))
continue;
- AttemptOperation(() =>
+ FileUtils.AttemptOperation(() =>
{
fileInfo.Refresh();
@@ -139,35 +139,5 @@ namespace osu.Game.IO
CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false);
}
}
-
- ///
- /// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
- ///
- /// The action to perform.
- /// The number of attempts (250ms wait between each).
- /// Whether to throw an exception on failure. If false, will silently fail.
- protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true)
- {
- while (true)
- {
- try
- {
- action();
- return true;
- }
- catch (Exception)
- {
- if (attempts-- == 0)
- {
- if (throwOnFailure)
- throw;
-
- return false;
- }
- }
-
- Thread.Sleep(250);
- }
- }
}
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 5a39c02185..436334cfe1 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -170,6 +170,8 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay),
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
+ new KeyBinding(InputKey.Comma, GlobalAction.StepReplayBackward),
+ new KeyBinding(InputKey.Period, GlobalAction.StepReplayForward),
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.ToggleReplaySettings),
};
@@ -411,7 +413,13 @@ namespace osu.Game.Input.Bindings
IncreaseOffset,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))]
- DecreaseOffset
+ DecreaseOffset,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayForward))]
+ StepReplayForward,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))]
+ StepReplayBackward,
}
public enum GlobalActionCategory
diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs
index 632a1ad0ea..b905b7ae1c 100644
--- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs
+++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs
@@ -10,9 +10,9 @@ namespace osu.Game.Localisation
private const string prefix = @"osu.Game.Resources.Localisation.BeatmapOffsetControl";
///
- /// "Beatmap offset"
+ /// "Audio offset (this beatmap)"
///
- public static LocalisableString BeatmapOffset => new TranslatableString(getKey(@"beatmap_offset"), @"Beatmap offset");
+ public static LocalisableString AudioOffsetThisBeatmap => new TranslatableString(getKey(@"beatmap_offset"), @"Audio offset (this beatmap)");
///
/// "Previous play:"
diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
index ca27d0ff95..703e0ff1ca 100644
--- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
+++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
@@ -324,6 +324,16 @@ namespace osu.Game.Localisation
///
public static LocalisableString SeekReplayBackward => new TranslatableString(getKey(@"seek_replay_backward"), @"Seek replay backward");
+ ///
+ /// "Seek replay forward one frame"
+ ///
+ public static LocalisableString StepReplayForward => new TranslatableString(getKey(@"step_replay_forward"), @"Seek replay forward one frame");
+
+ ///
+ /// "Step replay backward one frame"
+ ///
+ public static LocalisableString StepReplayBackward => new TranslatableString(getKey(@"step_replay_backward"), @"Step replay backward one frame");
+
///
/// "Toggle chat focus"
///
diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
index 1aedd9fc5b..60874da561 100644
--- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
+++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
@@ -9,6 +9,16 @@ namespace osu.Game.Localisation
{
private const string prefix = @"osu.Game.Resources.Localisation.PlaybackSettings";
+ ///
+ /// "Step backward one frame"
+ ///
+ public static LocalisableString StepBackward => new TranslatableString(getKey(@"step_backward_frame"), @"Step backward one frame");
+
+ ///
+ /// "Step forward one frame"
+ ///
+ public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame");
+
///
/// "Seek backward {0} seconds"
///
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index 67f2590ad8..0fd9597ac0 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -287,7 +287,7 @@ namespace osu.Game.Online.Leaderboards
double delay = 0;
- foreach (var s in scoreFlowContainer.Children)
+ foreach (var s in scoreFlowContainer)
{
using (s.BeginDelayedSequence(delay))
s.Show();
@@ -384,7 +384,7 @@ namespace osu.Game.Online.Leaderboards
if (scoreFlowContainer == null)
return;
- foreach (var c in scoreFlowContainer.Children)
+ foreach (var c in scoreFlowContainer)
{
float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y;
float bottomY = topY + LeaderboardScore.HEIGHT;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 0d6eb1ea44..c244708385 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -80,6 +80,13 @@ namespace osu.Game
[Cached(typeof(OsuGame))]
public partial class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
{
+#if DEBUG
+ // Different port allows runnning release and debug builds alongside each other.
+ public const int IPC_PORT = 44824;
+#else
+ public const int IPC_PORT = 44823;
+#endif
+
///
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
///
diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs
index 809ea2f11d..1699dcceb0 100644
--- a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs
+++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Chat.Listing
flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public)
.Select(c => new ChannelListingItem(c));
- foreach (var item in flow.Children)
+ foreach (var item in flow)
{
item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel);
item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel);
diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs
index 1c56763bd9..b2c5a054e1 100644
--- a/osu.Game/Overlays/Mods/ModSelectColumn.cs
+++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Mods
var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV();
var trianglesColour = Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f);
- triangles.Colour = ColourInfo.GradientVertical(trianglesColour, trianglesColour.MultiplyAlpha(0f));
+ triangles.Colour = ColourInfo.GradientVertical(trianglesColour, value);
}
}
@@ -95,6 +95,7 @@ namespace osu.Game.Overlays.Mods
Height = header_height,
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
Velocity = 0.7f,
+ ClampAxes = Axes.Y
},
headerText = new OsuTextFlowContainer(t =>
{
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index 7ec52364d4..ab99370603 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -405,6 +405,8 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(150),
FillMode = FillMode.Fill,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
},
new Box
{
diff --git a/osu.Game/Overlays/OverlayStreamControl.cs b/osu.Game/Overlays/OverlayStreamControl.cs
index 84de384fb5..bc37a57cab 100644
--- a/osu.Game/Overlays/OverlayStreamControl.cs
+++ b/osu.Game/Overlays/OverlayStreamControl.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Overlays
protected override bool OnHover(HoverEvent e)
{
- foreach (var streamBadge in TabContainer.Children.OfType>())
+ foreach (var streamBadge in TabContainer.OfType>())
streamBadge.UserHoveringArea = true;
return base.OnHover(e);
@@ -49,7 +49,7 @@ namespace osu.Game.Overlays
protected override void OnHoverLost(HoverLostEvent e)
{
- foreach (var streamBadge in TabContainer.Children.OfType>())
+ foreach (var streamBadge in TabContainer.OfType>())
streamBadge.UserHoveringArea = false;
base.OnHoverLost(e);
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs
index 90f5a59215..ef1691534f 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs
@@ -12,12 +12,14 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation;
+using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
namespace osu.Game.Overlays.Settings.Sections.Audio
@@ -67,7 +69,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new TimeSlider
+ new OffsetSliderBar
{
RelativeSizeAxes = Axes.X,
Current = { BindTarget = Current },
@@ -157,6 +159,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
: $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms.";
applySuggestion.Enabled.Value = SuggestedOffset.Value != null;
}
+
+ private partial class OffsetSliderBar : RoundedSliderBar
+ {
+ public override LocalisableString TooltipText => BeatmapOffsetControl.GetOffsetExplanatoryText(Current.Value);
+ }
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
index 3ff5556f4d..fe88413e6a 100644
--- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
@@ -15,6 +15,7 @@ using osu.Game.Localisation;
using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater;
+using osu.Game.Utils;
using SharpCompress.Archives.Zip;
namespace osu.Game.Overlays.Settings.Sections.General
@@ -111,7 +112,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
using (var outStream = storage.CreateFileSafely(archive_filename))
using (var zip = ZipArchive.Create())
{
- foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) zip.AddEntry(f, logStorage.GetStream(f), true);
+ foreach (string? f in logStorage.GetFiles(string.Empty, "*.log"))
+ FileUtils.AttemptOperation(z => z.AddEntry(f, logStorage.GetStream(f), true), zip);
zip.SaveTo(outStream);
}
diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs
index ada651b60e..7330f138ce 100644
--- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs
+++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs
@@ -45,13 +45,14 @@ namespace osu.Game.Rulesets.Judgements
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
{
this.RotateTo(-45);
- this.ScaleTo(1.8f);
+ this.ScaleTo(1.6f);
this.ScaleTo(1.2f, 100, Easing.In);
- this.MoveTo(Vector2.Zero);
- this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint);
+ this.FadeOutFromOne(400);
+ return;
}
- else if (Result.IsMiss())
+
+ if (Result.IsMiss())
{
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs
index 3b45acc7bb..1b0176cae5 100644
--- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs
+++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs
@@ -153,6 +153,9 @@ namespace osu.Game.Rulesets.Objects.Pooling
protected override bool CheckChildrenLife()
{
+ if (!IsPresent)
+ return false;
+
bool aliveChanged = base.CheckChildrenLife();
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
return aliveChanged;
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index 80e751422e..a092829317 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -370,7 +370,7 @@ namespace osu.Game.Rulesets.Scoring
if (rank.Value == ScoreRank.F)
return;
- rank.Value = RankFromAccuracy(Accuracy.Value);
+ rank.Value = RankFromScore(Accuracy.Value, ScoreResultCounts);
foreach (var mod in Mods.Value.OfType())
rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value);
}
@@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Scoring
///
/// Given an accuracy (0..1), return the correct .
///
- public virtual ScoreRank RankFromAccuracy(double accuracy)
+ public virtual ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results)
{
if (accuracy == accuracy_cutoff_x)
return ScoreRank.X;
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index 2af9916a6b..8c9cb262af 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -25,17 +25,15 @@ namespace osu.Game.Rulesets.UI
public ReplayInputHandler? ReplayInputHandler { get; set; }
///
- /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time.
+ /// The number of CPU milliseconds to spend at most during seek catch-up.
///
- public int MaxCatchUpFrames { get; set; } = 5;
+ private const double max_catchup_milliseconds = 10;
///
/// Whether to enable frame-stable playback.
///
internal bool FrameStablePlayback { get; set; } = true;
- protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid;
-
private readonly Bindable isCatchingUp = new Bindable();
private readonly Bindable waitingOnFrames = new Bindable();
@@ -61,6 +59,8 @@ namespace osu.Game.Rulesets.UI
///
private readonly FramedClock framedClock;
+ private readonly Stopwatch stopwatch = new Stopwatch();
+
///
/// The current direction of playback to be exposed to frame stable children.
///
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.UI
public override bool UpdateSubTree()
{
- int loops = MaxCatchUpFrames;
+ stopwatch.Restart();
do
{
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.UI
base.UpdateSubTree();
UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat);
- } while (state == PlaybackState.RequiresCatchUp && loops-- > 0);
+ } while (state == PlaybackState.RequiresCatchUp && stopwatch.ElapsedMilliseconds < max_catchup_milliseconds);
return true;
}
@@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.UI
// if waiting on frames, run one update loop to determine if frames have arrived.
state = PlaybackState.Valid;
}
- else if (IsPaused.Value)
+ else if (IsPaused.Value && !hasReplayAttached)
{
// time should not advance while paused, nor should anything run.
state = PlaybackState.NotValid;
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index b30fc7aee1..e51a95798b 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -4,6 +4,7 @@
#nullable disable
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -19,6 +20,7 @@ using osu.Game.Replays.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
using SharpCompress.Compressors.LZMA;
namespace osu.Game.Scoring.Legacy
@@ -38,7 +40,6 @@ namespace osu.Game.Scoring.Legacy
};
WorkingBeatmap workingBeatmap;
- byte[] compressedScoreInfo = null;
using (SerializationReader sr = new SerializationReader(stream))
{
@@ -107,6 +108,8 @@ namespace osu.Game.Scoring.Legacy
else if (version >= 20121008)
scoreInfo.LegacyOnlineID = sr.ReadInt32();
+ byte[] compressedScoreInfo = null;
+
if (version >= 30000001)
compressedScoreInfo = sr.ReadByteArray();
@@ -130,10 +133,12 @@ namespace osu.Game.Scoring.Legacy
}
}
- if (score.ScoreInfo.IsLegacyScore || compressedScoreInfo == null)
- PopulateLegacyAccuracyAndRank(score.ScoreInfo);
- else
- populateLazerAccuracyAndRank(score.ScoreInfo);
+ PopulateMaximumStatistics(score.ScoreInfo, workingBeatmap);
+
+ if (score.ScoreInfo.IsLegacyScore)
+ score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore;
+
+ StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, workingBeatmap);
// before returning for database import, we must restore the database-sourced BeatmapInfo.
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
@@ -171,121 +176,65 @@ namespace osu.Game.Scoring.Legacy
}
///
- /// Populates the accuracy of a given from its contained statistics.
+ /// Populates the for a given .
///
- ///
- /// Legacy use only.
- ///
- /// The to populate.
- public static void PopulateLegacyAccuracyAndRank(ScoreInfo score)
+ /// The score to populate the statistics of.
+ /// The corresponding .
+ internal static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap)
{
- int countMiss = score.GetCountMiss() ?? 0;
- int count50 = score.GetCount50() ?? 0;
- int count100 = score.GetCount100() ?? 0;
- int count300 = score.GetCount300() ?? 0;
- int countGeki = score.GetCountGeki() ?? 0;
- int countKatu = score.GetCountKatu() ?? 0;
+ Debug.Assert(score.BeatmapInfo != null);
- switch (score.Ruleset.OnlineID)
+ if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
+ return;
+
+ var ruleset = score.Ruleset.Detach();
+ var rulesetInstance = ruleset.CreateInstance();
+ var scoreProcessor = rulesetInstance.CreateScoreProcessor();
+
+ // Populate the maximum statistics.
+ HitResult maxBasicResult = rulesetInstance.GetHitResults()
+ .Select(h => h.result)
+ .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult);
+
+ foreach ((HitResult result, int count) in score.Statistics)
{
- case 0:
+ switch (result)
{
- int totalHits = count50 + count100 + count300 + countMiss;
- score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1;
+ case HitResult.LargeTickHit:
+ case HitResult.LargeTickMiss:
+ score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count;
+ break;
- float ratio300 = (float)count300 / totalHits;
- float ratio50 = (float)count50 / totalHits;
+ case HitResult.SmallTickHit:
+ case HitResult.SmallTickMiss:
+ score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count;
+ break;
- if (ratio300 == 1)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
- else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
- else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9)
- score.Rank = ScoreRank.A;
- else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8)
- score.Rank = ScoreRank.B;
- else if (ratio300 > 0.6)
- score.Rank = ScoreRank.C;
- else
- score.Rank = ScoreRank.D;
- break;
- }
+ case HitResult.IgnoreHit:
+ case HitResult.IgnoreMiss:
+ case HitResult.SmallBonus:
+ case HitResult.LargeBonus:
+ break;
- case 1:
- {
- int totalHits = count50 + count100 + count300 + countMiss;
- score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1;
-
- float ratio300 = (float)count300 / totalHits;
- float ratio50 = (float)count50 / totalHits;
-
- if (ratio300 == 1)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
- else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
- else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9)
- score.Rank = ScoreRank.A;
- else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8)
- score.Rank = ScoreRank.B;
- else if (ratio300 > 0.6)
- score.Rank = ScoreRank.C;
- else
- score.Rank = ScoreRank.D;
- break;
- }
-
- case 2:
- {
- int totalHits = count50 + count100 + count300 + countMiss + countKatu;
- score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300) / totalHits : 1;
-
- if (score.Accuracy == 1)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
- else if (score.Accuracy > 0.98)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
- else if (score.Accuracy > 0.94)
- score.Rank = ScoreRank.A;
- else if (score.Accuracy > 0.9)
- score.Rank = ScoreRank.B;
- else if (score.Accuracy > 0.85)
- score.Rank = ScoreRank.C;
- else
- score.Rank = ScoreRank.D;
- break;
- }
-
- case 3:
- {
- int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu;
- score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1;
-
- if (score.Accuracy == 1)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
- else if (score.Accuracy > 0.95)
- score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
- else if (score.Accuracy > 0.9)
- score.Rank = ScoreRank.A;
- else if (score.Accuracy > 0.8)
- score.Rank = ScoreRank.B;
- else if (score.Accuracy > 0.7)
- score.Rank = ScoreRank.C;
- else
- score.Rank = ScoreRank.D;
- break;
+ default:
+ score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count;
+ break;
}
}
- }
- private void populateLazerAccuracyAndRank(ScoreInfo scoreInfo)
- {
- scoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(scoreInfo);
+ if (!score.IsLegacyScore)
+ return;
- var rank = currentRuleset.CreateScoreProcessor().RankFromAccuracy(scoreInfo.Accuracy);
+#pragma warning disable CS0618
+ // In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
+ // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
+ var calculator = rulesetInstance.CreateDifficultyCalculator(workingBeatmap);
+ var attributes = calculator.Calculate(score.Mods);
- foreach (var mod in scoreInfo.Mods.OfType())
- rank = mod.AdjustRank(rank, scoreInfo.Accuracy);
-
- scoreInfo.Rank = rank;
+ int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
+ if (attributes.MaxCombo > maxComboFromStatistics)
+ score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
+#pragma warning restore CS0618
}
private void readLegacyReplay(Replay replay, StreamReader reader)
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
index 389b20b5c8..c74980abb6 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
@@ -43,9 +43,10 @@ namespace osu.Game.Scoring.Legacy
/// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct
/// method. Reconvert all scores.
///
+ /// - 30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores.
///
///
- public const int LATEST_VERSION = 30000012;
+ public const int LATEST_VERSION = 30000013;
///
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs
index 8e28707107..768c28cc38 100644
--- a/osu.Game/Scoring/ScoreImporter.cs
+++ b/osu.Game/Scoring/ScoreImporter.cs
@@ -17,7 +17,6 @@ using osu.Game.Scoring.Legacy;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Rulesets.Scoring;
using Realms;
namespace osu.Game.Scoring
@@ -91,8 +90,6 @@ namespace osu.Game.Scoring
ArgumentNullException.ThrowIfNull(model.BeatmapInfo);
ArgumentNullException.ThrowIfNull(model.Ruleset);
- PopulateMaximumStatistics(model);
-
if (string.IsNullOrEmpty(model.StatisticsJson))
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
@@ -103,75 +100,6 @@ namespace osu.Game.Scoring
// this requires: max combo, statistics, max statistics (where available), and mods to already be populated on the score.
if (StandardisedScoreMigrationTools.ShouldMigrateToNewStandardised(model))
model.TotalScore = StandardisedScoreMigrationTools.GetNewStandardised(model);
- else if (model.IsLegacyScore)
- {
- model.LegacyTotalScore = model.TotalScore;
- StandardisedScoreMigrationTools.UpdateFromLegacy(model, beatmaps());
- }
- }
-
- ///
- /// Populates the for a given .
- ///
- /// The score to populate the statistics of.
- public void PopulateMaximumStatistics(ScoreInfo score)
- {
- Debug.Assert(score.BeatmapInfo != null);
-
- if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
- return;
-
- var beatmap = score.BeatmapInfo!.Detach();
- var ruleset = score.Ruleset.Detach();
- var rulesetInstance = ruleset.CreateInstance();
- var scoreProcessor = rulesetInstance.CreateScoreProcessor();
-
- Debug.Assert(rulesetInstance != null);
-
- // Populate the maximum statistics.
- HitResult maxBasicResult = rulesetInstance.GetHitResults()
- .Select(h => h.result)
- .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult);
-
- foreach ((HitResult result, int count) in score.Statistics)
- {
- switch (result)
- {
- case HitResult.LargeTickHit:
- case HitResult.LargeTickMiss:
- score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count;
- break;
-
- case HitResult.SmallTickHit:
- case HitResult.SmallTickMiss:
- score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count;
- break;
-
- case HitResult.IgnoreHit:
- case HitResult.IgnoreMiss:
- case HitResult.SmallBonus:
- case HitResult.LargeBonus:
- break;
-
- default:
- score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count;
- break;
- }
- }
-
- if (!score.IsLegacyScore)
- return;
-
-#pragma warning disable CS0618
- // In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
- // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
- var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap));
- var attributes = calculator.Calculate(score.Mods);
-
- int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
- if (attributes.MaxCombo > maxComboFromStatistics)
- score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
-#pragma warning restore CS0618
}
// Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores).
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index 02d9e0a280..1ee99e9e93 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
@@ -26,6 +27,7 @@ namespace osu.Game.Scoring
{
public class ScoreManager : ModelManager, IModelImporter
{
+ private readonly Func beatmaps;
private readonly OsuConfigManager configManager;
private readonly ScoreImporter scoreImporter;
private readonly LegacyScoreExporter scoreExporter;
@@ -44,6 +46,7 @@ namespace osu.Game.Scoring
OsuConfigManager configManager = null)
: base(storage, realm)
{
+ this.beatmaps = beatmaps;
this.configManager = configManager;
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api)
@@ -171,7 +174,11 @@ namespace osu.Game.Scoring
/// Populates the for a given .
///
/// The score to populate the statistics of.
- public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score);
+ public void PopulateMaximumStatistics(ScoreInfo score)
+ {
+ Debug.Assert(score.BeatmapInfo != null);
+ LegacyScoreDecoder.PopulateMaximumStatistics(score, beatmaps().GetWorkingBeatmap(score.BeatmapInfo.Detach()));
+ }
#region Implementation of IPresentImports
diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs
index ffa4f01e75..4b0726658f 100644
--- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs
+++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs
@@ -29,10 +29,11 @@ namespace osu.Game.Screens.Edit
/// Set a divisor, updating the valid divisor range appropriately.
///
/// The intended divisor.
- public void SetArbitraryDivisor(int divisor)
+ /// Forces changing the valid divisors to a known preset.
+ public void SetArbitraryDivisor(int divisor, bool preferKnownPresets = false)
{
// If the current valid divisor range doesn't contain the proposed value, attempt to find one which does.
- if (!ValidDivisors.Value.Presets.Contains(divisor))
+ if (preferKnownPresets || !ValidDivisors.Value.Presets.Contains(divisor))
{
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
index b33edb9edb..da1a37d57f 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
@@ -208,11 +208,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
switch (currentType)
{
case BeatDivisorType.Common:
- beatDivisor.SetArbitraryDivisor(4);
+ beatDivisor.SetArbitraryDivisor(4, true);
break;
case BeatDivisorType.Triplets:
- beatDivisor.SetArbitraryDivisor(6);
+ beatDivisor.SetArbitraryDivisor(6, true);
break;
case BeatDivisorType.Custom:
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index 110beb0fa6..2d6e234e57 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnMouseDown(MouseDownEvent e)
{
bool selectionPerformed = performMouseDownActions(e);
- bool movementPossible = prepareSelectionMovement();
+ bool movementPossible = prepareSelectionMovement(e);
// check if selection has occurred
if (selectionPerformed)
@@ -536,9 +536,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
/// Attempts to begin the movement of any selected blueprints.
///
+ /// The defining the beginning of a movement.
/// Whether a movement is possible.
- private bool prepareSelectionMovement()
+ private bool prepareSelectionMovement(MouseDownEvent e)
{
+ if (e.Button == MouseButton.Right)
+ return false;
+
if (!SelectionHandler.SelectedBlueprints.Any())
return false;
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index b2b3fbd626..d742d2377f 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -343,7 +343,7 @@ namespace osu.Game.Screens.Menu
{
buttonArea.ButtonSystemState = state;
- foreach (var b in buttonArea.Children.OfType())
+ foreach (var b in buttonArea.OfType())
b.ButtonSystemState = state;
}
diff --git a/osu.Game/Screens/Menu/StarFountain.cs b/osu.Game/Screens/Menu/StarFountain.cs
index fd59ec3573..dd5171c6be 100644
--- a/osu.Game/Screens/Menu/StarFountain.cs
+++ b/osu.Game/Screens/Menu/StarFountain.cs
@@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Skinning;
@@ -43,8 +44,6 @@ namespace osu.Game.Screens.Menu
private const double shoot_duration = 800;
- protected override bool CanSpawnParticles => lastShootTime != null && Time.Current - lastShootTime < shoot_duration;
-
[Resolved]
private ISkinSource skin { get; set; } = null!;
@@ -57,7 +56,6 @@ namespace osu.Game.Screens.Menu
private void load(TextureStore textures)
{
Texture = skin.GetTexture("Menu/fountain-star") ?? textures.Get("Menu/fountain-star");
- Active.Value = true;
}
protected override FallingParticle CreateParticle()
@@ -81,8 +79,15 @@ namespace osu.Game.Screens.Menu
return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance);
}
+ private ScheduledDelegate? deactivateDelegate;
+
public void Shoot(int direction)
{
+ Active.Value = true;
+
+ deactivateDelegate?.Cancel();
+ deactivateDelegate = Scheduler.AddDelayed(() => Active.Value = false, shoot_duration);
+
lastShootTime = Clock.CurrentTime;
lastShootDirection = direction;
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index 6f5ff9c99c..3792a67896 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -40,8 +40,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
public override string Title => "Lounge";
- protected override bool PlayExitSound => false;
-
protected override BackgroundScreen CreateBackground() => new LoungeBackgroundScreen
{
SelectedRoom = { BindTarget = SelectedRoom }
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 8a85a46a2f..a37314de0e 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -44,8 +44,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public override string ShortTitle => "room";
- protected override bool PlayExitSound => !exitConfirmed;
-
[Resolved]
private MultiplayerClient client { get; set; }
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs
index b527bf98a2..fa1ee004c9 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Screens.OnlinePlay
public virtual string ShortTitle => Title;
+ protected sealed override bool PlayExitSound => false;
+
[Resolved]
protected IRoomManager? RoomManager { get; private set; }
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 490a1ae6b8..f719ef67c9 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -223,7 +223,12 @@ namespace osu.Game.Screens
public override bool OnExiting(ScreenExitEvent e)
{
- if (ValidForResume && PlayExitSound)
+ // Only play the exit sound if we are the last screen in the exit sequence.
+ // This stops many sample playbacks from stacking when a huge screen purge happens (ie. returning to menu via the home button
+ // from a deeply nested screen).
+ bool arrivingAtFinalDestination = e.Next == e.Destination;
+
+ if (ValidForResume && PlayExitSound && arrivingAtFinalDestination)
sampleExit?.Play();
if (ValidForResume && logo != null)
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 4def1d36bb..c039d1e535 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -78,8 +78,6 @@ namespace osu.Game.Screens.Play
isPaused.Value = false;
- PrepareStart();
-
// The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time.
// Because we generally update our own current time quicker than children can query it (via Start/Seek/Update),
// this means that the first frame ever exposed to children may have a non-zero current time.
@@ -99,14 +97,6 @@ namespace osu.Game.Screens.Play
});
}
- ///
- /// When is called, this will be run to give an opportunity to prepare the clock at the correct
- /// start location.
- ///
- protected virtual void PrepareStart()
- {
- }
-
///
/// Seek to a specific time in gameplay.
///
diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs
index f16669f865..f8c82feddd 100644
--- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs
+++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs
@@ -42,35 +42,30 @@ namespace osu.Game.Screens.Play.HUD
Origin = anchor;
AutoSizeAxes = Axes.Both;
- InternalChild = new FillFlowContainer
+ InternalChildren = new Drawable[]
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
+ labelText = new OsuSpriteText
{
- labelText = new OsuSpriteText
+ Alpha = 0,
+ Text = label.GetValueOrDefault(),
+ Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold),
+ Margin = new MarginPadding { Left = 2.5f },
+ },
+ NumberContainer = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ Children = new[]
{
- Alpha = 0,
- Text = label.GetValueOrDefault(),
- Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold),
- Margin = new MarginPadding { Left = 2.5f },
- },
- NumberContainer = new Container
- {
- AutoSizeAxes = Axes.Both,
- Children = new[]
+ wireframesPart = new ArgonCounterSpriteText(wireframesLookup)
{
- wireframesPart = new ArgonCounterSpriteText(wireframesLookup)
- {
- Anchor = anchor,
- Origin = anchor,
- },
- textPart = new ArgonCounterSpriteText(textLookup)
- {
- Anchor = anchor,
- Origin = anchor,
- },
- }
+ Anchor = anchor,
+ Origin = anchor,
+ },
+ textPart = new ArgonCounterSpriteText(textLookup)
+ {
+ Anchor = anchor,
+ Origin = anchor,
+ },
}
}
};
@@ -110,7 +105,11 @@ namespace osu.Game.Screens.Play.HUD
{
base.LoadComplete();
WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true);
- ShowLabel.BindValueChanged(s => labelText.Alpha = s.NewValue ? 1 : 0, true);
+ ShowLabel.BindValueChanged(s =>
+ {
+ labelText.Alpha = s.NewValue ? 1 : 0;
+ NumberContainer.Y = s.NewValue ? 12 : 0;
+ }, true);
}
private partial class ArgonCounterSpriteText : OsuSpriteText
diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs
index 348327d710..44b9fb3123 100644
--- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs
+++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD
public bool UsesFixedAnchor { get; set; }
- protected override LocalisableString FormatCount(long count) => count.ToLocalisableString();
+ protected override LocalisableString FormatCount(long count) => count.ToString();
protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper())
{
diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
index d990af32e7..d2b6b834f8 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play.HUD
if (!scroll.IsScrolledToEnd()) fadeBottom -= panel_height;
// logic is mostly shared with Leaderboard, copied here for simplicity.
- foreach (var c in Flow.Children)
+ foreach (var c in Flow)
{
float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, Flow).Y;
float bottomY = topY + panel_height;
diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs
index 326be55222..25e5464205 100644
--- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
CounterFlow.Direction = convertedDirection;
- foreach (var counter in CounterFlow.Children)
+ foreach (var counter in CounterFlow)
counter.Direction.Value = convertedDirection;
}, true);
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 128f8d5ffd..32ebb82f15 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -99,6 +98,9 @@ namespace osu.Game.Screens.Play
private readonly SkinComponentsContainer mainComponents;
+ [CanBeNull]
+ private readonly SkinComponentsContainer rulesetComponents;
+
///
/// A flow which sits at the left side of the screen to house leaderboard (and related) components.
/// Will automatically be positioned to avoid colliding with top scoring elements.
@@ -111,7 +113,6 @@ namespace osu.Game.Screens.Play
public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true)
{
- Drawable rulesetComponents;
this.drawableRuleset = drawableRuleset;
this.mods = mods;
@@ -125,8 +126,8 @@ namespace osu.Game.Screens.Play
clicksPerSecondController = new ClicksPerSecondController(),
InputCountController = new InputCountController(),
mainComponents = new HUDComponentsContainer { AlwaysPresent = true, },
- rulesetComponents = drawableRuleset != null
- ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }
+ drawableRuleset != null
+ ? (rulesetComponents = new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, })
: Empty(),
playfieldComponents = drawableRuleset != null
? new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, }
@@ -170,7 +171,10 @@ namespace osu.Game.Screens.Play
},
};
- hideTargets = new List { mainComponents, rulesetComponents, playfieldComponents, topRightElements };
+ hideTargets = new List { mainComponents, playfieldComponents, topRightElements };
+
+ if (rulesetComponents != null)
+ hideTargets.Add(rulesetComponents);
if (!alwaysShowLeaderboard)
hideTargets.Add(LeaderboardFlow);
@@ -254,39 +258,13 @@ namespace osu.Game.Screens.Play
Vector2? highestBottomScreenSpace = null;
- // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
- foreach (var element in mainComponents.Components.Cast())
+ foreach (var element in mainComponents.Components)
+ processDrawable(element);
+
+ if (rulesetComponents != null)
{
- // for now align some top components with the bottom-edge of the lowest top-anchored hud element.
- if (element.Anchor.HasFlagFast(Anchor.y0))
- {
- // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
- if (element is LegacyHealthDisplay)
- continue;
-
- float bottom = element.ScreenSpaceDrawQuad.BottomRight.Y;
-
- bool isRelativeX = element.RelativeSizeAxes == Axes.X;
-
- if (element.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX)
- {
- if (lowestTopScreenSpaceRight == null || bottom > lowestTopScreenSpaceRight.Value)
- lowestTopScreenSpaceRight = bottom;
- }
-
- if (element.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX)
- {
- if (lowestTopScreenSpaceLeft == null || bottom > lowestTopScreenSpaceLeft.Value)
- lowestTopScreenSpaceLeft = bottom;
- }
- }
- // and align bottom-right components with the top-edge of the highest bottom-anchored hud element.
- else if (element.Anchor.HasFlagFast(Anchor.BottomRight) || (element.Anchor.HasFlagFast(Anchor.y2) && element.RelativeSizeAxes == Axes.X))
- {
- var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
- if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
- highestBottomScreenSpace = topLeft;
- }
+ foreach (var element in rulesetComponents.Components)
+ processDrawable(element);
}
if (lowestTopScreenSpaceRight.HasValue)
@@ -303,6 +281,43 @@ namespace osu.Game.Screens.Play
bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight);
else
bottomRightElements.Y = 0;
+
+ void processDrawable(ISerialisableDrawable element)
+ {
+ // Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
+ Drawable drawable = (Drawable)element;
+
+ // for now align some top components with the bottom-edge of the lowest top-anchored hud element.
+ if (drawable.Anchor.HasFlagFast(Anchor.y0))
+ {
+ // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
+ if (element is LegacyHealthDisplay)
+ return;
+
+ float bottom = drawable.ScreenSpaceDrawQuad.BottomRight.Y;
+
+ bool isRelativeX = drawable.RelativeSizeAxes == Axes.X;
+
+ if (drawable.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX)
+ {
+ if (lowestTopScreenSpaceRight == null || bottom > lowestTopScreenSpaceRight.Value)
+ lowestTopScreenSpaceRight = bottom;
+ }
+
+ if (drawable.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX)
+ {
+ if (lowestTopScreenSpaceLeft == null || bottom > lowestTopScreenSpaceLeft.Value)
+ lowestTopScreenSpaceLeft = bottom;
+ }
+ }
+ // and align bottom-right components with the top-edge of the highest bottom-anchored hud element.
+ else if (drawable.Anchor.HasFlagFast(Anchor.BottomRight) || (drawable.Anchor.HasFlagFast(Anchor.y2) && drawable.RelativeSizeAxes == Axes.X))
+ {
+ var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
+ if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
+ highestBottomScreenSpace = topLeft;
+ }
+ }
}
private void updateVisibility()
diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs
index 12f541167f..93bdcb1cab 100644
--- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs
@@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
@@ -60,17 +59,6 @@ namespace osu.Game.Screens.Play
private readonly double skipTargetTime;
- ///
- /// Stores the time at which the last call was triggered.
- /// This is used to ensure we resume from that precise point in time, ignoring the proceeding frequency ramp.
- ///
- /// Optimally, we'd have gameplay ramp down with the frequency, but I believe this was intentionally disabled
- /// to avoid fails occurring after the pause screen has been shown.
- ///
- /// In the future I want to change this.
- ///
- internal double? LastStopTime;
-
[Resolved]
private MusicController musicController { get; set; } = null!;
@@ -113,71 +101,17 @@ namespace osu.Game.Screens.Play
return time;
}
- protected override void StopGameplayClock()
- {
- LastStopTime = GameplayClock.CurrentTime;
-
- if (IsLoaded)
- {
- // During normal operation, the source is stopped after performing a frequency ramp.
- this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 0, 200, Easing.Out).OnComplete(_ =>
- {
- if (IsPaused.Value)
- base.StopGameplayClock();
- });
- }
- else
- {
- base.StopGameplayClock();
-
- // If not yet loaded, we still want to ensure relevant state is correct, as it is used for offset calculations.
- GameplayClock.ExternalPauseFrequencyAdjust.Value = 0;
-
- // We must also process underlying gameplay clocks to update rate-adjusted offsets with the new frequency adjustment.
- // Without doing this, an initial seek may be performed with the wrong offset.
- GameplayClock.ProcessFrame();
- }
- }
-
public override void Seek(double time)
{
- // Safety in case the clock is seeked while stopped.
- LastStopTime = null;
elapsedValidationTime = null;
base.Seek(time);
}
- protected override void PrepareStart()
- {
- if (LastStopTime != null)
- {
- Seek(LastStopTime.Value);
- LastStopTime = null;
- }
- else
- base.PrepareStart();
- }
-
protected override void StartGameplayClock()
{
addAdjustmentsToTrack();
-
base.StartGameplayClock();
-
- if (IsLoaded)
- {
- this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 1, 200, Easing.In);
- }
- else
- {
- // If not yet loaded, we still want to ensure relevant state is correct, as it is used for offset calculations.
- GameplayClock.ExternalPauseFrequencyAdjust.Value = 1;
-
- // We must also process underlying gameplay clocks to update rate-adjusted offsets with the new frequency adjustment.
- // Without doing this, an initial seek may be performed with the wrong offset.
- GameplayClock.ProcessFrame();
- }
}
///
@@ -273,7 +207,6 @@ namespace osu.Game.Screens.Play
musicController.ResetTrackAdjustments();
track.BindAdjustments(AdjustmentsFromMods);
- track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
track.AddAdjustment(AdjustableProperty.Frequency, UserPlaybackRate);
speedAdjustmentsApplied = true;
@@ -285,7 +218,6 @@ namespace osu.Game.Screens.Play
return;
track.UnbindAdjustments(AdjustmentsFromMods);
- track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
track.RemoveAdjustment(AdjustableProperty.Frequency, UserPlaybackRate);
speedAdjustmentsApplied = false;
diff --git a/osu.Game/Screens/Play/OffsetCorrectionClock.cs b/osu.Game/Screens/Play/OffsetCorrectionClock.cs
index 207980f45c..e83ed7e464 100644
--- a/osu.Game/Screens/Play/OffsetCorrectionClock.cs
+++ b/osu.Game/Screens/Play/OffsetCorrectionClock.cs
@@ -1,15 +1,12 @@
// 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.Bindables;
using osu.Framework.Timing;
namespace osu.Game.Screens.Play
{
public class OffsetCorrectionClock : FramedOffsetClock
{
- private readonly BindableDouble pauseRateAdjust;
-
private double offset;
public new double Offset
@@ -28,10 +25,9 @@ namespace osu.Game.Screens.Play
public double RateAdjustedOffset => base.Offset;
- public OffsetCorrectionClock(IClock source, BindableDouble pauseRateAdjust)
+ public OffsetCorrectionClock(IClock source)
: base(source)
{
- this.pauseRateAdjust = pauseRateAdjust;
}
public override void ProcessFrame()
@@ -42,12 +38,8 @@ namespace osu.Game.Screens.Play
private void updateOffset()
{
- // changing this during the pause transform effect will cause a potentially large offset to be suddenly applied as we approach zero rate.
- if (pauseRateAdjust.Value == 1)
- {
- // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this.
- base.Offset = Offset * Rate;
- }
+ // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this.
+ base.Offset = Offset * Rate;
}
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index db39d06c54..ad1f9ec897 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -1240,9 +1240,14 @@ namespace osu.Game.Screens.Play
{
b.IgnoreUserSettings.Value = true;
- b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime);
- b.IsBreakTime.Value = false;
+ // May be null if the load never completed.
+ if (breakTracker != null)
+ {
+ b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime);
+ b.IsBreakTime.Value = false;
+ }
});
+
storyboardReplacesBackground.Value = false;
}
}
diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs
index 8efb80e771..9039604471 100644
--- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
new OffsetSliderBar
{
KeyboardStep = 5,
- LabelText = BeatmapOffsetControlStrings.BeatmapOffset,
+ LabelText = BeatmapOffsetControlStrings.AudioOffsetThisBeatmap,
Current = Current,
},
referenceScoreContainer = new FillFlowContainer
@@ -307,7 +307,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
}
}
- public partial class OffsetSliderBar : PlayerSliderBar
+ private partial class OffsetSliderBar : PlayerSliderBar
{
protected override Drawable CreateControl() => new CustomSliderBar();
diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs
index 44cfa8d811..b3d07421ed 100644
--- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs
@@ -1,19 +1,17 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Screens.Edit.Timing;
using osuTK;
-using osu.Game.Localisation;
namespace osu.Game.Screens.Play.PlayerSettings
{
@@ -28,26 +26,28 @@ namespace osu.Game.Screens.Play.PlayerSettings
Precision = 0.01,
};
- private readonly PlayerSliderBar rateSlider;
+ private PlayerSliderBar rateSlider = null!;
- private readonly OsuSpriteText multiplierText;
+ private OsuSpriteText multiplierText = null!;
- private readonly BindableBool isPaused = new BindableBool();
+ private readonly IBindable isPaused = new BindableBool();
[Resolved]
- private GameplayClockContainer? gameplayClock { get; set; }
+ private ReplayPlayer replayPlayer { get; set; } = null!;
[Resolved]
- private GameplayState? gameplayState { get; set; }
+ private GameplayClockContainer gameplayClock { get; set; } = null!;
+
+ private IconButton pausePlay = null!;
public PlaybackSettings()
: base("playback")
{
- const double seek_amount = 5000;
- const double seek_fast_amount = 10000;
-
- IconButton play;
+ }
+ [BackgroundDependencyLoader]
+ private void load()
+ {
Children = new Drawable[]
{
new FillFlowContainer
@@ -71,50 +71,62 @@ namespace osu.Game.Screens.Play.PlayerSettings
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.FastBackward,
- Action = () => seek(-1, seek_fast_amount),
- TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_fast_amount / 1000),
+ Action = () => replayPlayer.SeekInDirection(-10),
+ TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(10 * ReplayPlayer.BASE_SEEK_AMOUNT / 1000),
},
new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Backward,
- Action = () => seek(-1, seek_amount),
- TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_amount / 1000),
+ Action = () => replayPlayer.SeekInDirection(-1),
+ TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(ReplayPlayer.BASE_SEEK_AMOUNT / 1000),
},
- play = new IconButton
+ new SeekButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Icon = FontAwesome.Solid.StepBackward,
+ Action = () => replayPlayer.StepFrame(-1),
+ TooltipText = PlayerSettingsOverlayStrings.StepBackward,
+ },
+ pausePlay = new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
- Icon = FontAwesome.Regular.PlayCircle,
Action = () =>
{
- if (gameplayClock != null)
- {
- if (gameplayClock.IsRunning)
- gameplayClock.Stop();
- else
- gameplayClock.Start();
- }
+ if (gameplayClock.IsRunning)
+ gameplayClock.Stop();
+ else
+ gameplayClock.Start();
},
},
new SeekButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Icon = FontAwesome.Solid.StepForward,
+ Action = () => replayPlayer.StepFrame(1),
+ TooltipText = PlayerSettingsOverlayStrings.StepForward,
+ },
+ new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Forward,
- Action = () => seek(1, seek_amount),
- TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_amount / 1000),
+ Action = () => replayPlayer.SeekInDirection(1),
+ TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(ReplayPlayer.BASE_SEEK_AMOUNT / 1000),
},
new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.FastForward,
- Action = () => seek(1, seek_fast_amount),
- TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_fast_amount / 1000),
+ Action = () => replayPlayer.SeekInDirection(10),
+ TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(10 * ReplayPlayer.BASE_SEEK_AMOUNT / 1000),
},
},
},
@@ -141,26 +153,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
},
},
};
-
- isPaused.BindValueChanged(paused =>
- {
- if (!paused.NewValue)
- {
- play.TooltipText = ToastStrings.PauseTrack;
- play.Icon = FontAwesome.Regular.PauseCircle;
- }
- else
- {
- play.TooltipText = ToastStrings.PlayTrack;
- play.Icon = FontAwesome.Regular.PlayCircle;
- }
- }, true);
-
- void seek(int direction, double amount)
- {
- double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState?.Beatmap.GetLastObjectTime() ?? 0);
- gameplayClock?.Seek(target);
- }
}
protected override void LoadComplete()
@@ -168,8 +160,20 @@ namespace osu.Game.Screens.Play.PlayerSettings
base.LoadComplete();
rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.00}x", true);
- if (gameplayClock != null)
- isPaused.BindTarget = gameplayClock.IsPaused;
+ isPaused.BindTo(gameplayClock.IsPaused);
+ isPaused.BindValueChanged(paused =>
+ {
+ if (!paused.NewValue)
+ {
+ pausePlay.TooltipText = ToastStrings.PauseTrack;
+ pausePlay.Icon = FontAwesome.Regular.PauseCircle;
+ }
+ else
+ {
+ pausePlay.TooltipText = ToastStrings.PlayTrack;
+ pausePlay.Icon = FontAwesome.Regular.PlayCircle;
+ }
+ }, true);
}
private partial class SeekButton : IconButton
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 805f907466..3c5b85662a 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
@@ -22,8 +23,11 @@ using osu.Game.Users;
namespace osu.Game.Screens.Play
{
+ [Cached]
public partial class ReplayPlayer : Player, IKeyBindingHandler
{
+ public const double BASE_SEEK_AMOUNT = 1000;
+
private readonly Func, Score> createScore;
private readonly bool replayIsFailedScore;
@@ -52,7 +56,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OsuConfigManager config)
{
if (!LoadedBeatmapSuccessfully)
return;
@@ -60,7 +64,7 @@ namespace osu.Game.Screens.Play
var playbackSettings = new PlaybackSettings
{
Depth = float.MaxValue,
- Expanded = { Value = false }
+ Expanded = { BindTarget = config.GetBindable(OsuSetting.ReplayPlaybackControlsExpanded) }
};
if (GameplayClockContainer is MasterGameplayClockContainer master)
@@ -92,16 +96,22 @@ namespace osu.Game.Screens.Play
public bool OnPressed(KeyBindingPressEvent e)
{
- const double keyboard_seek_amount = 5000;
-
switch (e.Action)
{
+ case GlobalAction.StepReplayBackward:
+ StepFrame(-1);
+ return true;
+
+ case GlobalAction.StepReplayForward:
+ StepFrame(1);
+ return true;
+
case GlobalAction.SeekReplayBackward:
- keyboardSeek(-1);
+ SeekInDirection(-5);
return true;
case GlobalAction.SeekReplayForward:
- keyboardSeek(1);
+ SeekInDirection(5);
return true;
case GlobalAction.TogglePauseReplay:
@@ -113,13 +123,28 @@ namespace osu.Game.Screens.Play
}
return false;
+ }
- void keyboardSeek(int direction)
- {
- double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayState.Beatmap.GetLastObjectTime());
+ public void StepFrame(int direction)
+ {
+ GameplayClockContainer.Stop();
- Seek(target);
- }
+ var frames = GameplayState.Score.Replay.Frames;
+
+ if (frames.Count == 0)
+ return;
+
+ GameplayClockContainer.Seek(direction < 0
+ ? (frames.LastOrDefault(f => f.Time < GameplayClockContainer.CurrentTime) ?? frames.First()).Time
+ : (frames.FirstOrDefault(f => f.Time > GameplayClockContainer.CurrentTime) ?? frames.Last()).Time
+ );
+ }
+
+ public void SeekInDirection(float amount)
+ {
+ double target = Math.Clamp(GameplayClockContainer.CurrentTime + amount * BASE_SEEK_AMOUNT, 0, GameplayState.Beatmap.GetLastObjectTime());
+
+ Seek(target);
}
public void OnReleased(KeyBindingReleaseEvent e)
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
index 8cbca74466..0aff98df2b 100644
--- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
@@ -21,6 +21,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
@@ -111,6 +112,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
private readonly double accuracyD;
private readonly bool withFlair;
+ private readonly bool isFailedSDueToMisses;
+ private RankText failedSRankText;
+
public AccuracyCircle(ScoreInfo score, bool withFlair = false)
{
this.score = score;
@@ -119,10 +123,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor();
accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X);
accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S);
+
accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A);
accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B);
accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C);
accuracyD = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.D);
+
+ isFailedSDueToMisses = score.Accuracy >= accuracyS && score.Rank == ScoreRank.A;
}
[BackgroundDependencyLoader]
@@ -249,6 +256,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
if (withFlair)
{
+ if (isFailedSDueToMisses)
+ AddInternal(failedSRankText = new RankText(ScoreRank.S));
+
AddRangeInternal(new Drawable[]
{
rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)),
@@ -387,6 +397,31 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
});
}
}
+
+ if (isFailedSDueToMisses)
+ {
+ const double adjust_duration = 200;
+
+ using (BeginDelayedSequence(TEXT_APPEAR_DELAY - adjust_duration))
+ {
+ failedSRankText.FadeIn(adjust_duration);
+
+ using (BeginDelayedSequence(adjust_duration))
+ {
+ failedSRankText
+ .FadeColour(Color4.Red, 800, Easing.Out)
+ .RotateTo(10, 1000, Easing.Out)
+ .MoveToY(100, 1000, Easing.In)
+ .FadeOut(800, Easing.Out);
+
+ accuracyCircle
+ .FillTo(accuracyS - NOTCH_WIDTH_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint);
+
+ badges.Single(b => b.Rank == getRank(ScoreRank.S))
+ .FadeOut(70, Easing.OutQuint);
+ }
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs
index 7af327828e..8aea6045eb 100644
--- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
///
private readonly double displayPosition;
- private readonly ScoreRank rank;
+ public readonly ScoreRank Rank;
private Drawable rankContainer;
private Drawable overlay;
@@ -47,7 +47,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
Accuracy = accuracy;
displayPosition = position;
- this.rank = rank;
+ Rank = rank;
RelativeSizeAxes = Axes.Both;
Alpha = 0;
@@ -62,7 +62,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
Size = new Vector2(28, 14),
Children = new[]
{
- new DrawableRank(rank),
+ new DrawableRank(Rank),
overlay = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
@@ -71,7 +71,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
- Colour = OsuColour.ForRank(rank).Opacity(0.2f),
+ Colour = OsuColour.ForRank(Rank).Opacity(0.2f),
Radius = 10,
},
Child = new Box
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 4408634787..70ecde3858 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -868,7 +868,7 @@ namespace osu.Game.Screens.Select
{
var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1);
- foreach (var panel in Scroll.Children)
+ foreach (var panel in Scroll)
{
Debug.Assert(panel.Item != null);
@@ -899,7 +899,7 @@ namespace osu.Game.Screens.Select
// Update externally controlled state of currently visible items (e.g. x-offset and opacity).
// This is a per-frame update on all drawable panels.
- foreach (DrawableCarouselItem item in Scroll.Children)
+ foreach (DrawableCarouselItem item in Scroll)
{
updateItem(item);
@@ -1094,7 +1094,7 @@ namespace osu.Game.Screens.Select
// to enter clamp-special-case mode where it animates completely differently to normal.
float scrollChange = scrollTarget.Value - Scroll.Current;
Scroll.ScrollTo(scrollTarget.Value, false);
- foreach (var i in Scroll.Children)
+ foreach (var i in Scroll)
i.Y += scrollChange;
break;
}
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index 369db37e63..bd659d7423 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -242,7 +242,7 @@ namespace osu.Game.Screens.Select.Carousel
bool isSelected = Item?.State.Value == CarouselItemState.Selected;
- foreach (var panel in beatmapContainer.Children)
+ foreach (var panel in beatmapContainer)
{
Debug.Assert(panel.Item != null);
diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs
index a44bf3a43d..ec159873f8 100644
--- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs
+++ b/osu.Game/Skinning/GameplaySkinComponentLookup.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -28,8 +27,5 @@ namespace osu.Game.Skinning
protected virtual string RulesetPrefix => string.Empty;
protected virtual string ComponentName => Component.ToString();
-
- public string LookupName =>
- string.Join('/', new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s)));
}
}
diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs
index 1834a17279..a9f68bd378 100644
--- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs
+++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs
@@ -63,14 +63,10 @@ namespace osu.Game.Skinning
// missed ticks / slider end don't get the normal animation.
if (isMissedTick())
{
- this.ScaleTo(1.6f);
- this.ScaleTo(1, 100, Easing.In);
+ this.ScaleTo(1.2f);
+ this.ScaleTo(1f, 100, Easing.In);
- if (legacyVersion > 1.0m)
- {
- this.MoveTo(new Vector2(0, -2f));
- this.MoveToOffset(new Vector2(0, 10), fade_out_delay + fade_out_length, Easing.In);
- }
+ this.FadeOutFromOne(400);
}
else
{
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 10913e7369..cfa5f972d2 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -491,39 +491,30 @@ namespace osu.Game.Skinning
break;
}
- foreach (string name in getFallbackNames(componentName))
+ Texture? texture = null;
+ float ratio = 1;
+
+ if (AllowHighResolutionSprites)
{
- string lookupName = name;
- Texture? texture = null;
- float ratio = 1;
+ // some component names (especially user-controlled ones, like `HitX` in mania)
+ // may contain `@2x` scale specifications.
+ // stable happens to check for that and strip them, so do the same to match stable behaviour.
+ componentName = componentName.Replace(@"@2x", string.Empty);
- if (AllowHighResolutionSprites)
- {
- // some component names (especially user-controlled ones, like `HitX` in mania)
- // may contain `@2x` scale specifications.
- // stable happens to check for that and strip them, so do the same to match stable behaviour.
- lookupName = name.Replace(@"@2x", string.Empty);
+ string twoTimesFilename = $"{Path.ChangeExtension(componentName, null)}@2x{Path.GetExtension(componentName)}";
- string twoTimesFilename = $"{Path.ChangeExtension(lookupName, null)}@2x{Path.GetExtension(lookupName)}";
+ texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT);
- texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT);
+ if (texture != null)
ratio = 2;
- }
-
- if (texture == null)
- {
- ratio = 1;
- texture = Textures?.Get(lookupName, wrapModeS, wrapModeT);
- }
-
- if (texture == null)
- continue;
-
- texture.ScaleAdjust = ratio;
- return texture;
}
- return null;
+ texture ??= Textures?.Get(componentName, wrapModeS, wrapModeT);
+
+ if (texture != null)
+ texture.ScaleAdjust = ratio;
+
+ return texture;
}
public override ISample? GetSample(ISampleInfo sampleInfo)
@@ -534,7 +525,7 @@ namespace osu.Game.Skinning
lookupNames = getLegacyLookupNames(hitSample);
else
{
- lookupNames = sampleInfo.LookupNames.SelectMany(getFallbackNames);
+ lookupNames = sampleInfo.LookupNames.SelectMany(getFallbackSampleNames);
}
foreach (string lookup in lookupNames)
@@ -552,7 +543,7 @@ namespace osu.Game.Skinning
private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample)
{
- var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames);
+ var lookupNames = hitSample.LookupNames.SelectMany(getFallbackSampleNames);
if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix))
{
@@ -571,13 +562,13 @@ namespace osu.Game.Skinning
yield return hitSample.Name;
}
- private IEnumerable getFallbackNames(string componentName)
+ private IEnumerable getFallbackSampleNames(string name)
{
- // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin.
- yield return componentName;
+ // May be something like "Gameplay/normal-hitnormal" from lazer.
+ yield return name;
- // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
- yield return componentName.Split('/').Last();
+ // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/normal-hitnormal" -> "normal-hitnormal").
+ yield return name.Split('/').Last();
}
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
index cefd51b2aa..fae9ec7f2e 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
@@ -129,7 +129,7 @@ namespace osu.Game.Storyboards.Drawables
// When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored
// and resources are retrieved until the end of the animation.
- var skinTextures = skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, null, out _);
+ var skinTextures = skin.GetTextures(Path.ChangeExtension(Animation.Path, null), default, default, true, string.Empty, null, out _);
if (skinTextures.Length > 0)
{
diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs
index f3c69201e2..00e5b38b1a 100644
--- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs
+++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests
[CallerMemberName] string callingMethodName = @"")
: base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions
{
- BindIPC = bindIPC,
+ IPCPort = bindIPC ? OsuGame.IPC_PORT : null,
}, bypassCleanup: bypassCleanupOnDispose, realtime: realtime)
{
this.bypassCleanupOnSetup = bypassCleanupOnSetup;
diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs
index e04c71d193..1a9e03b2a4 100644
--- a/osu.Game/Tests/VisualTestRunner.cs
+++ b/osu.Game/Tests/VisualTestRunner.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development", new HostOptions { BindIPC = true, }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development"))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/osu.Game/Utils/FileUtils.cs b/osu.Game/Utils/FileUtils.cs
new file mode 100644
index 0000000000..063ab178f7
--- /dev/null
+++ b/osu.Game/Utils/FileUtils.cs
@@ -0,0 +1,72 @@
+// 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.Threading;
+
+namespace osu.Game.Utils
+{
+ public static class FileUtils
+ {
+ ///
+ /// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
+ ///
+ /// The action to perform.
+ /// The provided state.
+ /// The number of attempts (250ms wait between each).
+ /// Whether to throw an exception on failure. If false, will silently fail.
+ public static bool AttemptOperation(Action action, T state, int attempts = 10, bool throwOnFailure = true)
+ {
+ while (true)
+ {
+ try
+ {
+ action(state);
+ return true;
+ }
+ catch (Exception)
+ {
+ if (attempts-- == 0)
+ {
+ if (throwOnFailure)
+ throw;
+
+ return false;
+ }
+ }
+
+ Thread.Sleep(250);
+ }
+ }
+
+ ///
+ /// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
+ ///
+ /// The action to perform.
+ /// The number of attempts (250ms wait between each).
+ /// Whether to throw an exception on failure. If false, will silently fail.
+ public static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true)
+ {
+ while (true)
+ {
+ try
+ {
+ action();
+ return true;
+ }
+ catch (Exception)
+ {
+ if (attempts-- == 0)
+ {
+ if (throwOnFailure)
+ throw;
+
+ return false;
+ }
+ }
+
+ Thread.Sleep(250);
+ }
+ }
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f33ddac66f..1b1abe3971 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -21,12 +21,12 @@
-
+
-
-
-
-
+
+
+
+
@@ -36,13 +36,13 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
-
-
-
+
+
+