1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 15:17:27 +08:00

Merge branch 'master' into results-screen-sfx

This commit is contained in:
Dean Herbert 2021-06-09 18:00:11 +09:00
commit 8fda04c2d9
30 changed files with 293 additions and 192 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.604.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.604.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.607.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.608.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -4,10 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
@ -21,12 +19,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public class TestSceneCatchModHidden : ModTestScene public class TestSceneCatchModHidden : ModTestScene
{ {
[BackgroundDependencyLoader]
private void load()
{
LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
}
[Test] [Test]
public void TestJuiceStream() public void TestJuiceStream()
{ {

View File

@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods
} }
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ => ApplyNormalVisibilityState(hitObject, state);
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ {

View File

@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{ {
isLegacySkin = new Lazy<bool>(() => FindProvider(s => s.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null) != null); isLegacySkin = new Lazy<bool>(() => FindProvider(s => s.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null) != null);
hasKeyTexture = new Lazy<bool>(() => FindProvider(s => s.GetAnimation( hasKeyTexture = new Lazy<bool>(() => FindProvider(s => s.GetAnimation(
this.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value s.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value
?? "mania-key1", true, true) != null) != null); ?? "mania-key1", true, true) != null) != null);
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils; 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_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 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; private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
@ -74,23 +75,9 @@ namespace osu.Game.Rulesets.Osu.Mods
// update end position as it may have changed as a result of the position update. // update end position as it may have changed as a result of the position update.
current.EndPositionRandomised = current.PositionRandomised; current.EndPositionRandomised = current.PositionRandomised;
switch (hitObject) if (hitObject is Slider slider)
{
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); 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;
}
previous = current; previous = current;
} }
} }
@ -131,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Mods
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); 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, // Move hit objects back into the playfield if they are outside of it,
// which would sometimes happen during big jumps otherwise. // which would sometimes happen during big jumps otherwise.
@ -146,34 +133,41 @@ namespace osu.Game.Rulesets.Osu.Mods
/// </summary> /// </summary>
private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
{ {
// Min. distances from the slider's position to the playfield border var minMargin = getMinSliderMargin(slider);
var minMargin = new MarginPadding();
foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle)) slider.Position = new Vector2(
{ Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right),
if (!(hitObject is OsuHitObject osuHitObject)) Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom)
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);
currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.PositionRandomised = slider.Position;
currentObjectInfo.EndPositionRandomised = slider.EndPosition; currentObjectInfo.EndPositionRandomised = slider.EndPosition;
shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal);
}
/// <summary>
/// Calculates the min. distances from the <see cref="Slider"/>'s position to the playfield border for the slider to be fully inside of the playfield.
/// </summary>
private MarginPadding getMinSliderMargin(Slider slider)
{
var pathPositions = new List<Vector2>();
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;
} }
/// <summary> /// <summary>
@ -188,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Mods
if (!(hitObject is OsuHitObject osuHitObject)) if (!(hitObject is OsuHitObject osuHitObject))
continue; continue;
osuHitObject.Position = Vector2.Add(osuHitObject.Position, shift); osuHitObject.Position += shift;
} }
} }

View File

@ -128,5 +128,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
} }
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
{ {
public class MainCirclePiece : CompositeDrawable public class MainCirclePiece : CompositeDrawable
{ {
public override bool RemoveCompletedTransforms => false;
private readonly CirclePiece circle; private readonly CirclePiece circle;
private readonly RingPiece ring; private readonly RingPiece ring;
private readonly FlashPiece flash; private readonly FlashPiece flash;

View File

@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
public class LegacyMainCirclePiece : CompositeDrawable public class LegacyMainCirclePiece : CompositeDrawable
{ {
public override bool RemoveCompletedTransforms => false;
private readonly string priorityLookup; private readonly string priorityLookup;
private readonly bool hasNumber; private readonly bool hasNumber;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -11,6 +12,7 @@ using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Tests.Visual.Background namespace osu.Game.Tests.Visual.Background
@ -23,6 +25,9 @@ namespace osu.Game.Tests.Visual.Background
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault(); private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault();
[Resolved]
private SkinManager skins { get; set; }
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } private OsuConfigManager config { get; set; }
@ -35,7 +40,7 @@ namespace osu.Game.Tests.Visual.Background
} }
[Test] [Test]
public void TestTogglingStoryboardSwitchesBackgroundType() public void TestBackgroundTypeSwitch()
{ {
setSupporter(true); setSupporter(true);
@ -44,6 +49,12 @@ namespace osu.Game.Tests.Visual.Background
setSourceMode(BackgroundSource.BeatmapWithStoryboard); setSourceMode(BackgroundSource.BeatmapWithStoryboard);
AddUntilStep("is storyboard background", () => getCurrentBackground() is BeatmapBackgroundWithStoryboard); 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] [Test]
@ -61,15 +72,19 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground); AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground);
} }
[Test] [TestCase(BackgroundSource.Beatmap, typeof(BeatmapBackground))]
public void TestBeatmapDoesntReloadOnNoChange() [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); 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); AddAssert("next doesn't load new background", () => screen.Next() == false);
// doesn't really need to be checked but might as well. // 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()); 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) => 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) => private void setSupporter(bool isSupporter) =>
AddStep($"set supporter {isSupporter}", () => ((DummyAPIAccess)API).LocalUser.Value = new User AddStep($"set supporter {isSupporter}", () => ((DummyAPIAccess)API).LocalUser.Value = new User
@ -86,5 +118,16 @@ namespace osu.Game.Tests.Visual.Background
IsSupporter = isSupporter, IsSupporter = isSupporter,
Id = API.LocalUser.Value.Id + 1, 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();
} }
} }

View File

@ -9,16 +9,19 @@ using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar; using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Options; using osu.Game.Screens.Select.Options;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual.Multiplayer;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -152,6 +155,14 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying); 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<BackButton>().First().Action());
AddWaitStep("wait two frames", 2);
}
[Test] [Test]
public void TestExitSongSelectWithClick() public void TestExitSongSelectWithClick()
{ {
@ -298,6 +309,18 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden); 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<BackButton>().First().Action());
AddWaitStep("wait two frames", 2);
}
private void pushEscape() => private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape)); AddStep("Press escape", () => InputManager.Key(Key.Escape));
@ -322,5 +345,18 @@ namespace osu.Game.Tests.Visual.Navigation
protected override bool DisplayStableImportPrompt => false; 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();
}
} }
} }

View File

@ -264,14 +264,18 @@ namespace osu.Game.Collections
using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write)))
{ {
sw.Write(database_version); 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.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); sw.Write(b.MD5Hash);
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Backgrounds
/// <summary> /// <summary>
/// A background which offers blurring via a <see cref="BufferedContainer"/> on demand. /// A background which offers blurring via a <see cref="BufferedContainer"/> on demand.
/// </summary> /// </summary>
public class Background : CompositeDrawable public class Background : CompositeDrawable, IEquatable<Background>
{ {
private const float blur_scale = 0.5f; private const float blur_scale = 0.5f;
@ -71,5 +72,14 @@ namespace osu.Game.Graphics.Backgrounds
bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing); 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;
}
} }
} }

View File

@ -24,5 +24,14 @@ namespace osu.Game.Graphics.Backgrounds
{ {
Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); 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;
}
} }
} }

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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);
}
}
}

View File

@ -57,12 +57,6 @@ namespace osu.Game.Graphics.Backgrounds
} }
} }
/// <summary>
/// Whether we want to expire triangles as they exit our draw area completely.
/// </summary>
[Obsolete("Unused.")] // Can be removed 20210518
protected virtual bool ExpireOffScreenTriangles => true;
/// <summary> /// <summary>
/// Whether we should create new triangles as others expire. /// Whether we should create new triangles as others expire.
/// </summary> /// </summary>

View File

@ -21,7 +21,12 @@ namespace osu.Game.Online.API.Requests.Responses
{ {
var ruleset = rulesets.GetRuleset(OnlineRulesetID); var ruleset = rulesets.GetRuleset(OnlineRulesetID);
var mods = Mods != null ? ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty<Mod>(); var rulesetInstance = ruleset.CreateInstance();
var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty<Mod>();
// all API scores provided by this class are considered to be legacy.
mods = mods.Append(rulesetInstance.GetAllMods().OfType<ModClassic>().Single()).ToArray();
var scoreInfo = new ScoreInfo var scoreInfo = new ScoreInfo
{ {
@ -38,7 +43,6 @@ namespace osu.Game.Online.API.Requests.Responses
Rank = Rank, Rank = Rank,
Ruleset = ruleset, Ruleset = ruleset,
Mods = mods, Mods = mods,
IsLegacyScore = true
}; };
if (Statistics != null) if (Statistics != null)

View File

@ -651,9 +651,10 @@ namespace osu.Game
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Action = () => 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(); ScreenStack.Exit();
} }
}, },

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -32,18 +31,6 @@ namespace osu.Game.Rulesets.Judgements
private readonly Container aboveHitObjectsContent; private readonly Container aboveHitObjectsContent;
/// <summary>
/// Duration of initial fade in.
/// </summary>
[Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")]
protected virtual double FadeInDuration => 100;
/// <summary>
/// Duration to wait until fade out begins. Defaults to <see cref="FadeInDuration"/>.
/// </summary>
[Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")]
protected virtual double FadeOutDelay => FadeInDuration;
/// <summary> /// <summary>
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>. /// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
/// </summary> /// </summary>

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -69,14 +68,6 @@ namespace osu.Game.Rulesets.Judgements
/// </summary> /// </summary>
public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); public double MaxHealthIncrease => HealthIncreaseFor(MaxResult);
/// <summary>
/// Retrieves the numeric score representation of a <see cref="HitResult"/>.
/// </summary>
/// <param name="result">The <see cref="HitResult"/> to find the numeric score representation for.</param>
/// <returns>The numeric score representation of <paramref name="result"/>.</returns>
[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);
/// <summary> /// <summary>
/// Retrieves the numeric score representation of a <see cref="JudgementResult"/>. /// Retrieves the numeric score representation of a <see cref="JudgementResult"/>.
/// </summary> /// </summary>

View File

@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary> /// <summary>
/// Applies a hit object to be represented by this <see cref="DrawableHitObject"/>. /// Applies a hit object to be represented by this <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
[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) public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
{ {
if (lifetimeEntry != null) if (lifetimeEntry != null)
@ -409,11 +409,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
using (BeginAbsoluteSequence(StateUpdateTime, true)) using (BeginAbsoluteSequence(StateUpdateTime, true))
UpdateStartTimeStateTransforms(); UpdateStartTimeStateTransforms();
#pragma warning disable 618
using (BeginAbsoluteSequence(StateUpdateTime + (Result?.TimeOffset ?? 0), true))
UpdateStateTransforms(newState);
#pragma warning restore 618
using (BeginAbsoluteSequence(HitStateUpdateTime, true)) using (BeginAbsoluteSequence(HitStateUpdateTime, true))
UpdateHitStateTransforms(newState); 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. /// By default, this will fade in the object from zero with no duration.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This is called once before every <see cref="UpdateStateTransforms"/>. This is to ensure a good state in the case /// This is called once before every <see cref="UpdateHitStateTransforms"/>. This is to ensure a good state in the case
/// the <see cref="JudgementResult.TimeOffset"/> was negative and potentially altered the pre-hit transforms. /// the <see cref="JudgementResult.TimeOffset"/> was negative and potentially altered the pre-hit transforms.
/// </remarks> /// </remarks>
protected virtual void UpdateInitialTransforms() protected virtual void UpdateInitialTransforms()
@ -455,16 +450,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
this.FadeInFromZero(); this.FadeInFromZero();
} }
/// <summary>
/// Apply transforms based on the current <see cref="ArmedState"/>. Previous states are automatically cleared.
/// In the case of a non-idle <see cref="ArmedState"/>, and if <see cref="Drawable.LifetimeEnd"/> was not set during this call, <see cref="Drawable.Expire"/> will be invoked.
/// </summary>
/// <param name="state">The new armed state.</param>
[Obsolete("Use UpdateStartTimeStateTransforms and UpdateHitStateTransforms instead")] // Can be removed 20210504
protected virtual void UpdateStateTransforms(ArmedState state)
{
}
/// <summary> /// <summary>
/// Apply passive transforms at the <see cref="HitObject"/>'s StartTime. /// Apply passive transforms at the <see cref="HitObject"/>'s StartTime.
/// This is called each time <see cref="State"/> changes. /// This is called each time <see cref="State"/> changes.
@ -520,23 +505,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
AccentColour.Value = combo.GetComboColour(comboColours); AccentColour.Value = combo.GetComboColour(comboColours);
} }
/// <summary>
/// Called to retrieve the combo colour. Automatically assigned to <see cref="AccentColour"/>.
/// Defaults to using <see cref="IHasComboInformation.ComboIndex"/> to decide on a colour.
/// </summary>
/// <remarks>
/// This will only be called if the <see cref="HitObject"/> implements <see cref="IHasComboInformation"/>.
/// </remarks>
/// <param name="comboColours">A list of combo colours provided by the beatmap or skin. Can be null if not available.</param>
[Obsolete("Unused. Implement IHasComboInformation and IHasComboInformation.GetComboColour() on the HitObject model instead.")] // Can be removed 20210527
protected virtual Color4 GetComboColour(IReadOnlyList<Color4> 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;
}
/// <summary> /// <summary>
/// Called when a change is made to the skin. /// Called when a change is made to the skin.
/// </summary> /// </summary>
@ -630,7 +598,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary> /// <summary>
/// The time at which state transforms should be applied that line up to <see cref="HitObject"/>'s StartTime. /// The time at which state transforms should be applied that line up to <see cref="HitObject"/>'s StartTime.
/// This is used to offset calls to <see cref="UpdateStateTransforms"/>. /// This is used to offset calls to <see cref="UpdateStartTimeStateTransforms"/>.
/// </summary> /// </summary>
public double StateUpdateTime => HitObject.StartTime; public double StateUpdateTime => HitObject.StartTime;

View File

@ -65,6 +65,10 @@ namespace osu.Game.Scoring.Legacy
scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); 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<ModClassic>().Single()).ToArray();
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;

View File

@ -15,7 +15,16 @@ namespace osu.Game.Scoring.Legacy
{ {
public class LegacyScoreEncoder public class LegacyScoreEncoder
{ {
public const int LATEST_VERSION = 128; /// <summary>
/// Database version in stable-compatible YYYYMMDD format.
/// Should be incremented if any changes are made to the format/usage.
/// </summary>
public const int LATEST_VERSION = FIRST_LAZER_VERSION;
/// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
/// </summary>
public const int FIRST_LAZER_VERSION = 30000000;
private readonly Score score; private readonly Score score;
private readonly IBeatmap beatmap; private readonly IBeatmap beatmap;

View File

@ -76,9 +76,6 @@ namespace osu.Game.Scoring
else if (localAPIMods != null) else if (localAPIMods != null)
scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
if (IsLegacyScore)
scoreMods = scoreMods.Append(rulesetInstance.GetAllMods().OfType<ModClassic>().Single()).ToArray();
return scoreMods; return scoreMods;
} }
set set
@ -201,33 +198,12 @@ namespace osu.Game.Scoring
[JsonProperty("position")] [JsonProperty("position")]
public int? Position { get; set; } public int? Position { get; set; }
private bool isLegacyScore;
/// <summary> /// <summary>
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score. /// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
[NotMapped] [NotMapped]
public bool IsLegacyScore public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
{
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 | <username> | 1
// lazer | <username> | <userid>
// lazer (offline) | Guest | 1
return ID > 0 && UserID == 1 && UserString != "Guest";
}
set => isLegacyScore = value;
}
public IEnumerable<HitResultDisplayStatistic> GetStatisticsForDisplay() public IEnumerable<HitResultDisplayStatistic> GetStatisticsForDisplay()
{ {

View File

@ -112,16 +112,23 @@ namespace osu.Game.Screens.Backgrounds
newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName()); newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName());
newBackground ??= new BeatmapBackground(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). break;
// 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;
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; 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 ??= new Background(getBackgroundTextureName());
newBackground.Depth = currentDisplay; newBackground.Depth = currentDisplay;
@ -140,22 +147,5 @@ namespace osu.Game.Screens.Backgrounds
return $@"Menu/menu-background-{currentDisplay % background_count + 1}"; 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;
}
}
} }
} }

View File

@ -67,8 +67,11 @@ namespace osu.Game.Screens
/// Invoked when the back button has been pressed to close any overlays before exiting this <see cref="IOsuScreen"/>. /// Invoked when the back button has been pressed to close any overlays before exiting this <see cref="IOsuScreen"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If this <see cref="IOsuScreen"/> has not yet finished loading, the exit will occur immediately without this method being invoked.
/// <para>
/// Return <c>true</c> to block this <see cref="IOsuScreen"/> from being exited after closing an overlay. /// Return <c>true</c> to block this <see cref="IOsuScreen"/> from being exited after closing an overlay.
/// Return <c>false</c> if this <see cref="IOsuScreen"/> should continue exiting. /// Return <c>false</c> if this <see cref="IOsuScreen"/> should continue exiting.
/// </para>
/// </remarks> /// </remarks>
bool OnBackButton(); bool OnBackButton();
} }

View File

@ -22,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
// Isolates beatmap/ruleset to this screen. // Isolates beatmap/ruleset to this screen.
public override bool DisallowExternalBeatmapRulesetChanges => true; 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;
/// <summary> /// <summary>
/// Whether all spectating players have finished loading. /// Whether all spectating players have finished loading.
/// </summary> /// </summary>

View File

@ -253,7 +253,10 @@ namespace osu.Game.Screens.OnlinePlay
public override bool OnBackButton() 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; return true;
if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen))

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
@ -70,22 +71,48 @@ namespace osu.Game.Skinning
updateSample(); 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) protected override void SkinChanged(ISkinSource skin)
{ {
base.SkinChanged(skin); base.SkinChanged(skin);
updateSample(); updateSample();
} }
/// <summary>
/// Whether this sample was playing before a skin source change.
/// </summary>
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() private void updateSample()
{ {
if (sampleInfo == null) if (sampleInfo == null)
return; return;
bool wasPlaying = Playing;
sampleContainer.Clear();
Sample = null;
var sample = CurrentSkin.GetSample(sampleInfo); var sample = CurrentSkin.GetSample(sampleInfo);
if (sample == null) 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 #region Re-expose AudioContainer
public BindableNumber<double> Volume => sampleContainer.Volume; public BindableNumber<double> Volume => sampleContainer.Volume;

View File

@ -34,7 +34,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="ppy.osu.Framework" Version="2021.607.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.608.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.604.0"/> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.604.0"/>
<PackageReference Include="Sentry" Version="3.4.0" /> <PackageReference Include="Sentry" Version="3.4.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" /> <PackageReference Include="SharpCompress" Version="0.28.2" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.607.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.608.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.604.0"/> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.604.0"/>
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.607.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.608.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" /> <PackageReference Include="SharpCompress" Version="0.28.2" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />