diff --git a/osu.Android.props b/osu.Android.props
index 13e4457104..83b17f0d87 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
index 1248409b2a..09362929d2 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
@@ -4,10 +4,8 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
-using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
@@ -21,12 +19,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneCatchModHidden : ModTestScene
{
- [BackgroundDependencyLoader]
- private void load()
- {
- LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
- }
-
[Test]
public void TestJuiceStream()
{
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
index 7bad4c79cb..f9e106f097 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
@@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
- {
- }
+ => ApplyNormalVisibilityState(hitObject, state);
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 8aa0c85433..962a13ebea 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
isLegacySkin = new Lazy(() => FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null);
hasKeyTexture = new Lazy(() => FindProvider(s => s.GetAnimation(
- this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value
+ s.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value
?? "mania-key1", true, true) != null) != null);
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
index 4dfadbb835..c282a919ea 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Utils;
@@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio;
private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio;
- private static readonly Vector2 playfield_middle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2);
+ private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2;
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
@@ -74,22 +75,8 @@ namespace osu.Game.Rulesets.Osu.Mods
// update end position as it may have changed as a result of the position update.
current.EndPositionRandomised = current.PositionRandomised;
- switch (hitObject)
- {
- case Slider slider:
- // Shift nested objects the same distance as the slider got shifted in the randomisation process
- // so that moveSliderIntoPlayfield() can determine their relative distances to slider.Position and thus minMargin
- shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal));
-
- var oldPos = new Vector2(slider.Position.X, slider.Position.Y);
-
- moveSliderIntoPlayfield(slider, current);
-
- // Shift them again to move them to their final position after the slider got moved into the playfield
- shiftNestedObjects(slider, Vector2.Subtract(slider.Position, oldPos));
-
- break;
- }
+ if (hitObject is Slider slider)
+ moveSliderIntoPlayfield(slider, current);
previous = current;
}
@@ -131,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Mods
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
- var position = Vector2.Add(previous.EndPositionRandomised, posRelativeToPrev);
+ var position = previous.EndPositionRandomised + posRelativeToPrev;
// Move hit objects back into the playfield if they are outside of it,
// which would sometimes happen during big jumps otherwise.
@@ -146,34 +133,41 @@ namespace osu.Game.Rulesets.Osu.Mods
///
private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
{
- // Min. distances from the slider's position to the playfield border
- var minMargin = new MarginPadding();
+ var minMargin = getMinSliderMargin(slider);
- foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle))
- {
- if (!(hitObject is OsuHitObject osuHitObject))
- continue;
-
- var relativePos = Vector2.Subtract(osuHitObject.Position, slider.Position);
-
- minMargin.Left = Math.Max(minMargin.Left, -relativePos.X);
- minMargin.Right = Math.Max(minMargin.Right, relativePos.X);
- minMargin.Top = Math.Max(minMargin.Top, -relativePos.Y);
- minMargin.Bottom = Math.Max(minMargin.Bottom, relativePos.Y);
- }
-
- if (slider.Position.X < minMargin.Left)
- slider.Position = new Vector2(minMargin.Left, slider.Position.Y);
- else if (slider.Position.X + minMargin.Right > OsuPlayfield.BASE_SIZE.X)
- slider.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - minMargin.Right, slider.Position.Y);
-
- if (slider.Position.Y < minMargin.Top)
- slider.Position = new Vector2(slider.Position.X, minMargin.Top);
- else if (slider.Position.Y + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y)
- slider.Position = new Vector2(slider.Position.X, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom);
+ slider.Position = new Vector2(
+ Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right),
+ Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom)
+ );
currentObjectInfo.PositionRandomised = slider.Position;
currentObjectInfo.EndPositionRandomised = slider.EndPosition;
+
+ shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal);
+ }
+
+ ///
+ /// Calculates the min. distances from the 's position to the playfield border for the slider to be fully inside of the playfield.
+ ///
+ private MarginPadding getMinSliderMargin(Slider slider)
+ {
+ var pathPositions = new List();
+ slider.Path.GetPathToProgress(pathPositions, 0, 1);
+
+ var minMargin = new MarginPadding();
+
+ foreach (var pos in pathPositions)
+ {
+ minMargin.Left = Math.Max(minMargin.Left, -pos.X);
+ minMargin.Right = Math.Max(minMargin.Right, pos.X);
+ minMargin.Top = Math.Max(minMargin.Top, -pos.Y);
+ minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y);
+ }
+
+ minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right);
+ minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom);
+
+ return minMargin;
}
///
@@ -188,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Mods
if (!(hitObject is OsuHitObject osuHitObject))
continue;
- osuHitObject.Position = Vector2.Add(osuHitObject.Position, shift);
+ osuHitObject.Position += shift;
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs
index ae8c03dad1..df33bf52be 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs
@@ -128,5 +128,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (drawableSpinner != null)
+ drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs
index b52dc749f0..fece3494e6 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs
@@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
{
public class MainCirclePiece : CompositeDrawable
{
+ public override bool RemoveCompletedTransforms => false;
+
private readonly CirclePiece circle;
private readonly RingPiece ring;
private readonly FlashPiece flash;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index 822dad8523..e5200ac248 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public class LegacyMainCirclePiece : CompositeDrawable
{
+ public override bool RemoveCompletedTransforms => false;
+
private readonly string priorityLookup;
private readonly bool hasNumber;
diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
index 09fe9b3767..f7d42a2ee6 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.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 System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -11,6 +12,7 @@ using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
+using osu.Game.Skinning;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Background
@@ -23,6 +25,9 @@ namespace osu.Game.Tests.Visual.Background
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType().FirstOrDefault();
+ [Resolved]
+ private SkinManager skins { get; set; }
+
[Resolved]
private OsuConfigManager config { get; set; }
@@ -35,7 +40,7 @@ namespace osu.Game.Tests.Visual.Background
}
[Test]
- public void TestTogglingStoryboardSwitchesBackgroundType()
+ public void TestBackgroundTypeSwitch()
{
setSupporter(true);
@@ -44,6 +49,12 @@ namespace osu.Game.Tests.Visual.Background
setSourceMode(BackgroundSource.BeatmapWithStoryboard);
AddUntilStep("is storyboard background", () => getCurrentBackground() is BeatmapBackgroundWithStoryboard);
+
+ setSourceMode(BackgroundSource.Skin);
+ AddUntilStep("is default background", () => getCurrentBackground().GetType() == typeof(Graphics.Backgrounds.Background));
+
+ setCustomSkin();
+ AddUntilStep("is skin background", () => getCurrentBackground() is SkinBackground);
}
[Test]
@@ -61,15 +72,19 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground);
}
- [Test]
- public void TestBeatmapDoesntReloadOnNoChange()
+ [TestCase(BackgroundSource.Beatmap, typeof(BeatmapBackground))]
+ [TestCase(BackgroundSource.BeatmapWithStoryboard, typeof(BeatmapBackgroundWithStoryboard))]
+ [TestCase(BackgroundSource.Skin, typeof(SkinBackground))]
+ public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType)
{
- BeatmapBackground last = null;
+ Graphics.Backgrounds.Background last = null;
- setSourceMode(BackgroundSource.Beatmap);
+ setSourceMode(source);
setSupporter(true);
+ if (source == BackgroundSource.Skin)
+ setCustomSkin();
- AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground() as BeatmapBackground) != null);
+ AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType);
AddAssert("next doesn't load new background", () => screen.Next() == false);
// doesn't really need to be checked but might as well.
@@ -77,8 +92,25 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("ensure same background instance", () => last == getCurrentBackground());
}
+ [Test]
+ public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter)
+ {
+ Graphics.Backgrounds.Background last = null;
+
+ setSourceMode(BackgroundSource.Skin);
+ setSupporter(supporter);
+ setDefaultSkin();
+
+ AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
+ AddAssert("next cycles background", () => screen.Next());
+
+ // doesn't really need to be checked but might as well.
+ AddWaitStep("wait a bit", 5);
+ AddUntilStep("ensure different background instance", () => last != getCurrentBackground());
+ }
+
private void setSourceMode(BackgroundSource source) =>
- AddStep("set background mode to beatmap", () => config.SetValue(OsuSetting.MenuBackgroundSource, source));
+ AddStep($"set background mode to {source}", () => config.SetValue(OsuSetting.MenuBackgroundSource, source));
private void setSupporter(bool isSupporter) =>
AddStep($"set supporter {isSupporter}", () => ((DummyAPIAccess)API).LocalUser.Value = new User
@@ -86,5 +118,16 @@ namespace osu.Game.Tests.Visual.Background
IsSupporter = isSupporter,
Id = API.LocalUser.Value.Id + 1,
});
+
+ private void setCustomSkin()
+ {
+ // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
+ AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo { ID = 5 });
+ }
+
+ private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault());
+
+ [TearDownSteps]
+ public void TearDown() => setDefaultSkin();
}
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index 0308d74aa4..52401d32e5 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -9,16 +9,19 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Multiplayer;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Options;
using osu.Game.Tests.Beatmaps.IO;
+using osu.Game.Tests.Visual.Multiplayer;
using osuTK;
using osuTK.Input;
@@ -152,6 +155,14 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying);
}
+ [Test]
+ public void TestPushSongSelectAndPressBackButtonImmediately()
+ {
+ AddStep("push song select", () => Game.ScreenStack.Push(new TestPlaySongSelect()));
+ AddStep("press back button", () => Game.ChildrenOfType().First().Action());
+ AddWaitStep("wait two frames", 2);
+ }
+
[Test]
public void TestExitSongSelectWithClick()
{
@@ -298,6 +309,18 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
}
+ [Test]
+ public void TestPushMatchSubScreenAndPressBackButtonImmediately()
+ {
+ TestMultiplayer multiplayer = null;
+
+ PushAndConfirm(() => multiplayer = new TestMultiplayer());
+
+ AddStep("open room", () => multiplayer.OpenNewRoom());
+ AddStep("press back button", () => Game.ChildrenOfType().First().Action());
+ AddWaitStep("wait two frames", 2);
+ }
+
private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape));
@@ -322,5 +345,18 @@ namespace osu.Game.Tests.Visual.Navigation
protected override bool DisplayStableImportPrompt => false;
}
+
+ private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
+ {
+ [Cached(typeof(MultiplayerClient))]
+ public readonly TestMultiplayerClient Client;
+
+ public TestMultiplayer()
+ {
+ Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager);
+ }
+
+ protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
+ }
}
}
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index 086cc573d5..b53cc659f7 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -264,14 +264,18 @@ namespace osu.Game.Collections
using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write)))
{
sw.Write(database_version);
- sw.Write(Collections.Count);
- foreach (var c in Collections)
+ var collectionsCopy = Collections.ToArray();
+ sw.Write(collectionsCopy.Length);
+
+ foreach (var c in collectionsCopy)
{
sw.Write(c.Name.Value);
- sw.Write(c.Beatmaps.Count);
- foreach (var b in c.Beatmaps)
+ var beatmapsCopy = c.Beatmaps.ToArray();
+ sw.Write(beatmapsCopy.Length);
+
+ foreach (var b in beatmapsCopy)
sw.Write(b.MD5Hash);
}
}
diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs
index c90b1e0e98..cfc1eb1806 100644
--- a/osu.Game/Graphics/Backgrounds/Background.cs
+++ b/osu.Game/Graphics/Backgrounds/Background.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 System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Backgrounds
///
/// A background which offers blurring via a on demand.
///
- public class Background : CompositeDrawable
+ public class Background : CompositeDrawable, IEquatable
{
private const float blur_scale = 0.5f;
@@ -71,5 +72,14 @@ namespace osu.Game.Graphics.Backgrounds
bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing);
}
+
+ public virtual bool Equals(Background other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return other.GetType() == GetType()
+ && other.textureName == textureName;
+ }
}
}
diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs
index 058d2ed0f9..e0c15dd52a 100644
--- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs
+++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs
@@ -24,5 +24,14 @@ namespace osu.Game.Graphics.Backgrounds
{
Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName);
}
+
+ public override bool Equals(Background other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return other.GetType() == GetType()
+ && ((BeatmapBackground)other).Beatmap == Beatmap;
+ }
}
}
diff --git a/osu.Game/Graphics/Backgrounds/SkinBackground.cs b/osu.Game/Graphics/Backgrounds/SkinBackground.cs
new file mode 100644
index 0000000000..9266e7b17b
--- /dev/null
+++ b/osu.Game/Graphics/Backgrounds/SkinBackground.cs
@@ -0,0 +1,34 @@
+// 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.Allocation;
+using osu.Game.Skinning;
+
+namespace osu.Game.Graphics.Backgrounds
+{
+ internal class SkinBackground : Background
+ {
+ private readonly Skin skin;
+
+ public SkinBackground(Skin skin, string fallbackTextureName)
+ : base(fallbackTextureName)
+ {
+ this.skin = skin;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture;
+ }
+
+ public override bool Equals(Background other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return other.GetType() == GetType()
+ && ((SkinBackground)other).skin.SkinInfo.Equals(skin.SkinInfo);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 67cee883c8..269360c492 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -57,12 +57,6 @@ namespace osu.Game.Graphics.Backgrounds
}
}
- ///
- /// Whether we want to expire triangles as they exit our draw area completely.
- ///
- [Obsolete("Unused.")] // Can be removed 20210518
- protected virtual bool ExpireOffScreenTriangles => true;
-
///
/// Whether we should create new triangles as others expire.
///
diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
index 3d3c07a5ad..1b394185fd 100644
--- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
@@ -21,7 +21,12 @@ namespace osu.Game.Online.API.Requests.Responses
{
var ruleset = rulesets.GetRuleset(OnlineRulesetID);
- var mods = Mods != null ? ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty();
+ var rulesetInstance = ruleset.CreateInstance();
+
+ var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty();
+
+ // all API scores provided by this class are considered to be legacy.
+ mods = mods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray();
var scoreInfo = new ScoreInfo
{
@@ -38,7 +43,6 @@ namespace osu.Game.Online.API.Requests.Responses
Rank = Rank,
Ruleset = ruleset,
Mods = mods,
- IsLegacyScore = true
};
if (Statistics != null)
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index c51624341e..2dca91cbf3 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -651,9 +651,10 @@ namespace osu.Game
Origin = Anchor.BottomLeft,
Action = () =>
{
- var currentScreen = ScreenStack.CurrentScreen as IOsuScreen;
+ if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen))
+ return;
- if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton())
+ if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowBackButton && !currentScreen.OnBackButton()))
ScreenStack.Exit();
}
},
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index feeafb7151..8a57b4af91 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.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 System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@@ -32,18 +31,6 @@ namespace osu.Game.Rulesets.Judgements
private readonly Container aboveHitObjectsContent;
- ///
- /// Duration of initial fade in.
- ///
- [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")]
- protected virtual double FadeInDuration => 100;
-
- ///
- /// Duration to wait until fade out begins. Defaults to .
- ///
- [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")]
- protected virtual double FadeOutDelay => FadeInDuration;
-
///
/// Creates a drawable which visualises a .
///
diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs
index be69db5ca8..fd576e9b9f 100644
--- a/osu.Game/Rulesets/Judgements/Judgement.cs
+++ b/osu.Game/Rulesets/Judgements/Judgement.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.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -69,14 +68,6 @@ namespace osu.Game.Rulesets.Judgements
///
public double MaxHealthIncrease => HealthIncreaseFor(MaxResult);
- ///
- /// Retrieves the numeric score representation of a .
- ///
- /// The to find the numeric score representation for.
- /// The numeric score representation of .
- [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be made non-virtual 20210328
- protected virtual int NumericResultFor(HitResult result) => ToNumericResult(result);
-
///
/// Retrieves the numeric score representation of a .
///
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 6c688c1625..5fd2b2493e 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Applies a hit object to be represented by this .
///
- [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")]
+ [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] // Can be removed 20211021.
public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
{
if (lifetimeEntry != null)
@@ -409,11 +409,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
using (BeginAbsoluteSequence(StateUpdateTime, true))
UpdateStartTimeStateTransforms();
-#pragma warning disable 618
- using (BeginAbsoluteSequence(StateUpdateTime + (Result?.TimeOffset ?? 0), true))
- UpdateStateTransforms(newState);
-#pragma warning restore 618
-
using (BeginAbsoluteSequence(HitStateUpdateTime, true))
UpdateHitStateTransforms(newState);
@@ -447,7 +442,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// By default, this will fade in the object from zero with no duration.
///
///
- /// This is called once before every . This is to ensure a good state in the case
+ /// This is called once before every . This is to ensure a good state in the case
/// the was negative and potentially altered the pre-hit transforms.
///
protected virtual void UpdateInitialTransforms()
@@ -455,16 +450,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
this.FadeInFromZero();
}
- ///
- /// Apply transforms based on the current . Previous states are automatically cleared.
- /// In the case of a non-idle , and if was not set during this call, will be invoked.
- ///
- /// The new armed state.
- [Obsolete("Use UpdateStartTimeStateTransforms and UpdateHitStateTransforms instead")] // Can be removed 20210504
- protected virtual void UpdateStateTransforms(ArmedState state)
- {
- }
-
///
/// Apply passive transforms at the 's StartTime.
/// This is called each time changes.
@@ -520,23 +505,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
AccentColour.Value = combo.GetComboColour(comboColours);
}
- ///
- /// Called to retrieve the combo colour. Automatically assigned to .
- /// Defaults to using to decide on a colour.
- ///
- ///
- /// This will only be called if the implements .
- ///
- /// A list of combo colours provided by the beatmap or skin. Can be null if not available.
- [Obsolete("Unused. Implement IHasComboInformation and IHasComboInformation.GetComboColour() on the HitObject model instead.")] // Can be removed 20210527
- protected virtual Color4 GetComboColour(IReadOnlyList comboColours)
- {
- if (!(HitObject is IHasComboInformation combo))
- throw new InvalidOperationException($"{nameof(HitObject)} must implement {nameof(IHasComboInformation)}");
-
- return comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
- }
-
///
/// Called when a change is made to the skin.
///
@@ -630,7 +598,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// The time at which state transforms should be applied that line up to 's StartTime.
- /// This is used to offset calls to .
+ /// This is used to offset calls to .
///
public double StateUpdateTime => HitObject.StartTime;
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index 97cb5ca7ab..2f17167297 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -65,6 +65,10 @@ namespace osu.Game.Scoring.Legacy
scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
+ // lazer replays get a really high version number.
+ if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION)
+ scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType().Single()).ToArray();
+
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
index f8dd6953ad..288552879c 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
@@ -15,7 +15,16 @@ namespace osu.Game.Scoring.Legacy
{
public class LegacyScoreEncoder
{
- public const int LATEST_VERSION = 128;
+ ///
+ /// Database version in stable-compatible YYYYMMDD format.
+ /// Should be incremented if any changes are made to the format/usage.
+ ///
+ public const int LATEST_VERSION = FIRST_LAZER_VERSION;
+
+ ///
+ /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
+ ///
+ public const int FIRST_LAZER_VERSION = 30000000;
private readonly Score score;
private readonly IBeatmap beatmap;
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index a6faaf6379..3944c1d3de 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -76,9 +76,6 @@ namespace osu.Game.Scoring
else if (localAPIMods != null)
scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
- if (IsLegacyScore)
- scoreMods = scoreMods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray();
-
return scoreMods;
}
set
@@ -201,33 +198,12 @@ namespace osu.Game.Scoring
[JsonProperty("position")]
public int? Position { get; set; }
- private bool isLegacyScore;
-
///
/// Whether this represents a legacy (osu!stable) score.
///
[JsonIgnore]
[NotMapped]
- public bool IsLegacyScore
- {
- get
- {
- if (isLegacyScore)
- return true;
-
- // The above check will catch legacy online scores that have an appropriate UserString + UserId.
- // For non-online scores such as those imported in, a heuristic is used based on the following table:
- //
- // Mode | UserString | UserId
- // --------------- | ---------- | ---------
- // stable | | 1
- // lazer | |
- // lazer (offline) | Guest | 1
-
- return ID > 0 && UserID == 1 && UserString != "Guest";
- }
- set => isLegacyScore = value;
- }
+ public bool IsLegacyScore => Mods.OfType().Any();
public IEnumerable GetStatisticsForDisplay()
{
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
index 6bcfaac907..f0c90cc409 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
@@ -112,17 +112,24 @@ namespace osu.Game.Screens.Backgrounds
newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName());
newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName());
- // this method is called in many cases where the beatmap hasn't changed (ie. on screen transitions).
- // if a background is already displayed for the requested beatmap, we don't want to load it again.
- if (background?.GetType() == newBackground.GetType() &&
- (background as BeatmapBackground)?.Beatmap == beatmap.Value)
- return background;
-
break;
}
+
+ case BackgroundSource.Skin:
+ // default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them.
+ if (skin.Value is DefaultSkin || skin.Value is DefaultLegacySkin)
+ break;
+
+ newBackground = new SkinBackground(skin.Value, getBackgroundTextureName());
+ break;
}
}
+ // this method is called in many cases where the background might not necessarily need to change.
+ // if an equivalent background is currently being shown, we don't want to load it again.
+ if (newBackground?.Equals(background) == true)
+ return background;
+
newBackground ??= new Background(getBackgroundTextureName());
newBackground.Depth = currentDisplay;
@@ -140,22 +147,5 @@ namespace osu.Game.Screens.Backgrounds
return $@"Menu/menu-background-{currentDisplay % background_count + 1}";
}
}
-
- private class SkinnedBackground : Background
- {
- private readonly Skin skin;
-
- public SkinnedBackground(Skin skin, string fallbackTextureName)
- : base(fallbackTextureName)
- {
- this.skin = skin;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture;
- }
- }
}
}
diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs
index cc8778d9ae..0434135547 100644
--- a/osu.Game/Screens/IOsuScreen.cs
+++ b/osu.Game/Screens/IOsuScreen.cs
@@ -67,8 +67,11 @@ namespace osu.Game.Screens
/// Invoked when the back button has been pressed to close any overlays before exiting this .
///
///
+ /// If this has not yet finished loading, the exit will occur immediately without this method being invoked.
+ ///
/// Return true to block this from being exited after closing an overlay.
/// Return false if this should continue exiting.
+ ///
///
bool OnBackButton();
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index 277aa5d772..983daac909 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -22,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
// Isolates beatmap/ruleset to this screen.
public override bool DisallowExternalBeatmapRulesetChanges => true;
+ // We are managing our own adjustments. For now, this happens inside the Player instances themselves.
+ public override bool AllowRateAdjustments => false;
+
///
/// Whether all spectating players have finished loading.
///
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index 90e499c67f..e418d36d40 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -253,7 +253,10 @@ namespace osu.Game.Screens.OnlinePlay
public override bool OnBackButton()
{
- if ((screenStack.CurrentScreen as IOnlinePlaySubScreen)?.OnBackButton() == true)
+ if (!(screenStack.CurrentScreen is IOnlinePlaySubScreen onlineSubScreen))
+ return false;
+
+ if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowBackButton && onlineSubScreen.OnBackButton())
return true;
if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen))
diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs
index 33e8c137f4..3fcca74fb8 100644
--- a/osu.Game/Skinning/PoolableSkinnableSample.cs
+++ b/osu.Game/Skinning/PoolableSkinnableSample.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -70,22 +71,48 @@ namespace osu.Game.Skinning
updateSample();
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ CurrentSkin.SourceChanged += skinChangedImmediate;
+ }
+
+ private void skinChangedImmediate()
+ {
+ // Clean up the previous sample immediately on a source change.
+ // This avoids a potential call to Play() of an already disposed sample (samples are disposed along with the skin, but SkinChanged is scheduled).
+ clearPreviousSamples();
+ }
+
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin);
updateSample();
}
+ ///
+ /// Whether this sample was playing before a skin source change.
+ ///
+ private bool wasPlaying;
+
+ private void clearPreviousSamples()
+ {
+ // only run if the samples aren't already cleared.
+ // this ensures the "wasPlaying" state is stored correctly even if multiple clear calls are executed.
+ if (!sampleContainer.Any()) return;
+
+ wasPlaying = Playing;
+
+ sampleContainer.Clear();
+ Sample = null;
+ }
+
private void updateSample()
{
if (sampleInfo == null)
return;
- bool wasPlaying = Playing;
-
- sampleContainer.Clear();
- Sample = null;
-
var sample = CurrentSkin.GetSample(sampleInfo);
if (sample == null)
@@ -146,6 +173,14 @@ namespace osu.Game.Skinning
}
}
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (CurrentSkin != null)
+ CurrentSkin.SourceChanged -= skinChangedImmediate;
+ }
+
#region Re-expose AudioContainer
public BindableNumber Volume => sampleContainer.Volume;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 7eca28a023..ae3644d5cb 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -34,7 +34,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 096f497acb..6b9aaeafa9 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+