1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 01:02:55 +08:00

Merge branch 'master' into realm-live

This commit is contained in:
Dean Herbert 2021-10-13 15:24:34 +09:00 committed by GitHub
commit f5f0486e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 803 additions and 278 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.1012.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -509,5 +509,17 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(LinkAction.External, result.Action); Assert.AreEqual(LinkAction.External, result.Action);
Assert.AreEqual("/relative", result.Argument); Assert.AreEqual("/relative", result.Argument);
} }
[TestCase("https://dev.ppy.sh/home/changelog", "")]
[TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")]
public void TestChangelogLinks(string link, string expectedArg)
{
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
LinkDetails result = MessageFormatter.GetLinkDetails(link);
Assert.AreEqual(LinkAction.OpenChangelog, result.Action);
Assert.AreEqual(expectedArg, result.Argument);
}
} }
} }

View File

@ -168,14 +168,14 @@ namespace osu.Game.Tests.Online
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host); return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
} }
protected override BeatmapModelDownloader CreateBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider api, GameHost host) protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager manager, IAPIProvider api, GameHost host)
{ {
return new TestBeatmapModelDownloader(modelManager, api, host); return new TestBeatmapModelDownloader(manager, api, host);
} }
internal class TestBeatmapModelDownloader : BeatmapModelDownloader internal class TestBeatmapModelDownloader : BeatmapModelDownloader
{ {
public TestBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost) public TestBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost)
: base(modelManager, apiProvider, gameHost) : base(modelManager, apiProvider, gameHost)
{ {
} }

View File

@ -6,7 +6,6 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
@ -65,10 +64,9 @@ namespace osu.Game.Tests.Skins
public new void TriggerSourceChanged() => base.TriggerSourceChanged(); public new void TriggerSourceChanged() => base.TriggerSourceChanged();
protected override void OnSourceChanged() protected override void RefreshSources()
{ {
ResetSources(); SetSources(sources);
sources.ForEach(AddSource);
} }
} }

View File

@ -32,6 +32,8 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("wait for editor load", () => editor != null); AddUntilStep("wait for editor load", () => editor != null);
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
@ -41,11 +43,11 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
AddStep("Save and exit", () => AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
{
InputManager.Keys(PlatformAction.Save); AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
InputManager.Key(Key.Escape);
}); AddStep("Exit", () => InputManager.Key(Key.Escape));
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("Wait for editor load", () => editor != null); AddUntilStep("Wait for editor load", () => editor != null);
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
} }
} }
} }

View File

@ -103,6 +103,30 @@ namespace osu.Game.Tests.Visual.Gameplay
checkFrameCount(0); checkFrameCount(0);
} }
[Test]
public void TestRatePreservedWhenTimeNotProgressing()
{
AddStep("set manual clock rate", () => manualClock.Rate = 1);
seekManualTo(5000);
createStabilityContainer();
checkRate(1);
seekManualTo(10000);
checkRate(1);
AddWaitStep("wait some", 3);
checkRate(1);
seekManualTo(5000);
checkRate(-1);
AddWaitStep("wait some", 3);
checkRate(-1);
seekManualTo(10000);
checkRate(1);
}
private const int max_frames_catchup = 50; private const int max_frames_catchup = 50;
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
@ -116,6 +140,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private void checkFrameCount(int frames) => private void checkFrameCount(int frames) =>
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames); AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
private void checkRate(double rate) =>
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
public class ClockConsumingChild : CompositeDrawable public class ClockConsumingChild : CompositeDrawable
{ {
private readonly OsuSpriteText text; private readonly OsuSpriteText text;

View File

@ -90,8 +90,12 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(() => CreateTest(() =>
{ {
AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true); AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
AddStep("set storyboard duration to 1.3s", () => currentStoryboardDuration = 1300);
// Fail occurs at 164ms with the provided beatmap.
// Fail animation runs for 2.5s realtime but the gameplay time change is *variable* due to the frequency transform being applied, so we need a bit of lenience.
AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600);
}); });
AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);

View File

@ -0,0 +1,31 @@
// 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 System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestSceneStartupImport : OsuGameTestScene
{
private string importFilename;
protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { importFilename });
public override void SetUpSteps()
{
AddStep("Prepare import beatmap", () => importFilename = TestResources.GetTestBeatmapForImport());
base.SetUpSteps();
}
[Test]
public void TestImportCreatedNotification()
{
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ImportProgressNotification>().Count() == 1);
}
}
}

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Input.Handlers.Tablet;
@ -21,6 +22,9 @@ namespace osu.Game.Tests.Visual.Settings
private TestTabletHandler tabletHandler; private TestTabletHandler tabletHandler;
private TabletSettings settings; private TabletSettings settings;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {

View File

@ -142,6 +142,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("store selected beatmap", () => selected = Beatmap.Value); AddStep("store selected beatmap", () => selected = Beatmap.Value);
AddUntilStep("wait for beatmaps to load", () => songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>().Any());
AddStep("select next and enter", () => AddStep("select next and enter", () =>
{ {
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>() InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
@ -599,10 +601,10 @@ namespace osu.Game.Tests.Visual.SongSelect
}); });
FilterableDifficultyIcon difficultyIcon = null; FilterableDifficultyIcon difficultyIcon = null;
AddStep("Find an icon", () => AddUntilStep("Find an icon", () =>
{ {
difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>() return (difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
.First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex())) != null;
}); });
AddStep("Click on a difficulty", () => AddStep("Click on a difficulty", () =>
@ -765,10 +767,10 @@ namespace osu.Game.Tests.Visual.SongSelect
}); });
FilterableGroupedDifficultyIcon groupIcon = null; FilterableGroupedDifficultyIcon groupIcon = null;
AddStep("Find group icon for different ruleset", () => AddUntilStep("Find group icon for different ruleset", () =>
{ {
groupIcon = set.ChildrenOfType<FilterableGroupedDifficultyIcon>() return (groupIcon = set.ChildrenOfType<FilterableGroupedDifficultyIcon>()
.First(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3); .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3)) != null;
}); });
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0); AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);

View File

@ -0,0 +1,44 @@
// 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 System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneRoundedButton : OsuTestScene
{
[Test]
public void TestBasic()
{
RoundedButton button = null;
AddStep("create button", () => Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.DarkGray
},
button = new RoundedButton
{
Width = 400,
Text = "Test button",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => { }
}
}
});
AddToggleStep("toggle disabled", disabled => button.Action = disabled ? (Action)null : () => { });
}
}
}

View File

@ -40,7 +40,13 @@ namespace osu.Game.Beatmaps
public IBeatmap Convert(CancellationToken cancellationToken = default) public IBeatmap Convert(CancellationToken cancellationToken = default)
{ {
// We always operate on a clone of the original beatmap, to not modify it game-wide // We always operate on a clone of the original beatmap, to not modify it game-wide
return ConvertBeatmap(Beatmap.Clone(), cancellationToken); var original = Beatmap.Clone();
// Shallow clone isn't enough to ensure we don't mutate beatmap info unexpectedly.
// Can potentially be removed after `Beatmap.Difficulty` doesn't save back to `Beatmap.BeatmapInfo`.
original.BeatmapInfo = original.BeatmapInfo.Clone();
return ConvertBeatmap(original, cancellationToken);
} }
/// <summary> /// <summary>

View File

@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
} }
} }
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider api, GameHost host) protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider api, GameHost host)
{ {
return new BeatmapModelDownloader(modelManager, api, host); return new BeatmapModelDownloader(modelManager, api, host);
} }

View File

@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
new DownloadBeatmapSetRequest(set, minimiseDownloadSize); new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
public BeatmapModelDownloader(BeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null) public BeatmapModelDownloader(IBeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
: base(beatmapModelManager, api, host) : base(beatmapModelManager, api, host)
{ {
} }

View File

@ -10,7 +10,7 @@ namespace osu.Game.Configuration
[Description("Never repeat")] [Description("Never repeat")]
RandomPermutation, RandomPermutation,
[Description("Random")] [Description("True Random")]
Random Random
} }
} }

View File

@ -116,7 +116,7 @@ namespace osu.Game.Database
/// <param name="paths">One or more archive locations on disk.</param> /// <param name="paths">One or more archive locations on disk.</param>
public Task Import(params string[] paths) public Task Import(params string[] paths)
{ {
var notification = new ProgressNotification { State = ProgressNotificationState.Active }; var notification = new ImportProgressNotification();
PostNotification?.Invoke(notification); PostNotification?.Invoke(notification);
@ -125,7 +125,7 @@ namespace osu.Game.Database
public Task Import(params ImportTask[] tasks) public Task Import(params ImportTask[] tasks)
{ {
var notification = new ProgressNotification { State = ProgressNotificationState.Active }; var notification = new ImportProgressNotification();
PostNotification?.Invoke(notification); PostNotification?.Invoke(notification);

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Models; using osu.Game.Models;
#nullable enable
namespace osu.Game.Database namespace osu.Game.Database
{ {
/// <summary> /// <summary>

View File

@ -10,7 +10,7 @@ using osu.Game.Overlays.Notifications;
namespace osu.Game.Database namespace osu.Game.Database
{ {
/// <summary> /// <summary>
/// A class which handles importing of asociated models to the game store. /// A class which handles importing of associated models to the game store.
/// </summary> /// </summary>
/// <typeparam name="TModel">The model type.</typeparam> /// <typeparam name="TModel">The model type.</typeparam>
public interface IModelImporter<TModel> : IPostNotifications public interface IModelImporter<TModel> : IPostNotifications

View File

@ -3,15 +3,17 @@
using osu.Game.Models; using osu.Game.Models;
#nullable enable
namespace osu.Game.Database namespace osu.Game.Database
{ {
/// <summary> /// <summary>
/// Represent a join model which gives a filename and scope to a <see cref="File"/>. /// Represents a join model which gives a filename and scope to a <see cref="File"/>.
/// </summary> /// </summary>
public interface INamedFile public interface INamedFile
{ {
public string Filename { get; set; } string Filename { get; set; }
public RealmFile File { get; set; } RealmFile File { get; set; }
} }
} }

View File

@ -0,0 +1,15 @@
// 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.Game.Overlays.Notifications;
namespace osu.Game.Database
{
public class ImportProgressNotification : ProgressNotification
{
public ImportProgressNotification()
{
State = ProgressNotificationState.Active;
}
}
}

View File

@ -225,6 +225,16 @@ namespace osu.Game.Graphics
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Pink"/>'s <see cref="OverlayColourProvider.Colour3"/>.
/// </summary>
public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378");
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Blue"/>'s <see cref="OverlayColourProvider.Colour3"/>.
/// </summary>
public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc");
/// <summary> /// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>. /// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>.
/// </summary> /// </summary>

View File

@ -0,0 +1,49 @@
// 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 System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class RoundedButton : OsuButton, IFilterable
{
public override float Height
{
get => base.Height;
set
{
base.Height = value;
if (IsLoaded)
updateCornerRadius();
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Blue3;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateCornerRadius();
}
private void updateCornerRadius() => Content.CornerRadius = DrawHeight / 2;
public virtual IEnumerable<string> FilterTerms => new[] { Text.ToString() };
public bool MatchingFilter
{
set => this.FadeTo(value ? 1 : 0);
}
public bool FilteringActive { get; set; }
}
}

View File

@ -24,6 +24,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume"); public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
/// <summary>
/// "Output device"
/// </summary>
public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
/// <summary> /// <summary>
/// "Master" /// "Master"
/// </summary> /// </summary>

View File

@ -14,11 +14,36 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay"); public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
/// <summary>
/// "Beatmap"
/// </summary>
public static LocalisableString BeatmapHeader => new TranslatableString(getKey(@"beatmap_header"), @"Beatmap");
/// <summary> /// <summary>
/// "General" /// "General"
/// </summary> /// </summary>
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General"); public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
/// <summary>
/// "Audio"
/// </summary>
public static LocalisableString AudioHeader => new TranslatableString(getKey(@"audio"), @"Audio");
/// <summary>
/// "HUD"
/// </summary>
public static LocalisableString HUDHeader => new TranslatableString(getKey(@"h_u_d"), @"HUD");
/// <summary>
/// "Input"
/// </summary>
public static LocalisableString InputHeader => new TranslatableString(getKey(@"input"), @"Input");
/// <summary>
/// "Background"
/// </summary>
public static LocalisableString BackgroundHeader => new TranslatableString(getKey(@"background"), @"Background");
/// <summary> /// <summary>
/// "Background dim" /// "Background dim"
/// </summary> /// </summary>

View File

@ -104,6 +104,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting"); public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
/// <summary>
/// "Screenshots"
/// </summary>
public static LocalisableString Screenshots => new TranslatableString(getKey(@"screenshots"), @"Screenshots");
/// <summary> /// <summary>
/// "Screenshot format" /// "Screenshot format"
/// </summary> /// </summary>

View File

@ -0,0 +1,19 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class RulesetSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.RulesetSettings";
/// <summary>
/// "Rulesets"
/// </summary>
public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -14,6 +14,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin"); public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
/// <summary>
/// "Current skin"
/// </summary>
public static LocalisableString CurrentSkin => new TranslatableString(getKey(@"current_skin"), @"Current skin");
/// <summary> /// <summary>
/// "Skin layout editor" /// "Skin layout editor"
/// </summary> /// </summary>

View File

@ -97,11 +97,6 @@ namespace osu.Game.Models
#endregion #endregion
/// <summary>
/// Returns a shallow-clone of this <see cref="RealmBeatmap"/>.
/// </summary>
public RealmBeatmap Clone() => (RealmBeatmap)MemberwiseClone();
public bool AudioEquals(RealmBeatmap? other) => other != null public bool AudioEquals(RealmBeatmap? other) => other != null
&& BeatmapSet != null && BeatmapSet != null
&& other.BeatmapSet != null && other.BeatmapSet != null

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 osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using Realms; using Realms;
@ -8,6 +9,7 @@ using Realms;
namespace osu.Game.Models namespace osu.Game.Models
{ {
[ExcludeFromDynamicCompile]
[MapTo("BeatmapDifficulty")] [MapTo("BeatmapDifficulty")]
public class RealmBeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo public class RealmBeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo
{ {

View File

@ -26,7 +26,7 @@ namespace osu.Game.Models
[JsonProperty("artist_unicode")] [JsonProperty("artist_unicode")]
public string ArtistUnicode { get; set; } = string.Empty; public string ArtistUnicode { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty; // eventually should be linked to a persisted User. = string.Empty; public string Author { get; set; } = string.Empty; // eventually should be linked to a persisted User.
public string Source { get; set; } = string.Empty; public string Source { get; set; } = string.Empty;

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.IO; using System.IO;
using osu.Framework.Testing;
using osu.Game.IO; using osu.Game.IO;
using Realms; using Realms;
@ -9,6 +10,7 @@ using Realms;
namespace osu.Game.Models namespace osu.Game.Models
{ {
[ExcludeFromDynamicCompile]
[MapTo("File")] [MapTo("File")]
public class RealmFile : RealmObject, IFileInfo public class RealmFile : RealmObject, IFileInfo
{ {

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 JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO; using osu.Game.IO;
using Realms; using Realms;
@ -10,6 +11,7 @@ using Realms;
namespace osu.Game.Models namespace osu.Game.Models
{ {
[ExcludeFromDynamicCompile]
public class RealmNamedFileUsage : EmbeddedObject, INamedFile, INamedFileUsage public class RealmNamedFileUsage : EmbeddedObject, INamedFile, INamedFileUsage
{ {
public RealmFile File { get; set; } = null!; public RealmFile File { get; set; } = null!;

View File

@ -177,6 +177,24 @@ namespace osu.Game.Online.Chat
case "wiki": case "wiki":
return new LinkDetails(LinkAction.OpenWiki, string.Join('/', args.Skip(3))); return new LinkDetails(LinkAction.OpenWiki, string.Join('/', args.Skip(3)));
case "home":
if (mainArg != "changelog")
// handle link other than changelog as external for now
return new LinkDetails(LinkAction.External, url);
switch (args.Length)
{
case 4:
// https://osu.ppy.sh/home/changelog
return new LinkDetails(LinkAction.OpenChangelog, string.Empty);
case 6:
// https://osu.ppy.sh/home/changelog/lazer/2021.1006
return new LinkDetails(LinkAction.OpenChangelog, $"{args[4]}/{args[5]}");
}
break;
} }
} }
@ -324,6 +342,7 @@ namespace osu.Game.Online.Chat
SearchBeatmapSet, SearchBeatmapSet,
OpenWiki, OpenWiki,
Custom, Custom,
OpenChangelog,
} }
public class Link : IComparable<Link> public class Link : IComparable<Link>

View File

@ -90,6 +90,8 @@ namespace osu.Game
private WikiOverlay wikiOverlay; private WikiOverlay wikiOverlay;
private ChangelogOverlay changelogOverlay;
private SkinEditorOverlay skinEditor; private SkinEditorOverlay skinEditor;
private Container overlayContent; private Container overlayContent;
@ -209,13 +211,6 @@ namespace osu.Game
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
if (args?.Length > 0)
{
var paths = args.Where(a => !a.StartsWith('-')).ToArray();
if (paths.Length > 0)
Task.Run(() => Import(paths));
}
dependencies.CacheAs(this); dependencies.CacheAs(this);
dependencies.Cache(SentryLogger); dependencies.Cache(SentryLogger);
@ -336,6 +331,17 @@ namespace osu.Game
ShowWiki(link.Argument); ShowWiki(link.Argument);
break; break;
case LinkAction.OpenChangelog:
if (string.IsNullOrEmpty(link.Argument))
ShowChangelogListing();
else
{
var changelogArgs = link.Argument.Split("/");
ShowChangelogBuild(changelogArgs[0], changelogArgs[1]);
}
break;
default: default:
throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action.");
} }
@ -401,6 +407,18 @@ namespace osu.Game
/// <param name="path">The wiki page to show</param> /// <param name="path">The wiki page to show</param>
public void ShowWiki(string path) => waitForReady(() => wikiOverlay, _ => wikiOverlay.ShowPage(path)); public void ShowWiki(string path) => waitForReady(() => wikiOverlay, _ => wikiOverlay.ShowPage(path));
/// <summary>
/// Show changelog listing overlay
/// </summary>
public void ShowChangelogListing() => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowListing());
/// <summary>
/// Show changelog's build as an overlay
/// </summary>
/// <param name="updateStream">The update stream name</param>
/// <param name="version">The build version of the update stream</param>
public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version));
/// <summary> /// <summary>
/// Present a beatmap at song select immediately. /// Present a beatmap at song select immediately.
/// The user should have already requested this interactively. /// The user should have already requested this interactively.
@ -769,7 +787,7 @@ namespace osu.Game
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true); loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true);
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); loadComponentSingleFile(changelogOverlay = new ChangelogOverlay(), overlayContent.Add, true);
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
@ -842,6 +860,19 @@ namespace osu.Game
{ {
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
}; };
// Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup.
handleStartupImport();
}
private void handleStartupImport()
{
if (args?.Length > 0)
{
var paths = args.Where(a => !a.StartsWith('-')).ToArray();
if (paths.Length > 0)
Task.Run(() => Import(paths));
}
} }
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -410,11 +411,28 @@ namespace osu.Game
{ {
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
using (realmFactory.BlockAllOperations()) IDisposable realmBlocker = null;
try
{ {
ManualResetEventSlim readyToRun = new ManualResetEventSlim();
Scheduler.Add(() =>
{
realmBlocker = realmFactory.BlockAllOperations();
contextFactory.FlushConnections(); contextFactory.FlushConnections();
readyToRun.Set();
}, false);
readyToRun.Wait();
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); (Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
} }
finally
{
realmBlocker?.Dispose();
}
Logger.Log(@"Migration complete!"); Logger.Log(@"Migration complete!");
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Overlays.OSD
private Sample sampleChange; private Sample sampleChange;
public TrackedSettingToast(SettingDescription description) public TrackedSettingToast(SettingDescription description)
: base(description.Name, description.Value, description.Shortcut) : base(description.Name.ToString(), description.Value.ToString(), description.Shortcut.ToString())
{ {
FillFlowContainer<OptionLight> optionLights; FillFlowContainer<OptionLight> optionLights;

View File

@ -14,10 +14,7 @@ namespace osu.Game.Overlays.Settings
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BackgroundColour = colours.Pink; BackgroundColour = colours.Pink3;
Triangles.ColourDark = colours.PinkDark;
Triangles.ColourLight = colours.PinkLight;
} }
} }
} }

View File

@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{ {
dropdown = new AudioDeviceSettingsDropdown dropdown = new AudioDeviceSettingsDropdown
{ {
LabelText = AudioSettingsStrings.OutputDevice,
Keywords = new[] { "speaker", "headphone", "output" } Keywords = new[] { "speaker", "headphone", "output" }
} }
}; };

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.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class AudioSettings : SettingsSubsection
{
protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.PositionalHitsounds,
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
},
};
}
}
}

View File

@ -0,0 +1,48 @@
// 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.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class BackgroundSettings : SettingsSubsection
{
protected override LocalisableString Header => GameplaySettingsStrings.BackgroundHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new SettingsSlider<double>
{
LabelText = GameplaySettingsStrings.BackgroundDim,
Current = config.GetBindable<double>(OsuSetting.DimLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<double>
{
LabelText = GameplaySettingsStrings.BackgroundBlur,
Current = config.GetBindable<double>(OsuSetting.BlurLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.LightenDuringBreaks,
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
},
};
}
}
}

View File

@ -0,0 +1,44 @@
// 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.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class BeatmapSettings : SettingsSubsection
{
protected override LocalisableString Header => GameplaySettingsStrings.BeatmapHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.BeatmapSkins,
Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.BeatmapColours,
Current = config.GetBindable<bool>(OsuSetting.BeatmapColours)
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.BeatmapHitsounds,
Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
},
new SettingsCheckbox
{
LabelText = GraphicsSettingsStrings.StoryboardVideo,
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
},
};
}
}
}

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 osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
@ -20,77 +19,18 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsSlider<double>
{
LabelText = GameplaySettingsStrings.BackgroundDim,
Current = config.GetBindable<double>(OsuSetting.DimLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<double>
{
LabelText = GameplaySettingsStrings.BackgroundBlur,
Current = config.GetBindable<double>(OsuSetting.BlurLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.LightenDuringBreaks,
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
},
new SettingsEnumDropdown<HUDVisibilityMode>
{
LabelText = GameplaySettingsStrings.HUDVisibilityMode,
Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.PositionalHitsounds,
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
},
new SettingsEnumDropdown<ScoringMode> new SettingsEnumDropdown<ScoringMode>
{ {
LabelText = GameplaySettingsStrings.ScoreDisplayMode, LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode), Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" } Keywords = new[] { "scoring" }
}, },
new SettingsCheckbox
{
LabelText = GraphicsSettingsStrings.HitLighting,
Current = config.GetBindable<bool>(OsuSetting.HitLighting)
},
}; };
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
{
Add(new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.DisableWinKey,
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
});
}
} }
} }
} }

View File

@ -0,0 +1,45 @@
// 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.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class HUDSettings : SettingsSubsection
{
protected override LocalisableString Header => GameplaySettingsStrings.HUDHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new SettingsEnumDropdown<HUDVisibilityMode>
{
LabelText = GameplaySettingsStrings.HUDVisibilityMode,
Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }
},
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
},
};
}
}
}

View File

@ -0,0 +1,45 @@
// 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;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class InputSettings : SettingsSubsection
{
protected override LocalisableString Header => GameplaySettingsStrings.InputHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new SettingsSlider<float, SizeSlider>
{
LabelText = SkinSettingsStrings.GameplayCursorSize,
Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.AutoCursorSize,
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
},
};
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
{
Add(new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.DisableWinKey,
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
});
}
}
}
}

View File

@ -1,16 +1,11 @@
// 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.Graphics; using osu.Framework.Graphics;
using osu.Game.Overlays.Settings.Sections.Gameplay;
using osu.Game.Rulesets;
using System.Linq;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Gameplay;
namespace osu.Game.Overlays.Settings.Sections namespace osu.Game.Overlays.Settings.Sections
{ {
@ -20,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon public override Drawable CreateIcon() => new SpriteIcon
{ {
Icon = FontAwesome.Regular.Circle Icon = FontAwesome.Regular.DotCircle
}; };
public GameplaySection() public GameplaySection()
@ -28,27 +23,13 @@ namespace osu.Game.Overlays.Settings.Sections
Children = new Drawable[] Children = new Drawable[]
{ {
new GeneralSettings(), new GeneralSettings(),
new AudioSettings(),
new BeatmapSettings(),
new BackgroundSettings(),
new HUDSettings(),
new InputSettings(),
new ModsSettings(), new ModsSettings(),
}; };
} }
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
{
try
{
SettingsSubsection section = ruleset.CreateSettings();
if (section != null)
Add(section);
}
catch (Exception e)
{
Logger.Error(e, "Failed to load ruleset settings");
}
}
}
} }
} }

View File

@ -9,25 +9,15 @@ using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics namespace osu.Game.Overlays.Settings.Sections.Graphics
{ {
public class DetailSettings : SettingsSubsection public class ScreenshotSettings : SettingsSubsection
{ {
protected override LocalisableString Header => GraphicsSettingsStrings.DetailSettingsHeader; protected override LocalisableString Header => GraphicsSettingsStrings.Screenshots;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox
{
LabelText = GraphicsSettingsStrings.StoryboardVideo,
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
{
LabelText = GraphicsSettingsStrings.HitLighting,
Current = config.GetBindable<bool>(OsuSetting.HitLighting)
},
new SettingsEnumDropdown<ScreenshotFormat> new SettingsEnumDropdown<ScreenshotFormat>
{ {
LabelText = GraphicsSettingsStrings.ScreenshotFormat, LabelText = GraphicsSettingsStrings.ScreenshotFormat,

View File

@ -22,9 +22,9 @@ namespace osu.Game.Overlays.Settings.Sections
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new RendererSettings(),
new LayoutSettings(), new LayoutSettings(),
new DetailSettings(), new RendererSettings(),
new ScreenshotSettings(),
}; };
} }
} }

View File

@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Localisation; using osu.Game.Localisation;
@ -59,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
} }
} }
public class ResetButton : DangerousTriangleButton public class ResetButton : DangerousSettingsButton
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

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.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -8,16 +9,24 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Input.Handlers.Tablet;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Overlays.Settings.Sections.Input namespace osu.Game.Overlays.Settings.Sections.Input
{ {
internal class RotationPresetButtons : FillFlowContainer internal class RotationPresetButtons : CompositeDrawable
{ {
public new MarginPadding Padding
{
get => base.Padding;
set => base.Padding = value;
}
private readonly ITabletHandler tabletHandler; private readonly ITabletHandler tabletHandler;
private Bindable<float> rotation; private Bindable<float> rotation;
private readonly RotationButton[] rotationPresets = new RotationButton[preset_count];
private const int preset_count = 4;
private const int height = 50; private const int height = 50;
public RotationPresetButtons(ITabletHandler tabletHandler) public RotationPresetButtons(ITabletHandler tabletHandler)
@ -27,18 +36,39 @@ namespace osu.Game.Overlays.Settings.Sections.Input
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = height; Height = height;
for (int i = 0; i < 360; i += 90) IEnumerable<Dimension> createColumns(int count)
{ {
var presetRotation = i; for (int i = 0; i < count; ++i)
{
if (i > 0)
yield return new Dimension(GridSizeMode.Absolute, 10);
Add(new RotationButton(i) yield return new Dimension();
}
}
GridContainer grid;
InternalChild = grid = new GridContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.Both,
Height = height, ColumnDimensions = createColumns(preset_count).ToArray()
Width = 0.25f, };
Text = $@"{presetRotation}º",
Action = () => tabletHandler.Rotation.Value = presetRotation, grid.Content = new[] { new Drawable[preset_count * 2 - 1] };
});
for (int i = 0; i < preset_count; i++)
{
var rotationValue = i * 90;
var rotationPreset = new RotationButton(rotationValue)
{
RelativeSizeAxes = Axes.Both,
Height = 1,
Text = $@"{rotationValue}º",
Action = () => tabletHandler.Rotation.Value = rotationValue,
};
grid.Content[0][2 * i] = rotationPresets[i] = rotationPreset;
} }
} }
@ -49,16 +79,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input
rotation = tabletHandler.Rotation.GetBoundCopy(); rotation = tabletHandler.Rotation.GetBoundCopy();
rotation.BindValueChanged(val => rotation.BindValueChanged(val =>
{ {
foreach (var b in Children.OfType<RotationButton>()) foreach (var b in rotationPresets)
b.IsSelected = b.Preset == val.NewValue; b.IsSelected = b.Preset == val.NewValue;
}, true); }, true);
} }
public class RotationButton : TriangleButton public class RotationButton : RoundedButton
{ {
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
public readonly int Preset; public readonly int Preset;
public RotationButton(int preset) public RotationButton(int preset)
@ -91,18 +124,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private void updateColour() private void updateColour()
{ {
if (isSelected) BackgroundColour = isSelected ? colours.Blue3 : colourProvider.Background3;
{
BackgroundColour = colours.BlueDark;
Triangles.ColourDark = colours.BlueDarker;
Triangles.ColourLight = colours.Blue;
}
else
{
BackgroundColour = colours.Gray4;
Triangles.ColourDark = colours.Gray5;
Triangles.ColourLight = colours.Gray6;
}
} }
} }
} }

View File

@ -10,7 +10,6 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Collections; using osu.Game.Collections;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -21,15 +20,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
protected override LocalisableString Header => "General"; protected override LocalisableString Header => "General";
private TriangleButton importBeatmapsButton; private SettingsButton importBeatmapsButton;
private TriangleButton importScoresButton; private SettingsButton importScoresButton;
private TriangleButton importSkinsButton; private SettingsButton importSkinsButton;
private TriangleButton importCollectionsButton; private SettingsButton importCollectionsButton;
private TriangleButton deleteBeatmapsButton; private SettingsButton deleteBeatmapsButton;
private TriangleButton deleteScoresButton; private SettingsButton deleteScoresButton;
private TriangleButton deleteSkinsButton; private SettingsButton deleteSkinsButton;
private TriangleButton restoreButton; private SettingsButton restoreButton;
private TriangleButton undeleteButton; private SettingsButton undeleteButton;
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay) private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay)

View File

@ -0,0 +1,44 @@
// 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 System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Rulesets;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections
{
public class RulesetSection : SettingsSection
{
public override LocalisableString Header => RulesetSettingsStrings.Rulesets;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Chess
};
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
{
try
{
SettingsSubsection section = ruleset.CreateSettings();
if (section != null)
Add(section);
}
catch (Exception e)
{
Logger.Error(e, "Failed to load ruleset settings");
}
}
}
}
}

View File

@ -64,39 +64,16 @@ namespace osu.Game.Overlays.Settings.Sections
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
skinDropdown = new SkinSettingsDropdown(), skinDropdown = new SkinSettingsDropdown
{
LabelText = SkinSettingsStrings.CurrentSkin
},
new SettingsButton new SettingsButton
{ {
Text = SkinSettingsStrings.SkinLayoutEditor, Text = SkinSettingsStrings.SkinLayoutEditor,
Action = () => skinEditor?.Toggle(), Action = () => skinEditor?.Toggle(),
}, },
new ExportSkinButton(), new ExportSkinButton(),
new SettingsSlider<float, SizeSlider>
{
LabelText = SkinSettingsStrings.GameplayCursorSize,
Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.AutoCursorSize,
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.BeatmapSkins,
Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.BeatmapColours,
Current = config.GetBindable<bool>(OsuSetting.BeatmapColours)
},
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.BeatmapHitsounds,
Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
},
}; };
managerUpdated = skins.ItemUpdated.GetBoundCopy(); managerUpdated = skins.ItemUpdated.GetBoundCopy();

View File

@ -6,11 +6,11 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public class SettingsButton : TriangleButton, IHasTooltip public class SettingsButton : RoundedButton, IHasTooltip
{ {
public SettingsButton() public SettingsButton()
{ {

View File

@ -24,12 +24,13 @@ namespace osu.Game.Overlays
protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[] protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[]
{ {
new GeneralSection(), new GeneralSection(),
new GraphicsSection(), new SkinSection(),
new AudioSection(),
new InputSection(createSubPanel(new KeyBindingPanel())), new InputSection(createSubPanel(new KeyBindingPanel())),
new UserInterfaceSection(), new UserInterfaceSection(),
new GameplaySection(), new GameplaySection(),
new SkinSection(), new RulesetSection(),
new AudioSection(),
new GraphicsSection(),
new OnlineSection(), new OnlineSection(),
new MaintenanceSection(), new MaintenanceSection(),
new DebugSection(), new DebugSection(),

View File

@ -55,7 +55,10 @@ namespace osu.Game.Rulesets.UI
/// <summary> /// <summary>
/// The current direction of playback to be exposed to frame stable children. /// The current direction of playback to be exposed to frame stable children.
/// </summary> /// </summary>
private int direction; /// <remarks>
/// Initially it is presumed that playback will proceed in the forward direction.
/// </remarks>
private int direction = 1;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler) private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler)
@ -139,7 +142,9 @@ namespace osu.Game.Rulesets.UI
state = PlaybackState.NotValid; state = PlaybackState.NotValid;
} }
if (state == PlaybackState.Valid) // if the proposed time is the same as the current time, assume that the clock will continue progressing in the same direction as previously.
// this avoids spurious flips in direction from -1 to 1 during rewinds.
if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime)
direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; direction = proposedTime >= manualClock.CurrentTime ? 1 : -1;
double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime); double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime);

View File

@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play
{ {
/// <summary> /// <summary>
/// Manage the animation to be applied when a player fails. /// Manage the animation to be applied when a player fails.
/// Single file; automatically disposed after use. /// Single use and automatically disposed after use.
/// </summary> /// </summary>
public class FailAnimation : CompositeDrawable public class FailAnimation : CompositeDrawable
{ {

View File

@ -947,7 +947,7 @@ namespace osu.Game.Screens.Play
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)
{ {
screenSuspension?.Expire(); screenSuspension?.RemoveAndDisposeImmediately();
fadeOut(); fadeOut();
base.OnSuspending(next); base.OnSuspending(next);
@ -955,7 +955,8 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
{ {
screenSuspension?.Expire(); screenSuspension?.RemoveAndDisposeImmediately();
failAnimation?.RemoveAndDisposeImmediately();
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
if (prepareScoreForDisplayTask == null) if (prepareScoreForDisplayTask == null)

View File

@ -12,6 +12,9 @@ namespace osu.Game.Skinning
/// </summary> /// </summary>
public interface ISkinSource : ISkin public interface ISkinSource : ISkin
{ {
/// <summary>
/// Fired whenever a source change occurs, signalling that consumers should re-query as required.
/// </summary>
event Action SourceChanged; event Action SourceChanged;
/// <summary> /// <summary>

View File

@ -58,10 +58,8 @@ namespace osu.Game.Skinning
return base.CreateChildDependencies(parent); return base.CreateChildDependencies(parent);
} }
protected override void OnSourceChanged() protected override void RefreshSources()
{ {
ResetSources();
// Populate a local list first so we can adjust the returned order as we go. // Populate a local list first so we can adjust the returned order as we go.
var sources = new List<ISkin>(); var sources = new List<ISkin>();
@ -91,8 +89,7 @@ namespace osu.Game.Skinning
else else
sources.Add(rulesetResourcesSkin); sources.Add(rulesetResourcesSkin);
foreach (var skin in sources) SetSources(sources);
AddSource(skin);
} }
protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin)

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -40,10 +41,12 @@ namespace osu.Game.Skinning
protected virtual bool AllowColourLookup => true; protected virtual bool AllowColourLookup => true;
private readonly object sourceSetLock = new object();
/// <summary> /// <summary>
/// A dictionary mapping each <see cref="ISkin"/> source to a wrapper which handles lookup allowances. /// A dictionary mapping each <see cref="ISkin"/> source to a wrapper which handles lookup allowances.
/// </summary> /// </summary>
private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>(); private (ISkin skin, DisableableSkinSource wrapped)[] skinSources = Array.Empty<(ISkin skin, DisableableSkinSource wrapped)>();
/// <summary> /// <summary>
/// Constructs a new <see cref="SkinProvidingContainer"/> initialised with a single skin source. /// Constructs a new <see cref="SkinProvidingContainer"/> initialised with a single skin source.
@ -52,7 +55,7 @@ namespace osu.Game.Skinning
: this() : this()
{ {
if (skin != null) if (skin != null)
AddSource(skin); SetSources(new[] { skin });
} }
/// <summary> /// <summary>
@ -168,49 +171,42 @@ namespace osu.Game.Skinning
} }
/// <summary> /// <summary>
/// Add a new skin to this provider. Will be added to the end of the lookup order precedence. /// Replace the sources used for lookups in this container.
/// </summary> /// </summary>
/// <param name="skin">The skin to add.</param> /// <remarks>
protected void AddSource(ISkin skin) /// This does not implicitly fire a <see cref="SourceChanged"/> event. Consider calling <see cref="TriggerSourceChanged"/> if required.
/// </remarks>
/// <param name="sources">The new sources.</param>
protected void SetSources(IEnumerable<ISkin> sources)
{ {
skinSources.Add((skin, new DisableableSkinSource(skin, this))); lock (sourceSetLock)
if (skin is ISkinSource source)
source.SourceChanged += TriggerSourceChanged;
}
/// <summary>
/// Remove a skin from this provider.
/// </summary>
/// <param name="skin">The skin to remove.</param>
protected void RemoveSource(ISkin skin)
{ {
if (skinSources.RemoveAll(s => s.skin == skin) == 0) foreach (var skin in skinSources)
return; {
if (skin.skin is ISkinSource source)
if (skin is ISkinSource source)
source.SourceChanged -= TriggerSourceChanged; source.SourceChanged -= TriggerSourceChanged;
} }
/// <summary> skinSources = sources.Select(skin => (skin, new DisableableSkinSource(skin, this))).ToArray();
/// Clears all skin sources.
/// </summary> foreach (var skin in skinSources)
protected void ResetSources()
{ {
foreach (var i in skinSources.ToArray()) if (skin.skin is ISkinSource source)
RemoveSource(i.skin); source.SourceChanged += TriggerSourceChanged;
}
}
} }
/// <summary> /// <summary>
/// Invoked when any source has changed (either <see cref="ParentSource"/> or a source registered via <see cref="AddSource"/>). /// Invoked after any consumed source change, before the external <see cref="SourceChanged"/> event is fired.
/// This is also invoked once initially during <see cref="CreateChildDependencies"/> to ensure sources are ready for children consumption. /// This is also invoked once initially during <see cref="CreateChildDependencies"/> to ensure sources are ready for children consumption.
/// </summary> /// </summary>
protected virtual void OnSourceChanged() { } protected virtual void RefreshSources() { }
protected void TriggerSourceChanged() protected void TriggerSourceChanged()
{ {
// Expose to implementations, giving them a chance to react before notifying external consumers. // Expose to implementations, giving them a chance to react before notifying external consumers.
OnSourceChanged(); RefreshSources();
SourceChanged?.Invoke(); SourceChanged?.Invoke();
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -22,6 +23,8 @@ namespace osu.Game.Skinning
public bool ComponentsLoaded { get; private set; } public bool ComponentsLoaded { get; private set; }
private CancellationTokenSource cancellationSource;
public SkinnableTargetContainer(SkinnableTarget target) public SkinnableTargetContainer(SkinnableTarget target)
{ {
Target = target; Target = target;
@ -38,6 +41,9 @@ namespace osu.Game.Skinning
content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer; content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer;
cancellationSource?.Cancel();
cancellationSource = null;
if (content != null) if (content != null)
{ {
LoadComponentAsync(content, wrapper => LoadComponentAsync(content, wrapper =>
@ -45,7 +51,7 @@ namespace osu.Game.Skinning
AddInternal(wrapper); AddInternal(wrapper);
components.AddRange(wrapper.Children.OfType<ISkinnableDrawable>()); components.AddRange(wrapper.Children.OfType<ISkinnableDrawable>());
ComponentsLoaded = true; ComponentsLoaded = true;
}); }, (cancellationSource = new CancellationTokenSource()).Token);
} }
else else
ComponentsLoaded = true; ComponentsLoaded = true;

View File

@ -8,6 +8,7 @@ using osu.Framework.Extensions;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Models; using osu.Game.Models;
using Realms; using Realms;
@ -17,14 +18,16 @@ using Realms;
namespace osu.Game.Stores namespace osu.Game.Stores
{ {
/// <summary> /// <summary>
/// Handles the Store and retrieval of Files/FileSets to the database backing /// Handles the storing of files to the file system (and database) backing.
/// </summary> /// </summary>
[ExcludeFromDynamicCompile]
public class RealmFileStore public class RealmFileStore
{ {
private readonly RealmContextFactory realmFactory; private readonly RealmContextFactory realmFactory;
public readonly IResourceStore<byte[]> Store; public readonly IResourceStore<byte[]> Store;
public Storage Storage; public readonly Storage Storage;
public RealmFileStore(RealmContextFactory realmFactory, Storage storage) public RealmFileStore(RealmContextFactory realmFactory, Storage storage)
{ {

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -109,6 +110,8 @@ namespace osu.Game.Tests.Beatmaps
{ {
var beatmap = GetBeatmap(name); var beatmap = GetBeatmap(name);
string beforeConversion = beatmap.Serialize();
var converterResult = new Dictionary<HitObject, IEnumerable<HitObject>>(); var converterResult = new Dictionary<HitObject, IEnumerable<HitObject>>();
var working = new ConversionWorkingBeatmap(beatmap) var working = new ConversionWorkingBeatmap(beatmap)
@ -122,6 +125,10 @@ namespace osu.Game.Tests.Beatmaps
working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods); working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods);
string afterConversion = beatmap.Serialize();
Assert.AreEqual(beforeConversion, afterConversion, "Conversion altered original beatmap");
return new ConvertResult return new ConvertResult
{ {
Mappings = converterResult.Select(r => Mappings = converterResult.Select(r =>

View File

@ -78,9 +78,11 @@ namespace osu.Game.Tests.Visual
protected void CreateGame() protected void CreateGame()
{ {
AddGame(Game = new TestOsuGame(LocalStorage, API)); AddGame(Game = CreateTestGame());
} }
protected virtual TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API);
protected void PushAndConfirm(Func<Screen> newScreen) protected void PushAndConfirm(Func<Screen> newScreen)
{ {
Screen screen = null; Screen screen = null;
@ -135,7 +137,8 @@ namespace osu.Game.Tests.Visual
public new void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null) => base.PerformFromScreen(action, validScreens); public new void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null) => base.PerformFromScreen(action, validScreens);
public TestOsuGame(Storage storage, IAPIProvider api) public TestOsuGame(Storage storage, IAPIProvider api, string[] args = null)
: base(args)
{ {
Storage = storage; Storage = storage;
API = api; API = api;

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.6.0" /> <PackageReference Include="Realm" Version="10.6.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1012.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
<PackageReference Include="Sentry" Version="3.9.4" /> <PackageReference Include="Sentry" Version="3.9.4" />
<PackageReference Include="SharpCompress" Version="0.29.0" /> <PackageReference Include="SharpCompress" Version="0.29.0" />

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.1004.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1012.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.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.1004.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1012.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<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" />