mirror of
https://github.com/osukey/osukey.git
synced 2025-06-05 12:57:39 +09:00
Merge branch 'master' into countdown-button-ux
This commit is contained in:
commit
03f24c8b58
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.304.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.325.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.314.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.325.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. -->
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
|
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
|
||||||
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
|
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
|
||||||
: base(skin, storage, null, "skin.ini")
|
: base(skin, null, storage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
private class TestLegacySkin : LegacySkin
|
private class TestLegacySkin : LegacySkin
|
||||||
{
|
{
|
||||||
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
|
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
|
||||||
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
|
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, storage, fileName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
@ -64,6 +65,62 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(3, true)]
|
||||||
|
[TestCase(6, false)]
|
||||||
|
[TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)]
|
||||||
|
public void TestLegacyBeatmapReplayOffsetsDecode(int beatmapVersion, bool offsetApplied)
|
||||||
|
{
|
||||||
|
const double first_frame_time = 48;
|
||||||
|
const double second_frame_time = 65;
|
||||||
|
|
||||||
|
var decoder = new TestLegacyScoreDecoder(beatmapVersion);
|
||||||
|
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||||
|
{
|
||||||
|
var score = decoder.Parse(resourceStream);
|
||||||
|
|
||||||
|
Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0)));
|
||||||
|
Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3)]
|
||||||
|
[TestCase(6)]
|
||||||
|
[TestCase(LegacyBeatmapDecoder.LATEST_VERSION)]
|
||||||
|
public void TestLegacyBeatmapReplayOffsetsEncodeDecode(int beatmapVersion)
|
||||||
|
{
|
||||||
|
const double first_frame_time = 2000;
|
||||||
|
const double second_frame_time = 3000;
|
||||||
|
|
||||||
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||||
|
var beatmap = new TestBeatmap(ruleset)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
BeatmapVersion = beatmapVersion
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var score = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = scoreInfo,
|
||||||
|
Replay = new Replay
|
||||||
|
{
|
||||||
|
Frames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(first_frame_time, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(second_frame_time, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var decodedAfterEncode = encodeThenDecode(beatmapVersion, score, beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(first_frame_time));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(second_frame_time));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCultureInvariance()
|
public void TestCultureInvariance()
|
||||||
{
|
{
|
||||||
@ -86,15 +143,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
// rather than the classic ASCII U+002D HYPHEN-MINUS.
|
// rather than the classic ASCII U+002D HYPHEN-MINUS.
|
||||||
CultureInfo.CurrentCulture = new CultureInfo("se");
|
CultureInfo.CurrentCulture = new CultureInfo("se");
|
||||||
|
|
||||||
var encodeStream = new MemoryStream();
|
var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
|
||||||
|
|
||||||
var encoder = new LegacyScoreEncoder(score, beatmap);
|
|
||||||
encoder.Encode(encodeStream);
|
|
||||||
|
|
||||||
var decodeStream = new MemoryStream(encodeStream.GetBuffer());
|
|
||||||
|
|
||||||
var decoder = new TestLegacyScoreDecoder();
|
|
||||||
var decodedAfterEncode = decoder.Parse(decodeStream);
|
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -110,6 +159,20 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var encodeStream = new MemoryStream();
|
||||||
|
|
||||||
|
var encoder = new LegacyScoreEncoder(score, beatmap);
|
||||||
|
encoder.Encode(encodeStream);
|
||||||
|
|
||||||
|
var decodeStream = new MemoryStream(encodeStream.GetBuffer());
|
||||||
|
|
||||||
|
var decoder = new TestLegacyScoreDecoder(beatmapVersion);
|
||||||
|
var decodedAfterEncode = decoder.Parse(decodeStream);
|
||||||
|
return decodedAfterEncode;
|
||||||
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
public void TearDown()
|
public void TearDown()
|
||||||
{
|
{
|
||||||
@ -118,6 +181,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
private class TestLegacyScoreDecoder : LegacyScoreDecoder
|
private class TestLegacyScoreDecoder : LegacyScoreDecoder
|
||||||
{
|
{
|
||||||
|
private readonly int beatmapVersion;
|
||||||
|
|
||||||
private static readonly Dictionary<int, Ruleset> rulesets = new Ruleset[]
|
private static readonly Dictionary<int, Ruleset> rulesets = new Ruleset[]
|
||||||
{
|
{
|
||||||
new OsuRuleset(),
|
new OsuRuleset(),
|
||||||
@ -126,6 +191,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
new ManiaRuleset()
|
new ManiaRuleset()
|
||||||
}.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID);
|
}.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID);
|
||||||
|
|
||||||
|
public TestLegacyScoreDecoder(int beatmapVersion = LegacyBeatmapDecoder.LATEST_VERSION)
|
||||||
|
{
|
||||||
|
this.beatmapVersion = beatmapVersion;
|
||||||
|
}
|
||||||
|
|
||||||
protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId];
|
protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId];
|
||||||
|
|
||||||
protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap
|
protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap
|
||||||
@ -134,7 +204,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
MD5Hash = md5Hash,
|
MD5Hash = md5Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
Difficulty = new BeatmapDifficulty()
|
Difficulty = new BeatmapDifficulty(),
|
||||||
|
BeatmapVersion = beatmapVersion,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,10 @@ namespace osu.Game.Tests.Database
|
|||||||
Live<BeatmapSetInfo>? imported;
|
Live<BeatmapSetInfo>? imported;
|
||||||
|
|
||||||
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
||||||
|
{
|
||||||
imported = await importer.Import(reader);
|
imported = await importer.Import(reader);
|
||||||
|
EnsureLoaded(realm.Realm);
|
||||||
|
}
|
||||||
|
|
||||||
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count());
|
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count());
|
||||||
|
|
||||||
@ -510,6 +513,8 @@ namespace osu.Game.Tests.Database
|
|||||||
new ImportTask(zipStream, string.Empty)
|
new ImportTask(zipStream, string.Empty)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
checkBeatmapSetCount(realm.Realm, 0);
|
checkBeatmapSetCount(realm.Realm, 0);
|
||||||
checkBeatmapCount(realm.Realm, 0);
|
checkBeatmapCount(realm.Realm, 0);
|
||||||
|
|
||||||
@ -565,6 +570,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
checkBeatmapSetCount(realm.Realm, 1);
|
checkBeatmapSetCount(realm.Realm, 1);
|
||||||
checkBeatmapCount(realm.Realm, 12);
|
checkBeatmapCount(realm.Realm, 12);
|
||||||
|
|
||||||
@ -726,6 +733,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var imported = importer.Import(toImport);
|
var imported = importer.Import(toImport);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
Assert.NotNull(imported);
|
Assert.NotNull(imported);
|
||||||
Debug.Assert(imported != null);
|
Debug.Assert(imported != null);
|
||||||
|
|
||||||
@ -891,6 +900,8 @@ namespace osu.Game.Tests.Database
|
|||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
await importer.Import(temp);
|
await importer.Import(temp);
|
||||||
|
|
||||||
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
// Update via the beatmap, not the beatmap info, to ensure correct linking
|
// Update via the beatmap, not the beatmap info, to ensure correct linking
|
||||||
BeatmapSetInfo setToUpdate = realm.Realm.All<BeatmapSetInfo>().First();
|
BeatmapSetInfo setToUpdate = realm.Realm.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
private class TestSkin : LegacySkin
|
private class TestSkin : LegacySkin
|
||||||
{
|
{
|
||||||
public TestSkin(string resourceName, IStorageResourceProvider resources)
|
public TestSkin(string resourceName, IStorageResourceProvider resources)
|
||||||
: base(DefaultLegacySkin.CreateInfo(), new TestResourceStore(resourceName), resources, "skin.ini")
|
: base(DefaultLegacySkin.CreateInfo(), resources, new TestResourceStore(resourceName))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
namespace osu.Game.Tests.NonVisual.Skinning
|
namespace osu.Game.Tests.NonVisual.Skinning
|
||||||
{
|
{
|
||||||
@ -71,7 +80,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
var texture = legacySkin.GetTexture(requestedComponent);
|
var texture = legacySkin.GetTexture(requestedComponent);
|
||||||
|
|
||||||
Assert.IsNotNull(texture);
|
Assert.IsNotNull(texture);
|
||||||
Assert.AreEqual(textureStore.Textures[expectedTexture], texture);
|
Assert.AreEqual(textureStore.Textures[expectedTexture].Width, texture.Width);
|
||||||
Assert.AreEqual(expectedScale, texture.ScaleAdjust);
|
Assert.AreEqual(expectedScale, texture.ScaleAdjust);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,23 +97,50 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
|
|
||||||
private class TestLegacySkin : LegacySkin
|
private class TestLegacySkin : LegacySkin
|
||||||
{
|
{
|
||||||
public TestLegacySkin(TextureStore textureStore)
|
public TestLegacySkin(IResourceStore<TextureUpload> textureStore)
|
||||||
: base(new SkinInfo(), null, null, string.Empty)
|
: base(new SkinInfo(), new TestResourceProvider(textureStore), null, string.Empty)
|
||||||
{
|
{
|
||||||
Textures = textureStore;
|
}
|
||||||
|
|
||||||
|
private class TestResourceProvider : IStorageResourceProvider
|
||||||
|
{
|
||||||
|
private readonly IResourceStore<TextureUpload> textureStore;
|
||||||
|
|
||||||
|
public TestResourceProvider(IResourceStore<TextureUpload> textureStore)
|
||||||
|
{
|
||||||
|
this.textureStore = textureStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioManager AudioManager => null;
|
||||||
|
public IResourceStore<byte[]> Files => null;
|
||||||
|
public IResourceStore<byte[]> Resources => null;
|
||||||
|
public RealmAccess RealmAccess => null;
|
||||||
|
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => textureStore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestTextureStore : TextureStore
|
private class TestTextureStore : IResourceStore<TextureUpload>
|
||||||
{
|
{
|
||||||
public readonly Dictionary<string, Texture> Textures;
|
public readonly Dictionary<string, TextureUpload> Textures;
|
||||||
|
|
||||||
public TestTextureStore(params string[] fileNames)
|
public TestTextureStore(params string[] fileNames)
|
||||||
{
|
{
|
||||||
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1));
|
// use an incrementing width to allow assertion matching on correct textures as they turn from uploads into actual textures.
|
||||||
|
int width = 1;
|
||||||
|
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new TextureUpload(new Image<Rgba32>(width, width++)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => Textures.GetValueOrDefault(name);
|
public TextureUpload Get(string name) => Textures.GetValueOrDefault(name);
|
||||||
|
|
||||||
|
public Task<TextureUpload> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => Task.FromResult(Get(name));
|
||||||
|
|
||||||
|
public Stream GetStream(string name) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
public class BeatmapSkinSource : LegacyBeatmapSkin
|
public class BeatmapSkinSource : LegacyBeatmapSkin
|
||||||
{
|
{
|
||||||
public BeatmapSkinSource()
|
public BeatmapSkinSource()
|
||||||
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
|
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
public class BeatmapSkinSource : LegacyBeatmapSkin
|
public class BeatmapSkinSource : LegacyBeatmapSkin
|
||||||
{
|
{
|
||||||
public BeatmapSkinSource()
|
public BeatmapSkinSource()
|
||||||
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
|
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
{
|
{
|
||||||
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
||||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
||||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
AddUntilStep("countdown button visible", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
AddUntilStep("countdown button visible", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
||||||
|
|
||||||
AddStep("enable auto start", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { AutoStartDuration = TimeSpan.FromMinutes(1) }));
|
AddStep("enable auto start", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { AutoStartDuration = TimeSpan.FromMinutes(1) }).WaitSafely());
|
||||||
|
|
||||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestClickingReadyButtonUnReadiesDuringAutoStart()
|
public void TestClickingReadyButtonUnReadiesDuringAutoStart()
|
||||||
{
|
{
|
||||||
AddStep("enable auto start", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { AutoStartDuration = TimeSpan.FromMinutes(1) }));
|
AddStep("enable auto start", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { AutoStartDuration = TimeSpan.FromMinutes(1) }).WaitSafely());
|
||||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
|
@ -163,6 +163,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHostGetsPinnedToTop()
|
||||||
|
{
|
||||||
|
AddStep("add user", () => MultiplayerClient.AddUser(new APIUser
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "Second",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
|
||||||
|
AddAssert("second user above first", () =>
|
||||||
|
{
|
||||||
|
var first = this.ChildrenOfType<ParticipantPanel>().ElementAt(0);
|
||||||
|
var second = this.ChildrenOfType<ParticipantPanel>().ElementAt(1);
|
||||||
|
return second.Y < first.Y;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestKickButtonOnlyPresentWhenHost()
|
public void TestKickButtonOnlyPresentWhenHost()
|
||||||
{
|
{
|
||||||
@ -202,9 +221,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestManyUsers()
|
public void TestManyUsers()
|
||||||
{
|
{
|
||||||
|
const int users_count = 20;
|
||||||
|
|
||||||
AddStep("add many users", () =>
|
AddStep("add many users", () =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < users_count; i++)
|
||||||
{
|
{
|
||||||
MultiplayerClient.AddUser(new APIUser
|
MultiplayerClient.AddUser(new APIUser
|
||||||
{
|
{
|
||||||
@ -243,6 +264,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("switch hosts", () => MultiplayerClient.TransferHost(RNG.Next(0, users_count)), 10);
|
||||||
|
AddStep("give host back", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneModSettingsArea : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModToggleArea()
|
||||||
|
{
|
||||||
|
ModSettingsArea modSettingsArea = null;
|
||||||
|
|
||||||
|
AddStep("create content", () => Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Child = modSettingsArea = new ModSettingsArea()
|
||||||
|
});
|
||||||
|
AddStep("set DT", () => modSettingsArea.SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
||||||
|
AddStep("set DA", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
|
||||||
|
AddStep("set FL+WU+DA+AD", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() });
|
||||||
|
AddStep("set empty", () => modSettingsArea.SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true);
|
dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true);
|
||||||
|
|
||||||
Action = () => game.GracefullyExit();
|
Action = () => game.GracefullyExit();
|
||||||
folderButton.Action = storage.PresentExternally;
|
folderButton.Action = () => storage.PresentExternally();
|
||||||
|
|
||||||
ButtonText = "Close osu!";
|
ButtonText = "Close osu!";
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
public class LegacyBeatmapDecoder : LegacyDecoder<Beatmap>
|
public class LegacyBeatmapDecoder : LegacyDecoder<Beatmap>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An offset which needs to be applied to old beatmaps (v4 and lower) to correct timing changes that were applied at a game client level.
|
||||||
|
/// </summary>
|
||||||
|
public const int EARLY_VERSION_TIMING_OFFSET = 24;
|
||||||
|
|
||||||
internal static RulesetStore RulesetStore;
|
internal static RulesetStore RulesetStore;
|
||||||
|
|
||||||
private Beatmap beatmap;
|
private Beatmap beatmap;
|
||||||
@ -50,8 +55,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
RulesetStore = new AssemblyRulesetStore();
|
RulesetStore = new AssemblyRulesetStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
offset = FormatVersion < 5 ? EARLY_VERSION_TIMING_OFFSET : 0;
|
||||||
offset = FormatVersion < 5 ? 24 : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Beatmap CreateTemplateObject()
|
protected override Beatmap CreateTemplateObject()
|
||||||
|
@ -225,7 +225,7 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new LegacyBeatmapSkin(BeatmapInfo, resources.Files, resources);
|
return new LegacyBeatmapSkin(BeatmapInfo, resources);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@ -17,6 +19,7 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Statistics;
|
using osu.Framework.Statistics;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -28,8 +31,6 @@ using osu.Game.Stores;
|
|||||||
using Realms;
|
using Realms;
|
||||||
using Realms.Exceptions;
|
using Realms.Exceptions;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -46,6 +47,8 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
private readonly IDatabaseContextFactory? efContextFactory;
|
private readonly IDatabaseContextFactory? efContextFactory;
|
||||||
|
|
||||||
|
private readonly SynchronizationContext? updateThreadSyncContext;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version history:
|
/// Version history:
|
||||||
/// 6 ~2021-10-18 First tracked version.
|
/// 6 ~2021-10-18 First tracked version.
|
||||||
@ -143,12 +146,15 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="storage">The game storage which will be used to create the realm backing file.</param>
|
/// <param name="storage">The game storage which will be used to create the realm backing file.</param>
|
||||||
/// <param name="filename">The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified.</param>
|
/// <param name="filename">The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified.</param>
|
||||||
|
/// <param name="updateThread">The game update thread, used to post realm operations into a thread-safe context.</param>
|
||||||
/// <param name="efContextFactory">An EF factory used only for migration purposes.</param>
|
/// <param name="efContextFactory">An EF factory used only for migration purposes.</param>
|
||||||
public RealmAccess(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null)
|
public RealmAccess(Storage storage, string filename, GameThread? updateThread = null, IDatabaseContextFactory? efContextFactory = null)
|
||||||
{
|
{
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.efContextFactory = efContextFactory;
|
this.efContextFactory = efContextFactory;
|
||||||
|
|
||||||
|
updateThreadSyncContext = updateThread?.SynchronizationContext ?? SynchronizationContext.Current;
|
||||||
|
|
||||||
Filename = filename;
|
Filename = filename;
|
||||||
|
|
||||||
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
||||||
@ -379,9 +385,6 @@ namespace osu.Game.Database
|
|||||||
public IDisposable RegisterForNotifications<T>(Func<Realm, IQueryable<T>> query, NotificationCallbackDelegate<T> callback)
|
public IDisposable RegisterForNotifications<T>(Func<Realm, IQueryable<T>> query, NotificationCallbackDelegate<T> callback)
|
||||||
where T : RealmObjectBase
|
where T : RealmObjectBase
|
||||||
{
|
{
|
||||||
if (!ThreadSafety.IsUpdateThread)
|
|
||||||
throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
|
|
||||||
|
|
||||||
lock (realmLock)
|
lock (realmLock)
|
||||||
{
|
{
|
||||||
Func<Realm, IDisposable?> action = realm => query(realm).QueryAsyncWithNotifications(callback);
|
Func<Realm, IDisposable?> action = realm => query(realm).QueryAsyncWithNotifications(callback);
|
||||||
@ -459,23 +462,24 @@ namespace osu.Game.Database
|
|||||||
/// <returns>An <see cref="IDisposable"/> which should be disposed to unsubscribe any inner subscription.</returns>
|
/// <returns>An <see cref="IDisposable"/> which should be disposed to unsubscribe any inner subscription.</returns>
|
||||||
public IDisposable RegisterCustomSubscription(Func<Realm, IDisposable?> action)
|
public IDisposable RegisterCustomSubscription(Func<Realm, IDisposable?> action)
|
||||||
{
|
{
|
||||||
if (!ThreadSafety.IsUpdateThread)
|
if (updateThreadSyncContext == null)
|
||||||
throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
|
throw new InvalidOperationException("Attempted to register a realm subscription before update thread registration.");
|
||||||
|
|
||||||
var syncContext = SynchronizationContext.Current;
|
|
||||||
|
|
||||||
total_subscriptions.Value++;
|
total_subscriptions.Value++;
|
||||||
|
|
||||||
registerSubscription(action);
|
if (ThreadSafety.IsUpdateThread)
|
||||||
|
updateThreadSyncContext.Send(_ => registerSubscription(action), null);
|
||||||
|
else
|
||||||
|
updateThreadSyncContext.Post(_ => registerSubscription(action), null);
|
||||||
|
|
||||||
// This token is returned to the consumer.
|
// This token is returned to the consumer.
|
||||||
// When disposed, it will cause the registration to be permanently ceased (unsubscribed with realm and unregistered by this class).
|
// When disposed, it will cause the registration to be permanently ceased (unsubscribed with realm and unregistered by this class).
|
||||||
return new InvokeOnDisposal(() =>
|
return new InvokeOnDisposal(() =>
|
||||||
{
|
{
|
||||||
if (ThreadSafety.IsUpdateThread)
|
if (ThreadSafety.IsUpdateThread)
|
||||||
syncContext.Send(_ => unsubscribe(), null);
|
updateThreadSyncContext.Send(_ => unsubscribe(), null);
|
||||||
else
|
else
|
||||||
syncContext.Post(_ => unsubscribe(), null);
|
updateThreadSyncContext.Post(_ => unsubscribe(), null);
|
||||||
|
|
||||||
void unsubscribe()
|
void unsubscribe()
|
||||||
{
|
{
|
||||||
|
@ -70,9 +70,9 @@ namespace osu.Game.IO
|
|||||||
public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) =>
|
public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) =>
|
||||||
UnderlyingStorage.GetStream(MutatePath(path), access, mode);
|
UnderlyingStorage.GetStream(MutatePath(path), access, mode);
|
||||||
|
|
||||||
public override void OpenFileExternally(string filename) => UnderlyingStorage.OpenFileExternally(MutatePath(filename));
|
public override bool OpenFileExternally(string filename) => UnderlyingStorage.OpenFileExternally(MutatePath(filename));
|
||||||
|
|
||||||
public override void PresentFileExternally(string filename) => UnderlyingStorage.PresentFileExternally(MutatePath(filename));
|
public override bool PresentFileExternally(string filename) => UnderlyingStorage.PresentFileExternally(MutatePath(filename));
|
||||||
|
|
||||||
public override Storage GetStorageForDirectory(string path)
|
public override Storage GetStorageForDirectory(string path)
|
||||||
{
|
{
|
||||||
|
@ -171,6 +171,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
Room = joinedRoom;
|
Room = joinedRoom;
|
||||||
APIRoom = room;
|
APIRoom = room;
|
||||||
|
|
||||||
|
Debug.Assert(joinedRoom.Playlist.Count > 0);
|
||||||
|
|
||||||
APIRoom.Playlist.Clear();
|
APIRoom.Playlist.Clear();
|
||||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||||
|
|
||||||
@ -686,6 +688,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
|
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
|
||||||
APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId);
|
APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId);
|
||||||
|
|
||||||
|
Debug.Assert(Room.Playlist.Count > 0);
|
||||||
|
|
||||||
ItemRemoved?.Invoke(playlistItemId);
|
ItemRemoved?.Invoke(playlistItemId);
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
});
|
});
|
||||||
|
@ -200,7 +200,7 @@ namespace osu.Game
|
|||||||
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
|
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
|
||||||
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
|
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
|
||||||
|
|
||||||
dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory));
|
dependencies.Cache(realm = new RealmAccess(Storage, "client", Host.UpdateThread, EFContextFactory));
|
||||||
|
|
||||||
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
|
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
|
||||||
dependencies.CacheAs<IRulesetStore>(RulesetStore);
|
dependencies.CacheAs<IRulesetStore>(RulesetStore);
|
||||||
|
176
osu.Game/Overlays/Mods/ModSettingsArea.cs
Normal file
176
osu.Game/Overlays/Mods/ModSettingsArea.cs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public class ModSettingsArea : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>();
|
||||||
|
|
||||||
|
private readonly Box background;
|
||||||
|
private readonly FillFlowContainer modSettingsFlow;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; }
|
||||||
|
|
||||||
|
public ModSettingsArea()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 250;
|
||||||
|
|
||||||
|
Anchor = Anchor.BottomRight;
|
||||||
|
Origin = Anchor.BottomRight;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new OsuScrollContainer(Direction.Horizontal)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ScrollbarOverlapsContent = false,
|
||||||
|
Child = modSettingsFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Vertical = 7, Horizontal = 70 },
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Direction = FillDirection.Horizontal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
background.Colour = colourProvider.Dark3;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
SelectedMods.BindValueChanged(_ => updateMods());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMods()
|
||||||
|
{
|
||||||
|
modSettingsFlow.Clear();
|
||||||
|
|
||||||
|
foreach (var mod in SelectedMods.Value.OrderBy(mod => mod.Type).ThenBy(mod => mod.Acronym))
|
||||||
|
{
|
||||||
|
var settings = mod.CreateSettingsControls().ToList();
|
||||||
|
|
||||||
|
if (settings.Count > 0)
|
||||||
|
{
|
||||||
|
if (modSettingsFlow.Any())
|
||||||
|
{
|
||||||
|
modSettingsFlow.Add(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
Colour = colourProvider.Dark4,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modSettingsFlow.Add(new ModSettingsColumn(mod, settings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e) => true;
|
||||||
|
protected override bool OnHover(HoverEvent e) => true;
|
||||||
|
|
||||||
|
private class ModSettingsColumn : CompositeDrawable
|
||||||
|
{
|
||||||
|
public ModSettingsColumn(Mod mod, IEnumerable<Drawable> settingsControls)
|
||||||
|
{
|
||||||
|
Width = 250;
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Padding = new MarginPadding { Bottom = 7 };
|
||||||
|
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 10),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ModSwitchTiny(mod)
|
||||||
|
{
|
||||||
|
Active = { Value = true },
|
||||||
|
Scale = new Vector2(0.6f),
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = mod.Name,
|
||||||
|
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Bottom = 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new[] { Empty() },
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuScrollContainer(Direction.Vertical)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Right = 7 },
|
||||||
|
ChildrenEnumerable = settingsControls,
|
||||||
|
Spacing = new Vector2(0, 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
Add(new SettingsButton
|
Add(new SettingsButton
|
||||||
{
|
{
|
||||||
Text = GeneralSettingsStrings.OpenOsuFolder,
|
Text = GeneralSettingsStrings.OpenOsuFolder,
|
||||||
Action = storage.PresentExternally,
|
Action = () => storage.PresentExternally(),
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new SettingsButton
|
Add(new SettingsButton
|
||||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
private IBeatmap currentBeatmap;
|
private IBeatmap currentBeatmap;
|
||||||
private Ruleset currentRuleset;
|
private Ruleset currentRuleset;
|
||||||
|
|
||||||
|
private float beatmapOffset;
|
||||||
|
|
||||||
public Score Parse(Stream stream)
|
public Score Parse(Stream stream)
|
||||||
{
|
{
|
||||||
var score = new Score
|
var score = new Score
|
||||||
@ -72,6 +74,9 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
|
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
|
||||||
scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo;
|
scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo;
|
||||||
|
|
||||||
|
// As this is baked into hitobject timing (see `LegacyBeatmapDecoder`) we also need to apply this to replay frame timing.
|
||||||
|
beatmapOffset = currentBeatmap.BeatmapInfo.BeatmapVersion < 5 ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||||
|
|
||||||
/* score.HpGraphString = */
|
/* score.HpGraphString = */
|
||||||
sr.ReadString();
|
sr.ReadString();
|
||||||
|
|
||||||
@ -229,7 +234,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
|
|
||||||
private void readLegacyReplay(Replay replay, StreamReader reader)
|
private void readLegacyReplay(Replay replay, StreamReader reader)
|
||||||
{
|
{
|
||||||
float lastTime = 0;
|
float lastTime = beatmapOffset;
|
||||||
ReplayFrame currentFrame = null;
|
ReplayFrame currentFrame = null;
|
||||||
|
|
||||||
string[] frames = reader.ReadToEnd().Split(',');
|
string[] frames = reader.ReadToEnd().Split(',');
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.IO.Legacy;
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
@ -14,8 +17,6 @@ using osu.Game.Rulesets.Replays;
|
|||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using SharpCompress.Compressors.LZMA;
|
using SharpCompress.Compressors.LZMA;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace osu.Game.Scoring.Legacy
|
namespace osu.Game.Scoring.Legacy
|
||||||
{
|
{
|
||||||
public class LegacyScoreEncoder
|
public class LegacyScoreEncoder
|
||||||
@ -111,6 +112,9 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
{
|
{
|
||||||
StringBuilder replayData = new StringBuilder();
|
StringBuilder replayData = new StringBuilder();
|
||||||
|
|
||||||
|
// As this is baked into hitobject timing (see `LegacyBeatmapDecoder`) we also need to apply this to replay frame timing.
|
||||||
|
double offset = beatmap?.BeatmapInfo.BeatmapVersion < 5 ? -LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||||
|
|
||||||
if (score.Replay != null)
|
if (score.Replay != null)
|
||||||
{
|
{
|
||||||
int lastTime = 0;
|
int lastTime = 0;
|
||||||
@ -120,7 +124,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
var legacyFrame = getLegacyFrame(f);
|
var legacyFrame = getLegacyFrame(f);
|
||||||
|
|
||||||
// Rounding because stable could only parse integral values
|
// Rounding because stable could only parse integral values
|
||||||
int time = (int)Math.Round(legacyFrame.Time);
|
int time = (int)Math.Round(legacyFrame.Time + offset);
|
||||||
replayData.Append(FormattableString.Invariant($"{time - lastTime}|{legacyFrame.MouseX ?? 0}|{legacyFrame.MouseY ?? 0}|{(int)legacyFrame.ButtonState},"));
|
replayData.Append(FormattableString.Invariant($"{time - lastTime}|{legacyFrame.MouseX ?? 0}|{legacyFrame.MouseY ?? 0}|{(int)legacyFrame.ButtonState},"));
|
||||||
lastTime = time;
|
lastTime = time;
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,10 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private readonly ButtonArea buttonArea;
|
private readonly ButtonArea buttonArea;
|
||||||
|
|
||||||
private readonly Button backButton;
|
private readonly MainMenuButton backButton;
|
||||||
|
|
||||||
private readonly List<Button> buttonsTopLevel = new List<Button>();
|
private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>();
|
||||||
private readonly List<Button> buttonsPlay = new List<Button>();
|
private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>();
|
||||||
|
|
||||||
private Sample sampleBack;
|
private Sample sampleBack;
|
||||||
|
|
||||||
@ -100,8 +100,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
buttonArea.AddRange(new Drawable[]
|
buttonArea.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new Button(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
|
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
|
||||||
backButton = new Button(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
|
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
|
||||||
{
|
{
|
||||||
VisibleState = ButtonSystemState.Play,
|
VisibleState = ButtonSystemState.Play,
|
||||||
},
|
},
|
||||||
@ -126,24 +126,24 @@ namespace osu.Game.Screens.Menu
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
|
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
|
||||||
{
|
{
|
||||||
buttonsPlay.Add(new Button(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
|
||||||
buttonsPlay.Add(new Button(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
||||||
buttonsPlay.Add(new Button(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
||||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||||
|
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
|
||||||
|
|
||||||
if (host.CanExit)
|
if (host.CanExit)
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
|
||||||
|
|
||||||
buttonArea.AddRange(buttonsPlay);
|
buttonArea.AddRange(buttonsPlay);
|
||||||
buttonArea.AddRange(buttonsTopLevel);
|
buttonArea.AddRange(buttonsTopLevel);
|
||||||
|
|
||||||
buttonArea.ForEach(b =>
|
buttonArea.ForEach(b =>
|
||||||
{
|
{
|
||||||
if (b is Button)
|
if (b is MainMenuButton)
|
||||||
{
|
{
|
||||||
b.Origin = Anchor.CentreLeft;
|
b.Origin = Anchor.CentreLeft;
|
||||||
b.Anchor = Anchor.CentreLeft;
|
b.Anchor = Anchor.CentreLeft;
|
||||||
@ -305,7 +305,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
buttonArea.ButtonSystemState = state;
|
buttonArea.ButtonSystemState = state;
|
||||||
|
|
||||||
foreach (var b in buttonArea.Children.OfType<Button>())
|
foreach (var b in buttonArea.Children.OfType<MainMenuButton>())
|
||||||
b.ButtonSystemState = state;
|
b.ButtonSystemState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
/// Button designed specifically for the osu!next main menu.
|
/// Button designed specifically for the osu!next main menu.
|
||||||
/// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape).
|
/// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Button : BeatSyncedContainer, IStateful<ButtonState>
|
public class MainMenuButton : BeatSyncedContainer, IStateful<ButtonState>
|
||||||
{
|
{
|
||||||
public event Action<ButtonState> StateChanged;
|
public event Action<ButtonState> StateChanged;
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public Button(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
|
public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
|
||||||
{
|
{
|
||||||
this.sampleName = sampleName;
|
this.sampleName = sampleName;
|
||||||
this.clickAction = clickAction;
|
this.clickAction = clickAction;
|
||||||
@ -209,7 +209,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed)
|
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (TriggerKey == e.Key && TriggerKey != Key.Unknown)
|
if (TriggerKey == e.Key && TriggerKey != Key.Unknown)
|
@ -42,8 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
public MultiplayerCountdownButton()
|
public MultiplayerCountdownButton()
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.CaretDown;
|
Icon = FontAwesome.Regular.Clock;
|
||||||
IconScale = new Vector2(0.6f);
|
|
||||||
|
|
||||||
Add(background = new Box
|
Add(background = new Box
|
||||||
{
|
{
|
||||||
@ -52,6 +51,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
});
|
});
|
||||||
|
|
||||||
base.Action = this.ShowPopover;
|
base.Action = this.ShowPopover;
|
||||||
|
|
||||||
|
TooltipText = "Countdown settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -165,6 +165,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
LengthLimit = 100,
|
LengthLimit = 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// new Section("Room visibility")
|
||||||
|
// {
|
||||||
|
// Alpha = disabled_alpha,
|
||||||
|
// Child = AvailabilityPicker = new RoomAvailabilityPicker
|
||||||
|
// {
|
||||||
|
// Enabled = { Value = false }
|
||||||
|
// },
|
||||||
|
// },
|
||||||
new Section("Game type")
|
new Section("Game type")
|
||||||
{
|
{
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
countdown = room?.Countdown;
|
countdown = room?.Countdown;
|
||||||
|
|
||||||
if (room?.Countdown != null)
|
if (room?.Countdown != null)
|
||||||
countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 1000, true);
|
countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 100, true);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
countdownUpdateDelegate?.Cancel();
|
countdownUpdateDelegate?.Cancel();
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -187,9 +186,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
const double fade_time = 50;
|
const double fade_time = 50;
|
||||||
|
|
||||||
var currentItem = Playlist.GetCurrentItem();
|
var currentItem = Playlist.GetCurrentItem();
|
||||||
Debug.Assert(currentItem != null);
|
var ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null;
|
||||||
|
|
||||||
var ruleset = rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance();
|
|
||||||
|
|
||||||
int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
|
int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
|
||||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||||
@ -201,15 +198,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
else
|
else
|
||||||
userModsDisplay.FadeOut(fade_time);
|
userModsDisplay.FadeOut(fade_time);
|
||||||
|
|
||||||
if (Client.IsHost && !User.Equals(Client.LocalUser))
|
kickButton.Alpha = Client.IsHost && !User.Equals(Client.LocalUser) ? 1 : 0;
|
||||||
kickButton.FadeIn(fade_time);
|
crown.Alpha = Room.Host?.Equals(User) == true ? 1 : 0;
|
||||||
else
|
|
||||||
kickButton.FadeOut(fade_time);
|
|
||||||
|
|
||||||
if (Room.Host?.Equals(User) == true)
|
|
||||||
crown.FadeIn(fade_time);
|
|
||||||
else
|
|
||||||
crown.FadeOut(fade_time);
|
|
||||||
|
|
||||||
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
||||||
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
||||||
|
@ -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.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -15,6 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
{
|
{
|
||||||
private FillFlowContainer<ParticipantPanel> panels;
|
private FillFlowContainer<ParticipantPanel> panels;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private ParticipantPanel currentHostPanel;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -55,6 +59,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
// Add panels for all users new to the room.
|
// Add panels for all users new to the room.
|
||||||
foreach (var user in Room.Users.Except(panels.Select(p => p.User)))
|
foreach (var user in Room.Users.Except(panels.Select(p => p.User)))
|
||||||
panels.Add(new ParticipantPanel(user));
|
panels.Add(new ParticipantPanel(user));
|
||||||
|
|
||||||
|
if (currentHostPanel == null || !currentHostPanel.User.Equals(Room.Host))
|
||||||
|
{
|
||||||
|
// Reset position of previous host back to normal, if one existing.
|
||||||
|
if (currentHostPanel != null && panels.Contains(currentHostPanel))
|
||||||
|
panels.SetLayoutPosition(currentHostPanel, 0);
|
||||||
|
|
||||||
|
currentHostPanel = null;
|
||||||
|
|
||||||
|
// Change position of new host to display above all participants.
|
||||||
|
if (Room.Host != null)
|
||||||
|
{
|
||||||
|
currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(Room.Host));
|
||||||
|
|
||||||
|
if (currentHostPanel != null)
|
||||||
|
panels.SetLayoutPosition(currentHostPanel, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,9 @@ namespace osu.Game.Skinning
|
|||||||
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||||
: base(
|
: base(
|
||||||
skin,
|
skin,
|
||||||
new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy"),
|
|
||||||
resources,
|
resources,
|
||||||
// A default legacy skin may still have a skin.ini if it is modified by the user.
|
// In the case of the actual default legacy skin (ie. the fallback one, which a user hasn't applied any modifications to) we want to use the game provided resources.
|
||||||
// We must specify the stream directly as we are redirecting storage to the osu-resources location for other files.
|
skin.Protected ? new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy") : null
|
||||||
new LegacyDatabasedSkinResourceStore(skin, resources.Files).GetStream("skin.ini")
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// 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 JetBrains.Annotations;
|
#nullable enable
|
||||||
|
|
||||||
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;
|
||||||
@ -21,16 +22,14 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="component">The requested component.</param>
|
/// <param name="component">The requested component.</param>
|
||||||
/// <returns>A drawable representation for the requested component, or null if unavailable.</returns>
|
/// <returns>A drawable representation for the requested component, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
Drawable? GetDrawableComponent(ISkinComponent component);
|
||||||
Drawable GetDrawableComponent(ISkinComponent component);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="Texture"/>.
|
/// Retrieve a <see cref="Texture"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="componentName">The requested texture.</param>
|
/// <param name="componentName">The requested texture.</param>
|
||||||
/// <returns>A matching texture, or null if unavailable.</returns>
|
/// <returns>A matching texture, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
Texture? GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="Texture"/>.
|
/// Retrieve a <see cref="Texture"/>.
|
||||||
@ -39,23 +38,22 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param>
|
/// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param>
|
||||||
/// <param name="wrapModeT">The texture wrap mode in vertical direction.</param>
|
/// <param name="wrapModeT">The texture wrap mode in vertical direction.</param>
|
||||||
/// <returns>A matching texture, or null if unavailable.</returns>
|
/// <returns>A matching texture, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
||||||
Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="SampleChannel"/>.
|
/// Retrieve a <see cref="SampleChannel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sampleInfo">The requested sample.</param>
|
/// <param name="sampleInfo">The requested sample.</param>
|
||||||
/// <returns>A matching sample channel, or null if unavailable.</returns>
|
/// <returns>A matching sample channel, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
ISample? GetSample(ISampleInfo sampleInfo);
|
||||||
ISample GetSample(ISampleInfo sampleInfo);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a configuration value.
|
/// Retrieve a configuration value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lookup">The requested configuration value.</param>
|
/// <param name="lookup">The requested configuration value.</param>
|
||||||
/// <returns>A matching value boxed in an <see cref="IBindable{TValue}"/>, or null if unavailable.</returns>
|
/// <returns>A matching value boxed in an <see cref="IBindable{TValue}"/>, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
where TLookup : notnull
|
||||||
|
where TValue : notnull;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -20,14 +23,28 @@ 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 beatmapInfo, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
/// <summary>
|
||||||
: base(createSkinInfo(beatmapInfo), new LegacySkinResourceStore(beatmapInfo.BeatmapSet, storage), resources, beatmapInfo.Path)
|
/// Construct a new legacy beatmap skin instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmapInfo">The model for this beatmap.</param>
|
||||||
|
/// <param name="resources">Access to raw game resources.</param>
|
||||||
|
public LegacyBeatmapSkin(BeatmapInfo beatmapInfo, IStorageResourceProvider? resources)
|
||||||
|
: base(createSkinInfo(beatmapInfo), resources, createRealmBackedStore(beatmapInfo, resources), beatmapInfo.Path.AsNonNull())
|
||||||
{
|
{
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
private static IResourceStore<byte[]> createRealmBackedStore(BeatmapInfo beatmapInfo, IStorageResourceProvider? resources)
|
||||||
|
{
|
||||||
|
if (resources == null)
|
||||||
|
// should only ever be used in tests.
|
||||||
|
return new ResourceStore<byte[]>();
|
||||||
|
|
||||||
|
return new RealmBackedResourceStore(beatmapInfo.BeatmapSet, resources.Files, new[] { @"ogg" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (component is SkinnableTargetComponent targetComponent)
|
if (component is SkinnableTargetComponent targetComponent)
|
||||||
{
|
{
|
||||||
@ -46,7 +63,7 @@ namespace osu.Game.Skinning
|
|||||||
return base.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
@ -62,10 +79,10 @@ namespace osu.Game.Skinning
|
|||||||
return base.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IBindable<Color4> GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo)
|
protected override IBindable<Color4>? GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo)
|
||||||
=> base.GetComboColour(source, combo.ComboIndexWithOffsets, combo);
|
=> base.GetComboColour(source, combo.ComboIndexWithOffsets, combo);
|
||||||
|
|
||||||
public override ISample GetSample(ISampleInfo sampleInfo)
|
public override ISample? GetSample(ISampleInfo sampleInfo)
|
||||||
{
|
{
|
||||||
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
|
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
|
||||||
{
|
{
|
||||||
@ -77,6 +94,10 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) =>
|
private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) =>
|
||||||
new SkinInfo { Name = beatmapInfo.ToString(), Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty };
|
new SkinInfo
|
||||||
|
{
|
||||||
|
Name = beatmapInfo.ToString(),
|
||||||
|
Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -15,6 +17,7 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -27,12 +30,6 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
public class LegacySkin : Skin
|
public class LegacySkin : Skin
|
||||||
{
|
{
|
||||||
[CanBeNull]
|
|
||||||
protected TextureStore Textures;
|
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
protected ISampleStore Samples;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether texture for the keys exists.
|
/// Whether texture for the keys exists.
|
||||||
/// Used to determine if the mania ruleset is skinned.
|
/// Used to determine if the mania ruleset is skinned.
|
||||||
@ -51,7 +48,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||||
public LegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
public LegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||||
: this(skin, new LegacyDatabasedSkinResourceStore(skin, resources.Files), resources, "skin.ini")
|
: this(skin, resources, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,36 +56,12 @@ namespace osu.Game.Skinning
|
|||||||
/// Construct a new legacy skin instance.
|
/// Construct a new legacy skin instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="skin">The model for this skin.</param>
|
/// <param name="skin">The model for this skin.</param>
|
||||||
/// <param name="storage">A storage for looking up files within this skin using user-facing filenames.</param>
|
|
||||||
/// <param name="resources">Access to raw game resources.</param>
|
/// <param name="resources">Access to raw game resources.</param>
|
||||||
|
/// <param name="storage">An optional store which will be used for looking up skin resources. If null, one will be created from realm <see cref="IHasRealmFiles"/> pattern.</param>
|
||||||
/// <param name="configurationFilename">The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file.</param>
|
/// <param name="configurationFilename">The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file.</param>
|
||||||
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename)
|
protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage, string configurationFilename = @"skin.ini")
|
||||||
: this(skin, storage, resources, string.IsNullOrEmpty(configurationFilename) ? null : storage?.GetStream(configurationFilename))
|
: base(skin, resources, storage, configurationFilename)
|
||||||
{
|
{
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Construct a new legacy skin instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="skin">The model for this skin.</param>
|
|
||||||
/// <param name="storage">A storage for looking up files within this skin using user-facing filenames.</param>
|
|
||||||
/// <param name="resources">Access to raw game resources.</param>
|
|
||||||
/// <param name="configurationStream">An optional stream containing the contents of a skin.ini file.</param>
|
|
||||||
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, [CanBeNull] Stream configurationStream)
|
|
||||||
: base(skin, resources, configurationStream)
|
|
||||||
{
|
|
||||||
if (storage != null)
|
|
||||||
{
|
|
||||||
var samples = resources?.AudioManager?.GetSampleStore(storage);
|
|
||||||
if (samples != null)
|
|
||||||
samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
|
||||||
|
|
||||||
Samples = samples;
|
|
||||||
Textures = new TextureStore(resources?.CreateTextureLoaderStore(storage));
|
|
||||||
|
|
||||||
(storage as ResourceStore<byte[]>)?.AddExtension("ogg");
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution.
|
// todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution.
|
||||||
hasKeyTexture = new Lazy<bool>(() => this.GetAnimation(
|
hasKeyTexture = new Lazy<bool>(() => this.GetAnimation(
|
||||||
lookupForMania<string>(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true,
|
lookupForMania<string>(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true,
|
||||||
@ -110,7 +83,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
@ -156,7 +129,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<TValue> lookupForMania<TValue>(LegacyManiaSkinConfigurationLookup maniaLookup)
|
private IBindable<TValue>? lookupForMania<TValue>(LegacyManiaSkinConfigurationLookup maniaLookup)
|
||||||
{
|
{
|
||||||
if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing))
|
if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing))
|
||||||
maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys);
|
maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys);
|
||||||
@ -296,20 +269,20 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="source">The source to retrieve the combo colours from.</param>
|
/// <param name="source">The source to retrieve the combo colours from.</param>
|
||||||
/// <param name="colourIndex">The preferred index for retrieving the combo colour with.</param>
|
/// <param name="colourIndex">The preferred index for retrieving the combo colour with.</param>
|
||||||
/// <param name="combo">Information on the combo whose using the returned colour.</param>
|
/// <param name="combo">Information on the combo whose using the returned colour.</param>
|
||||||
protected virtual IBindable<Color4> GetComboColour(IHasComboColours source, int colourIndex, IHasComboInformation combo)
|
protected virtual IBindable<Color4>? GetComboColour(IHasComboColours source, int colourIndex, IHasComboInformation combo)
|
||||||
{
|
{
|
||||||
var colour = source.ComboColours?[colourIndex % source.ComboColours.Count];
|
var colour = source.ComboColours?[colourIndex % source.ComboColours.Count];
|
||||||
return colour.HasValue ? new Bindable<Color4>(colour.Value) : null;
|
return colour.HasValue ? new Bindable<Color4>(colour.Value) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<Color4> getCustomColour(IHasCustomColours source, string lookup)
|
private IBindable<Color4>? getCustomColour(IHasCustomColours source, string lookup)
|
||||||
=> source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null;
|
=> source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null;
|
||||||
|
|
||||||
private IBindable<string> getManiaImage(LegacyManiaSkinConfiguration source, string lookup)
|
private IBindable<string>? getManiaImage(LegacyManiaSkinConfiguration source, string lookup)
|
||||||
=> source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable<string>(image) : null;
|
=> source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable<string>(image) : null;
|
||||||
|
|
||||||
[CanBeNull]
|
private IBindable<TValue>? legacySettingLookup<TValue>(SkinConfiguration.LegacySetting legacySetting)
|
||||||
private IBindable<TValue> legacySettingLookup<TValue>(SkinConfiguration.LegacySetting legacySetting)
|
where TValue : notnull
|
||||||
{
|
{
|
||||||
switch (legacySetting)
|
switch (legacySetting)
|
||||||
{
|
{
|
||||||
@ -321,8 +294,9 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[CanBeNull]
|
private IBindable<TValue>? genericLookup<TLookup, TValue>(TLookup lookup)
|
||||||
private IBindable<TValue> genericLookup<TLookup, TValue>(TLookup lookup)
|
where TLookup : notnull
|
||||||
|
where TValue : notnull
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -345,7 +319,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (base.GetDrawableComponent(component) is Drawable c)
|
if (base.GetDrawableComponent(component) is Drawable c)
|
||||||
return c;
|
return c;
|
||||||
@ -385,26 +359,15 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Children = this.HasFont(LegacyFont.Score)
|
Children = new Drawable[]
|
||||||
? new Drawable[]
|
{
|
||||||
{
|
new LegacyComboCounter(),
|
||||||
new LegacyComboCounter(),
|
new LegacyScoreCounter(),
|
||||||
new LegacyScoreCounter(),
|
new LegacyAccuracyCounter(),
|
||||||
new LegacyAccuracyCounter(),
|
new LegacyHealthDisplay(),
|
||||||
new LegacyHealthDisplay(),
|
new SongProgress(),
|
||||||
new SongProgress(),
|
new BarHitErrorMeter(),
|
||||||
new BarHitErrorMeter(),
|
}
|
||||||
}
|
|
||||||
: new Drawable[]
|
|
||||||
{
|
|
||||||
// TODO: these should fallback to using osu!classic skin textures, rather than doing this.
|
|
||||||
new DefaultComboCounter(),
|
|
||||||
new DefaultScoreCounter(),
|
|
||||||
new DefaultAccuracyCounter(),
|
|
||||||
new DefaultHealthDisplay(),
|
|
||||||
new SongProgress(),
|
|
||||||
new BarHitErrorMeter(),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return skinnableTargetWrapper;
|
return skinnableTargetWrapper;
|
||||||
@ -414,7 +377,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
case GameplaySkinComponent<HitResult> resultComponent:
|
case GameplaySkinComponent<HitResult> resultComponent:
|
||||||
// TODO: this should be inside the judgement pieces.
|
// TODO: this should be inside the judgement pieces.
|
||||||
Func<Drawable> createDrawable = () => getJudgementAnimation(resultComponent.Component);
|
Func<Drawable?> createDrawable = () => getJudgementAnimation(resultComponent.Component);
|
||||||
|
|
||||||
// kind of wasteful that we throw this away, but should do for now.
|
// kind of wasteful that we throw this away, but should do for now.
|
||||||
if (createDrawable() != null)
|
if (createDrawable() != null)
|
||||||
@ -433,7 +396,7 @@ namespace osu.Game.Skinning
|
|||||||
return this.GetAnimation(component.LookupName, false, false);
|
return this.GetAnimation(component.LookupName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Texture getParticleTexture(HitResult result)
|
private Texture? getParticleTexture(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
@ -450,7 +413,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getJudgementAnimation(HitResult result)
|
private Drawable? getJudgementAnimation(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
@ -470,7 +433,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
foreach (string name in getFallbackNames(componentName))
|
foreach (string name in getFallbackNames(componentName))
|
||||||
{
|
{
|
||||||
@ -498,7 +461,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ISample GetSample(ISampleInfo sampleInfo)
|
public override ISample? GetSample(ISampleInfo sampleInfo)
|
||||||
{
|
{
|
||||||
IEnumerable<string> lookupNames;
|
IEnumerable<string> lookupNames;
|
||||||
|
|
||||||
@ -551,12 +514,5 @@ namespace osu.Game.Skinning
|
|||||||
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
|
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
|
||||||
yield return componentName.Split('/').Last();
|
yield return componentName.Split('/').Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
Textures?.Dispose();
|
|
||||||
Samples?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +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 System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Game.Extensions;
|
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
|
||||||
{
|
|
||||||
public class LegacySkinResourceStore : ResourceStore<byte[]>
|
|
||||||
{
|
|
||||||
private readonly IHasNamedFiles source;
|
|
||||||
|
|
||||||
public LegacySkinResourceStore(IHasNamedFiles source, IResourceStore<byte[]> underlyingStore)
|
|
||||||
: base(underlyingStore)
|
|
||||||
{
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<string> GetFilenames(string name)
|
|
||||||
{
|
|
||||||
foreach (string filename in base.GetFilenames(name))
|
|
||||||
{
|
|
||||||
string path = getPathForFile(filename.ToStandardisedPath());
|
|
||||||
if (path != null)
|
|
||||||
yield return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string getPathForFile(string filename) =>
|
|
||||||
source.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
|
|
||||||
|
|
||||||
public override IEnumerable<string> GetAvailableResources() => source.Files.Select(f => f.Filename);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,21 +4,29 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyDatabasedSkinResourceStore : ResourceStore<byte[]>
|
public class RealmBackedResourceStore : ResourceStore<byte[]>
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, string> fileToStoragePathMapping = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> fileToStoragePathMapping = new Dictionary<string, string>();
|
||||||
|
|
||||||
public LegacyDatabasedSkinResourceStore(SkinInfo source, IResourceStore<byte[]> underlyingStore)
|
public RealmBackedResourceStore(IHasRealmFiles source, IResourceStore<byte[]> underlyingStore, string[] extensions = null)
|
||||||
: base(underlyingStore)
|
: base(underlyingStore)
|
||||||
{
|
{
|
||||||
|
// Must be initialised before the file cache.
|
||||||
|
if (extensions != null)
|
||||||
|
{
|
||||||
|
foreach (string extension in extensions)
|
||||||
|
AddExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
initialiseFileCache(source);
|
initialiseFileCache(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialiseFileCache(SkinInfo source)
|
private void initialiseFileCache(IHasRealmFiles source)
|
||||||
{
|
{
|
||||||
fileToStoragePathMapping.Clear();
|
fileToStoragePathMapping.Clear();
|
||||||
foreach (var f in source.Files)
|
foreach (var f in source.Files)
|
@ -46,7 +46,10 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
where TLookup : notnull
|
||||||
|
where TValue : notnull
|
||||||
|
=> null;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
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;
|
||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
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.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
@ -24,8 +26,17 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
public abstract class Skin : IDisposable, ISkin
|
public abstract class Skin : IDisposable, ISkin
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A texture store which can be used to perform user file lookups for this skin.
|
||||||
|
/// </summary>
|
||||||
|
protected TextureStore? Textures { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample store which can be used to perform user file lookups for this skin.
|
||||||
|
/// </summary>
|
||||||
|
protected ISampleStore? Samples { get; }
|
||||||
|
|
||||||
public readonly Live<SkinInfo> SkinInfo;
|
public readonly Live<SkinInfo> SkinInfo;
|
||||||
private readonly IStorageResourceProvider resources;
|
|
||||||
|
|
||||||
public SkinConfiguration Configuration { get; set; }
|
public SkinConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
@ -33,66 +44,80 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private readonly Dictionary<SkinnableTarget, SkinnableInfo[]> drawableComponentInfo = new Dictionary<SkinnableTarget, SkinnableInfo[]>();
|
private readonly Dictionary<SkinnableTarget, SkinnableInfo[]> drawableComponentInfo = new Dictionary<SkinnableTarget, SkinnableInfo[]>();
|
||||||
|
|
||||||
public abstract ISample GetSample(ISampleInfo sampleInfo);
|
public abstract ISample? GetSample(ISampleInfo sampleInfo);
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
public Texture? GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
|
|
||||||
public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
public abstract Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
||||||
|
|
||||||
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
public abstract IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
where TLookup : notnull
|
||||||
|
where TValue : notnull;
|
||||||
|
|
||||||
protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null)
|
/// <summary>
|
||||||
|
/// Construct a new skin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skin">The skin's metadata. Usually a live realm object.</param>
|
||||||
|
/// <param name="resources">Access to game-wide resources.</param>
|
||||||
|
/// <param name="storage">An optional store which will *replace* all file lookups that are usually sourced from <paramref name="skin"/>.</param>
|
||||||
|
/// <param name="configurationFilename">An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini".</param>
|
||||||
|
protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = @"skin.ini")
|
||||||
{
|
{
|
||||||
SkinInfo = resources?.RealmAccess != null
|
if (resources != null)
|
||||||
? skin.ToLive(resources.RealmAccess)
|
{
|
||||||
// This path should only be used in some tests.
|
SkinInfo = skin.ToLive(resources.RealmAccess);
|
||||||
: skin.ToLiveUnmanaged();
|
|
||||||
|
|
||||||
this.resources = resources;
|
storage ??= new RealmBackedResourceStore(skin, resources.Files, new[] { @"ogg" });
|
||||||
|
|
||||||
configurationStream ??= getConfigurationStream();
|
var samples = resources.AudioManager?.GetSampleStore(storage);
|
||||||
|
if (samples != null)
|
||||||
|
samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
||||||
|
|
||||||
|
Samples = samples;
|
||||||
|
Textures = new TextureStore(resources.CreateTextureLoaderStore(storage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Generally only used for tests.
|
||||||
|
SkinInfo = skin.ToLiveUnmanaged();
|
||||||
|
}
|
||||||
|
|
||||||
|
var configurationStream = storage?.GetStream(configurationFilename);
|
||||||
|
|
||||||
if (configurationStream != null)
|
if (configurationStream != null)
|
||||||
|
{
|
||||||
// stream will be closed after use by LineBufferedReader.
|
// stream will be closed after use by LineBufferedReader.
|
||||||
ParseConfigurationStream(configurationStream);
|
ParseConfigurationStream(configurationStream);
|
||||||
|
Debug.Assert(Configuration != null);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Configuration = new SkinConfiguration();
|
Configuration = new SkinConfiguration();
|
||||||
|
|
||||||
// skininfo files may be null for default skin.
|
// skininfo files may be null for default skin.
|
||||||
SkinInfo.PerformRead(s =>
|
foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget)))
|
||||||
{
|
{
|
||||||
// we may want to move this to some kind of async operation in the future.
|
string filename = $"{skinnableTarget}.json";
|
||||||
foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget)))
|
|
||||||
|
byte[]? bytes = storage?.Get(filename);
|
||||||
|
|
||||||
|
if (bytes == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
string filename = $"{skinnableTarget}.json";
|
string jsonContent = Encoding.UTF8.GetString(bytes);
|
||||||
|
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(jsonContent);
|
||||||
|
|
||||||
// skininfo files may be null for default skin.
|
if (deserializedContent == null)
|
||||||
var fileInfo = s.Files.FirstOrDefault(f => f.Filename == filename);
|
|
||||||
|
|
||||||
if (fileInfo == null)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
byte[] bytes = resources?.Files.Get(fileInfo.File.GetStoragePath());
|
DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray();
|
||||||
|
|
||||||
if (bytes == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string jsonContent = Encoding.UTF8.GetString(bytes);
|
|
||||||
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(jsonContent);
|
|
||||||
|
|
||||||
if (deserializedContent == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Failed to load skin configuration.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error(ex, "Failed to load skin configuration.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ParseConfigurationStream(Stream stream)
|
protected virtual void ParseConfigurationStream(Stream stream)
|
||||||
@ -101,16 +126,6 @@ namespace osu.Game.Skinning
|
|||||||
Configuration = new LegacySkinDecoder().Decode(reader);
|
Configuration = new LegacySkinDecoder().Decode(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream getConfigurationStream()
|
|
||||||
{
|
|
||||||
string path = SkinInfo.PerformRead(s => s.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath());
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return resources?.Files.GetStream(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove all stored customisations for the provided target.
|
/// Remove all stored customisations for the provided target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -129,7 +144,7 @@ namespace osu.Game.Skinning
|
|||||||
DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray();
|
DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Drawable GetDrawableComponent(ISkinComponent component)
|
public virtual Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
switch (component)
|
switch (component)
|
||||||
{
|
{
|
||||||
@ -137,9 +152,23 @@ namespace osu.Game.Skinning
|
|||||||
if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo))
|
if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var components = new List<Drawable>();
|
||||||
|
|
||||||
|
foreach (var i in skinnableInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
components.Add(i.CreateInstance());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Unable to create skin component {i.Type.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new SkinnableTargetComponentsContainer
|
return new SkinnableTargetComponentsContainer
|
||||||
{
|
{
|
||||||
ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance())
|
Children = components,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +197,9 @@ namespace osu.Game.Skinning
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
|
|
||||||
|
Textures?.Dispose();
|
||||||
|
Samples?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -96,12 +96,14 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
AddStep("setup skins", () =>
|
AddStep("setup skins", () =>
|
||||||
{
|
{
|
||||||
userSkinInfo.Files.Clear();
|
userSkinInfo.Files.Clear();
|
||||||
userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile));
|
if (!string.IsNullOrEmpty(userFile))
|
||||||
|
userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile));
|
||||||
|
|
||||||
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||||
|
|
||||||
beatmapInfo.BeatmapSet.Files.Clear();
|
beatmapInfo.BeatmapSet.Files.Clear();
|
||||||
beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile));
|
if (!string.IsNullOrEmpty(beatmapFile))
|
||||||
|
beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile));
|
||||||
|
|
||||||
// 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, this));
|
dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this));
|
||||||
@ -191,22 +193,32 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap, IStorageResourceProvider
|
||||||
{
|
{
|
||||||
private readonly BeatmapInfo skinBeatmapInfo;
|
private readonly BeatmapInfo skinBeatmapInfo;
|
||||||
private readonly IResourceStore<byte[]> resourceStore;
|
|
||||||
|
|
||||||
private readonly IStorageResourceProvider resources;
|
private readonly IStorageResourceProvider resources;
|
||||||
|
|
||||||
public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore<byte[]> resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, IStorageResourceProvider resources)
|
public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore<byte[]> accessMarkingResourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock,
|
||||||
|
IStorageResourceProvider resources)
|
||||||
: base(beatmap, storyboard, referenceClock, resources.AudioManager)
|
: base(beatmap, storyboard, referenceClock, resources.AudioManager)
|
||||||
{
|
{
|
||||||
this.skinBeatmapInfo = skinBeatmapInfo;
|
this.skinBeatmapInfo = skinBeatmapInfo;
|
||||||
this.resourceStore = resourceStore;
|
Files = accessMarkingResourceStore;
|
||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, resources);
|
protected internal override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, this);
|
||||||
|
|
||||||
|
public AudioManager AudioManager => resources.AudioManager;
|
||||||
|
|
||||||
|
public IResourceStore<byte[]> Files { get; }
|
||||||
|
|
||||||
|
public IResourceStore<byte[]> Resources => resources.Resources;
|
||||||
|
|
||||||
|
public RealmAccess RealmAccess => resources.RealmAccess;
|
||||||
|
|
||||||
|
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => resources.CreateTextureLoaderStore(underlyingStore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -112,7 +111,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.DarkGoldenrod;
|
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.DarkGoldenrod;
|
||||||
|
|
||||||
public TestBeatmapSkin(BeatmapInfo beatmapInfo, bool hasColours)
|
public TestBeatmapSkin(BeatmapInfo beatmapInfo, bool hasColours)
|
||||||
: base(beatmapInfo, new ResourceStore<byte[]>(), null)
|
: base(beatmapInfo, null)
|
||||||
{
|
{
|
||||||
if (hasColours)
|
if (hasColours)
|
||||||
{
|
{
|
||||||
@ -141,7 +140,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.LightCyan;
|
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.LightCyan;
|
||||||
|
|
||||||
public TestSkin(bool hasCustomColours)
|
public TestSkin(bool hasCustomColours)
|
||||||
: base(new SkinInfo(), new ResourceStore<byte[]>(), null, string.Empty)
|
: base(new SkinInfo(), null, null)
|
||||||
{
|
{
|
||||||
if (hasCustomColours)
|
if (hasCustomColours)
|
||||||
{
|
{
|
||||||
|
@ -115,11 +115,13 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
{
|
{
|
||||||
headlessHostStorage = (parent.Get<GameHost>() as HeadlessGameHost)?.Storage;
|
var host = parent.Get<GameHost>();
|
||||||
|
|
||||||
|
headlessHostStorage = (host as HeadlessGameHost)?.Storage;
|
||||||
|
|
||||||
Resources = parent.Get<OsuGameBase>().Resources;
|
Resources = parent.Get<OsuGameBase>().Resources;
|
||||||
|
|
||||||
realm = new Lazy<RealmAccess>(() => new RealmAccess(LocalStorage, "client"));
|
realm = new Lazy<RealmAccess>(() => new RealmAccess(LocalStorage, "client", host.UpdateThread));
|
||||||
|
|
||||||
RecycleLocalStorage(false);
|
RecycleLocalStorage(false);
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = skin?.SkinInfo?.Value.Name ?? "none",
|
Text = skin?.SkinInfo.Value.Name ?? "none",
|
||||||
Scale = new Vector2(1.5f),
|
Scale = new Vector2(1.5f),
|
||||||
Padding = new MarginPadding(5),
|
Padding = new MarginPadding(5),
|
||||||
},
|
},
|
||||||
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
private readonly bool extrapolateAnimations;
|
private readonly bool extrapolateAnimations;
|
||||||
|
|
||||||
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources, bool extrapolateAnimations)
|
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources, bool extrapolateAnimations)
|
||||||
: base(skin, storage, resources, "skin.ini")
|
: base(skin, resources, storage)
|
||||||
{
|
{
|
||||||
this.extrapolateAnimations = extrapolateAnimations;
|
this.extrapolateAnimations = extrapolateAnimations;
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.10.0" />
|
<PackageReference Include="Realm" Version="10.10.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.314.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.325.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.304.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.325.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.14.1" />
|
<PackageReference Include="Sentry" Version="3.14.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
|
@ -61,8 +61,8 @@
|
|||||||
<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="2022.314.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.325.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.304.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.325.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.314.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.325.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user