diff --git a/global.json b/global.json
index 10b61047ac..2c93a533e4 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.2.3"
+ "Microsoft.Build.Traversal": "3.0.2"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 0b43fd73f5..aa4d9fa4ee 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 0feab9a717..62d8c17058 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -59,7 +59,7 @@ namespace osu.Desktop
try
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
- stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", "");
+ stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
if (checkExists(stableInstallPath))
return stableInstallPath;
@@ -138,7 +138,7 @@ namespace osu.Desktop
break;
// SDL2 DesktopWindow
- case DesktopWindow desktopWindow:
+ case SDL2DesktopWindow desktopWindow:
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name;
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 285a813d97..6ca7079654 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -22,9 +22,9 @@ namespace osu.Desktop
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
- bool useSdl = args.Contains("--sdl");
+ bool useOsuTK = args.Contains("--tk");
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl))
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 62e8f7c518..adf9c452f6 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -24,12 +24,12 @@
-
+
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
new file mode 100644
index 0000000000..64695153b5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -0,0 +1,14 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index e055f08dc2..c12f38723b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -10,14 +10,12 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
-using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -103,7 +101,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
- CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
},
});
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index d35f828e28..3e4995482d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Tests
if (juice.NestedHitObjects.Last() is CatchHitObject tail)
tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
- addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation));
+ addToPlayfield(new DrawableJuiceStream(juice));
}
private void spawnBananas(bool hit = false)
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 dfe3bf8af4..61ecd79e3d 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/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
index efb0958a3a..fb982bbdab 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.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 JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Utils;
@@ -10,7 +11,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => FruitVisualRepresentation.Banana;
- public DrawableBanana(Banana h)
+ public DrawableBanana()
+ : this(null)
+ {
+ }
+
+ public DrawableBanana([CanBeNull] Banana h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
index bf771f690e..9b2f95e221 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
@@ -1,26 +1,27 @@
// 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 JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableBananaShower : DrawableCatchHitObject
{
- private readonly Func> createDrawableRepresentation;
private readonly Container bananaContainer;
- public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null)
+ public DrawableBananaShower()
+ : this(null)
+ {
+ }
+
+ public DrawableBananaShower([CanBeNull] BananaShower s)
: base(s)
{
- this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
- X = 0;
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
}
@@ -34,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
- bananaContainer.Clear();
- }
-
- protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
- {
- switch (hitObject)
- {
- case Banana banana:
- return createDrawableRepresentation?.Invoke(banana);
- }
-
- return base.CreateNestedHitObject(hitObject);
+ bananaContainer.Clear(false);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
index 9e76265394..06ecd44488 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Utils;
@@ -13,7 +14,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public override bool StaysOnPlate => false;
- public DrawableDroplet(CatchHitObject h)
+ public DrawableDroplet()
+ : this(null)
+ {
+ }
+
+ public DrawableDroplet([CanBeNull] CatchHitObject h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
index 4338d80345..68cb649b66 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Utils;
@@ -16,7 +17,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4);
- public DrawableFruit(CatchHitObject h)
+ public DrawableFruit()
+ : this(null)
+ {
+ }
+
+ public DrawableFruit([CanBeNull] Fruit h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
index a7a5bfa5ad..a496a35842 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
@@ -1,37 +1,33 @@
// 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 JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableJuiceStream : DrawableCatchHitObject
{
- private readonly Func> createDrawableRepresentation;
private readonly Container dropletContainer;
- public override Vector2 OriginPosition => base.OriginPosition - new Vector2(0, CatchHitObject.OBJECT_RADIUS);
+ public DrawableJuiceStream()
+ : this(null)
+ {
+ }
- public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null)
+ public DrawableJuiceStream([CanBeNull] JuiceStream s)
: base(s)
{
- this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
- X = 0;
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
- hitObject.Origin = Anchor.BottomCentre;
-
base.AddNestedHitObject(hitObject);
dropletContainer.Add(hitObject);
}
@@ -39,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
- dropletContainer.Clear();
- }
-
- protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
- {
- switch (hitObject)
- {
- case CatchHitObject catchObject:
- return createDrawableRepresentation?.Invoke(catchObject);
- }
-
- throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
+ dropletContainer.Clear(false);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
index 8c4d821b4a..8f5a04dfda 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
@@ -1,13 +1,20 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
+
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableTinyDroplet : DrawableDroplet
{
protected override float ScaleFactor => base.ScaleFactor / 2;
- public DrawableTinyDroplet(TinyDroplet h)
+ public DrawableTinyDroplet()
+ : this(null)
+ {
+ }
+
+ public DrawableTinyDroplet([CanBeNull] TinyDroplet h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
index 1494ef3888..b8648f46f0 100644
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
@@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
{
private readonly string lookupName;
- private readonly IBindable accentColour = new Bindable();
+ private readonly Bindable accentColour = new Bindable();
+ private readonly Bindable hyperDash = new Bindable();
private Sprite colouredSprite;
public LegacyFruitPiece(string lookupName)
@@ -34,6 +35,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
accentColour.BindTo(drawableCatchObject.AccentColour);
+ hyperDash.BindTo(drawableCatchObject.HyperDash);
InternalChildren = new Drawable[]
{
@@ -51,9 +53,9 @@ namespace osu.Game.Rulesets.Catch.Skinning
},
};
- if (drawableCatchObject.HitObject.HyperDash)
+ if (hyperDash.Value)
{
- var hyperDash = new Sprite
+ var hyperDashOverlay = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -67,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
Catcher.DEFAULT_HYPER_DASH_COLOUR,
};
- AddInternal(hyperDash);
+ AddInternal(hyperDashOverlay);
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 9df32d8d36..820f08d439 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -2,6 +2,7 @@
// 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;
using osu.Game.Beatmaps;
@@ -42,7 +43,6 @@ namespace osu.Game.Rulesets.Catch.UI
CatcherArea = new CatcherArea(difficulty)
{
- CreateDrawableRepresentation = createDrawableRepresentation,
ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
@@ -57,6 +57,17 @@ namespace osu.Game.Rulesets.Catch.UI
};
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RegisterPool(50);
+ RegisterPool(50);
+ RegisterPool(100);
+ RegisterPool(100);
+ RegisterPool(10);
+ RegisterPool(2);
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 0f0b9df76e..11b6916a4c 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -107,6 +108,9 @@ namespace osu.Game.Rulesets.Catch.UI
private float hyperDashTargetPosition;
private Bindable hitLighting;
+ private DrawablePool hitExplosionPool;
+ private Container hitExplosionContainer;
+
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
{
this.trailsTarget = trailsTarget;
@@ -127,6 +131,7 @@ namespace osu.Game.Rulesets.Catch.UI
InternalChildren = new Drawable[]
{
+ hitExplosionPool = new DrawablePool(10),
caughtFruitContainer,
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
{
@@ -142,7 +147,12 @@ namespace osu.Game.Rulesets.Catch.UI
{
Anchor = Anchor.TopCentre,
Alpha = 0,
- }
+ },
+ hitExplosionContainer = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ },
};
trails = new CatcherTrailDisplay(this);
@@ -209,11 +219,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (hitLighting.Value)
{
- AddInternal(new HitExplosion(fruit)
- {
- X = fruit.X,
- Scale = new Vector2(fruit.HitObject.Scale)
- });
+ HitExplosion hitExplosion = hitExplosionPool.Get();
+ hitExplosion.X = fruit.X;
+ hitExplosion.Scale = new Vector2(fruit.HitObject.Scale);
+ hitExplosion.ObjectColour = fruit.AccentColour.Value;
+ hitExplosionContainer.Add(hitExplosion);
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index ad79a23279..26077aeba4 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -21,8 +20,6 @@ namespace osu.Game.Rulesets.Catch.UI
{
public const float CATCHER_SIZE = 106.75f;
- public Func> CreateDrawableRepresentation;
-
public readonly Catcher MovableCatcher;
private readonly CatchComboDisplay comboDisplay;
@@ -72,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit)
{
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
- var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
+ var caughtFruit = createCaughtFruit(fruit);
if (caughtFruit == null) return;
@@ -127,5 +124,26 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.X = MovableCatcher.X;
}
+
+ private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject)
+ {
+ switch (hitObject.HitObject)
+ {
+ case Banana banana:
+ return new DrawableBanana(banana);
+
+ case Fruit fruit:
+ return new DrawableFruit(fruit);
+
+ case TinyDroplet tiny:
+ return new DrawableTinyDroplet(tiny);
+
+ case Droplet droplet:
+ return new DrawableDroplet(droplet);
+
+ default:
+ return null;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index ebe45aa3ab..46733181e3 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -8,7 +8,6 @@ using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
@@ -40,30 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI
protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
- public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h)
- {
- switch (h)
- {
- case Banana banana:
- return new DrawableBanana(banana);
-
- case Fruit fruit:
- return new DrawableFruit(fruit);
-
- case JuiceStream stream:
- return new DrawableJuiceStream(stream, CreateDrawableRepresentation);
-
- case BananaShower shower:
- return new DrawableBananaShower(shower, CreateDrawableRepresentation);
-
- case TinyDroplet tiny:
- return new DrawableTinyDroplet(tiny);
-
- case Droplet droplet:
- return new DrawableDroplet(droplet);
- }
-
- return null;
- }
+ public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) => null;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
index 04a86f83be..24ca778248 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -5,35 +5,43 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Pooling;
using osu.Framework.Utils;
-using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
- public class HitExplosion : CompositeDrawable
+ public class HitExplosion : PoolableDrawable
{
- private readonly CircularContainer largeFaint;
+ private Color4 objectColour;
- public HitExplosion(DrawableCatchHitObject fruit)
+ public Color4 ObjectColour
+ {
+ get => objectColour;
+ set
+ {
+ if (objectColour == value) return;
+
+ objectColour = value;
+ onColourChanged();
+ }
+ }
+
+ private readonly CircularContainer largeFaint;
+ private readonly CircularContainer smallFaint;
+ private readonly CircularContainer directionalGlow1;
+ private readonly CircularContainer directionalGlow2;
+
+ public HitExplosion()
{
Size = new Vector2(20);
Anchor = Anchor.TopCentre;
Origin = Anchor.BottomCentre;
- Color4 objectColour = fruit.AccentColour.Value;
-
// scale roughly in-line with visual appearance of notes
-
- const float angle_variangle = 15; // should be less than 45
-
- const float roundness = 100;
-
const float initial_height = 10;
- var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
-
InternalChildren = new Drawable[]
{
largeFaint = new CircularContainer
@@ -42,33 +50,17 @@ namespace osu.Game.Rulesets.Catch.UI
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.8f),
Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- },
},
- new CircularContainer
+ smallFaint = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- },
},
- new CircularContainer
+ directionalGlow1 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -76,16 +68,8 @@ namespace osu.Game.Rulesets.Catch.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
},
- new CircularContainer
+ directionalGlow2 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -93,30 +77,57 @@ namespace osu.Game.Rulesets.Catch.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
}
};
}
- protected override void LoadComplete()
+ protected override void PrepareForUse()
{
- base.LoadComplete();
+ base.PrepareForUse();
const double duration = 400;
+ // we want our size to be very small so the glow dominates it.
+ largeFaint.Size = new Vector2(0.8f);
largeFaint
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
.FadeOut(duration * 2);
+ const float angle_variangle = 15; // should be less than 45
+ directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
+ directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
+
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
Expire(true);
}
+
+ private void onColourChanged()
+ {
+ const float roundness = 100;
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ };
+
+ smallFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ };
+
+ directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ }
}
}
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 892f27d27f..fa7bfd7169 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/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
index 7697f46160..d3cb3bcf59 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs
@@ -5,7 +5,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
- public class OsuModTestScene : ModTestScene
+ public abstract class OsuModTestScene : ModTestScene
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
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 3639c3616f..d6a03da807 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
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index c06904c0c2..e9838de63d 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private OsuColour colours { get; set; }
private IBindable sliderPosition;
+ private IBindable sliderScale;
private IBindable controlPointPosition;
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
@@ -69,13 +70,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(10),
+ Size = new Vector2(20),
},
markerRing = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(14),
+ Size = new Vector2(28),
Masking = true,
BorderThickness = 2,
BorderColour = Color4.White,
@@ -102,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
controlPointPosition = ControlPoint.Position.GetBoundCopy();
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
+ sliderScale = slider.ScaleBindable.GetBoundCopy();
+ sliderScale.BindValueChanged(_ => updateMarkerDisplay());
+
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
updateMarkerDisplay();
@@ -143,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
+ private Vector2 dragStartPosition;
+
protected override bool OnDragStart(DragStartEvent e)
{
if (RequestSelection == null)
@@ -150,6 +156,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (e.Button == MouseButton.Left)
{
+ dragStartPosition = ControlPoint.Position.Value;
changeHandler?.BeginChange();
return true;
}
@@ -174,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
}
else
- ControlPoint.Position.Value += e.Delta;
+ ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
}
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
@@ -194,6 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
colour = colour.Lighten(1);
marker.Colour = colour;
+ marker.Scale = new Vector2(slider.Scale);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index cf3964abc9..af5b609ec8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (userTriggered || Time.Current < HitObject.EndTime)
return;
- ApplyResult(r => r.Type = r.Judgement.MaxResult);
+ ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
public override void PlaySamples()
@@ -288,14 +288,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
case ArmedState.Hit:
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
+ if (sliderBody?.SnakingOut.Value == true)
+ Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
break;
}
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
}
- public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
-
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
private class DefaultSliderBody : PlaySliderBody
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 b59f3a4344..a89645d881 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.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index aa531ba106..35c6d62cb7 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -643,6 +643,55 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap);
}
+ [Test]
+ public void TestChangingRulesetOnMultiRulesetBeatmap()
+ {
+ int changeCount = 0;
+
+ AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false));
+ AddStep("bind beatmap changed", () =>
+ {
+ Beatmap.ValueChanged += onChange;
+ changeCount = 0;
+ });
+
+ changeRuleset(0);
+
+ createSongSelect();
+
+ AddStep("import multi-ruleset map", () =>
+ {
+ var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
+ manager.Import(createTestBeatmapSet(usableRulesets)).Wait();
+ });
+
+ int previousSetID = 0;
+
+ AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
+
+ AddStep("record set ID", () => previousSetID = Beatmap.Value.BeatmapSetInfo.ID);
+ AddAssert("selection changed once", () => changeCount == 1);
+
+ AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
+
+ changeRuleset(3);
+
+ AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
+
+ AddUntilStep("selection changed", () => changeCount > 1);
+
+ AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.ID == previousSetID);
+ AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3);
+
+ AddAssert("selection changed only fired twice", () => changeCount == 2);
+
+ AddStep("unbind beatmap changed", () => Beatmap.ValueChanged -= onChange);
+ AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
+
+ // ReSharper disable once AccessToModifiedClosure
+ void onChange(ValueChangedEvent valueChangedEvent) => changeCount++;
+ }
+
[Test]
public void TestDifficultyIconSelectingForDifferentRuleset()
{
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index c692bcd5e4..83d7b4135a 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 5d55196dcf..bc6b994988 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.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index 999ce61ac8..71417d1cc6 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -243,7 +243,7 @@ namespace osu.Game.Tournament.IPC
string stableInstallPath;
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
- stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
+ stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
if (ipcFileExistsInDirectory(stableInstallPath))
return stableInstallPath;
diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj
index 9cce40c9d3..b049542bb0 100644
--- a/osu.Game.Tournament/osu.Game.Tournament.csproj
+++ b/osu.Game.Tournament/osu.Game.Tournament.csproj
@@ -9,6 +9,6 @@
-
+
\ No newline at end of file
diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs
index 58b078db71..3d90dd0189 100644
--- a/osu.Game/Audio/HitSampleInfo.cs
+++ b/osu.Game/Audio/HitSampleInfo.cs
@@ -72,13 +72,13 @@ namespace osu.Game.Audio
///
/// Creates a new with overridden values.
///
- /// An optional new sample name.
- /// An optional new sample bank.
- /// An optional new lookup suffix.
- /// An optional new volume.
+ /// An optional new sample name.
+ /// An optional new sample bank.
+ /// An optional new lookup suffix.
+ /// An optional new volume.
/// The new .
- public virtual HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default)
- => new HitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), suffix.GetOr(Suffix), volume.GetOr(Volume));
+ public virtual HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default)
+ => new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume));
public bool Equals(HitSampleInfo? other)
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index 8064da1543..fd0b496335 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Beatmaps.ControlPoints
/// The . This will not be modified.
/// The modified . This does not share a reference with .
public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
- => hitSampleInfo.With(bank: hitSampleInfo.Bank ?? SampleBank, volume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume);
+ => hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume);
public override bool IsRedundant(ControlPoint existing)
=> existing is SampleControlPoint existingSample
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 9f16180e77..c9d139bdd0 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -183,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats
var baseInfo = base.ApplyTo(hitSampleInfo);
if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
- return legacy.With(customSampleBank: CustomSampleBank);
+ return legacy.With(newCustomSampleBank: CustomSampleBank);
return baseInfo;
}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 89a6ee8b07..a07e446d2e 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -184,7 +184,7 @@ namespace osu.Game.Configuration
return new TrackedSettings
{
new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled", LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons))),
- new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription(), $"cycle: shift-tab quick view: {LookupKeyBindings(GlobalAction.HoldForHUD)}")),
+ new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription(), $"cycle: {LookupKeyBindings(GlobalAction.ToggleInGameInterface)} quick view: {LookupKeyBindings(GlobalAction.HoldForHUD)}")),
new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
new TrackedSetting(OsuSetting.Skin, m =>
{
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 74eb2b0126..f4a4813b94 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -68,6 +68,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit),
new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
+ new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay),
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
@@ -200,5 +201,8 @@ namespace osu.Game.Input.Bindings
[Description("Pause / resume replay")]
TogglePauseReplay,
+
+ [Description("Toggle in-game interface")]
+ ToggleInGameInterface,
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 14b8dbfac0..62dc1dc806 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -208,7 +208,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private IReadOnlyList getResolutions()
{
var resolutions = new List { new Size(9999, 9999) };
- var currentDisplay = game.Window?.CurrentDisplay.Value;
+ var currentDisplay = game.Window?.CurrentDisplayBindable.Value;
if (currentDisplay != null)
{
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 762edf5a13..72025de131 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -486,12 +486,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
IsLayered = isLayered;
}
- public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default)
- => With(name, bank, volume);
+ public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default)
+ => With(newName, newBank, newVolume);
- public LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default,
- Optional isLayered = default)
- => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered));
+ public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newCustomSampleBank = default,
+ Optional newIsLayered = default)
+ => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
public bool Equals(LegacyHitSampleInfo? other)
=> base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered;
@@ -520,11 +520,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
Path.ChangeExtension(Filename, null)
};
- public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default)
- => With(volume: volume);
-
- public FileHitSampleInfo With(Optional filename = default, Optional volume = default)
- => new FileHitSampleInfo(filename.GetOr(Filename), volume.GetOr(Volume));
+ public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newCustomSampleBank = default,
+ Optional newIsLayered = default)
+ => new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
public bool Equals(FileHitSampleInfo? other)
=> base.Equals(other) && Filename == other.Filename;
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs
index 724256af8b..fb11b859a7 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs
@@ -25,6 +25,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
+ [Resolved(CanBeNull = true)]
+ private IEditorChangeHandler changeHandler { get; set; }
+
[Resolved]
private OsuColour colours { get; set; }
@@ -38,7 +41,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[BackgroundDependencyLoader]
private void load()
{
- beatDivisor.BindValueChanged(_ => tickCache.Invalidate());
+ beatDivisor.BindValueChanged(_ => invalidateTicks());
+
+ if (changeHandler != null)
+ // currently this is the best way to handle any kind of timing changes.
+ changeHandler.OnStateChange += invalidateTicks;
+ }
+
+ private void invalidateTicks()
+ {
+ tickCache.Invalidate();
}
///
@@ -165,5 +177,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return point;
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (changeHandler != null)
+ changeHandler.OnStateChange -= invalidateTicks;
+ }
}
}
diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
index aa1d57db31..897ddc6955 100644
--- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs
+++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
@@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Setup
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
- MinValue = 2,
- MaxValue = 7,
+ MinValue = 0,
+ MaxValue = 10,
Precision = 0.1f,
}
},
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index e83dded075..50195d571c 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -9,7 +9,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@@ -19,7 +18,6 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.HUD;
using osuTK;
-using osuTK.Input;
namespace osu.Game.Screens.Play
{
@@ -181,7 +179,7 @@ namespace osu.Game.Screens.Play
notificationOverlay?.Post(new SimpleNotification
{
- Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab."
+ Text = $"The score overlay is currently disabled. You can toggle this by pressing {config.LookupKeyBindings(GlobalAction.ToggleInGameInterface)}."
});
}
@@ -273,37 +271,6 @@ namespace osu.Game.Screens.Play
Progress.BindDrawableRuleset(drawableRuleset);
}
- protected override bool OnKeyDown(KeyDownEvent e)
- {
- if (e.Repeat) return false;
-
- if (e.ShiftPressed)
- {
- switch (e.Key)
- {
- case Key.Tab:
- switch (configVisibilityMode.Value)
- {
- case HUDVisibilityMode.Never:
- configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay;
- break;
-
- case HUDVisibilityMode.HideDuringGameplay:
- configVisibilityMode.Value = HUDVisibilityMode.Always;
- break;
-
- case HUDVisibilityMode.Always:
- configVisibilityMode.Value = HUDVisibilityMode.Never;
- break;
- }
-
- return true;
- }
- }
-
- return base.OnKeyDown(e);
- }
-
protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter();
protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter();
@@ -377,6 +344,24 @@ namespace osu.Game.Screens.Play
holdingForHUD = true;
updateVisibility();
return true;
+
+ case GlobalAction.ToggleInGameInterface:
+ switch (configVisibilityMode.Value)
+ {
+ case HUDVisibilityMode.Never:
+ configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay;
+ break;
+
+ case HUDVisibilityMode.HideDuringGameplay:
+ configVisibilityMode.Value = HUDVisibilityMode.Always;
+ break;
+
+ case HUDVisibilityMode.Always:
+ configVisibilityMode.Value = HUDVisibilityMode.Never;
+ break;
+ }
+
+ return true;
}
return false;
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index b55c0694ef..f32011a27a 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -376,7 +376,7 @@ namespace osu.Game.Screens.Select
if (selectionChangedDebounce?.Completed == false)
{
selectionChangedDebounce.RunTask();
- selectionChangedDebounce.Cancel(); // cancel the already scheduled task.
+ selectionChangedDebounce?.Cancel(); // cancel the already scheduled task.
selectionChangedDebounce = null;
}
@@ -465,19 +465,30 @@ namespace osu.Game.Screens.Select
void run()
{
+ // clear pending task immediately to track any potential nested debounce operation.
+ selectionChangedDebounce = null;
+
Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}");
if (transferRulesetValue())
{
Mods.Value = Array.Empty();
- // transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it.
+ // transferRulesetValue() may trigger a re-filter. If the current selection does not match the new ruleset, we want to switch away from it.
// The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here.
// We perform an early selection attempt and clear out the beatmap selection to avoid a second ruleset change (revert).
if (beatmap != null && !Carousel.SelectBeatmap(beatmap, false))
beatmap = null;
}
+ if (selectionChangedDebounce != null)
+ {
+ // a new nested operation was started; switch to it for further selection.
+ // this avoids having two separate debounces trigger from the same source.
+ selectionChangedDebounce.RunTask();
+ return;
+ }
+
// We may be arriving here due to another component changing the bindable Beatmap.
// In these cases, the other component has already loaded the beatmap, so we don't need to do so again.
if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs
new file mode 100644
index 0000000000..054f72400e
--- /dev/null
+++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.IO.Stores;
+using osu.Game.Rulesets;
+using osu.Game.Skinning;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public abstract class LegacySkinPlayerTestScene : PlayerTestScene
+ {
+ private ISkinSource legacySkinSource;
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource);
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, OsuGameBase game)
+ {
+ var legacySkin = new DefaultLegacySkin(new NamespacedResourceStore(game.Resources, "Skins/Legacy"), audio);
+ legacySkinSource = new SkinProvidingContainer(legacySkin);
+ }
+
+ public class SkinProvidingPlayer : TestPlayer
+ {
+ [Cached(typeof(ISkinSource))]
+ private readonly ISkinSource skinSource;
+
+ public SkinProvidingPlayer(ISkinSource skinSource)
+ {
+ this.skinSource = skinSource;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs
new file mode 100644
index 0000000000..118b08fe30
--- /dev/null
+++ b/osu.Game/Utils/StatelessRNG.cs
@@ -0,0 +1,79 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Utils
+{
+ ///
+ /// Provides a fast stateless function that can be used in randomly-looking visual elements.
+ ///
+ public static class StatelessRNG
+ {
+ private static ulong mix(ulong x)
+ {
+ unchecked
+ {
+ x ^= x >> 33;
+ x *= 0xff51afd7ed558ccd;
+ x ^= x >> 33;
+ x *= 0xc4ceb9fe1a85ec53;
+ x ^= x >> 33;
+ return x;
+ }
+ }
+
+ ///
+ /// Generate a random 64-bit unsigned integer from given seed.
+ ///
+ ///
+ /// The seed value of this random number generator.
+ ///
+ ///
+ /// The series number.
+ /// Different values are computed for the same seed in different series.
+ ///
+ public static ulong NextULong(int seed, int series = 0)
+ {
+ unchecked
+ {
+ var combined = ((ulong)(uint)series << 32) | (uint)seed;
+ // The xor operation is to not map (0, 0) to 0.
+ return mix(combined ^ 0x12345678);
+ }
+ }
+
+ ///
+ /// Generate a random integer in range [0, maxValue) from given seed.
+ ///
+ ///
+ /// The number of possible results.
+ ///
+ ///
+ /// The seed value of this random number generator.
+ ///
+ ///
+ /// The series number.
+ /// Different values are computed for the same seed in different series.
+ ///
+ public static int NextInt(int maxValue, int seed, int series = 0)
+ {
+ if (maxValue <= 0) throw new ArgumentOutOfRangeException(nameof(maxValue));
+
+ return (int)(NextULong(seed, series) % (ulong)maxValue);
+ }
+
+ ///
+ /// Compute a random floating point value between 0 and 1 (excluding 1) from given seed and series number.
+ ///
+ ///
+ /// The seed value of this random number generator.
+ ///
+ ///
+ /// The series number.
+ /// Different values are computed for the same seed in different series.
+ ///
+ public static float NextSingle(int seed, int series = 0) =>
+ (float)(NextULong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e201383d51..53b854caa3 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -18,7 +18,7 @@
-
+
@@ -26,11 +26,11 @@
-
+
-
+
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index e5f7581404..b32d3f900a 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -88,11 +88,11 @@
-
+
-
+