mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 01:52:55 +08:00
Merge branch 'master' into beatmap-refactor/download-tracker
This commit is contained in:
commit
3b095e1626
@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"smoogipoo.nvika": {
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"commands": [
|
||||
"nvika"
|
||||
]
|
||||
@ -33,4 +33,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public double Distance => Path.Distance;
|
||||
|
||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||
|
||||
public double? LegacyLastTickOffset { get; set; }
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Catcher:
|
||||
decimal version = GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
|
||||
if (version < 2.3m)
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
private IList<string> getSampleNames(IList<HitSampleInfo> hitSampleInfo)
|
||||
=> hitSampleInfo.Select(sample => sample.LookupNames.First()).ToList();
|
||||
|
||||
private IList<IList<string>> getNodeSampleNames(List<IList<HitSampleInfo>> hitSampleInfo)
|
||||
private IList<IList<string>> getNodeSampleNames(IList<IList<HitSampleInfo>> hitSampleInfo)
|
||||
=> hitSampleInfo?.Select(getSampleNames)
|
||||
.ToList();
|
||||
|
||||
|
@ -488,7 +488,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve node samples at.</param>
|
||||
private List<IList<HitSampleInfo>> nodeSamplesAt(int time)
|
||||
private IList<IList<HitSampleInfo>> nodeSamplesAt(int time)
|
||||
{
|
||||
if (!(HitObject is IHasPathWithRepeats curveData))
|
||||
return null;
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
}
|
||||
}
|
||||
|
||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The head note of the hold.
|
||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
float rightLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1;
|
||||
|
||||
bool hasLeftLine = leftLineWidth > 0;
|
||||
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|
||||
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|
||||
|| isLastColumn;
|
||||
|
||||
Color4 lineColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White;
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
this.beatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
isLegacySkin = new Lazy<bool>(() => GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
|
||||
isLegacySkin = new Lazy<bool>(() => GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version) != null);
|
||||
hasKeyTexture = new Lazy<bool>(() =>
|
||||
{
|
||||
string keyImage = this.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1";
|
||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
internal float LazyTravelDistance;
|
||||
|
||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||
|
||||
[JsonIgnore]
|
||||
public IList<HitSampleInfo> TailSamples { get; private set; }
|
||||
|
@ -14,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
@ -158,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
if (hasNumber)
|
||||
{
|
||||
decimal? legacyVersion = skin.GetConfig<LegacySetting, decimal>(LegacySetting.Version)?.Value;
|
||||
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
|
||||
|
||||
if (legacyVersion >= 2.0m)
|
||||
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
|
||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
if (shouldConvertSliderToHits(obj, beatmap, distanceData, out int taikoDuration, out double tickSpacing))
|
||||
{
|
||||
List<IList<HitSampleInfo>> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||
IList<IList<HitSampleInfo>> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||
|
||||
int i = 0;
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
// because the right half is flipped, we need to position using width - position to get the true "topleft" origin position
|
||||
float negativeScaleAdjust = content.Width / ratio;
|
||||
|
||||
if (skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m)
|
||||
if (skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m)
|
||||
{
|
||||
left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio;
|
||||
right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio;
|
||||
|
@ -7,7 +7,7 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||
using static osu.Game.Skinning.SkinConfiguration;
|
||||
|
||||
namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
|
@ -4,10 +4,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Skinning;
|
||||
using SharpCompress.Archives.Zip;
|
||||
@ -16,155 +18,179 @@ namespace osu.Game.Tests.Skins.IO
|
||||
{
|
||||
public class ImportSkinTest : ImportTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestBasicImport()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk"));
|
||||
|
||||
Assert.That(imported.Name, Is.EqualTo("test skin"));
|
||||
Assert.That(imported.Creator, Is.EqualTo("skinner"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
#region Testing filename metadata inclusion
|
||||
|
||||
[Test]
|
||||
public async Task TestImportTwiceWithSameMetadata()
|
||||
public Task TestSingleImportDifferentFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
||||
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk"));
|
||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk"));
|
||||
|
||||
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(1));
|
||||
|
||||
// the first should be overwritten by the second import.
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public async Task TestImportTwiceWithNoMetadata()
|
||||
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "test skin.osk"));
|
||||
|
||||
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
|
||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
|
||||
|
||||
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
// When the import filename matches it shouldn't be appended.
|
||||
assertCorrectMetadata(import1, "test skin", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public async Task TestImportTwiceWithDifferentMetadata()
|
||||
public Task TestSingleImportNoIniFile() => runSkinTest(async osu =>
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithNonIniFile(), "test skin.osk"));
|
||||
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk"));
|
||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk"));
|
||||
|
||||
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
|
||||
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
// When the import filename matches it shouldn't be appended.
|
||||
assertCorrectMetadata(import1, "test skin", "Unknown", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public async Task TestImportUpperCasedOskArchive()
|
||||
public Task TestEmptyImportImportsWithFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createEmptyOsk(), "test skin.osk"));
|
||||
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.OsK"));
|
||||
// When the import filename matches it shouldn't be appended.
|
||||
assertCorrectMetadata(import1, "test skin", "Unknown", osu);
|
||||
});
|
||||
|
||||
Assert.That(imported.Name, Is.EqualTo("name 1"));
|
||||
Assert.That(imported.Creator, Is.EqualTo("author 1"));
|
||||
#endregion
|
||||
|
||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.oSK"));
|
||||
|
||||
Assert.That(imported2.Hash, Is.EqualTo(imported.Hash));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
#region Cases where imports should match existing
|
||||
|
||||
[Test]
|
||||
public async Task TestSameMetadataNameDifferentFolderName()
|
||||
public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
||||
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1", false), "my custom skin 1"));
|
||||
Assert.That(imported.Name, Is.EqualTo("name 1 [my custom skin 1]"));
|
||||
Assert.That(imported.Creator, Is.EqualTo("author 1"));
|
||||
assertImportedOnce(import1, import2);
|
||||
});
|
||||
|
||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1", false), "my custom skin 2"));
|
||||
Assert.That(imported2.Name, Is.EqualTo("name 1 [my custom skin 2]"));
|
||||
Assert.That(imported2.Creator, Is.EqualTo("author 1"));
|
||||
[Test]
|
||||
public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk"));
|
||||
|
||||
Assert.That(imported2.Hash, Is.Not.EqualTo(imported.Hash));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
assertImportedOnce(import1, import2);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.OsK"));
|
||||
assertCorrectMetadata(import1, "name 1", "author 1", osu);
|
||||
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.oSK"));
|
||||
|
||||
assertImportedOnce(import1, import2);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
|
||||
|
||||
assertImportedOnce(import1, import2);
|
||||
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cases where imports should be uniquely imported
|
||||
|
||||
[Test]
|
||||
public Task TestImportTwiceWithSameMetadataButDifferentFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin2.osk"));
|
||||
|
||||
assertImportedBoth(import1, import2);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestImportTwiceWithNoMetadataDifferentDownloadFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download2.osk"));
|
||||
|
||||
assertImportedBoth(import1, import2);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestImportTwiceWithSameFilenameDifferentMetadata() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2", "skinner"), "skin.osk"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2.1", "skinner"), "skin.osk"));
|
||||
|
||||
assertImportedBoth(import1, import2);
|
||||
assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", osu);
|
||||
assertCorrectMetadata(import2, "test skin v2.1 [skin]", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSameMetadataNameDifferentFolderName() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
|
||||
var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 2"));
|
||||
|
||||
assertImportedBoth(import1, import2);
|
||||
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
|
||||
assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", osu);
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
private void assertCorrectMetadata(SkinInfo import1, string name, string creator, OsuGameBase osu)
|
||||
{
|
||||
Assert.That(import1.Name, Is.EqualTo(name));
|
||||
Assert.That(import1.Creator, Is.EqualTo(creator));
|
||||
|
||||
// for extra safety let's reconstruct the skin, reading from the skin.ini.
|
||||
var instance = import1.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager)));
|
||||
|
||||
Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
|
||||
Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
|
||||
}
|
||||
|
||||
private MemoryStream createOsk(string name, string author, bool makeUnique = true)
|
||||
private void assertImportedBoth(SkinInfo import1, SkinInfo import2)
|
||||
{
|
||||
Assert.That(import2.ID, Is.Not.EqualTo(import1.ID));
|
||||
Assert.That(import2.Hash, Is.Not.EqualTo(import1.Hash));
|
||||
Assert.That(import2.Files.Select(f => f.FileInfoID), Is.Not.EquivalentTo(import1.Files.Select(f => f.FileInfoID)));
|
||||
}
|
||||
|
||||
private void assertImportedOnce(SkinInfo import1, SkinInfo import2)
|
||||
{
|
||||
Assert.That(import2.ID, Is.EqualTo(import1.ID));
|
||||
Assert.That(import2.Hash, Is.EqualTo(import1.Hash));
|
||||
Assert.That(import2.Files.Select(f => f.FileInfoID), Is.EquivalentTo(import1.Files.Select(f => f.FileInfoID)));
|
||||
}
|
||||
|
||||
private MemoryStream createEmptyOsk()
|
||||
{
|
||||
var zipStream = new MemoryStream();
|
||||
using var zip = ZipArchive.Create();
|
||||
zip.SaveTo(zipStream);
|
||||
return zipStream;
|
||||
}
|
||||
|
||||
private MemoryStream createOskWithNonIniFile()
|
||||
{
|
||||
var zipStream = new MemoryStream();
|
||||
using var zip = ZipArchive.Create();
|
||||
zip.AddEntry("hitcircle.png", new MemoryStream(new byte[] { 0, 1, 2, 3 }));
|
||||
zip.SaveTo(zipStream);
|
||||
return zipStream;
|
||||
}
|
||||
|
||||
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false)
|
||||
{
|
||||
var zipStream = new MemoryStream();
|
||||
using var zip = ZipArchive.Create();
|
||||
@ -193,6 +219,22 @@ namespace osu.Game.Tests.Skins.IO
|
||||
return stream;
|
||||
}
|
||||
|
||||
private async Task runSkinTest(Func<OsuGameBase, Task> action, [CallerMemberName] string callingMethodName = @"")
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
await action(osu);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SkinInfo> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
|
||||
{
|
||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins
|
||||
var decoder = new LegacySkinDecoder();
|
||||
using (var resStream = TestResources.OpenResource("skin-latest.ini"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
Assert.AreEqual(LegacySkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion);
|
||||
Assert.AreEqual(SkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -151,7 +151,7 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
|
||||
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
|
||||
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
|
||||
AddAssert("Check legacy version lookup", () => requester.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -160,7 +160,7 @@ namespace osu.Game.Tests.Skins
|
||||
// completely ignoring beatmap versions for simplicity.
|
||||
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
|
||||
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m);
|
||||
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
|
||||
AddAssert("Check legacy version lookup", () => requester.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -169,14 +169,14 @@ namespace osu.Game.Tests.Skins
|
||||
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null);
|
||||
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
|
||||
AddAssert("Check legacy version lookup",
|
||||
() => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION);
|
||||
() => requester.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value == SkinConfiguration.LATEST_VERSION);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIniWithNoVersionFallsBackTo1()
|
||||
{
|
||||
AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream())));
|
||||
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
|
||||
AddAssert("Check legacy version lookup", () => requester.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
|
||||
}
|
||||
|
||||
public enum LookupType
|
||||
|
@ -38,8 +38,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
|
||||
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -204,7 +202,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestDownloadButtonHiddenWhenBeatmapExists()
|
||||
{
|
||||
createPlaylist(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo);
|
||||
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||
|
||||
AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait());
|
||||
|
||||
createPlaylist(beatmap);
|
||||
|
||||
assertDownloadButtonVisible(false);
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
|
||||
{
|
||||
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
|
||||
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
|
||||
|
||||
private RoomsContainer container;
|
||||
|
||||
|
@ -23,8 +23,6 @@ using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
@ -32,6 +30,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Spectate;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
@ -44,11 +43,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
|
||||
private DependenciesScreen dependenciesScreen;
|
||||
private TestMultiplayer multiplayerScreen;
|
||||
private TestMultiplayerClient client;
|
||||
private TestMultiplayerScreenStack multiplayerScreenStack;
|
||||
|
||||
private TestRequestHandlingMultiplayerRoomManager roomManager => multiplayerScreen.RoomManager;
|
||||
private TestMultiplayerClient client => multiplayerScreenStack.Client;
|
||||
private TestMultiplayerRoomManager roomManager => multiplayerScreenStack.RoomManager;
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||
@ -70,22 +68,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
});
|
||||
|
||||
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
|
||||
|
||||
AddStep("load dependencies", () =>
|
||||
{
|
||||
client = new TestMultiplayerClient(roomManager);
|
||||
|
||||
// The screen gets suspended so it stops receiving updates.
|
||||
Child = client;
|
||||
|
||||
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
|
||||
|
||||
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
|
||||
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
|
||||
AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
|
||||
AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
|
||||
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
}
|
||||
|
||||
@ -441,7 +425,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("start match externally", () => client.StartMatch());
|
||||
|
||||
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
|
||||
AddAssert("play not started", () => multiplayerScreenStack.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -485,7 +469,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
});
|
||||
|
||||
AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen());
|
||||
AddUntilStep("play started", () => multiplayerScreenStack.CurrentScreen is SpectatorScreen);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -527,16 +511,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("open mod overlay", () => this.ChildrenOfType<RoomSubScreen.UserModSelectButton>().Single().TriggerClick());
|
||||
|
||||
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
||||
AddStep("invoke on back button", () => multiplayerScreenStack.OnBackButton());
|
||||
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
||||
|
||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||
|
||||
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
||||
testLeave("back button", () => multiplayerScreenStack.OnBackButton());
|
||||
|
||||
// mimics home button and OS window close
|
||||
testLeave("forced exit", () => multiplayerScreen.Exit());
|
||||
testLeave("forced exit", () => multiplayerScreenStack.Exit());
|
||||
|
||||
void testLeave(string actionName, Action action)
|
||||
{
|
||||
@ -577,7 +561,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
||||
AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player);
|
||||
|
||||
// Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out.
|
||||
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
|
||||
@ -586,15 +570,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.GameplayClock.CurrentTime > time);
|
||||
}
|
||||
|
||||
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
|
||||
AddUntilStep("wait for results", () => multiplayerScreenStack.CurrentScreen is ResultsScreen);
|
||||
}
|
||||
|
||||
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
{
|
||||
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||
AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddWaitStep("wait for transition", 2);
|
||||
@ -607,26 +591,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
|
||||
/// </summary>
|
||||
private class DependenciesScreen : OsuScreen
|
||||
{
|
||||
[Cached(typeof(MultiplayerClient))]
|
||||
public readonly TestMultiplayerClient Client;
|
||||
|
||||
public DependenciesScreen(TestMultiplayerClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
||||
{
|
||||
public new TestRequestHandlingMultiplayerRoomManager RoomManager { get; private set; }
|
||||
|
||||
protected override RoomManager CreateRoomManager() => RoomManager = new TestRequestHandlingMultiplayerRoomManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API;
|
||||
@ -40,9 +39,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
|
||||
|
||||
AddStep("create leaderboard", () =>
|
||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -44,9 +43,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return room;
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
|
||||
|
||||
AddStep("create leaderboard", () =>
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
|
||||
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
|
||||
|
||||
private LoungeSubScreen loungeScreen;
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@ -14,8 +13,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[SetUp]
|
||||
public new void Setup() => Schedule(() =>
|
||||
{
|
||||
SelectedRoom.Value = new Room();
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -6,18 +6,14 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
@ -33,9 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
|
||||
private DependenciesScreen dependenciesScreen;
|
||||
private TestMultiplayer multiplayerScreen;
|
||||
private TestMultiplayerClient client;
|
||||
private TestMultiplayerScreenStack multiplayerScreenStack;
|
||||
|
||||
private TestMultiplayerClient client => multiplayerScreenStack.Client;
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||
@ -57,24 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
});
|
||||
|
||||
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
|
||||
|
||||
AddStep("load dependencies", () =>
|
||||
{
|
||||
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
|
||||
|
||||
// The screen gets suspended so it stops receiving updates.
|
||||
Child = client;
|
||||
|
||||
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for dependencies screen", () => Stack.CurrentScreen is DependenciesScreen);
|
||||
AddUntilStep("wait for dependencies to start load", () => dependenciesScreen.LoadState > LoadState.NotLoaded);
|
||||
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
|
||||
|
||||
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
|
||||
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
|
||||
AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
|
||||
AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
|
||||
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
}
|
||||
|
||||
@ -120,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("press button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(multiplayerScreen.ChildrenOfType<TeamDisplay>().First());
|
||||
InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType<TeamDisplay>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1);
|
||||
@ -154,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
{
|
||||
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddWaitStep("wait for transition", 2);
|
||||
@ -167,26 +147,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
|
||||
/// </summary>
|
||||
private class DependenciesScreen : OsuScreen
|
||||
{
|
||||
[Cached(typeof(MultiplayerClient))]
|
||||
public readonly TestMultiplayerClient Client;
|
||||
|
||||
public DependenciesScreen(TestMultiplayerClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
||||
{
|
||||
public new TestRequestHandlingMultiplayerRoomManager RoomManager { get; private set; }
|
||||
|
||||
protected override RoomManager CreateRoomManager() => RoomManager = new TestRequestHandlingMultiplayerRoomManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,21 +9,18 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -333,12 +330,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestPushMatchSubScreenAndPressBackButtonImmediately()
|
||||
{
|
||||
TestMultiplayer multiplayer = null;
|
||||
TestMultiplayerScreenStack multiplayerScreenStack = null;
|
||||
|
||||
PushAndConfirm(() => multiplayer = new TestMultiplayer());
|
||||
PushAndConfirm(() => multiplayerScreenStack = new TestMultiplayerScreenStack());
|
||||
|
||||
AddUntilStep("wait for lounge", () => multiplayer.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("open room", () => multiplayer.ChildrenOfType<LoungeSubScreen>().Single().Open());
|
||||
AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open());
|
||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
||||
AddWaitStep("wait two frames", 2);
|
||||
}
|
||||
@ -453,18 +450,5 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
protected override bool DisplayStableImportPrompt => false;
|
||||
}
|
||||
|
||||
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
||||
{
|
||||
[Cached(typeof(MultiplayerClient))]
|
||||
public readonly TestMultiplayerClient Client;
|
||||
|
||||
public TestMultiplayer()
|
||||
{
|
||||
Client = new TestMultiplayerClient((TestRequestHandlingMultiplayerRoomManager)RoomManager);
|
||||
}
|
||||
|
||||
protected override RoomManager CreateRoomManager() => new TestRequestHandlingMultiplayerRoomManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 10).ToArray()));
|
||||
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
|
||||
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
|
||||
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
|
||||
|
||||
private TestLoungeSubScreen loungeScreen;
|
||||
|
||||
|
@ -11,24 +11,27 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
|
||||
public class TestScenePlaylistsRoomCreation : OnlinePlayTestScene
|
||||
{
|
||||
private BeatmapManager manager;
|
||||
private RulesetStore rulesets;
|
||||
|
||||
private TestPlaylistsRoomSubScreen match;
|
||||
|
||||
private ILive<BeatmapSetInfo> importedBeatmap;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
@ -40,7 +43,9 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
public void SetupSteps()
|
||||
{
|
||||
AddStep("set room", () => SelectedRoom.Value = new Room());
|
||||
AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait());
|
||||
|
||||
importBeatmap();
|
||||
|
||||
AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value)));
|
||||
AddUntilStep("wait for load", () => match.IsCurrentScreen());
|
||||
}
|
||||
@ -48,44 +53,58 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[Test]
|
||||
public void TestLoadSimpleMatch()
|
||||
{
|
||||
AddStep("set room properties", () =>
|
||||
setupAndCreateRoom(room =>
|
||||
{
|
||||
SelectedRoom.Value.RoomID.Value = 1;
|
||||
SelectedRoom.Value.Name.Value = "my awesome room";
|
||||
SelectedRoom.Value.Host.Value = API.LocalUser.Value;
|
||||
SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value);
|
||||
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
|
||||
SelectedRoom.Value.Playlist.Add(new PlaylistItem
|
||||
room.Name.Value = "my awesome room";
|
||||
room.Host.Value = API.LocalUser.Value;
|
||||
room.RecentParticipants.Add(room.Host.Value);
|
||||
room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
|
||||
room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||
Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("Progress details are hidden", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent.Alpha == 0);
|
||||
|
||||
AddStep("start match", () => match.ChildrenOfType<PlaylistsReadyButton>().First().TriggerClick());
|
||||
AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlaylistItemSelectedOnCreate()
|
||||
public void TestAttemptLimitedMatch()
|
||||
{
|
||||
AddStep("set room properties", () =>
|
||||
setupAndCreateRoom(room =>
|
||||
{
|
||||
SelectedRoom.Value.Name.Value = "my awesome room";
|
||||
SelectedRoom.Value.Host.Value = API.LocalUser.Value;
|
||||
SelectedRoom.Value.Playlist.Add(new PlaylistItem
|
||||
room.Name.Value = "my awesome room";
|
||||
room.MaxAttempts.Value = 5;
|
||||
room.Host.Value = API.LocalUser.Value;
|
||||
room.RecentParticipants.Add(room.Host.Value);
|
||||
room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
|
||||
room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||
Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("move mouse to create button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
|
||||
});
|
||||
AddUntilStep("Progress details are visible", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent.Alpha == 1);
|
||||
}
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
[Test]
|
||||
public void TestPlaylistItemSelectedOnCreate()
|
||||
{
|
||||
setupAndCreateRoom(room =>
|
||||
{
|
||||
room.Name.Value = "my awesome room";
|
||||
room.Host.Value = API.LocalUser.Value;
|
||||
room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]);
|
||||
}
|
||||
@ -94,56 +113,51 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
public void TestBeatmapUpdatedOnReImport()
|
||||
{
|
||||
BeatmapSetInfo importedSet = null;
|
||||
TestBeatmap beatmap = null;
|
||||
|
||||
// this step is required to make sure the further imports actually get online IDs.
|
||||
// all the playlist logic relies on online ID matching.
|
||||
AddStep("remove all matching online IDs", () =>
|
||||
{
|
||||
beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList();
|
||||
|
||||
foreach (var s in existing)
|
||||
{
|
||||
s.OnlineBeatmapSetID = null;
|
||||
foreach (var b in s.Beatmaps)
|
||||
b.OnlineBeatmapID = null;
|
||||
manager.Update(s);
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("import altered beatmap", () =>
|
||||
{
|
||||
IBeatmap beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
|
||||
|
||||
// intentionally increment online IDs to clash with import below.
|
||||
beatmap.BeatmapInfo.OnlineBeatmapID++;
|
||||
beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++;
|
||||
|
||||
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
|
||||
});
|
||||
|
||||
AddStep("load room", () =>
|
||||
setupAndCreateRoom(room =>
|
||||
{
|
||||
SelectedRoom.Value.Name.Value = "my awesome room";
|
||||
SelectedRoom.Value.Host.Value = API.LocalUser.Value;
|
||||
SelectedRoom.Value.Playlist.Add(new PlaylistItem
|
||||
room.Name.Value = "my awesome room";
|
||||
room.Host.Value = API.LocalUser.Value;
|
||||
room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = importedSet.Beatmaps[0] },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(match.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1);
|
||||
|
||||
AddStep("re-import original beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait());
|
||||
importBeatmap();
|
||||
|
||||
AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1);
|
||||
}
|
||||
|
||||
private void setupAndCreateRoom(Action<Room> room)
|
||||
{
|
||||
AddStep("setup room", () => room(SelectedRoom.Value));
|
||||
|
||||
AddStep("click create button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
}
|
||||
|
||||
private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Result);
|
||||
|
||||
private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
|
||||
{
|
||||
public new Bindable<PlaylistItem> SelectedItem => base.SelectedItem;
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
@ -58,6 +59,13 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
Default = string.Empty,
|
||||
Value = "Sample text"
|
||||
};
|
||||
|
||||
[SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))]
|
||||
public Bindable<int?> IntTextboxBindable { get; } = new Bindable<int?>
|
||||
{
|
||||
Default = null,
|
||||
Value = null
|
||||
};
|
||||
}
|
||||
|
||||
private enum TestEnum
|
||||
|
83
osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs
Normal file
83
osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="OsuScreen"/> loadable into <see cref="ScreenTestScene"/>s via <see cref="ScreenTestScene.LoadScreen"/>,
|
||||
/// which provides dependencies for and loads an isolated <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen.
|
||||
/// <p>
|
||||
/// This screen:
|
||||
/// <list type="bullet">
|
||||
/// <item>Provides a <see cref="TestMultiplayerClient"/> to be resolved as a dependency in the <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen,
|
||||
/// which is typically a part of <see cref="OsuGameBase"/>.</item>
|
||||
/// <item>Rebinds the <see cref="DummyAPIAccess"/> to handle requests via a <see cref="TestRoomRequestsHandler"/>.</item>
|
||||
/// <item>Provides a <see cref="TestMultiplayerRoomManager"/> for the <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen.</item>
|
||||
/// </list>
|
||||
/// </p>
|
||||
/// </summary>
|
||||
public class TestMultiplayerScreenStack : OsuScreen
|
||||
{
|
||||
public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen => multiplayerScreen;
|
||||
|
||||
public TestMultiplayerRoomManager RoomManager => multiplayerScreen.RoomManager;
|
||||
|
||||
public IScreen CurrentScreen => screenStack.CurrentScreen;
|
||||
|
||||
public new bool IsLoaded => base.IsLoaded && MultiplayerScreen.IsLoaded;
|
||||
|
||||
[Cached(typeof(MultiplayerClient))]
|
||||
public readonly TestMultiplayerClient Client;
|
||||
|
||||
private readonly OsuScreenStack screenStack;
|
||||
private readonly TestMultiplayer multiplayerScreen;
|
||||
|
||||
public TestMultiplayerScreenStack()
|
||||
{
|
||||
multiplayerScreen = new TestMultiplayer();
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Client = new TestMultiplayerClient(RoomManager),
|
||||
screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
screenStack.Push(multiplayerScreen);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, OsuGameBase game)
|
||||
{
|
||||
((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, game);
|
||||
}
|
||||
|
||||
public override bool OnBackButton() => multiplayerScreen.OnBackButton();
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
if (screenStack.CurrentScreen == null)
|
||||
return base.OnExiting(next);
|
||||
|
||||
screenStack.Exit();
|
||||
return true;
|
||||
}
|
||||
|
||||
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
||||
{
|
||||
public new TestMultiplayerRoomManager RoomManager { get; private set; }
|
||||
public TestRoomRequestsHandler RequestsHandler { get; private set; }
|
||||
|
||||
protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler());
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,44 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestCompleteProgress()
|
||||
{
|
||||
ProgressNotification notification = null;
|
||||
AddStep("add progress notification", () =>
|
||||
{
|
||||
notification = new ProgressNotification
|
||||
{
|
||||
Text = @"Uploading to BSS...",
|
||||
CompletionText = "Uploaded to BSS!",
|
||||
};
|
||||
notificationOverlay.Post(notification);
|
||||
progressingNotifications.Add(notification);
|
||||
});
|
||||
|
||||
AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancelProgress()
|
||||
{
|
||||
ProgressNotification notification = null;
|
||||
AddStep("add progress notification", () =>
|
||||
{
|
||||
notification = new ProgressNotification
|
||||
{
|
||||
Text = @"Uploading to BSS...",
|
||||
CompletionText = "Uploaded to BSS!",
|
||||
};
|
||||
notificationOverlay.Post(notification);
|
||||
progressingNotifications.Add(notification);
|
||||
});
|
||||
|
||||
AddWaitStep("wait 3", 3);
|
||||
|
||||
AddStep("cancel notification", () => notification.State = ProgressNotificationState.Cancelled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicFlow()
|
||||
{
|
||||
@ -138,7 +176,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
|
||||
{
|
||||
if (n.Progress < 1)
|
||||
n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle();
|
||||
n.Progress += (float)(Time.Elapsed / 2000);
|
||||
else
|
||||
n.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("setup cover", () => Child = new UpdateableOnlineBeatmapSetCover(coverType)
|
||||
{
|
||||
BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet,
|
||||
OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
});
|
||||
@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
var cover = new UpdateableOnlineBeatmapSetCover(coverType)
|
||||
{
|
||||
BeatmapSet = setInfo,
|
||||
OnlineInfo = setInfo.OnlineInfo,
|
||||
Height = 100,
|
||||
Masking = true,
|
||||
};
|
||||
@ -99,12 +99,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover
|
||||
{
|
||||
BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet,
|
||||
OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
});
|
||||
|
||||
AddStep("change model", () => updateableCover.BeatmapSet = null);
|
||||
AddStep("change model", () => updateableCover.OnlineInfo = null);
|
||||
AddWaitStep("wait some", 5);
|
||||
AddAssert("no cover added", () => !updateableCover.ChildrenOfType<DelayedLoadUnloadWrapper>().Any());
|
||||
}
|
||||
@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover(0)
|
||||
{
|
||||
BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"),
|
||||
OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg").OnlineInfo,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Alpha = 0.4f
|
||||
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1);
|
||||
|
||||
AddStep("switch beatmap",
|
||||
() => updateableCover.BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg"));
|
||||
() => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg").OnlineInfo);
|
||||
AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType<OnlineBeatmapSetCover>().Except(new[] { initialCover }).Any());
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -23,14 +22,13 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 1091460 });
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = 1091460 });
|
||||
req.Success += success;
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
private void success(APIBeatmap apiBeatmap)
|
||||
private void success(APIBeatmap beatmap)
|
||||
{
|
||||
var beatmap = apiBeatmap.ToBeatmapInfo(rulesets);
|
||||
Add(new TournamentBeatmapPanel(beatmap)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -4,12 +4,12 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Tests.Components
|
||||
{
|
||||
@ -23,12 +23,10 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
|
||||
private FillFlowContainer<TournamentBeatmapPanel> fillFlow;
|
||||
|
||||
private BeatmapInfo beatmapInfo;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 490154 });
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = 490154 });
|
||||
req.Success += success;
|
||||
api.Queue(req);
|
||||
|
||||
@ -38,18 +36,17 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new osuTK.Vector2(10)
|
||||
Spacing = new Vector2(10)
|
||||
});
|
||||
}
|
||||
|
||||
private void success(APIBeatmap apiBeatmap)
|
||||
private void success(APIBeatmap beatmap)
|
||||
{
|
||||
beatmapInfo = apiBeatmap.ToBeatmapInfo(rulesets);
|
||||
var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods;
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
fillFlow.Add(new TournamentBeatmapPanel(beatmapInfo, mod.Acronym)
|
||||
fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
|
@ -44,8 +44,8 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
||||
{
|
||||
Beatmaps =
|
||||
{
|
||||
new RoundBeatmap { BeatmapInfo = TournamentTestScene.CreateSampleBeatmapInfo() },
|
||||
new RoundBeatmap { BeatmapInfo = TournamentTestScene.CreateSampleBeatmapInfo() },
|
||||
new RoundBeatmap { Beatmap = TournamentTestScene.CreateSampleBeatmap() },
|
||||
new RoundBeatmap { Beatmap = TournamentTestScene.CreateSampleBeatmap() },
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Tournament.Tests.Screens
|
||||
{
|
||||
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Mods = mods
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -74,19 +73,19 @@ namespace osu.Game.Tournament.Tests
|
||||
{
|
||||
new SeedingBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Score = 12345672,
|
||||
Seed = { Value = 24 },
|
||||
},
|
||||
new SeedingBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Score = 1234567,
|
||||
Seed = { Value = 12 },
|
||||
},
|
||||
new SeedingBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Score = 1234567,
|
||||
Seed = { Value = 16 },
|
||||
}
|
||||
@ -100,19 +99,19 @@ namespace osu.Game.Tournament.Tests
|
||||
{
|
||||
new SeedingBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Score = 234567,
|
||||
Seed = { Value = 3 },
|
||||
},
|
||||
new SeedingBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Score = 234567,
|
||||
Seed = { Value = 6 },
|
||||
},
|
||||
new SeedingBeatmap
|
||||
{
|
||||
BeatmapInfo = CreateSampleBeatmapInfo(),
|
||||
Beatmap = CreateSampleBeatmap(),
|
||||
Score = 234567,
|
||||
Seed = { Value = 12 },
|
||||
}
|
||||
@ -152,16 +151,15 @@ namespace osu.Game.Tournament.Tests
|
||||
}
|
||||
};
|
||||
|
||||
public static BeatmapInfo CreateSampleBeatmapInfo() =>
|
||||
new BeatmapInfo
|
||||
public static APIBeatmap CreateSampleBeatmap() =>
|
||||
new APIBeatmap
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
BeatmapSet = new APIBeatmapSet
|
||||
{
|
||||
Title = "Test Title",
|
||||
Artist = "Test Artist",
|
||||
ID = RNG.Next(0, 1000000)
|
||||
},
|
||||
OnlineInfo = new APIBeatmap(),
|
||||
OnlineID = RNG.Next(0, 1000000),
|
||||
};
|
||||
|
||||
protected override ITestSceneTestRunner CreateRunner() => new TournamentTestSceneTestRunner();
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK;
|
||||
@ -21,22 +22,21 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
public class SongBar : CompositeDrawable
|
||||
{
|
||||
private BeatmapInfo beatmapInfo;
|
||||
private APIBeatmap beatmap;
|
||||
|
||||
public const float HEIGHT = 145 / 2f;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
public BeatmapInfo BeatmapInfo
|
||||
public APIBeatmap Beatmap
|
||||
{
|
||||
get => beatmapInfo;
|
||||
set
|
||||
{
|
||||
if (beatmapInfo == value)
|
||||
if (beatmap == value)
|
||||
return;
|
||||
|
||||
beatmapInfo = value;
|
||||
beatmap = value;
|
||||
update();
|
||||
}
|
||||
}
|
||||
@ -95,18 +95,18 @@ namespace osu.Game.Tournament.Components
|
||||
|
||||
private void update()
|
||||
{
|
||||
if (beatmapInfo == null)
|
||||
if (beatmap == null)
|
||||
{
|
||||
flow.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
double bpm = beatmapInfo.BeatmapSet.OnlineInfo.BPM;
|
||||
double length = beatmapInfo.Length;
|
||||
double bpm = beatmap.BPM;
|
||||
double length = beatmap.Length;
|
||||
string hardRockExtra = "";
|
||||
string srExtra = "";
|
||||
|
||||
float ar = beatmapInfo.BaseDifficulty.ApproachRate;
|
||||
float ar = beatmap.Difficulty.ApproachRate;
|
||||
|
||||
if ((mods & LegacyMods.HardRock) > 0)
|
||||
{
|
||||
@ -132,9 +132,9 @@ namespace osu.Game.Tournament.Components
|
||||
default:
|
||||
stats = new (string heading, string content)[]
|
||||
{
|
||||
("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
|
||||
("CS", $"{beatmap.Difficulty.CircleSize:0.#}{hardRockExtra}"),
|
||||
("AR", $"{ar:0.#}{hardRockExtra}"),
|
||||
("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
|
||||
("OD", $"{beatmap.Difficulty.OverallDifficulty:0.#}{hardRockExtra}"),
|
||||
};
|
||||
break;
|
||||
|
||||
@ -142,15 +142,15 @@ namespace osu.Game.Tournament.Components
|
||||
case 3:
|
||||
stats = new (string heading, string content)[]
|
||||
{
|
||||
("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
|
||||
("HP", $"{beatmapInfo.BaseDifficulty.DrainRate:0.#}{hardRockExtra}")
|
||||
("OD", $"{beatmap.Difficulty.OverallDifficulty:0.#}{hardRockExtra}"),
|
||||
("HP", $"{beatmap.Difficulty.DrainRate:0.#}{hardRockExtra}")
|
||||
};
|
||||
break;
|
||||
|
||||
case 2:
|
||||
stats = new (string heading, string content)[]
|
||||
{
|
||||
("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
|
||||
("CS", $"{beatmap.Difficulty.CircleSize:0.#}{hardRockExtra}"),
|
||||
("AR", $"{ar:0.#}"),
|
||||
};
|
||||
break;
|
||||
@ -186,7 +186,7 @@ namespace osu.Game.Tournament.Components
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DiffPiece(stats),
|
||||
new DiffPiece(("Star Rating", $"{beatmapInfo.StarDifficulty:0.#}{srExtra}"))
|
||||
new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.#}{srExtra}"))
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
@ -229,7 +229,7 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
}
|
||||
},
|
||||
new TournamentBeatmapPanel(beatmapInfo)
|
||||
new TournamentBeatmapPanel(beatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Tournament.Models;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -20,7 +21,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
public class TournamentBeatmapPanel : CompositeDrawable
|
||||
{
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
public readonly APIBeatmap Beatmap;
|
||||
|
||||
private readonly string mod;
|
||||
|
||||
@ -32,11 +33,11 @@ namespace osu.Game.Tournament.Components
|
||||
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
|
||||
private Box flash;
|
||||
|
||||
public TournamentBeatmapPanel(IBeatmapInfo beatmapInfo, string mod = null)
|
||||
public TournamentBeatmapPanel(APIBeatmap beatmap, string mod = null)
|
||||
{
|
||||
if (beatmapInfo == null) throw new ArgumentNullException(nameof(beatmapInfo));
|
||||
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
|
||||
|
||||
BeatmapInfo = beatmapInfo;
|
||||
Beatmap = beatmap;
|
||||
this.mod = mod;
|
||||
|
||||
Width = 400;
|
||||
@ -62,7 +63,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(0.5f),
|
||||
BeatmapSet = BeatmapInfo.BeatmapSet as IBeatmapSetOnlineInfo,
|
||||
OnlineInfo = Beatmap.BeatmapSet,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@ -75,7 +76,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
new TournamentSpriteText
|
||||
{
|
||||
Text = BeatmapInfo.GetDisplayTitleRomanisable(false),
|
||||
Text = Beatmap.GetDisplayTitleRomanisable(false),
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
|
||||
},
|
||||
new FillFlowContainer
|
||||
@ -92,7 +93,7 @@ namespace osu.Game.Tournament.Components
|
||||
},
|
||||
new TournamentSpriteText
|
||||
{
|
||||
Text = BeatmapInfo.Metadata?.Author,
|
||||
Text = Beatmap.Metadata.Author,
|
||||
Padding = new MarginPadding { Right = 20 },
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14)
|
||||
},
|
||||
@ -104,7 +105,7 @@ namespace osu.Game.Tournament.Components
|
||||
},
|
||||
new TournamentSpriteText
|
||||
{
|
||||
Text = BeatmapInfo.DifficultyName,
|
||||
Text = Beatmap.DifficultyName,
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14)
|
||||
},
|
||||
}
|
||||
@ -148,7 +149,7 @@ namespace osu.Game.Tournament.Components
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == BeatmapInfo.OnlineID);
|
||||
var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap.OnlineID);
|
||||
|
||||
bool doFlash = found != choice;
|
||||
choice = found;
|
||||
|
@ -11,10 +11,10 @@ using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tournament.Models;
|
||||
|
||||
@ -87,14 +87,14 @@ namespace osu.Game.Tournament.IPC
|
||||
|
||||
lastBeatmapId = beatmapId;
|
||||
|
||||
var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null);
|
||||
var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.Beatmap != null);
|
||||
|
||||
if (existing != null)
|
||||
Beatmap.Value = existing.BeatmapInfo;
|
||||
Beatmap.Value = existing.Beatmap;
|
||||
else
|
||||
{
|
||||
beatmapLookupRequest = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
|
||||
beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmapInfo(Rulesets);
|
||||
beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId });
|
||||
beatmapLookupRequest.Success += b => Beatmap.Value = b;
|
||||
API.Queue(beatmapLookupRequest);
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,14 @@
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Tournament.IPC
|
||||
{
|
||||
public class MatchIPCInfo : Component
|
||||
{
|
||||
public Bindable<BeatmapInfo> Beatmap { get; } = new Bindable<BeatmapInfo>();
|
||||
public Bindable<APIBeatmap> Beatmap { get; } = new Bindable<APIBeatmap>();
|
||||
public Bindable<LegacyMods> Mods { get; } = new Bindable<LegacyMods>();
|
||||
public Bindable<TourneyState> State { get; } = new Bindable<TourneyState>();
|
||||
public Bindable<string> ChatChannel { get; } = new Bindable<string>();
|
||||
|
@ -1,7 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Tournament.Models
|
||||
{
|
||||
@ -10,6 +11,7 @@ namespace osu.Game.Tournament.Models
|
||||
public int ID;
|
||||
public string Mods;
|
||||
|
||||
public BeatmapInfo BeatmapInfo;
|
||||
[JsonProperty("BeatmapInfo")]
|
||||
public APIBeatmap Beatmap;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Tournament.Models
|
||||
{
|
||||
@ -10,7 +11,8 @@ namespace osu.Game.Tournament.Models
|
||||
{
|
||||
public int ID;
|
||||
|
||||
public BeatmapInfo BeatmapInfo;
|
||||
[JsonProperty("BeatmapInfo")]
|
||||
public APIBeatmap Beatmap;
|
||||
|
||||
public long Score;
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osu.Game.Tournament.IPC;
|
||||
|
||||
@ -37,10 +37,10 @@ namespace osu.Game.Tournament.Screens
|
||||
SongBar.Mods = mods.NewValue;
|
||||
}
|
||||
|
||||
private void beatmapChanged(ValueChangedEvent<BeatmapInfo> beatmap)
|
||||
private void beatmapChanged(ValueChangedEvent<APIBeatmap> beatmap)
|
||||
{
|
||||
SongBar.FadeInFromZero(300, Easing.OutQuint);
|
||||
SongBar.BeatmapInfo = beatmap.NewValue;
|
||||
SongBar.Beatmap = beatmap.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tournament.Components;
|
||||
@ -226,25 +226,25 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Model.ID = id.NewValue ?? 0;
|
||||
|
||||
if (id.NewValue != id.OldValue)
|
||||
Model.BeatmapInfo = null;
|
||||
Model.Beatmap = null;
|
||||
|
||||
if (Model.BeatmapInfo != null)
|
||||
if (Model.Beatmap != null)
|
||||
{
|
||||
updatePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID });
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID });
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
Model.BeatmapInfo = res.ToBeatmapInfo(rulesets);
|
||||
Model.Beatmap = res;
|
||||
updatePanel();
|
||||
};
|
||||
|
||||
req.Failure += _ =>
|
||||
{
|
||||
Model.BeatmapInfo = null;
|
||||
Model.Beatmap = null;
|
||||
updatePanel();
|
||||
};
|
||||
|
||||
@ -259,9 +259,9 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
drawableContainer.Clear();
|
||||
|
||||
if (Model.BeatmapInfo != null)
|
||||
if (Model.Beatmap != null)
|
||||
{
|
||||
drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods)
|
||||
drawableContainer.Child = new TournamentBeatmapPanel(Model.Beatmap, Model.Mods)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
@ -7,10 +7,10 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tournament.Components;
|
||||
@ -234,25 +234,25 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Model.ID = id.NewValue ?? 0;
|
||||
|
||||
if (id.NewValue != id.OldValue)
|
||||
Model.BeatmapInfo = null;
|
||||
Model.Beatmap = null;
|
||||
|
||||
if (Model.BeatmapInfo != null)
|
||||
if (Model.Beatmap != null)
|
||||
{
|
||||
updatePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID });
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID });
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
Model.BeatmapInfo = res.ToBeatmapInfo(rulesets);
|
||||
Model.Beatmap = res;
|
||||
updatePanel();
|
||||
};
|
||||
|
||||
req.Failure += _ =>
|
||||
{
|
||||
Model.BeatmapInfo = null;
|
||||
Model.Beatmap = null;
|
||||
updatePanel();
|
||||
};
|
||||
|
||||
@ -267,9 +267,9 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
drawableContainer.Clear();
|
||||
|
||||
if (Model.BeatmapInfo != null)
|
||||
if (Model.Beatmap != null)
|
||||
{
|
||||
drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, result.Mod.Value)
|
||||
drawableContainer.Child = new TournamentBeatmapPanel(Model.Beatmap, result.Mod.Value)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
@ -8,8 +8,8 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osu.Game.Tournament.IPC;
|
||||
using osu.Game.Tournament.Models;
|
||||
@ -105,14 +105,14 @@ namespace osu.Game.Tournament.Screens.MapPool
|
||||
ipc.Beatmap.BindValueChanged(beatmapChanged);
|
||||
}
|
||||
|
||||
private void beatmapChanged(ValueChangedEvent<BeatmapInfo> beatmap)
|
||||
private void beatmapChanged(ValueChangedEvent<APIBeatmap> beatmap)
|
||||
{
|
||||
if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2)
|
||||
return;
|
||||
|
||||
// if bans have already been placed, beatmap changes result in a selection being made autoamtically
|
||||
if (beatmap.NewValue.OnlineBeatmapID != null)
|
||||
addForBeatmap(beatmap.NewValue.OnlineBeatmapID.Value);
|
||||
if (beatmap.NewValue.OnlineID > 0)
|
||||
addForBeatmap(beatmap.NewValue.OnlineID);
|
||||
}
|
||||
|
||||
private void setMode(TeamColour colour, ChoiceType choiceType)
|
||||
@ -147,11 +147,11 @@ namespace osu.Game.Tournament.Screens.MapPool
|
||||
|
||||
if (map != null)
|
||||
{
|
||||
if (e.Button == MouseButton.Left && map.BeatmapInfo.OnlineID > 0)
|
||||
addForBeatmap(map.BeatmapInfo.OnlineID);
|
||||
if (e.Button == MouseButton.Left && map.Beatmap.OnlineID > 0)
|
||||
addForBeatmap(map.Beatmap.OnlineID);
|
||||
else
|
||||
{
|
||||
var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.BeatmapInfo.OnlineID);
|
||||
var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineID);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
@ -179,7 +179,7 @@ namespace osu.Game.Tournament.Screens.MapPool
|
||||
if (CurrentMatch.Value == null)
|
||||
return;
|
||||
|
||||
if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.BeatmapInfo.OnlineBeatmapID != beatmapId))
|
||||
if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.Beatmap.OnlineID != beatmapId))
|
||||
// don't attempt to add if the beatmap isn't in our pool
|
||||
return;
|
||||
|
||||
@ -245,7 +245,7 @@ namespace osu.Game.Tournament.Screens.MapPool
|
||||
flowCount = 1;
|
||||
}
|
||||
|
||||
currentFlow.Add(new TournamentBeatmapPanel(b.BeatmapInfo, b.Mods)
|
||||
currentFlow.Add(new TournamentBeatmapPanel(b.Beatmap, b.Mods)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
|
@ -141,9 +141,9 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Title, Colour = TournamentGame.TEXT_COLOUR, },
|
||||
new TournamentSpriteText { Text = beatmap.Beatmap.Metadata.Title, Colour = TournamentGame.TEXT_COLOUR, },
|
||||
new TournamentSpriteText { Text = "by", Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
||||
new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Artist, Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
||||
new TournamentSpriteText { Text = beatmap.Beatmap.Metadata.Artist, Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
@ -7,12 +7,14 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Tournament.IO;
|
||||
using osu.Game.Tournament.IPC;
|
||||
using osu.Game.Tournament.Models;
|
||||
@ -39,9 +41,18 @@ namespace osu.Game.Tournament
|
||||
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
}
|
||||
|
||||
private TournamentSpriteText initialisationText;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(Storage baseStorage)
|
||||
{
|
||||
AddInternal(initialisationText = new TournamentSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Torus.With(size: 32),
|
||||
});
|
||||
|
||||
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
|
||||
|
||||
dependencies.CacheAs<Storage>(storage = new TournamentStorage(baseStorage));
|
||||
@ -123,7 +134,8 @@ namespace osu.Game.Tournament
|
||||
}
|
||||
|
||||
addedInfo |= addPlayers();
|
||||
addedInfo |= addBeatmaps();
|
||||
addedInfo |= addRoundBeatmaps();
|
||||
addedInfo |= addSeedingBeatmaps();
|
||||
|
||||
if (addedInfo)
|
||||
SaveChanges();
|
||||
@ -145,6 +157,8 @@ namespace osu.Game.Tournament
|
||||
Add(ipc);
|
||||
|
||||
taskCompletionSource.SetResult(true);
|
||||
|
||||
initialisationText.Expire();
|
||||
});
|
||||
}
|
||||
|
||||
@ -153,81 +167,108 @@ namespace osu.Game.Tournament
|
||||
/// </summary>
|
||||
private bool addPlayers()
|
||||
{
|
||||
bool addedInfo = false;
|
||||
var playersRequiringPopulation = ladder.Teams
|
||||
.SelectMany(t => t.Players)
|
||||
.Where(p => string.IsNullOrEmpty(p.Username)
|
||||
|| p.Statistics?.GlobalRank == null
|
||||
|| p.Statistics?.CountryRank == null).ToList();
|
||||
|
||||
foreach (var t in ladder.Teams)
|
||||
if (playersRequiringPopulation.Count == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < playersRequiringPopulation.Count; i++)
|
||||
{
|
||||
foreach (var p in t.Players)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p.Username)
|
||||
|| p.Statistics?.GlobalRank == null
|
||||
|| p.Statistics?.CountryRank == null)
|
||||
{
|
||||
PopulateUser(p, immediate: true);
|
||||
addedInfo = true;
|
||||
}
|
||||
}
|
||||
var p = playersRequiringPopulation[i];
|
||||
PopulateUser(p, immediate: true);
|
||||
updateLoadProgressMessage($"Populating user stats ({i} / {playersRequiringPopulation.Count})");
|
||||
}
|
||||
|
||||
return addedInfo;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add missing beatmap info based on beatmap IDs
|
||||
/// </summary>
|
||||
private bool addBeatmaps()
|
||||
private bool addRoundBeatmaps()
|
||||
{
|
||||
bool addedInfo = false;
|
||||
var beatmapsRequiringPopulation = ladder.Rounds
|
||||
.SelectMany(r => r.Beatmaps)
|
||||
.Where(b => string.IsNullOrEmpty(b.Beatmap?.BeatmapSet?.Title) && b.ID > 0).ToList();
|
||||
|
||||
foreach (var r in ladder.Rounds)
|
||||
if (beatmapsRequiringPopulation.Count == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < beatmapsRequiringPopulation.Count; i++)
|
||||
{
|
||||
foreach (var b in r.Beatmaps.ToList())
|
||||
{
|
||||
if (b.BeatmapInfo != null)
|
||||
continue;
|
||||
var b = beatmapsRequiringPopulation[i];
|
||||
|
||||
if (b.ID > 0)
|
||||
{
|
||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
|
||||
API.Perform(req);
|
||||
b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore);
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = b.ID });
|
||||
API.Perform(req);
|
||||
b.Beatmap = req.Response ?? new APIBeatmap();
|
||||
|
||||
addedInfo = true;
|
||||
}
|
||||
|
||||
if (b.BeatmapInfo == null)
|
||||
// if online population couldn't be performed, ensure we don't leave a null value behind
|
||||
r.Beatmaps.Remove(b);
|
||||
}
|
||||
updateLoadProgressMessage($"Populating round beatmaps ({i} / {beatmapsRequiringPopulation.Count})");
|
||||
}
|
||||
|
||||
foreach (var t in ladder.Teams)
|
||||
{
|
||||
foreach (var s in t.SeedingResults)
|
||||
{
|
||||
foreach (var b in s.Beatmaps)
|
||||
{
|
||||
if (b.BeatmapInfo == null && b.ID > 0)
|
||||
{
|
||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
|
||||
req.Perform(API);
|
||||
b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore);
|
||||
|
||||
addedInfo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addedInfo;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add missing beatmap info based on beatmap IDs
|
||||
/// </summary>
|
||||
private bool addSeedingBeatmaps()
|
||||
{
|
||||
var beatmapsRequiringPopulation = ladder.Teams
|
||||
.SelectMany(r => r.SeedingResults)
|
||||
.SelectMany(r => r.Beatmaps)
|
||||
.Where(b => string.IsNullOrEmpty(b.Beatmap?.BeatmapSet?.Title) && b.ID > 0).ToList();
|
||||
|
||||
if (beatmapsRequiringPopulation.Count == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < beatmapsRequiringPopulation.Count; i++)
|
||||
{
|
||||
var b = beatmapsRequiringPopulation[i];
|
||||
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = b.ID });
|
||||
API.Perform(req);
|
||||
b.Beatmap = req.Response ?? new APIBeatmap();
|
||||
|
||||
updateLoadProgressMessage($"Populating seeding beatmaps ({i} / {beatmapsRequiringPopulation.Count})");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateLoadProgressMessage(string s) => Schedule(() => initialisationText.Text = s);
|
||||
|
||||
public void PopulateUser(User user, Action success = null, Action failure = null, bool immediate = false)
|
||||
{
|
||||
var req = new GetUserRequest(user.Id, Ruleset.Value);
|
||||
|
||||
req.Success += res =>
|
||||
if (immediate)
|
||||
{
|
||||
API.Perform(req);
|
||||
populate();
|
||||
}
|
||||
else
|
||||
{
|
||||
req.Success += res => { populate(); };
|
||||
req.Failure += _ =>
|
||||
{
|
||||
user.Id = 1;
|
||||
failure?.Invoke();
|
||||
};
|
||||
|
||||
API.Queue(req);
|
||||
}
|
||||
|
||||
void populate()
|
||||
{
|
||||
var res = req.Response;
|
||||
|
||||
if (res == null)
|
||||
return;
|
||||
|
||||
user.Id = res.Id;
|
||||
|
||||
user.Username = res.Username;
|
||||
@ -236,18 +277,7 @@ namespace osu.Game.Tournament
|
||||
user.Cover = res.Cover;
|
||||
|
||||
success?.Invoke();
|
||||
};
|
||||
|
||||
req.Failure += _ =>
|
||||
{
|
||||
user.Id = 1;
|
||||
failure?.Invoke();
|
||||
};
|
||||
|
||||
if (immediate)
|
||||
API.Perform(req);
|
||||
else
|
||||
API.Queue(req);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -269,18 +299,19 @@ namespace osu.Game.Tournament
|
||||
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
||||
.ToList();
|
||||
|
||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||
string serialisedLadder = JsonConvert.SerializeObject(ladder,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||
Converters = new JsonConverter[] { new JsonPointConverter() }
|
||||
});
|
||||
|
||||
using (var stream = storage.GetStream(bracket_filename, FileAccess.Write, FileMode.Create))
|
||||
using (var sw = new StreamWriter(stream))
|
||||
{
|
||||
sw.Write(JsonConvert.SerializeObject(ladder,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||
Converters = new JsonConverter[] { new JsonPointConverter() }
|
||||
}));
|
||||
}
|
||||
sw.Write(serialisedLadder);
|
||||
}
|
||||
|
||||
protected override UserInputManager CreateUserInputManager() => new TournamentInputManager();
|
||||
|
@ -14,7 +14,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>, ISoftDelete, IEquatable<BeatmapSetInfo>, IBeatmapSetInfo, IBeatmapSetOnlineInfo
|
||||
public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>, ISoftDelete, IEquatable<BeatmapSetInfo>, IBeatmapSetInfo
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
@ -35,6 +35,7 @@ namespace osu.Game.Beatmaps
|
||||
[NotNull]
|
||||
public List<BeatmapSetFileInfo> Files { get; set; } = new List<BeatmapSetFileInfo>();
|
||||
|
||||
// This field is temporary and only used by `APIBeatmapSet.ToBeatmapSet` (soon to be removed) and tests (to be updated to provide APIBeatmapSet instead).
|
||||
[NotMapped]
|
||||
public APIBeatmapSet OnlineInfo { get; set; }
|
||||
|
||||
|
@ -12,9 +12,9 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// <summary>
|
||||
/// Display a beatmap background from a local source, but fallback to online source if not available.
|
||||
/// </summary>
|
||||
public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<BeatmapInfo>
|
||||
public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<IBeatmapInfo>
|
||||
{
|
||||
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
|
||||
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
|
||||
|
||||
protected override double LoadDelay => 500;
|
||||
|
||||
@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
protected override double TransformDuration => 400;
|
||||
|
||||
protected override Drawable CreateDrawable(BeatmapInfo model)
|
||||
protected override Drawable CreateDrawable(IBeatmapInfo model)
|
||||
{
|
||||
var drawable = getDrawableForModel(model);
|
||||
drawable.RelativeSizeAxes = Axes.Both;
|
||||
@ -50,15 +50,21 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private Drawable getDrawableForModel(BeatmapInfo model)
|
||||
private Drawable getDrawableForModel(IBeatmapInfo model)
|
||||
{
|
||||
// prefer online cover where available.
|
||||
if (model?.BeatmapSet?.OnlineInfo != null)
|
||||
return new OnlineBeatmapSetCover(model.BeatmapSet, beatmapSetCoverType);
|
||||
if (model?.BeatmapSet is IBeatmapSetOnlineInfo online)
|
||||
return new OnlineBeatmapSetCover(online, beatmapSetCoverType);
|
||||
|
||||
return model?.ID > 0
|
||||
? new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(model))
|
||||
: new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap);
|
||||
if (model is BeatmapInfo localModel)
|
||||
{
|
||||
if (localModel.BeatmapSet?.OnlineInfo != null)
|
||||
return new OnlineBeatmapSetCover(localModel.BeatmapSet.OnlineInfo, beatmapSetCoverType);
|
||||
|
||||
return new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(localModel));
|
||||
}
|
||||
|
||||
return new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
private readonly BeatmapSetCoverType coverType;
|
||||
|
||||
public IBeatmapSetOnlineInfo BeatmapSet
|
||||
public IBeatmapSetOnlineInfo OnlineInfo
|
||||
{
|
||||
get => Model;
|
||||
set => Model = value;
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Database
|
||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||
public abstract class ArchiveModelManager<TModel, TFileModel> : IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
|
||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||
where TFileModel : class, INamedFileInfo, new()
|
||||
where TFileModel : class, INamedFileInfo, IHasPrimaryKey, new()
|
||||
{
|
||||
private const int import_queue_request_concurrency = 1;
|
||||
|
||||
@ -315,25 +315,29 @@ namespace osu.Game.Database
|
||||
/// <remarks>
|
||||
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
|
||||
/// </remarks>
|
||||
protected virtual string ComputeHash(TModel item, ArchiveReader reader = null)
|
||||
protected virtual string ComputeHash(TModel item)
|
||||
{
|
||||
if (reader != null)
|
||||
// fast hashing for cases where the item's files may not be populated.
|
||||
return computeHashFast(reader);
|
||||
var hashableFiles = item.Files
|
||||
.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(f => f.Filename)
|
||||
.ToArray();
|
||||
|
||||
// for now, concatenate all hashable files in the set to create a unique hash.
|
||||
MemoryStream hashable = new MemoryStream();
|
||||
|
||||
foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename))
|
||||
if (hashableFiles.Length > 0)
|
||||
{
|
||||
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath))
|
||||
s.CopyTo(hashable);
|
||||
// for now, concatenate all hashable files in the set to create a unique hash.
|
||||
MemoryStream hashable = new MemoryStream();
|
||||
|
||||
foreach (TFileModel file in hashableFiles)
|
||||
{
|
||||
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath))
|
||||
s.CopyTo(hashable);
|
||||
}
|
||||
|
||||
if (hashable.Length > 0)
|
||||
return hashable.ComputeSHA2Hash();
|
||||
}
|
||||
|
||||
if (hashable.Length > 0)
|
||||
return hashable.ComputeSHA2Hash();
|
||||
|
||||
return item.Hash;
|
||||
return generateFallbackHash();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -393,7 +397,7 @@ namespace osu.Game.Database
|
||||
LogForModel(item, @"Beginning import...");
|
||||
|
||||
item.Files = archive != null ? createFileInfos(archive, Files) : new List<TFileModel>();
|
||||
item.Hash = ComputeHash(item, archive);
|
||||
item.Hash = ComputeHash(item);
|
||||
|
||||
await Populate(item, archive, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@ -516,9 +520,12 @@ namespace osu.Game.Database
|
||||
{
|
||||
Files.Dereference(file.FileInfo);
|
||||
|
||||
// This shouldn't be required, but here for safety in case the provided TModel is not being change tracked
|
||||
// Definitely can be removed once we rework the database backend.
|
||||
usage.Context.Set<TFileModel>().Remove(file);
|
||||
if (file.ID > 0)
|
||||
{
|
||||
// This shouldn't be required, but here for safety in case the provided TModel is not being change tracked
|
||||
// Definitely can be removed once we rework the database backend.
|
||||
usage.Context.Set<TFileModel>().Remove(file);
|
||||
}
|
||||
}
|
||||
|
||||
model.Files.Remove(file);
|
||||
@ -540,9 +547,10 @@ namespace osu.Game.Database
|
||||
Filename = filename,
|
||||
FileInfo = Files.Add(contents)
|
||||
});
|
||||
|
||||
Update(model);
|
||||
}
|
||||
|
||||
if (model.ID > 0)
|
||||
Update(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -684,7 +692,7 @@ namespace osu.Game.Database
|
||||
if (hashable.Length > 0)
|
||||
return hashable.ComputeSHA2Hash();
|
||||
|
||||
return reader.Name.ComputeSHA2Hash();
|
||||
return generateFallbackHash();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -897,6 +905,14 @@ namespace osu.Game.Database
|
||||
|
||||
#endregion
|
||||
|
||||
private static string generateFallbackHash()
|
||||
{
|
||||
// if a hash could no be generated from file content, presume a unique / new import.
|
||||
// therefore, let's use a guaranteed unique hash.
|
||||
// this doesn't follow the SHA2 hashing schema intentionally, so such entries on the data store can be identified.
|
||||
return Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
private string getValidFilename(string filename)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
|
23
osu.Game/Migrations/20211020081609_ResetSkinHashes.cs
Normal file
23
osu.Game/Migrations/20211020081609_ResetSkinHashes.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
[DbContext(typeof(OsuDbContext))]
|
||||
[Migration("20211020081609_ResetSkinHashes")]
|
||||
public partial class ResetSkinHashes : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -54,10 +54,15 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"accuracy")]
|
||||
private float overallDifficulty { get; set; }
|
||||
|
||||
public double Length => TimeSpan.FromSeconds(lengthInSeconds).TotalMilliseconds;
|
||||
[JsonIgnore]
|
||||
public double Length { get; set; }
|
||||
|
||||
[JsonProperty(@"total_length")]
|
||||
private double lengthInSeconds { get; set; }
|
||||
private double lengthInSeconds
|
||||
{
|
||||
get => TimeSpan.FromMilliseconds(Length).TotalSeconds;
|
||||
set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds;
|
||||
}
|
||||
|
||||
[JsonProperty(@"count_circles")]
|
||||
public int CircleCount { get; set; }
|
||||
|
@ -164,7 +164,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => throw new NotImplementedException();
|
||||
double IBeatmapSetInfo.MaxStarDifficulty => throw new NotImplementedException();
|
||||
double IBeatmapSetInfo.MaxLength => throw new NotImplementedException();
|
||||
double IBeatmapSetInfo.MaxBPM => throw new NotImplementedException();
|
||||
double IBeatmapSetInfo.MaxBPM => BPM;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -158,6 +158,8 @@ namespace osu.Game
|
||||
|
||||
private Bindable<int> configRuleset;
|
||||
|
||||
private Bindable<float> uiScale;
|
||||
|
||||
private Bindable<int> configSkin;
|
||||
|
||||
private readonly string[] args;
|
||||
@ -219,6 +221,7 @@ namespace osu.Game
|
||||
|
||||
// bind config int to database RulesetInfo
|
||||
configRuleset = LocalConfig.GetBindable<int>(OsuSetting.Ruleset);
|
||||
uiScale = LocalConfig.GetBindable<float>(OsuSetting.UIScale);
|
||||
|
||||
var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value);
|
||||
|
||||
@ -1020,6 +1023,28 @@ namespace osu.Game
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
|
||||
{
|
||||
const float adjustment_increment = 0.05f;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case PlatformAction.ZoomIn:
|
||||
uiScale.Value += adjustment_increment;
|
||||
return true;
|
||||
|
||||
case PlatformAction.ZoomOut:
|
||||
uiScale.Value -= adjustment_increment;
|
||||
return true;
|
||||
|
||||
case PlatformAction.ZoomDefault:
|
||||
uiScale.SetDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnPressed(e);
|
||||
}
|
||||
|
||||
#region Inactive audio dimming
|
||||
|
||||
private readonly BindableDouble inactiveVolumeFade = new BindableDouble();
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
return;
|
||||
}
|
||||
|
||||
beatmapCover.BeatmapSet = value;
|
||||
beatmapCover.OnlineInfo = value.OnlineInfo;
|
||||
beatmapCover.FadeTo(0.1f, 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
protected Drawable CreateBackground() => new UpdateableOnlineBeatmapSetCover
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BeatmapSet = SetInfo,
|
||||
OnlineInfo = SetInfo.OnlineInfo,
|
||||
};
|
||||
|
||||
public class Statistic : FillFlowContainer
|
||||
|
@ -231,7 +231,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
BeatmapSet.BindValueChanged(setInfo =>
|
||||
{
|
||||
Picker.BeatmapSet = rulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue;
|
||||
cover.BeatmapSet = setInfo.NewValue;
|
||||
cover.OnlineInfo = setInfo.NewValue?.OnlineInfo;
|
||||
|
||||
downloadTracker?.RemoveAndDisposeImmediately();
|
||||
|
||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BeatmapSet = SetInfo
|
||||
OnlineInfo = SetInfo.OnlineInfo
|
||||
}
|
||||
},
|
||||
new Container
|
||||
|
@ -4,11 +4,15 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -16,6 +20,8 @@ namespace osu.Game.Overlays.Notifications
|
||||
{
|
||||
public class ProgressNotification : Notification, IHasCompletionTarget
|
||||
{
|
||||
private const float loading_spinner_size = 22;
|
||||
|
||||
public string Text
|
||||
{
|
||||
set => Schedule(() => textDrawable.Text = value);
|
||||
@ -65,29 +71,53 @@ namespace osu.Game.Overlays.Notifications
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
const double colour_fade_duration = 200;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ProgressNotificationState.Queued:
|
||||
Light.Colour = colourQueued;
|
||||
Light.Pulsate = false;
|
||||
progressBar.Active = false;
|
||||
|
||||
iconBackground.FadeColour(ColourInfo.GradientVertical(colourQueued, colourQueued.Lighten(0.5f)), colour_fade_duration);
|
||||
loadingSpinner.Show();
|
||||
break;
|
||||
|
||||
case ProgressNotificationState.Active:
|
||||
Light.Colour = colourActive;
|
||||
Light.Pulsate = true;
|
||||
progressBar.Active = true;
|
||||
|
||||
iconBackground.FadeColour(ColourInfo.GradientVertical(colourActive, colourActive.Lighten(0.5f)), colour_fade_duration);
|
||||
loadingSpinner.Show();
|
||||
break;
|
||||
|
||||
case ProgressNotificationState.Cancelled:
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
iconBackground.FadeColour(ColourInfo.GradientVertical(Color4.Gray, Color4.Gray.Lighten(0.5f)), colour_fade_duration);
|
||||
loadingSpinner.Hide();
|
||||
|
||||
var icon = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.Ban,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(loading_spinner_size),
|
||||
};
|
||||
|
||||
IconContent.Add(icon);
|
||||
|
||||
icon.FadeInFromZero(200, Easing.OutQuint);
|
||||
|
||||
Light.Colour = colourCancelled;
|
||||
Light.Pulsate = false;
|
||||
progressBar.Active = false;
|
||||
break;
|
||||
|
||||
case ProgressNotificationState.Completed:
|
||||
loadingSpinner.Hide();
|
||||
NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint);
|
||||
this.FadeOut(200).Finally(d => Completed());
|
||||
break;
|
||||
@ -115,15 +145,13 @@ namespace osu.Game.Overlays.Notifications
|
||||
private Color4 colourActive;
|
||||
private Color4 colourCancelled;
|
||||
|
||||
private Box iconBackground;
|
||||
private LoadingSpinner loadingSpinner;
|
||||
|
||||
private readonly TextFlowContainer textDrawable;
|
||||
|
||||
public ProgressNotification()
|
||||
{
|
||||
IconContent.Add(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
});
|
||||
|
||||
Content.Add(textDrawable = new OsuTextFlowContainer
|
||||
{
|
||||
Colour = OsuColour.Gray(128),
|
||||
@ -138,6 +166,9 @@ namespace osu.Game.Overlays.Notifications
|
||||
RelativeSizeAxes = Axes.X,
|
||||
});
|
||||
|
||||
// make some extra space for the progress bar.
|
||||
IconContent.Margin = new MarginPadding { Bottom = 5 };
|
||||
|
||||
State = ProgressNotificationState.Queued;
|
||||
|
||||
// don't close on click by default.
|
||||
@ -150,6 +181,19 @@ namespace osu.Game.Overlays.Notifications
|
||||
colourQueued = colours.YellowDark;
|
||||
colourActive = colours.Blue;
|
||||
colourCancelled = colours.Red;
|
||||
|
||||
IconContent.AddRange(new Drawable[]
|
||||
{
|
||||
iconBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White,
|
||||
},
|
||||
loadingSpinner = new LoadingSpinner
|
||||
{
|
||||
Size = new Vector2(loading_spinner_size),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = cover_width,
|
||||
BeatmapSet = mostPlayed.BeatmapSet,
|
||||
OnlineInfo = mostPlayed.BeatmapSet,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
List<IList<HitSampleInfo>> nodeSamples)
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
newCombo |= forceNewCombo;
|
||||
comboOffset += extraComboOffset;
|
||||
|
@ -408,7 +408,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
List<IList<HitSampleInfo>> nodeSamples);
|
||||
IList<IList<HitSampleInfo>> nodeSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy Spinner-type hit object.
|
||||
@ -481,7 +481,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled
|
||||
/// using the <see cref="LegacySkinConfiguration.LegacySetting.LayeredHitSounds"/> skin config option.
|
||||
/// using the <see cref="SkinConfiguration.LegacySetting.LayeredHitSounds"/> skin config option.
|
||||
/// </remarks>
|
||||
public readonly bool IsLayered;
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
public double Distance => Path.Distance;
|
||||
|
||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||
public int RepeatCount { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
List<IList<HitSampleInfo>> nodeSamples)
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return new ConvertSlider
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
List<IList<HitSampleInfo>> nodeSamples)
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
newCombo |= forceNewCombo;
|
||||
comboOffset += extraComboOffset;
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
List<IList<HitSampleInfo>> nodeSamples)
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return new ConvertSlider
|
||||
{
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// n-1: The last repeat.<br />
|
||||
/// n: The last node.
|
||||
/// </summary>
|
||||
List<IList<HitSampleInfo>> NodeSamples { get; }
|
||||
IList<IList<HitSampleInfo>> NodeSamples { get; }
|
||||
}
|
||||
|
||||
public static class HasRepeatsExtensions
|
||||
|
@ -3,13 +3,15 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public abstract class ReadyButton : TriangleButton
|
||||
public abstract class ReadyButton : TriangleButton, IHasTooltip
|
||||
{
|
||||
public new readonly BindableBool Enabled = new BindableBool();
|
||||
|
||||
@ -24,6 +26,18 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
Enabled.BindValueChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value;
|
||||
private void updateState() =>
|
||||
base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value;
|
||||
|
||||
public virtual LocalisableString TooltipText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Enabled.Value)
|
||||
return string.Empty;
|
||||
|
||||
return "Beatmap not downloaded";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
private OsuSpriteText attemptDisplay;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public RoomLocalUserInfo()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -54,6 +57,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts);
|
||||
attemptDisplay.Text += $" ({remaining} remaining)";
|
||||
|
||||
if (remaining == 0)
|
||||
attemptDisplay.Colour = colours.RedLight;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -332,13 +332,14 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
public PanelBackground()
|
||||
{
|
||||
UpdateableBeatmapBackgroundSprite backgroundSprite;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new UpdateableBeatmapBackgroundSprite
|
||||
backgroundSprite = new UpdateableBeatmapBackgroundSprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Beatmap = { BindTarget = Beatmap }
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@ -373,6 +374,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// manual binding required as playlists don't expose IBeatmapInfo currently.
|
||||
// may be removed in the future if this changes.
|
||||
Beatmap.BindValueChanged(beatmap => backgroundSprite.Beatmap.Value = beatmap.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
@ -16,6 +18,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
[Resolved(typeof(Room), nameof(Room.EndDate))]
|
||||
private Bindable<DateTimeOffset?> endDate { get; set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.MaxAttempts))]
|
||||
private Bindable<int?> maxAttempts { get; set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.UserScore))]
|
||||
private Bindable<PlaylistAggregateScore> userScore { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> gameBeatmap { get; set; }
|
||||
|
||||
@ -32,11 +40,49 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
Triangles.ColourLight = colours.GreenLight;
|
||||
}
|
||||
|
||||
private bool hasRemainingAttempts = true;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
userScore.BindValueChanged(aggregate =>
|
||||
{
|
||||
if (maxAttempts.Value == null)
|
||||
return;
|
||||
|
||||
int remaining = maxAttempts.Value.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts);
|
||||
|
||||
hasRemainingAttempts = remaining > 0;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Enabled.Value = endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value;
|
||||
Enabled.Value = hasRemainingAttempts && enoughTimeLeft;
|
||||
}
|
||||
|
||||
public override LocalisableString TooltipText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Enabled.Value)
|
||||
return string.Empty;
|
||||
|
||||
if (!enoughTimeLeft)
|
||||
return "No time left!";
|
||||
|
||||
if (!hasRemainingAttempts)
|
||||
return "Attempts exhausted!";
|
||||
|
||||
return base.TooltipText;
|
||||
}
|
||||
}
|
||||
|
||||
private bool enoughTimeLeft =>
|
||||
// This should probably consider the length of the currently selected item, rather than a constant 30 seconds.
|
||||
endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value;
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
private MatchLeaderboard leaderboard;
|
||||
private SelectionPollingComponent selectionPollingComponent;
|
||||
|
||||
private FillFlowContainer progressSection;
|
||||
|
||||
public PlaylistsRoomSubScreen(Room room)
|
||||
: base(room, false) // Editing is temporarily not allowed.
|
||||
{
|
||||
@ -67,6 +69,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault());
|
||||
}
|
||||
}, true);
|
||||
|
||||
Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true);
|
||||
}
|
||||
|
||||
protected override Drawable CreateMainContent() => new GridContainer
|
||||
@ -153,6 +157,22 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
progressSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Progress"),
|
||||
new RoomLocalUserInfo(),
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Leaderboard")
|
||||
},
|
||||
@ -162,6 +182,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
|
@ -19,7 +19,14 @@ namespace osu.Game.Skinning
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||
: base(skin, new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy"), resources, string.Empty)
|
||||
: base(
|
||||
skin,
|
||||
new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy"),
|
||||
resources,
|
||||
// A default legacy skin may still have a skin.ini if it is modified by the user.
|
||||
// We must specify the stream directly as we are redirecting storage to the osu-resources location for other files.
|
||||
new LegacySkinResourceStore<SkinFileInfo>(skin, resources.Files).GetStream("skin.ini")
|
||||
)
|
||||
{
|
||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||
Configuration.CustomComboColours = new List<Color4>
|
||||
|
@ -35,7 +35,6 @@ namespace osu.Game.Skinning
|
||||
: base(skin, resources)
|
||||
{
|
||||
this.resources = resources;
|
||||
Configuration = new DefaultSkinConfiguration();
|
||||
}
|
||||
|
||||
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||
|
@ -1,12 +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.
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
/// <summary>
|
||||
/// A skin configuration pre-populated with sane defaults.
|
||||
/// </summary>
|
||||
public class DefaultSkinConfiguration : SkinConfiguration
|
||||
{
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version:
|
||||
case SkinConfiguration.LegacySetting s when s == SkinConfiguration.LegacySetting.Version:
|
||||
// For lookup simplicity, ignore beatmap-level versioning completely.
|
||||
|
||||
// If it is decided that we need this due to beatmaps somehow using it, the default (1.0 specified in LegacySkinDecoder.CreateTemplateObject)
|
||||
|
@ -47,12 +47,6 @@ namespace osu.Game.Skinning
|
||||
/// </summary>
|
||||
protected virtual bool UseCustomSampleBanks => false;
|
||||
|
||||
public new LegacySkinConfiguration Configuration
|
||||
{
|
||||
get => base.Configuration as LegacySkinConfiguration;
|
||||
set => base.Configuration = value;
|
||||
}
|
||||
|
||||
private readonly Dictionary<int, LegacyManiaSkinConfiguration> maniaConfigurations = new Dictionary<int, LegacyManiaSkinConfiguration>();
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
@ -69,29 +63,20 @@ namespace osu.Game.Skinning
|
||||
/// <param name="resources">Access to raw game resources.</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)
|
||||
: base(skin, resources)
|
||||
: this(skin, storage, resources, storage?.GetStream(configurationFilename))
|
||||
{
|
||||
using (var stream = storage?.GetStream(configurationFilename))
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
using (LineBufferedReader reader = new LineBufferedReader(stream, true))
|
||||
Configuration = new LegacySkinDecoder().Decode(reader);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (LineBufferedReader reader = new LineBufferedReader(stream))
|
||||
{
|
||||
var maniaList = new LegacyManiaSkinDecoder().Decode(reader);
|
||||
|
||||
foreach (var config in maniaList)
|
||||
maniaConfigurations[config.Keys] = config;
|
||||
}
|
||||
}
|
||||
else
|
||||
Configuration = new LegacySkinConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
@ -110,6 +95,21 @@ namespace osu.Game.Skinning
|
||||
true) != null);
|
||||
}
|
||||
|
||||
protected override void ParseConfigurationStream(Stream stream)
|
||||
{
|
||||
base.ParseConfigurationStream(stream);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (LineBufferedReader reader = new LineBufferedReader(stream))
|
||||
{
|
||||
var maniaList = new LegacyManiaSkinDecoder().Decode(reader);
|
||||
|
||||
foreach (var config in maniaList)
|
||||
maniaConfigurations[config.Keys] = config;
|
||||
}
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
switch (lookup)
|
||||
@ -146,7 +146,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
break;
|
||||
|
||||
case LegacySkinConfiguration.LegacySetting legacy:
|
||||
case SkinConfiguration.LegacySetting legacy:
|
||||
return legacySettingLookup<TValue>(legacy);
|
||||
|
||||
default:
|
||||
@ -189,7 +189,7 @@ namespace osu.Game.Skinning
|
||||
case LegacyManiaSkinConfigurationLookups.ExplosionScale:
|
||||
Debug.Assert(maniaLookup.TargetColumn != null);
|
||||
|
||||
if (GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m)
|
||||
if (GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value < 2.5m)
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(1));
|
||||
|
||||
if (existing.ExplosionWidth[maniaLookup.TargetColumn.Value] != 0)
|
||||
@ -236,7 +236,7 @@ namespace osu.Game.Skinning
|
||||
case LegacyManiaSkinConfigurationLookups.HoldNoteLightScale:
|
||||
Debug.Assert(maniaLookup.TargetColumn != null);
|
||||
|
||||
if (GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m)
|
||||
if (GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value < 2.5m)
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(1));
|
||||
|
||||
if (existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] != 0)
|
||||
@ -309,15 +309,15 @@ namespace osu.Game.Skinning
|
||||
=> source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable<string>(image) : null;
|
||||
|
||||
[CanBeNull]
|
||||
private IBindable<TValue> legacySettingLookup<TValue>(LegacySkinConfiguration.LegacySetting legacySetting)
|
||||
private IBindable<TValue> legacySettingLookup<TValue>(SkinConfiguration.LegacySetting legacySetting)
|
||||
{
|
||||
switch (legacySetting)
|
||||
{
|
||||
case LegacySkinConfiguration.LegacySetting.Version:
|
||||
return SkinUtils.As<TValue>(new Bindable<decimal>(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION));
|
||||
case SkinConfiguration.LegacySetting.Version:
|
||||
return SkinUtils.As<TValue>(new Bindable<decimal>(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION));
|
||||
|
||||
default:
|
||||
return genericLookup<LegacySkinConfiguration.LegacySetting, TValue>(legacySetting);
|
||||
return genericLookup<SkinConfiguration.LegacySetting, TValue>(legacySetting);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +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.
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacySkinConfiguration : SkinConfiguration
|
||||
{
|
||||
public const decimal LATEST_VERSION = 2.7m;
|
||||
|
||||
/// <summary>
|
||||
/// Legacy version of this skin.
|
||||
/// </summary>
|
||||
public decimal? LegacyVersion { get; internal set; }
|
||||
|
||||
public enum LegacySetting
|
||||
{
|
||||
Version,
|
||||
ComboPrefix,
|
||||
ComboOverlap,
|
||||
ScorePrefix,
|
||||
ScoreOverlap,
|
||||
HitCirclePrefix,
|
||||
HitCircleOverlap,
|
||||
AnimationFramerate,
|
||||
LayeredHitSounds
|
||||
}
|
||||
}
|
||||
}
|
@ -6,14 +6,14 @@ using osu.Game.Beatmaps.Formats;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacySkinDecoder : LegacyDecoder<LegacySkinConfiguration>
|
||||
public class LegacySkinDecoder : LegacyDecoder<SkinConfiguration>
|
||||
{
|
||||
public LegacySkinDecoder()
|
||||
: base(1)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ParseLine(LegacySkinConfiguration skin, Section section, string line)
|
||||
protected override void ParseLine(SkinConfiguration skin, Section section, string line)
|
||||
{
|
||||
if (section != Section.Colours)
|
||||
{
|
||||
@ -34,7 +34,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
case @"Version":
|
||||
if (pair.Value == "latest")
|
||||
skin.LegacyVersion = LegacySkinConfiguration.LATEST_VERSION;
|
||||
skin.LegacyVersion = SkinConfiguration.LATEST_VERSION;
|
||||
else if (decimal.TryParse(pair.Value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal version))
|
||||
skin.LegacyVersion = version;
|
||||
|
||||
@ -57,7 +57,7 @@ namespace osu.Game.Skinning
|
||||
base.ParseLine(skin, section, line);
|
||||
}
|
||||
|
||||
protected override LegacySkinConfiguration CreateTemplateObject()
|
||||
protected override SkinConfiguration CreateTemplateObject()
|
||||
{
|
||||
var config = base.CreateTemplateObject();
|
||||
config.LegacyVersion = 1.0m;
|
||||
|
@ -12,7 +12,7 @@ using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||
using static osu.Game.Skinning.SkinConfiguration;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||
using static osu.Game.Skinning.SkinConfiguration;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
@ -21,8 +23,9 @@ namespace osu.Game.Skinning
|
||||
public abstract class Skin : IDisposable, ISkin
|
||||
{
|
||||
public readonly SkinInfo SkinInfo;
|
||||
private readonly IStorageResourceProvider resources;
|
||||
|
||||
public SkinConfiguration Configuration { get; protected set; }
|
||||
public SkinConfiguration Configuration { get; set; }
|
||||
|
||||
public IDictionary<SkinnableTarget, SkinnableInfo[]> DrawableComponentInfo => drawableComponentInfo;
|
||||
|
||||
@ -36,9 +39,18 @@ namespace osu.Game.Skinning
|
||||
|
||||
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
||||
|
||||
protected Skin(SkinInfo skin, IStorageResourceProvider resources)
|
||||
protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null)
|
||||
{
|
||||
SkinInfo = skin;
|
||||
this.resources = resources;
|
||||
|
||||
configurationStream ??= getConfigurationStream();
|
||||
|
||||
if (configurationStream != null)
|
||||
// stream will be closed after use by LineBufferedReader.
|
||||
ParseConfigurationStream(configurationStream);
|
||||
else
|
||||
Configuration = new SkinConfiguration();
|
||||
|
||||
// we may want to move this to some kind of async operation in the future.
|
||||
foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget)))
|
||||
@ -73,6 +85,22 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ParseConfigurationStream(Stream stream)
|
||||
{
|
||||
using (LineBufferedReader reader = new LineBufferedReader(stream, true))
|
||||
Configuration = new LegacySkinDecoder().Decode(reader);
|
||||
}
|
||||
|
||||
private Stream getConfigurationStream()
|
||||
{
|
||||
string path = SkinInfo.Files.SingleOrDefault(f => f.Filename == "skin.ini")?.FileInfo.StoragePath;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
return resources?.Files.GetStream(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all stored customisations for the provided target.
|
||||
/// </summary>
|
||||
|
@ -14,11 +14,31 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public readonly SkinInfo SkinInfo = new SkinInfo();
|
||||
|
||||
public const decimal LATEST_VERSION = 2.7m;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow <see cref="DefaultComboColours"/> as a fallback list for when no combo colours are provided.
|
||||
/// </summary>
|
||||
internal bool AllowDefaultComboColoursFallback = true;
|
||||
|
||||
/// <summary>
|
||||
/// Legacy version of this skin.
|
||||
/// </summary>
|
||||
public decimal? LegacyVersion { get; internal set; }
|
||||
|
||||
public enum LegacySetting
|
||||
{
|
||||
Version,
|
||||
ComboPrefix,
|
||||
ComboOverlap,
|
||||
ScorePrefix,
|
||||
ScoreOverlap,
|
||||
HitCirclePrefix,
|
||||
HitCircleOverlap,
|
||||
AnimationFramerate,
|
||||
LayeredHitSounds
|
||||
}
|
||||
|
||||
public static List<Color4> DefaultComboColours { get; } = new List<Color4>
|
||||
{
|
||||
new Color4(255, 192, 0, 255),
|
||||
|
@ -18,12 +18,12 @@ namespace osu.Game.Skinning
|
||||
|
||||
public int ID { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Creator { get; set; } = string.Empty;
|
||||
|
||||
public string Hash { get; set; }
|
||||
|
||||
public string Creator { get; set; }
|
||||
|
||||
public string InstantiationInfo { get; set; }
|
||||
|
||||
public virtual Skin CreateInstance(IStorageResourceProvider resources)
|
||||
|
@ -14,11 +14,11 @@ using Newtonsoft.Json;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
public override IEnumerable<string> HandledExtensions => new[] { ".osk" };
|
||||
|
||||
protected override string[] HashableFileTypes => new[] { ".ini" };
|
||||
protected override string[] HashableFileTypes => new[] { ".ini", ".json" };
|
||||
|
||||
protected override string ImportFromStablePath => "Skins";
|
||||
|
||||
@ -85,6 +85,27 @@ namespace osu.Game.Skinning
|
||||
|
||||
SourceChanged?.Invoke();
|
||||
};
|
||||
|
||||
// can be removed 20220420.
|
||||
populateMissingHashes();
|
||||
}
|
||||
|
||||
private void populateMissingHashes()
|
||||
{
|
||||
var skinsWithoutHashes = ModelStore.ConsumableItems.Where(i => i.Hash == null).ToArray();
|
||||
|
||||
foreach (SkinInfo skin in skinsWithoutHashes)
|
||||
{
|
||||
try
|
||||
{
|
||||
Update(skin);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Delete(skin);
|
||||
Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk";
|
||||
@ -128,28 +149,118 @@ namespace osu.Game.Skinning
|
||||
CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID);
|
||||
}
|
||||
|
||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
|
||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? "No name" };
|
||||
|
||||
private const string unknown_creator_string = "Unknown";
|
||||
|
||||
protected override bool HasCustomHashFunction => true;
|
||||
|
||||
protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null)
|
||||
protected override string ComputeHash(SkinInfo item)
|
||||
{
|
||||
var instance = GetSkin(item);
|
||||
|
||||
// in the case the skin has a skin.ini file, we are going to create a hash based on that.
|
||||
// we don't want to do this in the case we don't have a skin.ini, as it would match only on the filename portion,
|
||||
// causing potentially unique skin imports to be considered as a duplicate.
|
||||
if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name))
|
||||
{
|
||||
// we need to populate early to create a hash based off skin.ini contents
|
||||
populateMetadata(item, instance, reader?.Name);
|
||||
// This function can be run on fresh import or save. The logic here ensures a skin.ini file is in a good state for both operations.
|
||||
|
||||
return item.ToString().ComputeSHA2Hash();
|
||||
// `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above.
|
||||
string skinIniSourcedName = instance.Configuration.SkinInfo.Name;
|
||||
string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator;
|
||||
string archiveName = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
bool isImport = item.ID == 0;
|
||||
|
||||
if (isImport)
|
||||
{
|
||||
item.Name = !string.IsNullOrEmpty(skinIniSourcedName) ? skinIniSourcedName : archiveName;
|
||||
item.Creator = !string.IsNullOrEmpty(skinIniSourcedCreator) ? skinIniSourcedCreator : unknown_creator_string;
|
||||
|
||||
// For imports, we want to use the archive or folder name as part of the metadata, in addition to any existing skin.ini metadata.
|
||||
// In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications.
|
||||
// In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin.
|
||||
if (archiveName != item.Name)
|
||||
item.Name = $"{item.Name} [{archiveName}]";
|
||||
}
|
||||
|
||||
return base.ComputeHash(item, reader);
|
||||
// By this point, the metadata in SkinInfo will be correct.
|
||||
// Regardless of whether this is an import or not, let's write the skin.ini if non-existing or non-matching.
|
||||
// This is (weirdly) done inside ComputeHash to avoid adding a new method to handle this case. After switching to realm it can be moved into another place.
|
||||
if (skinIniSourcedName != item.Name)
|
||||
updateSkinIniMetadata(item);
|
||||
|
||||
return base.ComputeHash(item);
|
||||
}
|
||||
|
||||
private void updateSkinIniMetadata(SkinInfo item)
|
||||
{
|
||||
string nameLine = $"Name: {item.Name}";
|
||||
string authorLine = $"Author: {item.Creator}";
|
||||
|
||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename == "skin.ini");
|
||||
|
||||
if (existingFile != null)
|
||||
{
|
||||
List<string> outputLines = new List<string>();
|
||||
|
||||
bool addedName = false;
|
||||
bool addedAuthor = false;
|
||||
|
||||
using (var stream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath))
|
||||
using (var sr = new StreamReader(stream))
|
||||
{
|
||||
string line;
|
||||
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("Name:", StringComparison.Ordinal))
|
||||
{
|
||||
outputLines.Add(nameLine);
|
||||
addedName = true;
|
||||
}
|
||||
else if (line.StartsWith("Author:", StringComparison.Ordinal))
|
||||
{
|
||||
outputLines.Add(authorLine);
|
||||
addedAuthor = true;
|
||||
}
|
||||
else
|
||||
outputLines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (!addedName || !addedAuthor)
|
||||
{
|
||||
outputLines.AddRange(new[]
|
||||
{
|
||||
"[General]",
|
||||
nameLine,
|
||||
authorLine,
|
||||
});
|
||||
}
|
||||
|
||||
using (Stream stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
foreach (string line in outputLines)
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
|
||||
ReplaceFile(item, existingFile, stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Stream stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
sw.WriteLine("[General]");
|
||||
sw.WriteLine(nameLine);
|
||||
sw.WriteLine(authorLine);
|
||||
sw.WriteLine("Version: latest");
|
||||
}
|
||||
|
||||
AddFile(item, stream, "skin.ini");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||
@ -158,32 +269,12 @@ namespace osu.Game.Skinning
|
||||
|
||||
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
||||
|
||||
populateMetadata(model, instance, archive?.Name);
|
||||
model.Name = instance.Configuration.SkinInfo.Name;
|
||||
model.Creator = instance.Configuration.SkinInfo.Creator;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void populateMetadata(SkinInfo item, Skin instance, string archiveName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name))
|
||||
{
|
||||
item.Name = instance.Configuration.SkinInfo.Name;
|
||||
item.Creator = instance.Configuration.SkinInfo.Creator;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase);
|
||||
item.Creator ??= unknown_creator_string;
|
||||
}
|
||||
|
||||
// generally when importing from a folder, the ".osk" extension will not be present.
|
||||
// if we ever need a more reliable method of determining this, the type of `ArchiveReader` can be checked.
|
||||
bool isArchiveImport = archiveName?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
if (archiveName != null && !isArchiveImport && archiveName != item.Name)
|
||||
item.Name = $"{item.Name} [{archiveName}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a <see cref="Skin"/> instance for the provided <see cref="SkinInfo"/>
|
||||
/// </summary>
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
@ -16,6 +17,9 @@ namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
public class TestBeatmap : Beatmap
|
||||
{
|
||||
private static int onlineSetID;
|
||||
private static int onlineBeatmapID;
|
||||
|
||||
public TestBeatmap(RulesetInfo ruleset, bool withHitObjects = true)
|
||||
{
|
||||
var baseBeatmap = CreateBeatmap();
|
||||
@ -31,8 +35,10 @@ namespace osu.Game.Tests.Beatmaps
|
||||
BeatmapInfo.RulesetID = ruleset.ID ?? 0;
|
||||
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
|
||||
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
|
||||
BeatmapInfo.BeatmapSet.OnlineBeatmapSetID = Interlocked.Increment(ref onlineSetID);
|
||||
BeatmapInfo.Length = 75000;
|
||||
BeatmapInfo.OnlineInfo = new APIBeatmap();
|
||||
BeatmapInfo.OnlineBeatmapID = Interlocked.Increment(ref onlineBeatmapID);
|
||||
BeatmapInfo.BeatmapSet.OnlineInfo = new APIBeatmapSet
|
||||
{
|
||||
Status = BeatmapSetOnlineStatus.Ranked,
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -154,7 +153,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
}
|
||||
|
||||
protected override string ComputeHash(BeatmapSetInfo item, ArchiveReader reader = null)
|
||||
protected override string ComputeHash(BeatmapSetInfo item)
|
||||
=> string.Empty;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
/// <summary>
|
||||
/// The cached <see cref="IRoomManager"/>.
|
||||
/// </summary>
|
||||
new TestRequestHandlingMultiplayerRoomManager RoomManager { get; }
|
||||
new TestMultiplayerRoomManager RoomManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The cached <see cref="UserLookupCache"/>.
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public const int PLAYER_2_ID = 56;
|
||||
|
||||
public TestMultiplayerClient Client => OnlinePlayDependencies.Client;
|
||||
public new TestRequestHandlingMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager;
|
||||
public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager;
|
||||
public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache;
|
||||
public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient;
|
||||
|
||||
@ -35,12 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public new void Setup() => Schedule(() =>
|
||||
{
|
||||
if (joinRoom)
|
||||
{
|
||||
var room = CreateRoom();
|
||||
|
||||
RoomManager.CreateRoom(room);
|
||||
SelectedRoom.Value = room;
|
||||
}
|
||||
SelectedRoom.Value = CreateRoom();
|
||||
});
|
||||
|
||||
protected virtual Room CreateRoom()
|
||||
@ -64,7 +59,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
base.SetUpSteps();
|
||||
|
||||
if (joinRoom)
|
||||
{
|
||||
AddStep("join room", () => RoomManager.CreateRoom(SelectedRoom.Value));
|
||||
AddUntilStep("wait for room join", () => Client.Room != null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies();
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public TestMultiplayerClient Client { get; }
|
||||
public TestUserLookupCache LookupCache { get; }
|
||||
public TestSpectatorClient SpectatorClient { get; }
|
||||
public new TestRequestHandlingMultiplayerRoomManager RoomManager => (TestRequestHandlingMultiplayerRoomManager)base.RoomManager;
|
||||
public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager;
|
||||
|
||||
public MultiplayerTestSceneDependencies()
|
||||
{
|
||||
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
CacheAs<SpectatorClient>(SpectatorClient);
|
||||
}
|
||||
|
||||
protected override IRoomManager CreateRoomManager() => new TestRequestHandlingMultiplayerRoomManager();
|
||||
protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(RequestsHandler);
|
||||
|
||||
protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient();
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private readonly TestRequestHandlingMultiplayerRoomManager roomManager;
|
||||
private readonly TestMultiplayerRoomManager roomManager;
|
||||
|
||||
public TestMultiplayerClient(TestRequestHandlingMultiplayerRoomManager roomManager)
|
||||
public TestMultiplayerClient(TestMultiplayerRoomManager roomManager)
|
||||
{
|
||||
this.roomManager = roomManager;
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
@ -12,25 +10,24 @@ using osu.Game.Tests.Visual.OnlinePlay;
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="RoomManager"/> for use in multiplayer test scenes, backed by a <see cref="TestRoomRequestsHandler"/>.
|
||||
/// A <see cref="RoomManager"/> for use in multiplayer test scenes.
|
||||
/// Should generally not be used by itself outside of a <see cref="MultiplayerTestScene"/>.
|
||||
/// </summary>
|
||||
public class TestRequestHandlingMultiplayerRoomManager : MultiplayerRoomManager
|
||||
public class TestMultiplayerRoomManager : MultiplayerRoomManager
|
||||
{
|
||||
public IReadOnlyList<Room> ServerSideRooms => handler.ServerSideRooms;
|
||||
private readonly TestRoomRequestsHandler requestsHandler;
|
||||
|
||||
private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, OsuGameBase game)
|
||||
public TestMultiplayerRoomManager(TestRoomRequestsHandler requestsHandler)
|
||||
{
|
||||
((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
|
||||
this.requestsHandler = requestsHandler;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Room> ServerSideRooms => requestsHandler.ServerSideRooms;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
|
||||
/// </summary>
|
||||
/// <param name="room">The room.</param>
|
||||
public void AddServerSideRoom(Room room) => handler.AddServerSideRoom(room);
|
||||
public void AddServerSideRoom(Room room) => requestsHandler.AddServerSideRoom(room);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
|
||||
@ -27,11 +28,14 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
/// </summary>
|
||||
protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies;
|
||||
|
||||
private DelegatedDependencyContainer dependencies;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
private readonly Container content;
|
||||
private readonly Container drawableDependenciesContainer;
|
||||
private DelegatedDependencyContainer dependencies;
|
||||
|
||||
protected OnlinePlayTestScene()
|
||||
{
|
||||
@ -57,6 +61,17 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents);
|
||||
});
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("setup API", () =>
|
||||
{
|
||||
var handler = OnlinePlayDependencies.RequestsHandler;
|
||||
((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, game);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the room dependencies. Called every <see cref="Setup"/>.
|
||||
/// </summary>
|
||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
public IRoomManager RoomManager { get; }
|
||||
public OngoingOperationTracker OngoingOperationTracker { get; }
|
||||
public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; }
|
||||
public TestRoomRequestsHandler RequestsHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All cached dependencies which are also <see cref="Drawable"/> components.
|
||||
@ -33,12 +34,14 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
public OnlinePlayTestSceneDependencies()
|
||||
{
|
||||
SelectedRoom = new Bindable<Room>();
|
||||
RoomManager = CreateRoomManager();
|
||||
RequestsHandler = new TestRoomRequestsHandler();
|
||||
OngoingOperationTracker = new OngoingOperationTracker();
|
||||
AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||
RoomManager = CreateRoomManager();
|
||||
|
||||
dependencies = new DependencyContainer(new CachedModelDependencyContainer<Room>(null) { Model = { BindTarget = SelectedRoom } });
|
||||
|
||||
CacheAs(RequestsHandler);
|
||||
CacheAs(SelectedRoom);
|
||||
CacheAs(RoomManager);
|
||||
CacheAs(OngoingOperationTracker);
|
||||
@ -71,6 +74,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
drawableComponents.Add(drawable);
|
||||
}
|
||||
|
||||
protected virtual IRoomManager CreateRoomManager() => new TestRequestHandlingRoomManager();
|
||||
protected virtual IRoomManager CreateRoomManager() => new TestRoomManager();
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
@ -15,20 +13,12 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
/// <summary>
|
||||
/// A very simple <see cref="RoomManager"/> for use in online play test scenes.
|
||||
/// </summary>
|
||||
public class TestRequestHandlingRoomManager : RoomManager
|
||||
public class TestRoomManager : RoomManager
|
||||
{
|
||||
public Action<Room, string> JoinRoomRequested;
|
||||
|
||||
private int currentRoomId;
|
||||
|
||||
private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, OsuGameBase game)
|
||||
{
|
||||
((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
|
||||
}
|
||||
|
||||
public override void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
JoinRoomRequested?.Invoke(room, password);
|
Loading…
Reference in New Issue
Block a user