1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 15:33:05 +08:00

Merge branch 'master' into realtime-leaderboard

This commit is contained in:
Bartłomiej Dach 2020-12-23 09:56:47 +01:00 committed by GitHub
commit 64095307de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 675 additions and 226 deletions

View File

@ -157,10 +157,16 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var h in hitObjects) foreach (var h in hitObjects)
{ {
h.Position = new Vector2( var newPosition = h.Position;
quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X),
quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y) // guard against no-ops and NaN.
); if (scale.X != 0 && quad.Width > 0)
newPosition.X = quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X);
if (scale.Y != 0 && quad.Height > 0)
newPosition.Y = quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y);
h.Position = newPosition;
} }
} }

View File

@ -10,9 +10,11 @@ using NUnit.Framework;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -27,7 +29,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay namespace osu.Game.Tests.Gameplay
{ {
[HeadlessTest] [HeadlessTest]
public class TestSceneStoryboardSamples : OsuTestScene public class TestSceneStoryboardSamples : OsuTestScene, IStorageResourceProvider
{ {
[Test] [Test]
public void TestRetrieveTopLevelSample() public void TestRetrieveTopLevelSample()
@ -35,7 +37,7 @@ namespace osu.Game.Tests.Gameplay
ISkin skin = null; ISkin skin = null;
SampleChannel channel = null; SampleChannel channel = null;
AddStep("create skin", () => skin = new TestSkin("test-sample", Audio)); AddStep("create skin", () => skin = new TestSkin("test-sample", this));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample"))); AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
AddAssert("sample is non-null", () => channel != null); AddAssert("sample is non-null", () => channel != null);
@ -47,7 +49,7 @@ namespace osu.Game.Tests.Gameplay
ISkin skin = null; ISkin skin = null;
SampleChannel channel = null; SampleChannel channel = null;
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio)); AddStep("create skin", () => skin = new TestSkin("folder/test-sample", this));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample"))); AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
AddAssert("sample is non-null", () => channel != null); AddAssert("sample is non-null", () => channel != null);
@ -105,7 +107,7 @@ namespace osu.Game.Tests.Gameplay
AddStep("setup storyboard sample", () => AddStep("setup storyboard sample", () =>
{ {
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
SelectedMods.Value = new[] { testedMod }; SelectedMods.Value = new[] { testedMod };
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
@ -128,8 +130,8 @@ namespace osu.Game.Tests.Gameplay
private class TestSkin : LegacySkin private class TestSkin : LegacySkin
{ {
public TestSkin(string resourceName, AudioManager audioManager) public TestSkin(string resourceName, IStorageResourceProvider resources)
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini") : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), resources, "skin.ini")
{ {
} }
} }
@ -158,15 +160,15 @@ namespace osu.Game.Tests.Gameplay
private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
{ {
private readonly AudioManager audio; private readonly IStorageResourceProvider resources;
public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio) public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, IStorageResourceProvider resources)
: base(ruleset, null, audio) : base(ruleset, null, resources.AudioManager)
{ {
this.audio = audio; this.resources = resources;
} }
protected override ISkin GetSkin() => new TestSkin("test-sample", audio); protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
} }
private class TestDrawableStoryboardSample : DrawableStoryboardSample private class TestDrawableStoryboardSample : DrawableStoryboardSample
@ -176,5 +178,13 @@ namespace osu.Game.Tests.Gameplay
{ {
} }
} }
#region IResourceStorageProvider
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => null;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
#endregion
} }
} }

View File

@ -8,7 +8,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneParticipantsList : MultiplayerTestScene public class TestSceneTimeshiftParticipantsList : MultiplayerTestScene
{ {
[SetUp] [SetUp]
public new void Setup() => Schedule(() => public new void Setup() => Schedule(() =>
@ -20,6 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Room.RecentParticipants.Add(new User Room.RecentParticipants.Add(new User
{ {
Username = "peppy", Username = "peppy",
CurrentModeRank = 1234,
Id = 2 Id = 2
}); });
} }

View File

@ -55,8 +55,14 @@ namespace osu.Game.Tests.Visual.Navigation
var secondimport = importBeatmap(3); var secondimport = importBeatmap(3);
presentAndConfirm(secondimport); presentAndConfirm(secondimport);
// Test presenting same beatmap more than once
presentAndConfirm(secondimport);
presentSecondDifficultyAndConfirm(firstImport, 1); presentSecondDifficultyAndConfirm(firstImport, 1);
presentSecondDifficultyAndConfirm(secondimport, 3); presentSecondDifficultyAndConfirm(secondimport, 3);
// Test presenting same beatmap more than once
presentSecondDifficultyAndConfirm(secondimport, 3);
} }
[Test] [Test]

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Online.RealtimeMultiplayer;
using osu.Game.Screens.Multi.RealtimeMultiplayer.Participants; using osu.Game.Screens.Multi.RealtimeMultiplayer.Participants;
using osu.Game.Users; using osu.Game.Users;
@ -13,7 +14,7 @@ using osuTK;
namespace osu.Game.Tests.Visual.RealtimeMultiplayer namespace osu.Game.Tests.Visual.RealtimeMultiplayer
{ {
public class TestSceneParticipantsList : RealtimeMultiplayerTestScene public class TestSceneRealtimeMultiplayerParticipantsList : RealtimeMultiplayerTestScene
{ {
[SetUp] [SetUp]
public new void Setup() => Schedule(() => public new void Setup() => Schedule(() =>
@ -65,13 +66,13 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer
[Test] [Test]
public void TestToggleReadyState() public void TestToggleReadyState()
{ {
AddAssert("ready mark invisible", () => !this.ChildrenOfType<ReadyMark>().Single().IsPresent); AddAssert("ready mark invisible", () => !this.ChildrenOfType<StateDisplay>().Single().IsPresent);
AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Ready)); AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Ready));
AddUntilStep("ready mark visible", () => this.ChildrenOfType<ReadyMark>().Single().IsPresent); AddUntilStep("ready mark visible", () => this.ChildrenOfType<StateDisplay>().Single().IsPresent);
AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle)); AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle));
AddUntilStep("ready mark invisible", () => !this.ChildrenOfType<ReadyMark>().Single().IsPresent); AddUntilStep("ready mark invisible", () => !this.ChildrenOfType<StateDisplay>().Single().IsPresent);
} }
[Test] [Test]
@ -104,11 +105,11 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer
{ {
Id = i, Id = i,
Username = $"User {i}", Username = $"User {i}",
CurrentModeRank = RNG.Next(1, 100000),
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
}); });
if (i % 2 == 0) Client.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1));
Client.ChangeUserState(i, MultiplayerUserState.Ready);
} }
}); });
} }

View File

@ -0,0 +1,211 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Tests.Visual.Navigation;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.SongSelect
{
public class TestSceneBeatmapRecommendations : OsuGameTestScene
{
[SetUpSteps]
public override void SetUpSteps()
{
AddStep("register request handling", () =>
{
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case GetUserRequest userRequest:
userRequest.TriggerSuccess(getUser(userRequest.Ruleset.ID));
break;
}
};
});
base.SetUpSteps();
User getUser(int? rulesetID)
{
return new User
{
Username = @"Dummy",
Id = 1001,
Statistics = new UserStatistics
{
PP = getNecessaryPP(rulesetID)
}
};
}
decimal getNecessaryPP(int? rulesetID)
{
switch (rulesetID)
{
case 0:
return 336; // recommended star rating of 2
case 1:
return 928; // SR 3
case 2:
return 1905; // SR 4
case 3:
return 3329; // SR 5
default:
return 0;
}
}
}
[Test]
public void TestPresentedBeatmapIsRecommended()
{
List<BeatmapSetInfo> beatmapSets = null;
const int import_count = 5;
AddStep("import 5 maps", () =>
{
beatmapSets = new List<BeatmapSetInfo>();
for (int i = 0; i < import_count; ++i)
{
beatmapSets.Add(importBeatmapSet(i, Enumerable.Repeat(new OsuRuleset().RulesetInfo, 5)));
}
});
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(beatmapSets));
presentAndConfirm(() => beatmapSets[3], 2);
}
[Test]
public void TestCurrentRulesetIsRecommended()
{
BeatmapSetInfo catchSet = null, mixedSet = null;
AddStep("create catch beatmapset", () => catchSet = importBeatmapSet(0, new[] { new CatchRuleset().RulesetInfo }));
AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(1,
new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new ManiaRuleset().RulesetInfo }));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { catchSet, mixedSet }));
// Switch to catch
presentAndConfirm(() => catchSet, 1);
// Present mixed difficulty set, expect current ruleset to be selected
presentAndConfirm(() => mixedSet, 2);
}
[Test]
public void TestBestRulesetIsRecommended()
{
BeatmapSetInfo osuSet = null, mixedSet = null;
AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(0, new[] { new OsuRuleset().RulesetInfo }));
AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(1,
new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new ManiaRuleset().RulesetInfo }));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, mixedSet }));
// Make sure we are on standard ruleset
presentAndConfirm(() => osuSet, 1);
// Present mixed difficulty set, expect ruleset with highest star difficulty
presentAndConfirm(() => mixedSet, 3);
}
[Test]
public void TestSecondBestRulesetIsRecommended()
{
BeatmapSetInfo osuSet = null, mixedSet = null;
AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(0, new[] { new OsuRuleset().RulesetInfo }));
AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(1,
new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new TaikoRuleset().RulesetInfo }));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, mixedSet }));
// Make sure we are on standard ruleset
presentAndConfirm(() => osuSet, 1);
// Present mixed difficulty set, expect ruleset with second highest star difficulty
presentAndConfirm(() => mixedSet, 2);
}
[Test]
public void TestCorrectStarRatingIsUsed()
{
BeatmapSetInfo osuSet = null, maniaSet = null;
AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(0, new[] { new OsuRuleset().RulesetInfo }));
AddStep("create mania beatmapset", () => maniaSet = importBeatmapSet(1, Enumerable.Repeat(new ManiaRuleset().RulesetInfo, 10)));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, maniaSet }));
// Make sure we are on standard ruleset
presentAndConfirm(() => osuSet, 1);
// Present mania set, expect the difficulty that matches recommended mania star rating
presentAndConfirm(() => maniaSet, 5);
}
private BeatmapSetInfo importBeatmapSet(int importID, IEnumerable<RulesetInfo> difficultyRulesets)
{
var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
AuthorString = "SomeAuthor",
Title = $"import {importID}"
};
var beatmapSet = new BeatmapSetInfo
{
Hash = Guid.NewGuid().ToString(),
OnlineBeatmapSetID = importID,
Metadata = metadata,
Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo
{
OnlineBeatmapID = importID * 1024 + difficultyIndex,
Metadata = metadata,
BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset,
StarDifficulty = difficultyIndex + 1,
Version = $"SR{difficultyIndex + 1}"
}).ToList()
};
return Game.BeatmapManager.Import(beatmapSet).Result;
}
private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null);
private void presentAndConfirm(Func<BeatmapSetInfo> getImport, int expectedDiff)
{
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("recommended beatmap displayed", () =>
{
int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineBeatmapID;
return Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == expectedID;
});
}
}
}

View File

@ -16,6 +16,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -28,8 +29,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Users;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Users;
using Decoder = osu.Game.Beatmaps.Formats.Decoder; using Decoder = osu.Game.Beatmaps.Formats.Decoder;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
@ -38,7 +39,7 @@ namespace osu.Game.Beatmaps
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
/// </summary> /// </summary>
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>, IDisposable public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>, IDisposable, IBeatmapResourceProvider
{ {
/// <summary> /// <summary>
/// Fired when a single difficulty has been hidden. /// Fired when a single difficulty has been hidden.
@ -68,9 +69,12 @@ namespace osu.Game.Beatmaps
private readonly RulesetStore rulesets; private readonly RulesetStore rulesets;
private readonly BeatmapStore beatmaps; private readonly BeatmapStore beatmaps;
private readonly AudioManager audioManager; private readonly AudioManager audioManager;
private readonly TextureStore textureStore; private readonly LargeTextureStore largeTextureStore;
private readonly ITrackStore trackStore; private readonly ITrackStore trackStore;
[CanBeNull]
private readonly GameHost host;
[CanBeNull] [CanBeNull]
private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
@ -80,6 +84,7 @@ namespace osu.Game.Beatmaps
{ {
this.rulesets = rulesets; this.rulesets = rulesets;
this.audioManager = audioManager; this.audioManager = audioManager;
this.host = host;
DefaultBeatmap = defaultBeatmap; DefaultBeatmap = defaultBeatmap;
@ -92,7 +97,7 @@ namespace osu.Game.Beatmaps
if (performOnlineLookups) if (performOnlineLookups)
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store));
trackStore = audioManager.GetTrackStore(Files.Store); trackStore = audioManager.GetTrackStore(Files.Store);
} }
@ -302,7 +307,7 @@ namespace osu.Game.Beatmaps
beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager)); workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
return working; return working;
} }
@ -492,6 +497,16 @@ namespace osu.Game.Beatmaps
onlineLookupQueue?.Dispose(); onlineLookupQueue?.Dispose();
} }
#region IResourceStorageProvider
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
AudioManager IStorageResourceProvider.AudioManager => audioManager;
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
#endregion
/// <summary> /// <summary>
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
/// </summary> /// </summary>

View File

@ -2,11 +2,10 @@
// 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.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
@ -21,16 +20,13 @@ namespace osu.Game.Beatmaps
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
private class BeatmapManagerWorkingBeatmap : WorkingBeatmap private class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{ {
private readonly IResourceStore<byte[]> store; [NotNull]
private readonly TextureStore textureStore; private readonly IBeatmapResourceProvider resources;
private readonly ITrackStore trackStore;
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager) public BeatmapManagerWorkingBeatmap(BeatmapInfo beatmapInfo, [NotNull] IBeatmapResourceProvider resources)
: base(beatmapInfo, audioManager) : base(beatmapInfo, resources.AudioManager)
{ {
this.store = store; this.resources = resources;
this.textureStore = textureStore;
this.trackStore = trackStore;
} }
protected override IBeatmap GetBeatmap() protected override IBeatmap GetBeatmap()
@ -40,7 +36,7 @@ namespace osu.Game.Beatmaps
try try
{ {
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path))))
return Decoder.GetDecoder<Beatmap>(stream).Decode(stream); return Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
} }
catch (Exception e) catch (Exception e)
@ -61,7 +57,7 @@ namespace osu.Game.Beatmaps
try try
{ {
return textureStore.Get(getPathForFile(Metadata.BackgroundFile)); return resources.LargeTextureStore.Get(getPathForFile(Metadata.BackgroundFile));
} }
catch (Exception e) catch (Exception e)
{ {
@ -77,7 +73,7 @@ namespace osu.Game.Beatmaps
try try
{ {
return trackStore.Get(getPathForFile(Metadata.AudioFile)); return resources.Tracks.Get(getPathForFile(Metadata.AudioFile));
} }
catch (Exception e) catch (Exception e)
{ {
@ -93,7 +89,7 @@ namespace osu.Game.Beatmaps
try try
{ {
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); var trackData = resources.Files.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData); return trackData == null ? null : new Waveform(trackData);
} }
catch (Exception e) catch (Exception e)
@ -109,7 +105,7 @@ namespace osu.Game.Beatmaps
try try
{ {
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path))))
{ {
var decoder = Decoder.GetDecoder<Storyboard>(stream); var decoder = Decoder.GetDecoder<Storyboard>(stream);
@ -118,7 +114,7 @@ namespace osu.Game.Beatmaps
storyboard = decoder.Decode(stream); storyboard = decoder.Decode(stream);
else else
{ {
using (var secondaryStream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) using (var secondaryStream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
storyboard = decoder.Decode(stream, secondaryStream); storyboard = decoder.Decode(stream, secondaryStream);
} }
} }
@ -138,7 +134,7 @@ namespace osu.Game.Beatmaps
{ {
try try
{ {
return new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager); return new LegacyBeatmapSkin(BeatmapInfo, resources.Files, resources);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -4,17 +4,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Rulesets; using osu.Game.Rulesets;
namespace osu.Game.Screens.Select namespace osu.Game.Beatmaps
{ {
/// <summary>
/// A class which will recommend the most suitable difficulty for the local user from a beatmap set.
/// This requires the user to be logged in, as it sources from the user's online profile.
/// </summary>
public class DifficultyRecommender : Component public class DifficultyRecommender : Component
{ {
[Resolved] [Resolved]
@ -26,7 +30,12 @@ namespace osu.Game.Screens.Select
[Resolved] [Resolved]
private Bindable<RulesetInfo> ruleset { get; set; } private Bindable<RulesetInfo> ruleset { get; set; }
private readonly Dictionary<RulesetInfo, double> recommendedStarDifficulty = new Dictionary<RulesetInfo, double>(); /// <summary>
/// The user for which the last requests were run.
/// </summary>
private int? requestedUserId;
private readonly Dictionary<RulesetInfo, double> recommendedDifficultyMapping = new Dictionary<RulesetInfo, double>();
private readonly IBindable<APIState> apiState = new Bindable<APIState>(); private readonly IBindable<APIState> apiState = new Bindable<APIState>();
@ -45,42 +54,64 @@ namespace osu.Game.Screens.Select
/// </remarks> /// </remarks>
/// <param name="beatmaps">A collection of beatmaps to select a difficulty from.</param> /// <param name="beatmaps">A collection of beatmaps to select a difficulty from.</param>
/// <returns>The recommended difficulty, or null if a recommendation could not be provided.</returns> /// <returns>The recommended difficulty, or null if a recommendation could not be provided.</returns>
[CanBeNull]
public BeatmapInfo GetRecommendedBeatmap(IEnumerable<BeatmapInfo> beatmaps) public BeatmapInfo GetRecommendedBeatmap(IEnumerable<BeatmapInfo> beatmaps)
{ {
if (recommendedStarDifficulty.TryGetValue(ruleset.Value, out var stars)) foreach (var r in orderedRulesets)
{ {
return beatmaps.OrderBy(b => if (!recommendedDifficultyMapping.TryGetValue(r, out var recommendation))
continue;
BeatmapInfo beatmap = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b =>
{ {
var difference = b.StarDifficulty - stars; var difference = b.StarDifficulty - recommendation;
return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder
}).FirstOrDefault(); }).FirstOrDefault();
if (beatmap != null)
return beatmap;
} }
return null; return null;
} }
private void calculateRecommendedDifficulties() private void fetchRecommendedValues()
{ {
rulesets.AvailableRulesets.ForEach(rulesetInfo => if (recommendedDifficultyMapping.Count > 0 && api.LocalUser.Value.Id == requestedUserId)
return;
requestedUserId = api.LocalUser.Value.Id;
// only query API for built-in rulesets
rulesets.AvailableRulesets.Where(ruleset => ruleset.ID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID).ForEach(rulesetInfo =>
{ {
var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo);
req.Success += result => req.Success += result =>
{ {
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
recommendedStarDifficulty[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; recommendedDifficultyMapping[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195;
}; };
api.Queue(req); api.Queue(req);
}); });
} }
/// <returns>
/// Rulesets ordered descending by their respective recommended difficulties.
/// The currently selected ruleset will always be first.
/// </returns>
private IEnumerable<RulesetInfo> orderedRulesets =>
recommendedDifficultyMapping
.OrderByDescending(pair => pair.Value).Select(pair => pair.Key).Where(r => !r.Equals(ruleset.Value))
.Prepend(ruleset.Value);
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() => private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
{ {
switch (state.NewValue) switch (state.NewValue)
{ {
case APIState.Online: case APIState.Online:
calculateRecommendedDifficulties(); fetchRecommendedValues();
break; break;
} }
}); });

View File

@ -0,0 +1,22 @@
// 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.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.IO;
namespace osu.Game.Beatmaps
{
public interface IBeatmapResourceProvider : IStorageResourceProvider
{
/// <summary>
/// Retrieve a global large texture store, used for loading beatmap backgrounds.
/// </summary>
TextureStore LargeTextureStore { get; }
/// <summary>
/// Access a global track store for retrieving beatmap tracks from.
/// </summary>
ITrackStore Tracks { get; }
}
}

View File

@ -0,0 +1,29 @@
// 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.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
namespace osu.Game.IO
{
public interface IStorageResourceProvider
{
/// <summary>
/// Retrieve the game-wide audio manager.
/// </summary>
AudioManager AudioManager { get; }
/// <summary>
/// Access game-wide user files.
/// </summary>
IResourceStore<byte[]> Files { get; }
/// <summary>
/// Create a texture loader store based on an underlying data store.
/// </summary>
/// <param name="underlyingStore">The underlying provider of texture data (in arbitrary image formats).</param>
/// <returns>A texture loader store.</returns>
IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore);
}
}

View File

@ -9,14 +9,14 @@ namespace osu.Game.Online.API.Requests
public class GetUserRequest : APIRequest<User> public class GetUserRequest : APIRequest<User>
{ {
private readonly long? userId; private readonly long? userId;
private readonly RulesetInfo ruleset; public readonly RulesetInfo Ruleset;
public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
{ {
this.userId = userId; this.userId = userId;
this.ruleset = ruleset; Ruleset = ruleset;
} }
protected override string Target => userId.HasValue ? $@"users/{userId}/{ruleset?.ShortName}" : $@"me/{ruleset?.ShortName}"; protected override string Target => userId.HasValue ? $@"users/{userId}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}";
} }
} }

View File

@ -80,6 +80,9 @@ namespace osu.Game
private BeatmapSetOverlay beatmapSetOverlay; private BeatmapSetOverlay beatmapSetOverlay;
[Cached]
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
[Cached] [Cached]
private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
@ -335,15 +338,17 @@ namespace osu.Game
/// The user should have already requested this interactively. /// The user should have already requested this interactively.
/// </summary> /// </summary>
/// <param name="beatmap">The beatmap to select.</param> /// <param name="beatmap">The beatmap to select.</param>
/// <param name="difficultyCriteria"> /// <param name="difficultyCriteria">Optional predicate used to narrow the set of difficulties to select from when presenting.</param>
/// Optional predicate used to try and find a difficulty to select. /// <remarks>
/// If omitted, this will try to present the first beatmap from the current ruleset. /// Among items satisfying the predicate, the order of preference is:
/// In case of failure the first difficulty of the set will be presented, ignoring the predicate. /// <list type="bullet">
/// </param> /// <item>beatmap with recommended difficulty, as provided by <see cref="DifficultyRecommender"/>,</item>
/// <item>first beatmap from the current ruleset,</item>
/// <item>first beatmap from any ruleset.</item>
/// </list>
/// </remarks>
public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null) public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null)
{ {
difficultyCriteria ??= b => b.Ruleset.Equals(Ruleset.Value);
var databasedSet = beatmap.OnlineBeatmapSetID != null var databasedSet = beatmap.OnlineBeatmapSetID != null
? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
@ -361,16 +366,23 @@ namespace osu.Game
menuScreen.LoadToSolo(); menuScreen.LoadToSolo();
// we might even already be at the song // we might even already be at the song
if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && difficultyCriteria(Beatmap.Value.BeatmapInfo)) if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true))
{
return; return;
}
// Find first beatmap that matches our predicate. // Find beatmaps that match our predicate.
var first = databasedSet.Beatmaps.Find(difficultyCriteria) ?? databasedSet.Beatmaps.First(); var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList();
Ruleset.Value = first.Ruleset; // Use all beatmaps if predicate matched nothing
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); if (beatmaps.Count == 0)
beatmaps = databasedSet.Beatmaps;
// Prefer recommended beatmap if recommendations are available, else fallback to a sane selection.
var selection = difficultyRecommender.GetRecommendedBeatmap(beatmaps)
?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value))
?? beatmaps.First();
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
}, validScreens: new[] { typeof(PlaySongSelect) }); }, validScreens: new[] { typeof(PlaySongSelect) });
} }
@ -630,6 +642,8 @@ namespace osu.Game
GetStableStorage = GetStorageForStableInstall GetStableStorage = GetStorageForStableInstall
}, Add, true); }, Add, true);
loadComponentSingleFile(difficultyRecommender, Add);
loadComponentSingleFile(screenshotManager, Add); loadComponentSingleFile(screenshotManager, Add);
// dependency on notification overlay, dependent by settings overlay // dependency on notification overlay, dependent by settings overlay

View File

@ -1,7 +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;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -25,9 +25,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private FillFlowContainer<SettingsSlider<float>> scalingSettings; private FillFlowContainer<SettingsSlider<float>> scalingSettings;
private readonly IBindable<Display> currentDisplay = new Bindable<Display>();
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
private Bindable<ScalingMode> scalingMode; private Bindable<ScalingMode> scalingMode;
private Bindable<Size> sizeFullscreen; private Bindable<Size> sizeFullscreen;
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
private readonly BindableList<Size> resolutions = new BindableList<Size>(new[] { new Size(9999, 9999) });
[Resolved] [Resolved]
private OsuGameBase game { get; set; } private OsuGameBase game { get; set; }
@ -53,9 +57,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY); scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
if (host.Window != null) if (host.Window != null)
{
currentDisplay.BindTo(host.Window.CurrentDisplayBindable);
windowModes.BindTo(host.Window.SupportedWindowModes); windowModes.BindTo(host.Window.SupportedWindowModes);
}
Container resolutionSettingsContainer;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -65,10 +70,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
ItemSource = windowModes, ItemSource = windowModes,
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode), Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
}, },
resolutionSettingsContainer = new Container resolutionDropdown = new ResolutionSettingsDropdown
{ {
RelativeSizeAxes = Axes.X, LabelText = "Resolution",
AutoSizeAxes = Axes.Y ShowsDefaultIndicator = false,
ItemSource = resolutions,
Current = sizeFullscreen
}, },
new SettingsSlider<float, UIScaleSlider> new SettingsSlider<float, UIScaleSlider>
{ {
@ -126,32 +133,34 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}, },
}; };
scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); windowModes.BindCollectionChanged((sender, args) =>
var resolutions = getResolutions();
if (resolutions.Count > 1)
{ {
resolutionSettingsContainer.Child = resolutionDropdown = new ResolutionSettingsDropdown if (windowModes.Count > 1)
{ windowModeDropdown.Show();
LabelText = "Resolution",
ShowsDefaultIndicator = false,
Items = resolutions,
Current = sizeFullscreen
};
windowModeDropdown.Current.BindValueChanged(mode =>
{
if (mode.NewValue == WindowMode.Fullscreen)
{
resolutionDropdown.Show();
sizeFullscreen.TriggerChange();
}
else else
resolutionDropdown.Hide(); windowModeDropdown.Hide();
}, true); }, true);
windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown();
currentDisplay.BindValueChanged(display => Schedule(() =>
{
resolutions.RemoveRange(1, resolutions.Count - 1);
if (display.NewValue != null)
{
resolutions.AddRange(display.NewValue.DisplayModes
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
.OrderByDescending(m => Math.Max(m.Size.Height, m.Size.Width))
.Select(m => m.Size)
.Distinct());
} }
updateResolutionDropdown();
}), true);
scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
scalingMode.BindValueChanged(mode => scalingMode.BindValueChanged(mode =>
{ {
scalingSettings.ClearTransforms(); scalingSettings.ClearTransforms();
@ -163,17 +172,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything); scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything);
}, true); }, true);
windowModes.CollectionChanged += (sender, args) => windowModesChanged(); void updateResolutionDropdown()
windowModesChanged();
}
private void windowModesChanged()
{ {
if (windowModes.Count > 1) if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen)
windowModeDropdown.Show(); resolutionDropdown.Show();
else else
windowModeDropdown.Hide(); resolutionDropdown.Hide();
}
} }
/// <summary> /// <summary>
@ -205,24 +210,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
preview.Expire(); preview.Expire();
} }
private IReadOnlyList<Size> getResolutions()
{
var resolutions = new List<Size> { new Size(9999, 9999) };
var currentDisplay = game.Window?.CurrentDisplayBindable.Value;
if (currentDisplay != null)
{
resolutions.AddRange(currentDisplay.DisplayModes
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
.OrderByDescending(m => m.Size.Width)
.ThenByDescending(m => m.Size.Height)
.Select(m => m.Size)
.Distinct());
}
return resolutions;
}
private class ScalingPreview : ScalingContainer private class ScalingPreview : ScalingContainer
{ {
public ScalingPreview() public ScalingPreview()

View File

@ -5,6 +5,8 @@ namespace osu.Game.Rulesets
{ {
public interface ILegacyRuleset public interface ILegacyRuleset
{ {
const int MAX_LEGACY_RULESET_ID = 3;
/// <summary> /// <summary>
/// Identifies the server-side ID of a legacy ruleset. /// Identifies the server-side ID of a legacy ruleset.
/// </summary> /// </summary>

View File

@ -30,7 +30,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants
[Resolved] [Resolved]
private IAPIProvider api { get; set; } private IAPIProvider api { get; set; }
private ReadyMark readyMark; private StateDisplay userStateDisplay;
private SpriteIcon crown; private SpriteIcon crown;
public ParticipantPanel(MultiplayerRoomUser user) public ParticipantPanel(MultiplayerRoomUser user)
@ -122,12 +122,11 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants
} }
} }
}, },
readyMark = new ReadyMark userStateDisplay = new StateDisplay
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 10 }, Margin = new MarginPadding { Right = 10 },
Alpha = 0
} }
} }
} }
@ -144,10 +143,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants
const double fade_time = 50; const double fade_time = 50;
if (User.State == MultiplayerUserState.Ready) userStateDisplay.Status = User.State;
readyMark.FadeIn(fade_time);
else
readyMark.FadeOut(fade_time);
if (Room.Host?.Equals(User) == true) if (Room.Host?.Equals(User) == true)
crown.FadeIn(fade_time); crown.FadeIn(fade_time);

View File

@ -1,51 +0,0 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants
{
public class ReadyMark : CompositeDrawable
{
public ReadyMark()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5),
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 12),
Text = "ready",
Colour = Color4Extensions.FromHex("#DDFFFF")
},
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = FontAwesome.Solid.CheckCircle,
Size = new Vector2(12),
Colour = Color4Extensions.FromHex("#AADD00")
}
}
};
}
}
}

View File

@ -0,0 +1,129 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.RealtimeMultiplayer;
using osuTK;
namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants
{
public class StateDisplay : CompositeDrawable
{
public StateDisplay()
{
AutoSizeAxes = Axes.Both;
Alpha = 0;
}
private MultiplayerUserState status;
private OsuSpriteText text;
private SpriteIcon icon;
private const double fade_time = 50;
public MultiplayerUserState Status
{
set
{
if (value == status)
return;
status = value;
if (IsLoaded)
updateStatus();
}
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 12),
Colour = Color4Extensions.FromHex("#DDFFFF")
},
icon = new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = FontAwesome.Solid.CheckCircle,
Size = new Vector2(12),
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updateStatus();
}
[Resolved]
private OsuColour colours { get; set; }
private void updateStatus()
{
switch (status)
{
default:
this.FadeOut(fade_time);
return;
case MultiplayerUserState.Ready:
text.Text = "ready";
icon.Icon = FontAwesome.Solid.CheckCircle;
icon.Colour = Color4Extensions.FromHex("#AADD00");
break;
case MultiplayerUserState.WaitingForLoad:
text.Text = "loading";
icon.Icon = FontAwesome.Solid.PauseCircle;
icon.Colour = colours.Yellow;
break;
case MultiplayerUserState.Loaded:
text.Text = "loaded";
icon.Icon = FontAwesome.Solid.DotCircle;
icon.Colour = colours.YellowLight;
break;
case MultiplayerUserState.Playing:
text.Text = "playing";
icon.Icon = FontAwesome.Solid.PlayCircle;
icon.Colour = colours.BlueLight;
break;
case MultiplayerUserState.FinishedPlay:
text.Text = "results pending";
icon.Icon = FontAwesome.Solid.ArrowAltCircleUp;
icon.Colour = colours.BlueLighter;
break;
case MultiplayerUserState.Results:
text.Text = "results";
icon.Icon = FontAwesome.Solid.ArrowAltCircleUp;
icon.Colour = colours.BlueLighter;
break;
}
this.FadeIn(fade_time);
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Online.RealtimeMultiplayer;
using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Components;
@ -41,7 +42,17 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError); => base.CreateRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError);
public override void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) public override void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
=> base.JoinRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError); {
// this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join.
// should probably be done at a higher level, but due to the current structure of things this is the easiest place for now.
if (room.Status.Value is RoomStatusEnded)
{
onError?.Invoke("Cannot join an ended room.");
return;
}
base.JoinRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError);
}
public override void PartRoom() public override void PartRoom()
{ {

View File

@ -80,8 +80,6 @@ namespace osu.Game.Screens.Select
protected BeatmapCarousel Carousel { get; private set; } protected BeatmapCarousel Carousel { get; private set; }
private readonly DifficultyRecommender recommender = new DifficultyRecommender();
private BeatmapInfoWedge beatmapInfoWedge; private BeatmapInfoWedge beatmapInfoWedge;
private DialogOverlay dialogOverlay; private DialogOverlay dialogOverlay;
@ -105,7 +103,7 @@ namespace osu.Game.Screens.Select
private MusicController music { get; set; } private MusicController music { get; set; }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections, ManageCollectionsDialog manageCollectionsDialog) private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
{ {
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
transferRulesetValue(); transferRulesetValue();
@ -120,12 +118,11 @@ namespace osu.Game.Screens.Select
BleedBottom = Footer.HEIGHT, BleedBottom = Footer.HEIGHT,
SelectionChanged = updateSelectedBeatmap, SelectionChanged = updateSelectedBeatmap,
BeatmapSetsChanged = carouselBeatmapsLoaded, BeatmapSetsChanged = carouselBeatmapsLoaded,
GetRecommendedBeatmap = recommender.GetRecommendedBeatmap, GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
}, c => carouselContainer.Child = c); }, c => carouselContainer.Child = c);
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
recommender,
new ResetScrollContainer(() => Carousel.ScrollToSelected()) new ResetScrollContainer(() => Carousel.ScrollToSelected())
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,

View File

@ -1,16 +1,16 @@
// 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.Audio;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.IO;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public class DefaultLegacySkin : LegacySkin public class DefaultLegacySkin : LegacySkin
{ {
public DefaultLegacySkin(IResourceStore<byte[]> storage, AudioManager audioManager) public DefaultLegacySkin(IResourceStore<byte[]> storage, IStorageResourceProvider resources)
: base(Info, storage, audioManager, string.Empty) : base(Info, storage, resources, string.Empty)
{ {
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
Configuration.AddComboColours( Configuration.AddComboColours(

View File

@ -1,12 +1,12 @@
// 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.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
namespace osu.Game.Skinning namespace osu.Game.Skinning
@ -16,8 +16,8 @@ namespace osu.Game.Skinning
protected override bool AllowManiaSkin => false; protected override bool AllowManiaSkin => false;
protected override bool UseCustomSampleBanks => true; protected override bool UseCustomSampleBanks => true;
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, AudioManager audioManager) public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), audioManager, beatmap.Path) : base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), resources, beatmap.Path)
{ {
// Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer) // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
Configuration.AllowDefaultComboColoursFallback = false; Configuration.AllowDefaultComboColoursFallback = false;

View File

@ -7,7 +7,6 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -54,12 +53,12 @@ namespace osu.Game.Skinning
private readonly Dictionary<int, LegacyManiaSkinConfiguration> maniaConfigurations = new Dictionary<int, LegacyManiaSkinConfiguration>(); private readonly Dictionary<int, LegacyManiaSkinConfiguration> maniaConfigurations = new Dictionary<int, LegacyManiaSkinConfiguration>();
public LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager) public LegacySkin(SkinInfo skin, IStorageResourceProvider resources)
: this(skin, new LegacySkinResourceStore<SkinFileInfo>(skin, storage), audioManager, "skin.ini") : this(skin, new LegacySkinResourceStore<SkinFileInfo>(skin, resources.Files), resources, "skin.ini")
{ {
} }
protected LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager, string filename) protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, string filename)
: base(skin) : base(skin)
{ {
using (var stream = storage?.GetStream(filename)) using (var stream = storage?.GetStream(filename))
@ -85,12 +84,12 @@ namespace osu.Game.Skinning
if (storage != null) if (storage != null)
{ {
var samples = audioManager?.GetSampleStore(storage); var samples = resources?.AudioManager?.GetSampleStore(storage);
if (samples != null) if (samples != null)
samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
Samples = samples; Samples = samples;
Textures = new TextureStore(new TextureLoaderStore(storage)); Textures = new TextureStore(resources?.CreateTextureLoaderStore(storage));
(storage as ResourceStore<byte[]>)?.AddExtension("ogg"); (storage as ResourceStore<byte[]>)?.AddExtension("ogg");
} }

View File

@ -22,15 +22,18 @@ using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource, IStorageResourceProvider
{ {
private readonly AudioManager audio; private readonly AudioManager audio;
private readonly GameHost host;
private readonly IResourceStore<byte[]> legacyDefaultResources; private readonly IResourceStore<byte[]> legacyDefaultResources;
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin()); public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin());
@ -42,10 +45,12 @@ namespace osu.Game.Skinning
protected override string ImportFromStablePath => "Skins"; protected override string ImportFromStablePath => "Skins";
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio, IResourceStore<byte[]> legacyDefaultResources) public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, AudioManager audio, IResourceStore<byte[]> legacyDefaultResources)
: base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
{ {
this.audio = audio; this.audio = audio;
this.host = host;
this.legacyDefaultResources = legacyDefaultResources; this.legacyDefaultResources = legacyDefaultResources;
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
@ -148,9 +153,9 @@ namespace osu.Game.Skinning
return new DefaultSkin(); return new DefaultSkin();
if (skinInfo == DefaultLegacySkin.Info) if (skinInfo == DefaultLegacySkin.Info)
return new DefaultLegacySkin(legacyDefaultResources, audio); return new DefaultLegacySkin(legacyDefaultResources, this);
return new LegacySkin(skinInfo, Files.Store, audio); return new LegacySkin(skinInfo, this);
} }
/// <summary> /// <summary>
@ -169,5 +174,13 @@ namespace osu.Game.Skinning
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => CurrentSkin.Value.GetConfig<TLookup, TValue>(lookup); public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => CurrentSkin.Value.GetConfig<TLookup, TValue>(lookup);
#region IResourceStorageProvider
AudioManager IStorageResourceProvider.AudioManager => audio;
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
#endregion
} }
} }

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -25,7 +26,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Beatmaps namespace osu.Game.Tests.Beatmaps
{ {
[HeadlessTest] [HeadlessTest]
public abstract class HitObjectSampleTest : PlayerTestScene public abstract class HitObjectSampleTest : PlayerTestScene, IStorageResourceProvider
{ {
protected abstract IResourceStore<byte[]> Resources { get; } protected abstract IResourceStore<byte[]> Resources { get; }
protected LegacySkin Skin { get; private set; } protected LegacySkin Skin { get; private set; }
@ -58,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps
protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap;
protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio); => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, this);
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
@ -109,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps
}; };
// Need to refresh the cached skin source to refresh the skin resource store. // Need to refresh the cached skin source to refresh the skin resource store.
dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this));
}); });
} }
@ -122,6 +123,14 @@ namespace osu.Game.Tests.Beatmaps
protected void AssertNoLookup(string name) => AddAssert($"\"{name}\" not looked up", protected void AssertNoLookup(string name) => AddAssert($"\"{name}\" not looked up",
() => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && !userSkinResourceStore.PerformedLookups.Contains(name)); () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && !userSkinResourceStore.PerformedLookups.Contains(name));
#region IResourceStorageProvider
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => userSkinResourceStore;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
#endregion
private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer
{ {
public ISkinSource SkinSource; public ISkinSource SkinSource;
@ -191,14 +200,17 @@ namespace osu.Game.Tests.Beatmaps
private readonly BeatmapInfo skinBeatmapInfo; private readonly BeatmapInfo skinBeatmapInfo;
private readonly IResourceStore<byte[]> resourceStore; private readonly IResourceStore<byte[]> resourceStore;
public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore<byte[]> resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) private readonly IStorageResourceProvider resources;
: base(beatmap, storyboard, referenceClock, audio)
public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore<byte[]> resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, IStorageResourceProvider resources)
: base(beatmap, storyboard, referenceClock, resources.AudioManager)
{ {
this.skinBeatmapInfo = skinBeatmapInfo; this.skinBeatmapInfo = skinBeatmapInfo;
this.resourceStore = resourceStore; this.resourceStore = resourceStore;
this.resources = resources;
} }
protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, resources);
} }
} }
} }

View File

@ -3,7 +3,6 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -18,9 +17,9 @@ namespace osu.Game.Tests.Visual
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, OsuGameBase game) private void load(OsuGameBase game, SkinManager skins)
{ {
var legacySkin = new DefaultLegacySkin(new NamespacedResourceStore<byte[]>(game.Resources, "Skins/Legacy"), audio); var legacySkin = new DefaultLegacySkin(new NamespacedResourceStore<byte[]>(game.Resources, "Skins/Legacy"), skins);
legacySkinSource = new SkinProvidingContainer(legacySkin); legacySkinSource = new SkinProvidingContainer(legacySkin);
} }

View File

@ -13,8 +13,10 @@ using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -22,13 +24,16 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
public abstract class SkinnableTestScene : OsuGridTestScene public abstract class SkinnableTestScene : OsuGridTestScene, IStorageResourceProvider
{ {
private Skin metricsSkin; private Skin metricsSkin;
private Skin defaultSkin; private Skin defaultSkin;
private Skin specialSkin; private Skin specialSkin;
private Skin oldSkin; private Skin oldSkin;
[Resolved]
private GameHost host { get; set; }
protected SkinnableTestScene() protected SkinnableTestScene()
: base(2, 3) : base(2, 3)
{ {
@ -39,10 +44,10 @@ namespace osu.Game.Tests.Visual
{ {
var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), this, true);
defaultSkin = new DefaultLegacySkin(new NamespacedResourceStore<byte[]>(game.Resources, "Skins/Legacy"), audio); defaultSkin = new DefaultLegacySkin(new NamespacedResourceStore<byte[]>(game.Resources, "Skins/Legacy"), this);
specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/special_skin"), audio, true); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/special_skin"), this, true);
oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/old_skin"), audio, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/old_skin"), this, true);
} }
private readonly List<Drawable> createdDrawables = new List<Drawable>(); private readonly List<Drawable> createdDrawables = new List<Drawable>();
@ -147,6 +152,14 @@ namespace osu.Game.Tests.Visual
protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value); protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value);
#region IResourceStorageProvider
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => null;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
#endregion
private class OutlineBox : CompositeDrawable private class OutlineBox : CompositeDrawable
{ {
public OutlineBox() public OutlineBox()
@ -170,8 +183,8 @@ namespace osu.Game.Tests.Visual
{ {
private readonly bool extrapolateAnimations; private readonly bool extrapolateAnimations;
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager, bool extrapolateAnimations) public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources, bool extrapolateAnimations)
: base(skin, storage, audioManager, "skin.ini") : base(skin, storage, resources, "skin.ini")
{ {
this.extrapolateAnimations = extrapolateAnimations; this.extrapolateAnimations = extrapolateAnimations;
} }