Merge branch 'master' into fix-autopilot-touch-devices

This commit is contained in:
Dean Herbert 2021-12-15 12:04:35 +09:00 committed by GitHub
commit 6bffeb6a24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 1011 additions and 575 deletions

View File

@ -27,10 +27,10 @@
] ]
}, },
"ppy.localisationanalyser.tools": { "ppy.localisationanalyser.tools": {
"version": "2021.725.0", "version": "2021.1210.0",
"commands": [ "commands": [
"localisation" "localisation"
] ]
} }
} }
} }

View File

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

View File

@ -70,7 +70,9 @@ namespace osu.Desktop
if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath)) if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
return stableInstallPath; return stableInstallPath;
} }
catch { } catch
{
}
} }
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
@ -113,7 +115,7 @@ namespace osu.Desktop
base.LoadComplete(); base.LoadComplete();
if (!noVersionOverlay) if (!noVersionOverlay)
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
LoadComponentAsync(new DiscordRichPresence(), Add); LoadComponentAsync(new DiscordRichPresence(), Add);

View File

@ -55,73 +55,75 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
bool firstDeltaSwitch = false; bool firstDeltaSwitch = false;
for (int i = Previous.Count - 2; i > 0; i--) int rhythmStart = 0;
while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max)
rhythmStart++;
for (int i = rhythmStart; i > 0; i--)
{ {
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1]; OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1];
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i]; OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i];
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1]; OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1];
double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
if (currHistoricalDecay != 0) currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
double currDelta = currObj.StrainTime;
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
windowPenalty = Math.Min(1, windowPenalty);
double effectiveRatio = windowPenalty * currRatio;
if (firstDeltaSwitch)
{ {
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count. if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
double currDelta = currObj.StrainTime;
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
windowPenalty = Math.Min(1, windowPenalty);
double effectiveRatio = windowPenalty * currRatio;
if (firstDeltaSwitch)
{ {
if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) if (islandSize < 7)
{ islandSize++; // island is still progressing, count size.
if (islandSize < 7)
islandSize++; // island is still progressing, count size.
}
else
{
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
effectiveRatio *= 0.125;
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
effectiveRatio *= 0.25;
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
effectiveRatio *= 0.25;
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
effectiveRatio *= 0.50;
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
effectiveRatio *= 0.125;
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
startRatio = effectiveRatio;
previousIslandSize = islandSize; // log the last island size.
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
islandSize = 1;
}
} }
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up. else
{ {
// Begin counting island until we change speed again. if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
firstDeltaSwitch = true; effectiveRatio *= 0.125;
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
effectiveRatio *= 0.25;
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
effectiveRatio *= 0.25;
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
effectiveRatio *= 0.50;
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
effectiveRatio *= 0.125;
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
startRatio = effectiveRatio; startRatio = effectiveRatio;
previousIslandSize = islandSize; // log the last island size.
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
islandSize = 1; islandSize = 1;
} }
} }
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
{
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
startRatio = effectiveRatio;
islandSize = 1;
}
} }
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)

View File

@ -62,7 +62,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestCultureInvariance() public void TestCultureInvariance()
{ {
var ruleset = new OsuRuleset().RulesetInfo; var ruleset = new OsuRuleset().RulesetInfo;
var scoreInfo = new TestScoreInfo(ruleset); var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
var beatmap = new TestBeatmap(ruleset); var beatmap = new TestBeatmap(ruleset);
var score = new Score var score = new Score
{ {

View File

@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{ {
return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
{ {
OnlineScoreID = 2, OnlineID = 2,
BeatmapInfo = beatmapInfo, BeatmapInfo = beatmapInfo,
BeatmapInfoID = beatmapInfo.ID BeatmapInfoID = beatmapInfo.ID
}, new ImportScoreTest.TestArchiveReader()); }, new ImportScoreTest.TestArchiveReader());

View File

@ -809,7 +809,7 @@ namespace osu.Game.Tests.Database
// TODO: reimplement when we have score support in realm. // TODO: reimplement when we have score support in realm.
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo // return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
// { // {
// OnlineScoreID = 2, // OnlineID = 2,
// Beatmap = beatmap, // Beatmap = beatmap,
// BeatmapInfoID = beatmap.ID // BeatmapInfoID = beatmap.ID
// }, new ImportScoreTest.TestArchiveReader()); // }, new ImportScoreTest.TestArchiveReader());

View File

@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database
Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2)); Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2));
} }
[Test]
public void TestDefaultsPopulationRemovesExcess()
{
Assert.That(queryCount(), Is.EqualTo(0));
KeyBindingContainer testContainer = new TestKeyBindingContainer();
// Add some excess bindings for an action which only supports 1.
using (var realm = realmContextFactory.CreateContext())
using (var transaction = realm.BeginWrite())
{
realm.Add(new RealmKeyBinding
{
Action = GlobalAction.Back,
KeyCombination = new KeyCombination(InputKey.A)
});
realm.Add(new RealmKeyBinding
{
Action = GlobalAction.Back,
KeyCombination = new KeyCombination(InputKey.S)
});
realm.Add(new RealmKeyBinding
{
Action = GlobalAction.Back,
KeyCombination = new KeyCombination(InputKey.D)
});
transaction.Commit();
}
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
}
private int queryCount(GlobalAction? match = null) private int queryCount(GlobalAction? match = null)
{ {
using (var realm = realmContextFactory.CreateContext()) using (var realm = realmContextFactory.CreateContext())

View File

@ -45,8 +45,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5); AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5);
checkPlayingUserCount(0); checkPlayingUserCount(0);
AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
changeState(3, MultiplayerUserState.WaitingForLoad); changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3); checkPlayingUserCount(3);
@ -64,8 +62,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("leave room", () => Client.LeaveRoom()); AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0); checkPlayingUserCount(0);
AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
} }
[Test] [Test]

View File

@ -0,0 +1,90 @@
// 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 NUnit.Framework;
using osu.Game.Online.Chat;
namespace osu.Game.Tests.Online.Chat
{
[TestFixture]
public class MessageNotifierTest
{
[Test]
public void TestContainsUsernameMidlinePositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test message", "Test"));
}
[Test]
public void TestContainsUsernameStartOfLinePositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test message", "Test"));
}
[Test]
public void TestContainsUsernameEndOfLinePositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test", "Test"));
}
[Test]
public void TestContainsUsernameMidlineNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a testmessage for notifications", "Test"));
}
[Test]
public void TestContainsUsernameStartOfLineNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Testmessage", "Test"));
}
[Test]
public void TestContainsUsernameEndOfLineNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a notificationtest", "Test"));
}
[Test]
public void TestContainsUsernameBetweenInterpunction()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test"));
}
[Test]
public void TestContainsUsernameUnicode()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test \u0460\u0460 message", "\u0460\u0460"));
}
[Test]
public void TestContainsUsernameUnicodeNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460"));
}
[Test]
public void TestContainsUsernameSpecialCharactersPositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test [#^-^#] message", "[#^-^#]"));
}
[Test]
public void TestContainsUsernameSpecialCharactersNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]"));
}
[Test]
public void TestContainsUsernameAtSign()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("@username hi", "username"));
}
[Test]
public void TestContainsUsernameColon()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("username: hi", "username"));
}
}
}

View File

@ -13,7 +13,6 @@ using osu.Game.Online.Solo;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -94,7 +93,7 @@ namespace osu.Game.Tests.Online
[Test] [Test]
public void TestDeserialiseSubmittableScoreWithEmptyMods() public void TestDeserialiseSubmittableScoreWithEmptyMods()
{ {
var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }); var score = new SubmittableScore(new ScoreInfo());
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score)); var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
@ -106,7 +105,6 @@ namespace osu.Game.Tests.Online
{ {
var score = new SubmittableScore(new ScoreInfo var score = new SubmittableScore(new ScoreInfo
{ {
Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
}); });

View File

@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum() public void TestTrackerRespectsChecksum()
{ {
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
AddStep("import altered beatmap", () => AddStep("import altered beatmap", () =>
{ {
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
}); });
addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{ {
SelectedItem = { BindTarget = selectedItem } SelectedItem = { BindTarget = selectedItem }
}); });
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait());
addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
} }
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected) private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using NUnit.Framework; using NUnit.Framework;
@ -12,8 +13,12 @@ using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Tests.Resources namespace osu.Game.Tests.Resources
{ {
@ -137,5 +142,63 @@ namespace osu.Game.Tests.Resources
} }
} }
} }
/// <summary>
/// Create a test score model.
/// </summary>
/// <param name="ruleset">The ruleset for which the score was set against.</param>
/// <returns></returns>
public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null) =>
CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First());
/// <summary>
/// Create a test score model.
/// </summary>
/// <param name="beatmap">The beatmap for which the score was set against.</param>
/// <returns></returns>
public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap) => new ScoreInfo
{
User = new APIUser
{
Id = 2,
Username = "peppy",
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
},
BeatmapInfo = beatmap,
Ruleset = beatmap.Ruleset,
RulesetID = beatmap.Ruleset.ID ?? 0,
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
TotalScore = 2845370,
Accuracy = 0.95,
MaxCombo = 999,
Position = 1,
Rank = ScoreRank.S,
Date = DateTimeOffset.Now,
Statistics = new Dictionary<HitResult, int>
{
[HitResult.Miss] = 1,
[HitResult.Meh] = 50,
[HitResult.Ok] = 100,
[HitResult.Good] = 200,
[HitResult.Great] = 300,
[HitResult.Perfect] = 320,
[HitResult.SmallTickHit] = 50,
[HitResult.SmallTickMiss] = 25,
[HitResult.LargeTickHit] = 100,
[HitResult.LargeTickMiss] = 50,
[HitResult.SmallBonus] = 10,
[HitResult.SmallBonus] = 50
},
};
private class TestModHardRock : ModHardRock
{
public override double ScoreMultiplier => 1;
}
private class TestModDoubleTime : ModDoubleTime
{
public override double ScoreMultiplier => 1;
}
} }
} }

View File

@ -40,7 +40,7 @@ namespace osu.Game.Tests.Scores.IO
Combo = 250, Combo = 250,
User = new APIUser { Username = "Test user" }, User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now, Date = DateTimeOffset.Now,
OnlineScoreID = 12345, OnlineID = 12345,
}; };
var imported = await LoadScoreIntoOsu(osu, toImport); var imported = await LoadScoreIntoOsu(osu, toImport);
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Scores.IO
Assert.AreEqual(toImport.Combo, imported.Combo); Assert.AreEqual(toImport.Combo, imported.Combo);
Assert.AreEqual(toImport.User.Username, imported.User.Username); Assert.AreEqual(toImport.User.Username, imported.User.Username);
Assert.AreEqual(toImport.Date, imported.Date); Assert.AreEqual(toImport.Date, imported.Date);
Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID); Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
} }
finally finally
{ {
@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO
{ {
var osu = LoadOsuIntoHost(host, true); var osu = LoadOsuIntoHost(host, true);
await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader());
var scoreManager = osu.Dependencies.Get<ScoreManager>(); var scoreManager = osu.Dependencies.Get<ScoreManager>();
// Note: A new score reference is used here since the import process mutates the original object to set an ID // Note: A new score reference is used here since the import process mutates the original object to set an ID
Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 })); Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 }));
} }
finally finally
{ {

View File

@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO
Assert.That(score1, Is.EqualTo(score2)); Assert.That(score1, Is.EqualTo(score2));
} }
[Test]
public void TestNonMatchingByHash()
{
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
ScoreInfo score2 = new ScoreInfo { Hash = "b" };
Assert.That(score1, Is.Not.EqualTo(score2));
}
[Test]
public void TestMatchingByHash()
{
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
ScoreInfo score2 = new ScoreInfo { Hash = "a" };
Assert.That(score1, Is.EqualTo(score2));
}
[Test] [Test]
public void TestNonMatchingByNull() public void TestNonMatchingByNull()
{ {

View File

@ -5,8 +5,11 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
@ -15,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Background namespace osu.Game.Tests.Visual.Background
{ {
@ -22,8 +26,7 @@ namespace osu.Game.Tests.Visual.Background
public class TestSceneBackgroundScreenDefault : OsuTestScene public class TestSceneBackgroundScreenDefault : OsuTestScene
{ {
private BackgroundScreenStack stack; private BackgroundScreenStack stack;
private BackgroundScreenDefault screen; private TestBackgroundScreenDefault screen;
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault(); private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault();
[Resolved] [Resolved]
@ -36,10 +39,95 @@ namespace osu.Game.Tests.Visual.Background
public void SetUpSteps() public void SetUpSteps()
{ {
AddStep("create background stack", () => Child = stack = new BackgroundScreenStack()); AddStep("create background stack", () => Child = stack = new BackgroundScreenStack());
AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false))); AddStep("push default screen", () => stack.Push(screen = new TestBackgroundScreenDefault()));
AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen()); AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen());
} }
[Test]
public void TestBeatmapBackgroundTracksBeatmap()
{
setSupporter(true);
setSourceMode(BackgroundSource.Beatmap);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
Graphics.Backgrounds.Background last = null;
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
AddStep("store background", () => last = getCurrentBackground());
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
AddStep("store background", () => last = getCurrentBackground());
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
}
[Test]
public void TestBeatmapBackgroundTracksBeatmapWhenSuspended()
{
setSupporter(true);
setSourceMode(BackgroundSource.Beatmap);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
BackgroundScreenBeatmap nestedScreen = null;
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("pop screen back to top level", () => screen.MakeCurrent());
AddAssert("top level background changed", () => screen.CheckLastLoadChange() == true);
}
[Test]
public void TestBeatmapBackgroundIgnoresNoChangeWhenSuspended()
{
BackgroundScreenBeatmap nestedScreen = null;
WorkingBeatmap originalWorking = null;
setSupporter(true);
setSourceMode(BackgroundSource.Beatmap);
AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
// we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("pop screen back to top level", () => screen.MakeCurrent());
AddStep("top level screen is current", () => screen.IsCurrentScreen());
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
}
[Test] [Test]
public void TestBackgroundTypeSwitch() public void TestBackgroundTypeSwitch()
{ {
@ -78,36 +166,24 @@ namespace osu.Game.Tests.Visual.Background
[TestCase(BackgroundSource.Skin, typeof(SkinBackground))] [TestCase(BackgroundSource.Skin, typeof(SkinBackground))]
public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType) public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType)
{ {
Graphics.Backgrounds.Background last = null;
setSourceMode(source); setSourceMode(source);
setSupporter(true); setSupporter(true);
if (source == BackgroundSource.Skin) if (source == BackgroundSource.Skin)
setCustomSkin(); setCustomSkin();
AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType); AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == backgroundType);
AddAssert("next doesn't load new background", () => screen.Next() == false); AddAssert("next doesn't load new background", () => screen.Next() == false);
// doesn't really need to be checked but might as well.
AddWaitStep("wait a bit", 5);
AddUntilStep("ensure same background instance", () => last == getCurrentBackground());
} }
[Test] [Test]
public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter) public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter)
{ {
Graphics.Backgrounds.Background last = null;
setSourceMode(BackgroundSource.Skin); setSourceMode(BackgroundSource.Skin);
setSupporter(supporter); setSupporter(supporter);
setDefaultSkin(); setDefaultSkin();
AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
AddAssert("next cycles background", () => screen.Next()); AddAssert("next cycles background", () => screen.Next());
// doesn't really need to be checked but might as well.
AddWaitStep("wait a bit", 5);
AddUntilStep("ensure different background instance", () => last != getCurrentBackground());
} }
private void setSourceMode(BackgroundSource source) => private void setSourceMode(BackgroundSource source) =>
@ -120,6 +196,42 @@ namespace osu.Game.Tests.Visual.Background
Id = API.LocalUser.Value.Id + 1, Id = API.LocalUser.Value.Id + 1,
}); });
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
private class TestBackgroundScreenDefault : BackgroundScreenDefault
{
private bool? lastLoadTriggerCausedChange;
public TestBackgroundScreenDefault()
: base(false)
{
}
public override bool Next()
{
bool didChange = base.Next();
lastLoadTriggerCausedChange = didChange;
return didChange;
}
public bool? CheckLastLoadChange()
{
bool? lastChange = lastLoadTriggerCausedChange;
lastLoadTriggerCausedChange = null;
return lastChange;
}
}
private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
{
public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
: base(new Beatmap(), null, audioManager)
{
}
protected override Texture GetBackground() => new Texture(1, 1);
}
private void setCustomSkin() private void setCustomSkin()
{ {
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.

View File

@ -18,7 +18,6 @@ using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -28,7 +27,6 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background
FadeAccessibleResults results = null; FadeAccessibleResults results = null;
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
{
User = new APIUser { Username = "osu!" },
BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo,
Ruleset = Ruleset.Value,
})));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());

View File

@ -9,6 +9,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
@ -44,6 +45,7 @@ namespace osu.Game.Tests.Visual.Editing
protected override void LoadEditor() protected override void LoadEditor()
{ {
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
SelectedMods.Value = new[] { new ModCinema() };
base.LoadEditor(); base.LoadEditor();
} }
@ -67,6 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(); var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
}); });
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
} }
[Test] [Test]

View File

@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("total number of results == 1", () => AddAssert("total number of results == 1", () =>
{ {
var score = new ScoreInfo(); var score = new ScoreInfo();
((FailPlayer)Player).ScoreProcessor.PopulateScore(score); ((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
return score.Statistics.Values.Sum() == 1; return score.Statistics.Values.Sum() == 1;

View File

@ -85,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay
loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); double targetTime = addEventToLoop ? 20000 : 0;
target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1);
// these should be ignored due to being in the future. // these should be ignored due to being in the future.
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
storyboard.GetLayer("Background").Add(sprite); storyboard.GetLayer("Background").Add(sprite);

View File

@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private ScoreInfo getScoreInfo(bool replayAvailable) private ScoreInfo getScoreInfo(bool replayAvailable)
{ {
return new APIScoreInfo return new APIScore
{ {
OnlineID = 2553163309, OnlineID = 2553163309,
RulesetID = 0, RulesetID = 0,

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestFirstItemSelectedByDefault() public void TestFirstItemSelectedByDefault()
{ {
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
} }
[Test] [Test]
@ -27,13 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);
AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2);
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID);
addItem(() => InitialBeatmap); addItem(() => InitialBeatmap);
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID);
AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); AddAssert("first item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
} }
[Test] [Test]
@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1); AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1);
AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true); AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true);
AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); AddAssert("last item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
} }
[Test] [Test]
@ -55,12 +53,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
RunGameplay(); RunGameplay();
AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true); AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID);
RunGameplay(); RunGameplay();
AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true); AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true);
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID); AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[2].ID);
} }
[Test] [Test]
@ -74,15 +72,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly)); AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly));
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID); AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2);
} }
[Test] [Test]
public void TestCorrectItemSelectedAfterNewItemAdded() public void TestCorrectItemSelectedAfterNewItemAdded()
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);
AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
} }
private void addItem(Func<BeatmapInfo> beatmap) private void addItem(Func<BeatmapInfo> beatmap)

View File

@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
@ -20,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestFirstItemSelectedByDefault() public void TestFirstItemSelectedByDefault()
{ {
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
} }
[Test] [Test]
@ -28,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
selectNewItem(() => InitialBeatmap); selectNewItem(() => InitialBeatmap);
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
} }
[Test] [Test]
@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
selectNewItem(() => OtherBeatmap); selectNewItem(() => OtherBeatmap);
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
} }
[Test] [Test]
@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true); AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false); AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); AddAssert("second playlist item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID);
} }
[Test] [Test]
@ -85,6 +86,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void selectNewItem(Func<BeatmapInfo> beatmap) private void selectNewItem(Func<BeatmapInfo> beatmap)
{ {
AddUntilStep("wait for playlist panels to load", () =>
{
var queueList = this.ChildrenOfType<MultiplayerQueueList>().Single();
return queueList.ChildrenOfType<DrawableRoomPlaylistItem>().Count() == queueList.Items.Count;
});
AddStep("click edit button", () => AddStep("click edit button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().First()); InputManager.MoveMouseTo(this.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().First());
@ -97,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID); AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID);
} }
private void addItem(Func<BeatmapInfo> beatmap) private void addItem(Func<BeatmapInfo> beatmap)

View File

@ -391,9 +391,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready)); pressReadyButton();
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
} }
@ -413,10 +413,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
pressReadyButton();
AddStep("Enter song select", () => AddStep("Enter song select", () =>
{ {
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value); ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
}); });
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true); AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
@ -592,19 +594,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value); pressReadyButton();
pressReadyButton();
AddStep("click ready button", () =>
{
InputManager.MoveMouseTo(readyButton);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("wait for player", () => multiplayerScreenStack.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. // Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out.
@ -665,7 +656,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
} }
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single(); private ReadyButton readyButton => this.ChildrenOfType<ReadyButton>().Single();
private void pressReadyButton()
{
AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value);
MultiplayerUserState lastState = MultiplayerUserState.Idle;
AddStep("click ready button", () =>
{
lastState = client.LocalUser?.State ?? MultiplayerUserState.Idle;
InputManager.MoveMouseTo(readyButton);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for state change", () => client.LocalUser?.State != lastState);
}
private void createRoom(Func<Room> room) private void createRoom(Func<Room> room)
{ {

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
@ -27,7 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("initialise gameplay", () => AddStep("initialise gameplay", () =>
{ {
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray())); Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
}, Client.Room?.Users.ToArray()));
}); });
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -27,11 +28,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneMultiplayerQueueList : MultiplayerTestScene public class TestSceneMultiplayerQueueList : MultiplayerTestScene
{ {
private MultiplayerQueueList playlist; private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
[Cached(typeof(UserLookupCache))] [Cached(typeof(UserLookupCache))]
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
private MultiplayerQueueList playlist;
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
private RulesetStore rulesets; private RulesetStore rulesets;
private BeatmapSetInfo importedSet; private BeatmapSetInfo importedSet;
@ -50,12 +52,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create playlist", () => AddStep("create playlist", () =>
{ {
selectedItem.Value = null;
Child = playlist = new MultiplayerQueueList Child = playlist = new MultiplayerQueueList
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(500, 300), Size = new Vector2(500, 300),
SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem }, SelectedItem = { BindTarget = selectedItem },
Items = { BindTarget = Client.APIRoom!.Playlist } Items = { BindTarget = Client.APIRoom!.Playlist }
}; };
}); });
@ -107,22 +111,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
assertDeleteButtonVisibility(0, false);
addPlaylistItem(() => API.LocalUser.Value.OnlineID); addPlaylistItem(() => API.LocalUser.Value.OnlineID);
AddStep("select item 0", () => selectedItem.Value = playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().ElementAt(0).Model);
assertDeleteButtonVisibility(0, false); assertDeleteButtonVisibility(0, false);
assertDeleteButtonVisibility(1, true); assertDeleteButtonVisibility(1, true);
// Run through gameplay. AddStep("select item 1", () => selectedItem.Value = playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().ElementAt(1).Model);
AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready)); assertDeleteButtonVisibility(0, true);
AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
AddStep("start match", () => Client.StartMatch());
AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded));
AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing);
AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay));
AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results);
assertDeleteButtonVisibility(1, false); assertDeleteButtonVisibility(1, false);
} }

View File

@ -1,13 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@ -22,20 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
var rulesetInfo = new OsuRuleset().RulesetInfo; var rulesetInfo = new OsuRuleset().RulesetInfo;
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
var score = new ScoreInfo
{
Rank = ScoreRank.B,
TotalScore = 987654,
Accuracy = 0.8,
MaxCombo = 500,
Combo = 250,
BeatmapInfo = beatmapInfo,
User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineScoreID = 12345,
Ruleset = rulesetInfo,
};
PlaylistItem playlistItem = new PlaylistItem PlaylistItem playlistItem = new PlaylistItem
{ {

View File

@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@ -26,20 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
var rulesetInfo = new OsuRuleset().RulesetInfo; var rulesetInfo = new OsuRuleset().RulesetInfo;
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
var score = new ScoreInfo
{
Rank = ScoreRank.B,
TotalScore = 987654,
Accuracy = 0.8,
MaxCombo = 500,
Combo = 250,
BeatmapInfo = beatmapInfo,
User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineScoreID = 12345,
Ruleset = rulesetInfo,
};
PlaylistItem playlistItem = new PlaylistItem PlaylistItem playlistItem = new PlaylistItem
{ {

View File

@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation
imported = Game.ScoreManager.Import(new ScoreInfo imported = Game.ScoreManager.Import(new ScoreInfo
{ {
Hash = Guid.NewGuid().ToString(), Hash = Guid.NewGuid().ToString(),
OnlineScoreID = i, OnlineID = i,
BeatmapInfo = beatmap.Beatmaps.First(), BeatmapInfo = beatmap.Beatmaps.First(),
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}).Result.Value; }).Result.Value;

View File

@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Online
var allScores = new APIScoresCollection var allScores = new APIScoresCollection
{ {
Scores = new List<APIScoreInfo> Scores = new List<APIScore>
{ {
new APIScoreInfo new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 1234567890, TotalScore = 1234567890,
Accuracy = 1, Accuracy = 1,
}, },
new APIScoreInfo new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 1234789, TotalScore = 1234789,
Accuracy = 0.9997, Accuracy = 0.9997,
}, },
new APIScoreInfo new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 12345678, TotalScore = 12345678,
Accuracy = 0.9854, Accuracy = 0.9854,
}, },
new APIScoreInfo new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 1234567, TotalScore = 1234567,
Accuracy = 0.8765, Accuracy = 0.8765,
}, },
new APIScoreInfo new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Online
var myBestScore = new APIScoreWithPosition var myBestScore = new APIScoreWithPosition
{ {
Score = new APIScoreInfo Score = new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Online
var myBestScoreWithNullPosition = new APIScoreWithPosition var myBestScoreWithNullPosition = new APIScoreWithPosition
{ {
Score = new APIScoreInfo Score = new APIScore
{ {
User = new APIUser User = new APIUser
{ {
@ -212,9 +212,9 @@ namespace osu.Game.Tests.Visual.Online
var oneScore = new APIScoresCollection var oneScore = new APIScoresCollection
{ {
Scores = new List<APIScoreInfo> Scores = new List<APIScore>
{ {
new APIScoreInfo new APIScore
{ {
User = new APIUser User = new APIUser
{ {

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online
{ {
public TestSceneUserProfileScores() public TestSceneUserProfileScores()
{ {
var firstScore = new APIScoreInfo var firstScore = new APIScore
{ {
PP = 1047.21, PP = 1047.21,
Rank = ScoreRank.SH, Rank = ScoreRank.SH,
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
Accuracy = 0.9813 Accuracy = 0.9813
}; };
var secondScore = new APIScoreInfo var secondScore = new APIScore
{ {
PP = 134.32, PP = 134.32,
Rank = ScoreRank.A, Rank = ScoreRank.A,
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
Accuracy = 0.998546 Accuracy = 0.998546
}; };
var thirdScore = new APIScoreInfo var thirdScore = new APIScore
{ {
PP = 96.83, PP = 96.83,
Rank = ScoreRank.S, Rank = ScoreRank.S,
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Online
Accuracy = 0.9726 Accuracy = 0.9726
}; };
var noPPScore = new APIScoreInfo var noPPScore = new APIScore
{ {
Rank = ScoreRank.B, Rank = ScoreRank.B,
Beatmap = new APIBeatmap Beatmap = new APIBeatmap

View File

@ -22,14 +22,17 @@ using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Playlists namespace osu.Game.Tests.Visual.Playlists
{ {
public class TestScenePlaylistsResultsScreen : ScreenTestScene public class TestScenePlaylistsResultsScreen : ScreenTestScene
{ {
private const int scores_per_result = 10; private const int scores_per_result = 10;
private const int real_user_position = 200;
private TestResultsScreen resultsScreen; private TestResultsScreen resultsScreen;
private int currentScoreId; private int currentScoreId;
private bool requestComplete; private bool requestComplete;
private int totalCount; private int totalCount;
@ -37,7 +40,7 @@ namespace osu.Game.Tests.Visual.Playlists
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
{ {
currentScoreId = 0; currentScoreId = 1;
requestComplete = false; requestComplete = false;
totalCount = 0; totalCount = 0;
bindHandler(); bindHandler();
@ -50,13 +53,17 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("bind user score info handler", () => AddStep("bind user score info handler", () =>
{ {
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; userScore = TestResources.CreateTestScoreInfo();
userScore.OnlineID = currentScoreId++;
bindHandler(userScore: userScore); bindHandler(userScore: userScore);
}); });
createResults(() => userScore); createResults(() => userScore);
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded);
AddAssert($"score panel position is {real_user_position}",
() => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).ScorePosition.Value == real_user_position);
} }
[Test] [Test]
@ -74,14 +81,16 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("bind user score info handler", () => AddStep("bind user score info handler", () =>
{ {
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; userScore = TestResources.CreateTestScoreInfo();
userScore.OnlineID = currentScoreId++;
bindHandler(true, userScore); bindHandler(true, userScore);
}); });
createResults(() => userScore); createResults(() => userScore);
AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1);
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded);
} }
[Test] [Test]
@ -123,7 +132,9 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("bind user score info handler", () => AddStep("bind user score info handler", () =>
{ {
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; userScore = TestResources.CreateTestScoreInfo();
userScore.OnlineID = currentScoreId++;
bindHandler(userScore: userScore); bindHandler(userScore: userScore);
}); });
@ -230,12 +241,12 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
var multiplayerUserScore = new MultiplayerScore var multiplayerUserScore = new MultiplayerScore
{ {
ID = (int)(userScore.OnlineScoreID ?? currentScoreId++), ID = (int)(userScore.OnlineID > 0 ? userScore.OnlineID : currentScoreId++),
Accuracy = userScore.Accuracy, Accuracy = userScore.Accuracy,
EndedAt = userScore.Date, EndedAt = userScore.Date,
Passed = userScore.Passed, Passed = userScore.Passed,
Rank = userScore.Rank, Rank = userScore.Rank,
Position = 200, Position = real_user_position,
MaxCombo = userScore.MaxCombo, MaxCombo = userScore.MaxCombo,
TotalScore = userScore.TotalScore, TotalScore = userScore.TotalScore,
User = userScore.User, User = userScore.User,

View File

@ -15,9 +15,11 @@ using osu.Game.Database;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input; using osuTK.Input;
@ -112,37 +114,80 @@ namespace osu.Game.Tests.Visual.Playlists
[Test] [Test]
public void TestBeatmapUpdatedOnReImport() public void TestBeatmapUpdatedOnReImport()
{ {
BeatmapSetInfo importedSet = null; string realHash = null;
int realOnlineId = 0;
int realOnlineSetId = 0;
AddStep("import altered beatmap", () => AddStep("store real beatmap values", () =>
{ {
IBeatmap beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash;
realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID ?? -1;
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; realOnlineSetId = importedBeatmap.Value.OnlineID ?? -1;
// intentionally increment online IDs to clash with import below.
beatmap.BeatmapInfo.OnlineID++;
beatmap.BeatmapInfo.BeatmapSet.OnlineID++;
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
}); });
AddStep("import modified beatmap", () =>
{
var modifiedBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
OnlineID = realOnlineId,
BeatmapSet =
{
OnlineID = realOnlineSetId
}
},
};
modifiedBeatmap.HitObjects.Clear();
modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 });
manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait();
});
// Create the room using the real beatmap values.
setupAndCreateRoom(room => setupAndCreateRoom(room =>
{ {
room.Name.Value = "my awesome room"; room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value; room.Host.Value = API.LocalUser.Value;
room.Playlist.Add(new PlaylistItem room.Playlist.Add(new PlaylistItem
{ {
Beatmap = { Value = importedSet.Beatmaps[0] }, Beatmap =
{
Value = new BeatmapInfo
{
MD5Hash = realHash,
OnlineID = realOnlineId,
BeatmapSet = new BeatmapSetInfo
{
OnlineID = realOnlineSetId,
}
}
},
Ruleset = { Value = new OsuRuleset().RulesetInfo } Ruleset = { Value = new OsuRuleset().RulesetInfo }
}); });
}); });
AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1); AddAssert("match has default beatmap", () => match.Beatmap.IsDefault);
importBeatmap(); AddStep("reimport original beatmap", () =>
{
var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
OnlineID = realOnlineId,
BeatmapSet =
{
OnlineID = realOnlineSetId
}
},
};
AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1); manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait();
});
AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash);
} }
private void setupAndCreateRoom(Action<Room> room) private void setupAndCreateRoom(Action<Room> room)

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -13,6 +14,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Contracted;
using osu.Game.Tests.Resources;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
@ -22,13 +24,18 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestShowPanel() public void TestShowPanel()
{ {
AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo()));
} }
[Test] [Test]
public void TestExcessMods() public void TestExcessMods()
{ {
AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo, true))); AddStep("show excess mods score", () =>
{
var score = TestResources.CreateTestScoreInfo();
score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray();
showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), score);
});
} }
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)

View File

@ -20,6 +20,7 @@ using osu.Game.Scoring;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Expanded; using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
@ -34,21 +35,21 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
var author = new APIUser { Username = "mapper_name" }; var author = new APIUser { Username = "mapper_name" };
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author))));
{
BeatmapInfo = createTestBeatmap(author)
}));
} }
[Test] [Test]
public void TestExcessMods() public void TestExcessMods()
{ {
var author = new APIUser { Username = "mapper_name" }; AddStep("show excess mods score", () =>
AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true)
{ {
BeatmapInfo = createTestBeatmap(author) var author = new APIUser { Username = "mapper_name" };
}));
var score = TestResources.CreateTestScoreInfo(createTestBeatmap(author));
score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray();
showPanel(score);
});
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Current.Value == "mapper_name")); AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Current.Value == "mapper_name"));
} }
@ -56,10 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestMapWithUnknownMapper() public void TestMapWithUnknownMapper()
{ {
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new APIUser()))));
{
BeatmapInfo = createTestBeatmap(new APIUser())
}));
AddAssert("mapped by text not present", () => AddAssert("mapped by text not present", () =>
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by")));
@ -77,12 +75,12 @@ namespace osu.Game.Tests.Visual.Ranking
var mods = new Mod[] { ruleset.GetAutoplayMod() }; var mods = new Mod[] { ruleset.GetAutoplayMod() };
var beatmap = createTestBeatmap(new APIUser()); var beatmap = createTestBeatmap(new APIUser());
showPanel(new TestScoreInfo(ruleset.RulesetInfo) var score = TestResources.CreateTestScoreInfo(beatmap);
{
Mods = mods, score.Mods = mods;
BeatmapInfo = beatmap, score.Date = default;
Date = default,
}); showPanel(score);
}); });
AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any()); AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());

View File

@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Ranking.Expanded; using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Tests.Resources;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#444"), Colour = Color4Extensions.FromHex("#444"),
}, },
new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User), new ExpandedPanelTopContent(TestResources.CreateTestScoreInfo().User),
} }
}; };
} }

View File

@ -15,12 +15,12 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Tests.Resources;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -72,11 +72,10 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
TestResultsScreen screen = null; TestResultsScreen screen = null;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) var score = TestResources.CreateTestScoreInfo();
{
Accuracy = accuracy, score.Accuracy = accuracy;
Rank = rank score.Rank = rank;
};
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score))); AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score)));
AddUntilStep("wait for loaded", () => screen.IsLoaded); AddUntilStep("wait for loaded", () => screen.IsLoaded);
@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
DelayedFetchResultsScreen screen = null; DelayedFetchResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), 3000)));
AddUntilStep("wait for loaded", () => screen.IsLoaded); AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddStep("click expanded panel", () => AddStep("click expanded panel", () =>
{ {
@ -237,9 +236,9 @@ namespace osu.Game.Tests.Visual.Ranking
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value); AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
} }
private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo)); private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo());
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo());
private class TestResultsContainer : Container private class TestResultsContainer : Container
{ {
@ -282,7 +281,7 @@ namespace osu.Game.Tests.Visual.Ranking
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i; score.TotalScore += 10 - i;
score.Hash = $"test{i}"; score.Hash = $"test{i}";
scores.Add(score); scores.Add(score);
@ -316,7 +315,7 @@ namespace osu.Game.Tests.Visual.Ranking
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i; score.TotalScore += 10 - i;
scores.Add(score); scores.Add(score);
} }

View File

@ -3,10 +3,10 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
{ {
@ -17,7 +17,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestDRank() public void TestDRank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.5;
score.Rank = ScoreRank.D;
addPanelStep(score); addPanelStep(score);
} }
@ -25,7 +27,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestCRank() public void TestCRank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.75;
score.Rank = ScoreRank.C;
addPanelStep(score); addPanelStep(score);
} }
@ -33,7 +37,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestBRank() public void TestBRank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.85;
score.Rank = ScoreRank.B;
addPanelStep(score); addPanelStep(score);
} }
@ -41,7 +47,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestARank() public void TestARank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
addPanelStep(score); addPanelStep(score);
} }
@ -49,7 +57,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestSRank() public void TestSRank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.975;
score.Rank = ScoreRank.S;
addPanelStep(score); addPanelStep(score);
} }
@ -57,7 +67,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAlmostSSRank() public void TestAlmostSSRank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.9999;
score.Rank = ScoreRank.S;
addPanelStep(score); addPanelStep(score);
} }
@ -65,7 +77,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestSSRank() public void TestSSRank()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 1;
score.Rank = ScoreRank.X;
addPanelStep(score); addPanelStep(score);
} }
@ -73,7 +87,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAllHitResults() public void TestAllHitResults()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } }; var score = TestResources.CreateTestScoreInfo();
score.Statistics[HitResult.Perfect] = 350;
score.Statistics[HitResult.Ok] = 200;
addPanelStep(score); addPanelStep(score);
} }
@ -81,7 +97,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestContractedPanel() public void TestContractedPanel()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
addPanelStep(score, PanelState.Contracted); addPanelStep(score, PanelState.Contracted);
} }
@ -89,7 +107,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestExpandAndContract() public void TestExpandAndContract()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; var score = TestResources.CreateTestScoreInfo();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
addPanelStep(score, PanelState.Contracted); addPanelStep(score, PanelState.Contracted);
AddWaitStep("wait for transition", 10); AddWaitStep("wait for transition", 10);

View File

@ -7,9 +7,9 @@ using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Tests.Resources;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
@ -29,14 +29,14 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
createListStep(() => new ScorePanelList createListStep(() => new ScorePanelList
{ {
SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) } SelectedScore = { Value = TestResources.CreateTestScoreInfo() }
}); });
} }
[Test] [Test]
public void TestAddPanelAfterSelectingScore() public void TestAddPanelAfterSelectingScore()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); var score = TestResources.CreateTestScoreInfo();
createListStep(() => new ScorePanelList createListStep(() => new ScorePanelList
{ {
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAddPanelBeforeSelectingScore() public void TestAddPanelBeforeSelectingScore()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); var score = TestResources.CreateTestScoreInfo();
createListStep(() => new ScorePanelList()); createListStep(() => new ScorePanelList());
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("add many scores", () => AddStep("add many scores", () =>
{ {
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); list.AddScore(TestResources.CreateTestScoreInfo());
}); });
assertFirstPanelCentred(); assertFirstPanelCentred();
@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAddManyScoresAfterExpandedPanel() public void TestAddManyScoresAfterExpandedPanel()
{ {
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var initialScore = TestResources.CreateTestScoreInfo();
createListStep(() => new ScorePanelList()); createListStep(() => new ScorePanelList());
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("add many scores", () => AddStep("add many scores", () =>
{ {
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1));
}); });
assertScoreState(initialScore, true); assertScoreState(initialScore, true);
@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAddManyScoresBeforeExpandedPanel() public void TestAddManyScoresBeforeExpandedPanel()
{ {
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var initialScore = TestResources.CreateTestScoreInfo();
createListStep(() => new ScorePanelList()); createListStep(() => new ScorePanelList());
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("add scores", () => AddStep("add scores", () =>
{ {
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1));
}); });
assertScoreState(initialScore, true); assertScoreState(initialScore, true);
@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAddManyPanelsOnBothSidesOfExpandedPanel() public void TestAddManyPanelsOnBothSidesOfExpandedPanel()
{ {
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var initialScore = TestResources.CreateTestScoreInfo();
createListStep(() => new ScorePanelList()); createListStep(() => new ScorePanelList());
@ -143,10 +143,10 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("add scores after", () => AddStep("add scores after", () =>
{ {
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1));
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1));
}); });
assertScoreState(initialScore, true); assertScoreState(initialScore, true);
@ -156,11 +156,11 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestSelectMultipleScores() public void TestSelectMultipleScores()
{ {
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var firstScore = TestResources.CreateTestScoreInfo();
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var secondScore = TestResources.CreateTestScoreInfo();
firstScore.User.Username = "A"; firstScore.UserString = "A";
secondScore.User.Username = "B"; secondScore.UserString = "B";
createListStep(() => new ScorePanelList()); createListStep(() => new ScorePanelList());
@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestAddScoreImmediately() public void TestAddScoreImmediately()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); var score = TestResources.CreateTestScoreInfo();
createListStep(() => createListStep(() =>
{ {
@ -206,9 +206,14 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestKeyboardNavigation() public void TestKeyboardNavigation()
{ {
var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 }; var lowestScore = TestResources.CreateTestScoreInfo();
var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 }; lowestScore.MaxCombo = 100;
var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 };
var middleScore = TestResources.CreateTestScoreInfo();
middleScore.MaxCombo = 200;
var highestScore = TestResources.CreateTestScoreInfo();
highestScore.MaxCombo = 300;
createListStep(() => new ScorePanelList()); createListStep(() => new ScorePanelList());
@ -270,6 +275,13 @@ namespace osu.Game.Tests.Visual.Ranking
assertExpandedPanelCentred(); assertExpandedPanelCentred();
} }
private ScoreInfo createScoreForTotalScore(long totalScore)
{
var score = TestResources.CreateTestScoreInfo();
score.TotalScore = totalScore;
return score;
}
private void createListStep(Func<ScorePanelList> creationFunc) private void createListStep(Func<ScorePanelList> creationFunc)
{ {
AddStep("create list", () => Child = list = creationFunc().With(d => AddStep("create list", () => Child = list = creationFunc().With(d =>

View File

@ -6,11 +6,11 @@ using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Resources;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
@ -20,10 +20,8 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestScoreWithTimeStatistics() public void TestScoreWithTimeStatistics()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) var score = TestResources.CreateTestScoreInfo();
{ score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents();
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents()
};
loadPanel(score); loadPanel(score);
} }
@ -31,10 +29,8 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestScoreWithPositionStatistics() public void TestScoreWithPositionStatistics()
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) var score = TestResources.CreateTestScoreInfo();
{ score.HitEvents = createPositionDistributedHitEvents();
HitEvents = createPositionDistributedHitEvents()
};
loadPanel(score); loadPanel(score);
} }
@ -42,7 +38,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test] [Test]
public void TestScoreWithoutStatistics() public void TestScoreWithoutStatistics()
{ {
loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); loadPanel(TestResources.CreateTestScoreInfo());
} }
[Test] [Test]

View File

@ -835,12 +835,7 @@ namespace osu.Game.Tests.Visual.SongSelect
// this beatmap change should be overridden by the present. // this beatmap change should be overridden by the present.
Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap());
songSelect.PresentScore(new ScoreInfo songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap()));
{
User = new APIUser { Username = "woo" },
BeatmapInfo = getPresentBeatmap(),
Ruleset = getPresentBeatmap().Ruleset
});
}); });
AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen());

View File

@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
var score = new ScoreInfo var score = new ScoreInfo
{ {
OnlineScoreID = i, OnlineID = i,
BeatmapInfo = beatmapInfo, BeatmapInfo = beatmapInfo,
BeatmapInfoID = beatmapInfo.ID, BeatmapInfoID = beatmapInfo.ID,
Accuracy = RNG.NextDouble(), Accuracy = RNG.NextDouble(),
@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface
}); });
AddUntilStep("wait for fetch", () => leaderboard.Scores != null); AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID)); AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID));
} }
[Test] [Test]
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
AddUntilStep("wait for fetch", () => leaderboard.Scores != null); AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID)); AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID));
} }
} }
} }

View File

@ -147,7 +147,7 @@ namespace osu.Game.Database
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty); modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
modelBuilder.Entity<ScoreInfo>().HasIndex(b => b.OnlineScoreID).IsUnique(); modelBuilder.Entity<ScoreInfo>().HasIndex(b => b.OnlineID).IsUnique();
} }
private class OsuDbLoggerFactory : ILoggerFactory private class OsuDbLoggerFactory : ILoggerFactory

View File

@ -104,6 +104,14 @@ namespace osu.Game.Extensions
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns> /// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other); public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other);
/// <summary>
/// Check whether the online ID of two <see cref="IScoreInfo"/>s match.
/// </summary>
/// <param name="instance">The instance to compare.</param>
/// <param name="other">The other instance to compare against.</param>
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
public static bool MatchesOnlineID(this IScoreInfo? instance, IScoreInfo? other) => matchesOnlineID(instance, other);
private static bool matchesOnlineID(this IHasOnlineID<long>? instance, IHasOnlineID<long>? other) private static bool matchesOnlineID(this IHasOnlineID<long>? instance, IHasOnlineID<long>? other)
{ {
if (instance == null || other == null) if (instance == null || other == null)

View File

@ -81,20 +81,37 @@ namespace osu.Game.Input
// compare counts in database vs defaults for each action type. // compare counts in database vs defaults for each action type.
foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
{ {
// avoid performing redundant queries when the database is empty and needs to be re-filled. IEnumerable<RealmKeyBinding> existing = existingBindings.Where(k =>
int existingCount = existingBindings.Count(k => k.RulesetName == rulesetName && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); k.RulesetName == rulesetName
&& k.Variant == variant
&& k.ActionInt == (int)defaultsForAction.Key);
if (defaultsForAction.Count() <= existingCount) int defaultsCount = defaultsForAction.Count();
continue; int existingCount = existing.Count();
// insert any defaults which are missing. if (defaultsCount > existingCount)
realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
{ {
KeyCombinationString = k.KeyCombination.ToString(), // insert any defaults which are missing.
ActionInt = (int)k.Action, realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
RulesetName = rulesetName, {
Variant = variant KeyCombinationString = k.KeyCombination.ToString(),
})); ActionInt = (int)k.Action,
RulesetName = rulesetName,
Variant = variant
}));
}
else if (defaultsCount < existingCount)
{
// generally this shouldn't happen, but if the user has more key bindings for an action than we expect,
// remove the last entries until the count matches for sanity.
foreach (var k in existing.TakeLast(existingCount - defaultsCount).ToArray())
{
realm.Remove(k);
// Remove from the local flattened/cached list so future lookups don't query now deleted rows.
existingBindings.Remove(k);
}
}
} }
} }

View File

@ -8,7 +8,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScoreInfo>> public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScore>>
{ {
private readonly long userId; private readonly long userId;
private readonly ScoreType type; private readonly ScoreType type;

View File

@ -13,10 +13,11 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Scoring.Legacy; using osu.Game.Scoring.Legacy;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses namespace osu.Game.Online.API.Requests.Responses
{ {
public class APIScoreInfo : IScoreInfo public class APIScore : IScoreInfo
{ {
[JsonProperty(@"score")] [JsonProperty(@"score")]
public long TotalScore { get; set; } public long TotalScore { get; set; }
@ -101,7 +102,7 @@ namespace osu.Game.Online.API.Requests.Responses
BeatmapInfo = beatmap, BeatmapInfo = beatmap,
User = User, User = User,
Accuracy = Accuracy, Accuracy = Accuracy,
OnlineScoreID = OnlineID, OnlineID = OnlineID,
Date = Date, Date = Date,
PP = PP, PP = PP,
RulesetID = RulesetID, RulesetID = RulesetID,
@ -150,6 +151,11 @@ namespace osu.Game.Online.API.Requests.Responses
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException(); IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException();
#region Implementation of IScoreInfo
IBeatmapInfo IScoreInfo.Beatmap => Beatmap; IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
IUser IScoreInfo.User => User;
#endregion
} }
} }

View File

@ -14,7 +14,7 @@ namespace osu.Game.Online.API.Requests.Responses
public int? Position; public int? Position;
[JsonProperty(@"score")] [JsonProperty(@"score")]
public APIScoreInfo Score; public APIScore Score;
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
{ {

View File

@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests.Responses
public class APIScoresCollection public class APIScoresCollection
{ {
[JsonProperty(@"scores")] [JsonProperty(@"scores")]
public List<APIScoreInfo> Scores; public List<APIScore> Scores;
[JsonProperty(@"userScore")] [JsonProperty(@"userScore")]
public APIScoreWithPosition UserScore; public APIScoreWithPosition UserScore;

View File

@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -120,16 +120,21 @@ namespace osu.Game.Online.Chat
private void checkForMentions(Channel channel, Message message) private void checkForMentions(Channel channel, Message message)
{ {
if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return; if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return;
notifications.Post(new MentionNotification(message.Sender.Username, channel)); notifications.Post(new MentionNotification(message.Sender.Username, channel));
} }
/// <summary> /// <summary>
/// Checks if <paramref name="message"/> contains <paramref name="username"/>. /// Checks if <paramref name="message"/> mentions <paramref name="username"/>.
/// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces).
/// </summary> /// </summary>
private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); public static bool CheckContainsUsername(string message, string username)
{
string fullName = Regex.Escape(username);
string underscoreName = Regex.Escape(username.Replace(' ', '_'));
return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase);
}
public class PrivateMessageNotification : OpenChannelNotification public class PrivateMessageNotification : OpenChannelNotification
{ {

View File

@ -111,7 +111,7 @@ namespace osu.Game.Online.Leaderboards
background = new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, Colour = user.OnlineID == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black,
Alpha = background_alpha, Alpha = background_alpha,
}, },
}, },

View File

@ -95,8 +95,6 @@ namespace osu.Game.Online.Multiplayer
protected readonly BindableList<int> PlayingUserIds = new BindableList<int>(); protected readonly BindableList<int> PlayingUserIds = new BindableList<int>();
public readonly Bindable<PlaylistItem?> CurrentMatchPlayingItem = new Bindable<PlaylistItem?>();
/// <summary> /// <summary>
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available. /// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
/// </summary> /// </summary>
@ -162,9 +160,6 @@ namespace osu.Game.Online.Multiplayer
var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
Debug.Assert(joinedRoom != null); Debug.Assert(joinedRoom != null);
// Populate playlist items.
var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false);
// Populate users. // Populate users.
Debug.Assert(joinedRoom.Users != null); Debug.Assert(joinedRoom.Users != null);
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false);
@ -176,7 +171,7 @@ namespace osu.Game.Online.Multiplayer
APIRoom = room; APIRoom = room;
APIRoom.Playlist.Clear(); APIRoom.Playlist.Clear();
APIRoom.Playlist.AddRange(playlistItems); APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
Debug.Assert(LocalUser != null); Debug.Assert(LocalUser != null);
addUserToAPIRoom(LocalUser); addUserToAPIRoom(LocalUser);
@ -219,7 +214,6 @@ namespace osu.Game.Online.Multiplayer
{ {
APIRoom = null; APIRoom = null;
Room = null; Room = null;
CurrentMatchPlayingItem.Value = null;
PlayingUserIds.Clear(); PlayingUserIds.Clear();
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
@ -477,28 +471,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null); Debug.Assert(APIRoom != null);
Debug.Assert(Room != null); Debug.Assert(Room != null);
Scheduler.Add(() => Scheduler.Add(() => updateLocalRoomSettings(newSettings));
{
// ensure the new selected item is populated immediately.
var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId);
if (playlistItem != null)
{
GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b =>
{
// Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards.
bool success = b.Exception == null;
Scheduler.Add(() =>
{
if (success)
playlistItem.Beatmap.Value = b.Result;
updateLocalRoomSettings(newSettings);
});
});
}
});
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -653,12 +626,10 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task PlaylistItemAdded(MultiplayerPlaylistItem item) public Task PlaylistItemAdded(MultiplayerPlaylistItem item)
{ {
if (Room == null) if (Room == null)
return; return Task.CompletedTask;
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
Scheduler.Add(() => Scheduler.Add(() =>
{ {
@ -668,11 +639,13 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null); Debug.Assert(APIRoom != null);
Room.Playlist.Add(item); Room.Playlist.Add(item);
APIRoom.Playlist.Add(playlistItem); APIRoom.Playlist.Add(createPlaylistItem(item));
ItemAdded?.Invoke(item); ItemAdded?.Invoke(item);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
}); });
return Task.CompletedTask;
} }
public Task PlaylistItemRemoved(long playlistItemId) public Task PlaylistItemRemoved(long playlistItemId)
@ -697,12 +670,10 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task PlaylistItemChanged(MultiplayerPlaylistItem item) public Task PlaylistItemChanged(MultiplayerPlaylistItem item)
{ {
if (Room == null) if (Room == null)
return; return Task.CompletedTask;
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
Scheduler.Add(() => Scheduler.Add(() =>
{ {
@ -715,15 +686,13 @@ namespace osu.Game.Online.Multiplayer
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
APIRoom.Playlist.RemoveAt(existingIndex); APIRoom.Playlist.RemoveAt(existingIndex);
APIRoom.Playlist.Insert(existingIndex, playlistItem); APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
// If the currently-selected item was the one that got replaced, update the selected item to the new one.
if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID)
CurrentMatchPlayingItem.Value = playlistItem;
ItemChanged?.Invoke(item); ItemChanged?.Invoke(item);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
}); });
return Task.CompletedTask;
} }
/// <summary> /// <summary>
@ -752,12 +721,11 @@ namespace osu.Game.Online.Multiplayer
APIRoom.Password.Value = Room.Settings.Password; APIRoom.Password.Value = Room.Settings.Password;
APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.Type.Value = Room.Settings.MatchType;
APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.QueueMode.Value = Room.Settings.QueueMode;
RoomUpdated?.Invoke();
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); RoomUpdated?.Invoke();
} }
private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately) private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item)
{ {
var ruleset = Rulesets.GetRuleset(item.RulesetID); var ruleset = Rulesets.GetRuleset(item.RulesetID);
@ -779,9 +747,6 @@ namespace osu.Game.Online.Multiplayer
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance)));
if (populateBeatmapImmediately)
playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
return playlistItem; return playlistItem;
} }
@ -791,7 +756,7 @@ namespace osu.Game.Online.Multiplayer
/// <param name="beatmapId">The beatmap ID.</param> /// <param name="beatmapId">The beatmap ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param> /// <param name="cancellationToken">A token to cancel the request.</param>
/// <returns>The <see cref="APIBeatmap"/> retrieval task.</returns> /// <returns>The <see cref="APIBeatmap"/> retrieval task.</returns>
protected abstract Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default); public abstract Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>. /// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.

View File

@ -178,7 +178,7 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId);
} }
protected override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) public override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
{ {
return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken); return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken);
} }

View File

@ -69,7 +69,7 @@ namespace osu.Game.Online.Rooms
var scoreInfo = new ScoreInfo var scoreInfo = new ScoreInfo
{ {
OnlineScoreID = ID, OnlineID = ID,
TotalScore = TotalScore, TotalScore = TotalScore,
MaxCombo = MaxCombo, MaxCombo = MaxCombo,
BeatmapInfo = beatmap, BeatmapInfo = beatmap,

View File

@ -40,6 +40,11 @@ namespace osu.Game.Online.Rooms
private BeatmapDownloadTracker downloadTracker; private BeatmapDownloadTracker downloadTracker;
/// <summary>
/// The beatmap matching the required hash (and providing a final <see cref="BeatmapAvailability.LocallyAvailable"/> state).
/// </summary>
private BeatmapInfo matchingHash;
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -71,13 +76,34 @@ namespace osu.Game.Online.Rooms
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
}, true); }, true);
}, true); }, true);
// These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs.
// During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one.
// This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching).
beatmapManager.ItemUpdated += itemUpdated;
beatmapManager.ItemRemoved += itemRemoved;
} }
private void itemUpdated(BeatmapSetInfo item) => Schedule(() =>
{
if (matchingHash?.BeatmapSet.ID == item.ID || SelectedItem.Value?.Beatmap.Value.BeatmapSet?.OnlineID == item.OnlineID)
updateAvailability();
});
private void itemRemoved(BeatmapSetInfo item) => Schedule(() =>
{
if (matchingHash?.BeatmapSet.ID == item.ID)
updateAvailability();
});
private void updateAvailability() private void updateAvailability()
{ {
if (downloadTracker == null) if (downloadTracker == null)
return; return;
// will be repopulated below if still valid.
matchingHash = null;
switch (downloadTracker.State.Value) switch (downloadTracker.State.Value)
{ {
case DownloadState.NotDownloaded: case DownloadState.NotDownloaded:
@ -93,7 +119,9 @@ namespace osu.Game.Online.Rooms
break; break;
case DownloadState.LocallyAvailable: case DownloadState.LocallyAvailable:
bool hashMatches = checkHashValidity(); matchingHash = findMatchingHash();
bool hashMatches = matchingHash != null;
availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded();
@ -108,12 +136,23 @@ namespace osu.Game.Online.Rooms
} }
} }
private bool checkHashValidity() private BeatmapInfo findMatchingHash()
{ {
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmapManager != null)
{
beatmapManager.ItemUpdated -= itemUpdated;
beatmapManager.ItemRemoved -= itemRemoved;
}
} }
} }
} }

View File

@ -31,6 +31,7 @@ namespace osu.Game.Online.Rooms
req.ContentType = "application/json"; req.ContentType = "application/json";
req.Method = HttpMethod.Put; req.Method = HttpMethod.Put;
req.Timeout = 30000;
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
{ {

View File

@ -3,6 +3,7 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -35,7 +36,7 @@ namespace osu.Game.Online
var scoreInfo = new ScoreInfo var scoreInfo = new ScoreInfo
{ {
ID = TrackedItem.ID, ID = TrackedItem.ID,
OnlineScoreID = TrackedItem.OnlineScoreID OnlineID = TrackedItem.OnlineID
}; };
if (Manager.IsAvailableLocally(scoreInfo)) if (Manager.IsAvailableLocally(scoreInfo))
@ -113,7 +114,7 @@ namespace osu.Game.Online
UpdateState(DownloadState.NotDownloaded); UpdateState(DownloadState.NotDownloaded);
}); });
private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.OnlineID == y.OnlineID; private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.MatchesOnlineID(y);
#region Disposal #region Disposal

View File

@ -31,6 +31,7 @@ namespace osu.Game.Online.Solo
req.ContentType = "application/json"; req.ContentType = "application/json";
req.Method = HttpMethod.Put; req.Method = HttpMethod.Put;
req.Timeout = 30000;
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
{ {

View File

@ -16,7 +16,7 @@ namespace osu.Game.Online.Solo
{ {
/// <summary> /// <summary>
/// A class specifically for sending scores to the API during score submission. /// A class specifically for sending scores to the API during score submission.
/// This is used instead of <see cref="APIScoreInfo"/> due to marginally different serialisation naming requirements. /// This is used instead of <see cref="APIScore"/> due to marginally different serialisation naming requirements.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class SubmittableScore public class SubmittableScore

View File

@ -103,7 +103,7 @@ namespace osu.Game
private Container topMostOverlayContent; private Container topMostOverlayContent;
private ScalingContainer screenContainer; protected ScalingContainer ScreenContainer { get; private set; }
protected Container ScreenOffsetContainer { get; private set; } protected Container ScreenOffsetContainer { get; private set; }
@ -179,7 +179,7 @@ namespace osu.Game
} }
private void updateBlockingOverlayFade() => private void updateBlockingOverlayFade() =>
screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
public void AddBlockingOverlay(OverlayContainer overlay) public void AddBlockingOverlay(OverlayContainer overlay)
{ {
@ -487,8 +487,8 @@ namespace osu.Game
// to ensure all the required data for presenting a replay are present. // to ensure all the required data for presenting a replay are present.
ScoreInfo databasedScoreInfo = null; ScoreInfo databasedScoreInfo = null;
if (score.OnlineScoreID != null) if (score.OnlineID > 0)
databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID); databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID);
databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash); databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash);
@ -698,7 +698,7 @@ namespace osu.Game
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) ScreenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -801,7 +801,7 @@ namespace osu.Game
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
loadComponentSingleFile(skinEditor = new SkinEditorOverlay(screenContainer), overlayContent.Add, true); loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true);
loadComponentSingleFile(new LoginOverlay loadComponentSingleFile(new LoginOverlay
{ {

View File

@ -94,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
topScoresContainer.Add(new DrawableTopScore(topScore)); topScoresContainer.Add(new DrawableTopScore(topScore));
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) if (userScoreInfo != null && userScoreInfo.OnlineID != topScore.OnlineID)
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
}), TaskContinuationOptions.OnlyOnRanToCompletion); }), TaskContinuationOptions.OnlyOnRanToCompletion);
}); });

View File

@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
private const float performance_background_shear = 0.45f; private const float performance_background_shear = 0.45f;
protected readonly APIScoreInfo Score; protected readonly APIScore Score;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
[Resolved] [Resolved]
private OverlayColourProvider colourProvider { get; set; } private OverlayColourProvider colourProvider { get; set; }
public DrawableProfileScore(APIScoreInfo score) public DrawableProfileScore(APIScore score)
{ {
Score = score; Score = score;

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
private readonly double weight; private readonly double weight;
public DrawableProfileWeightedScore(APIScoreInfo score, double weight) public DrawableProfileWeightedScore(APIScore score, double weight)
: base(score) : base(score)
{ {
this.weight = weight; this.weight = weight;

View File

@ -15,7 +15,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Overlays.Profile.Sections.Ranks namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScoreInfo> public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScore>
{ {
private readonly ScoreType type; private readonly ScoreType type;
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
} }
} }
protected override void OnItemsReceived(List<APIScoreInfo> items) protected override void OnItemsReceived(List<APIScore> items)
{ {
if (VisiblePages == 0) if (VisiblePages == 0)
drawableItemIndex = 0; drawableItemIndex = 0;
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
base.OnItemsReceived(items); base.OnItemsReceived(items);
} }
protected override APIRequest<List<APIScoreInfo>> CreateRequest() => protected override APIRequest<List<APIScore>> CreateRequest() =>
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
private int drawableItemIndex; private int drawableItemIndex;
protected override Drawable CreateDrawableItem(APIScoreInfo model) protected override Drawable CreateDrawableItem(APIScore model)
{ {
switch (type) switch (type)
{ {

View File

@ -4,14 +4,14 @@
using System; using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users;
namespace osu.Game.Scoring namespace osu.Game.Scoring
{ {
public interface IScoreInfo : IHasOnlineID<long>, IHasNamedFiles public interface IScoreInfo : IHasOnlineID<long>, IHasNamedFiles
{ {
APIUser User { get; } IUser User { get; }
long TotalScore { get; } long TotalScore { get; }

View File

@ -80,12 +80,9 @@ namespace osu.Game.Scoring.Legacy
byte[] compressedReplay = sr.ReadByteArray(); byte[] compressedReplay = sr.ReadByteArray();
if (version >= 20140721) if (version >= 20140721)
scoreInfo.OnlineScoreID = sr.ReadInt64(); scoreInfo.OnlineID = sr.ReadInt64();
else if (version >= 20121008) else if (version >= 20121008)
scoreInfo.OnlineScoreID = sr.ReadInt32(); scoreInfo.OnlineID = sr.ReadInt32();
if (scoreInfo.OnlineScoreID <= 0)
scoreInfo.OnlineScoreID = null;
if (compressedReplay?.Length > 0) if (compressedReplay?.Length > 0)
{ {

View File

@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
using osu.Game.Utils; using osu.Game.Utils;
namespace osu.Game.Scoring namespace osu.Game.Scoring
@ -136,7 +137,14 @@ namespace osu.Game.Scoring
[Column("Beatmap")] [Column("Beatmap")]
public BeatmapInfo BeatmapInfo { get; set; } public BeatmapInfo BeatmapInfo { get; set; }
public long? OnlineScoreID { get; set; } private long? onlineID;
[Column("OnlineScoreID")]
public long? OnlineID
{
get => onlineID;
set => onlineID = value > 0 ? value : null;
}
public DateTimeOffset Date { get; set; } public DateTimeOffset Date { get; set; }
@ -231,24 +239,18 @@ namespace osu.Game.Scoring
public bool Equals(ScoreInfo other) public bool Equals(ScoreInfo other)
{ {
if (other == null) if (ReferenceEquals(this, other)) return true;
return false; if (other == null) return false;
if (ID != 0 && other.ID != 0) if (ID != 0 && other.ID != 0)
return ID == other.ID; return ID == other.ID;
if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) return false;
return OnlineScoreID == other.OnlineScoreID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash;
return ReferenceEquals(this, other);
} }
#region Implementation of IHasOnlineID #region Implementation of IHasOnlineID
public long OnlineID => OnlineScoreID ?? -1; long IHasOnlineID<long>.OnlineID => OnlineID ?? -1;
#endregion #endregion
@ -256,6 +258,7 @@ namespace osu.Game.Scoring
IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo;
IRulesetInfo IScoreInfo.Ruleset => Ruleset; IRulesetInfo IScoreInfo.Ruleset => Ruleset;
IUser IScoreInfo.User => User;
bool IScoreInfo.HasReplay => Files.Any(); bool IScoreInfo.HasReplay => Files.Any();
#endregion #endregion

View File

@ -71,7 +71,7 @@ namespace osu.Game.Scoring
return scores.Select((score, index) => (score, totalScore: totalScores[index])) return scores.Select((score, index) => (score, totalScore: totalScores[index]))
.OrderByDescending(g => g.totalScore) .OrderByDescending(g => g.totalScore)
.ThenBy(g => g.score.OnlineScoreID) .ThenBy(g => g.score.OnlineID)
.Select(g => g.score) .Select(g => g.score)
.ToArray(); .ToArray();
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
@ -17,6 +18,6 @@ namespace osu.Game.Scoring
protected override ArchiveDownloadRequest<IScoreInfo> CreateDownloadRequest(IScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); protected override ArchiveDownloadRequest<IScoreInfo> CreateDownloadRequest(IScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score);
public override ArchiveDownloadRequest<IScoreInfo> GetExistingDownload(IScoreInfo model) public override ArchiveDownloadRequest<IScoreInfo> GetExistingDownload(IScoreInfo model)
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); => CurrentDownloads.Find(r => r.Model.MatchesOnlineID(model));
} }
} }

View File

@ -66,6 +66,6 @@ namespace osu.Game.Scoring
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items) protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
=> base.CheckLocalAvailability(model, items) => base.CheckLocalAvailability(model, items)
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); || (model.OnlineID > 0 && items.Any(i => i.OnlineID == model.OnlineID));
} }
} }

View File

@ -48,16 +48,19 @@ namespace osu.Game.Screens.Backgrounds
AddInternal(seasonalBackgroundLoader); AddInternal(seasonalBackgroundLoader);
user.ValueChanged += _ => Next(); user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
skin.ValueChanged += _ => Next(); skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
mode.ValueChanged += _ => Next(); mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
beatmap.ValueChanged += _ => Next(); beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
introSequence.ValueChanged += _ => Next(); introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Next(); seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired);
currentDisplay = RNG.Next(0, background_count); currentDisplay = RNG.Next(0, background_count);
Next(); Next();
// helper function required for AddOnce usage.
void loadNextIfRequired() => Next();
} }
private ScheduledDelegate nextTask; private ScheduledDelegate nextTask;
@ -67,7 +70,7 @@ namespace osu.Game.Screens.Backgrounds
/// Request loading the next background. /// Request loading the next background.
/// </summary> /// </summary>
/// <returns>Whether a new background was queued for load. May return false if the current background is still valid.</returns> /// <returns>Whether a new background was queued for load. May return false if the current background is still valid.</returns>
public bool Next() public virtual bool Next()
{ {
var nextBackground = createBackground(); var nextBackground = createBackground();

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -9,6 +10,7 @@ using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -53,6 +55,14 @@ namespace osu.Game.Screens.Edit
}); });
} }
protected override void LoadComplete()
{
base.LoadComplete();
// will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`.
Mods.Value = Array.Empty<Mod>();
}
protected virtual Editor CreateEditor() => new Editor(this); protected virtual Editor CreateEditor() => new Editor(this);
protected override void LogoArriving(OsuLogo logo, bool resuming) protected override void LogoArriving(OsuLogo logo, bool resuming)

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Development;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -107,6 +108,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
public void AddOrUpdateRoom(Room room) public void AddOrUpdateRoom(Room room)
{ {
Debug.Assert(ThreadSafety.IsUpdateThread);
Debug.Assert(room.RoomID.Value != null); Debug.Assert(room.RoomID.Value != null);
if (ignoredRooms.Contains(room.RoomID.Value.Value)) if (ignoredRooms.Contains(room.RoomID.Value.Value))
@ -136,12 +138,16 @@ namespace osu.Game.Screens.OnlinePlay.Components
public void RemoveRoom(Room room) public void RemoveRoom(Room room)
{ {
Debug.Assert(ThreadSafety.IsUpdateThread);
rooms.Remove(room); rooms.Remove(room);
notifyRoomsUpdated(); notifyRoomsUpdated();
} }
public void ClearRooms() public void ClearRooms()
{ {
Debug.Assert(ThreadSafety.IsUpdateThread);
rooms.Clear(); rooms.Clear();
notifyRoomsUpdated(); notifyRoomsUpdated();
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -24,6 +25,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -90,6 +92,10 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved] [Resolved]
private UserLookupCache userLookupCache { get; set; } private UserLookupCache userLookupCache { get; set; }
[CanBeNull]
[Resolved(CanBeNull = true)]
private MultiplayerClient multiplayerClient { get; set; }
[Resolved] [Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; } private BeatmapLookupCache beatmapLookupCache { get; set; }
@ -157,7 +163,15 @@ namespace osu.Game.Screens.OnlinePlay
if (Item.Beatmap.Value == null) if (Item.Beatmap.Value == null)
{ {
var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); IBeatmapInfo foundBeatmap;
if (multiplayerClient != null)
// This call can eventually go away (and use the else case below).
// Currently required only due to the method being overridden to provide special behaviour in tests.
foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false);
else
foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false);
Schedule(() => Item.Beatmap.Value = foundBeatmap); Schedule(() => Item.Beatmap.Value = foundBeatmap);
} }
} }

View File

@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
{ {
[Cached(typeof(IBindable<PlaylistItem>))] [Cached(typeof(IBindable<PlaylistItem>))]
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>(); public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
public override bool? AllowTrackAdjustments => true; public override bool? AllowTrackAdjustments => true;
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected OnlinePlayScreen ParentScreen { get; private set; } protected OnlinePlayScreen ParentScreen { get; private set; }
[Cached] [Cached]
private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; } private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
protected IBindable<BeatmapAvailability> BeatmapAvailability => beatmapAvailabilityTracker.Availability; protected IBindable<BeatmapAvailability> BeatmapAvailability => beatmapAvailabilityTracker.Availability;
@ -90,11 +90,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
Padding = new MarginPadding { Top = Header.HEIGHT }; Padding = new MarginPadding { Top = Header.HEIGHT };
beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = SelectedItem }
};
RoomId.BindTo(room.RoomID); RoomId.BindTo(room.RoomID);
} }
@ -247,10 +242,10 @@ namespace osu.Game.Screens.OnlinePlay.Match
}, true); }, true);
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
beatmapManager.ItemUpdated += beatmapUpdated;
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
} }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@ -374,8 +369,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
} }
} }
private void beatmapUpdated(BeatmapSetInfo set) => Schedule(updateWorkingBeatmap);
private void updateWorkingBeatmap() private void updateWorkingBeatmap()
{ {
var beatmap = SelectedItem.Value?.Beatmap.Value; var beatmap = SelectedItem.Value?.Beatmap.Value;
@ -443,14 +436,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
/// <param name="room">The room to change the settings of.</param> /// <param name="room">The room to change the settings of.</param>
protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmapManager != null)
beatmapManager.ItemUpdated -= beatmapUpdated;
}
public class UserModSelectButton : PurpleTriangleButton public class UserModSelectButton : PurpleTriangleButton
{ {
} }

View File

@ -63,6 +63,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready"); sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready");
} }
protected override void LoadComplete()
{
base.LoadComplete();
SelectedItem.BindValueChanged(_ => updateState());
}
protected override void OnRoomUpdated() protected override void OnRoomUpdated()
{ {
base.OnRoomUpdated(); base.OnRoomUpdated();
@ -104,7 +111,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
bool enableButton = bool enableButton =
Room?.State == MultiplayerRoomState.Open Room?.State == MultiplayerRoomState.Open
&& Client.CurrentMatchPlayingItem.Value?.Expired == false && SelectedItem.Value?.ID == Room.Settings.PlaylistItemId
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
&& !operationInProgress.Value; && !operationInProgress.Value;
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved] [Resolved]
private MultiplayerClient client { get; set; } private MultiplayerClient client { get; set; }
private readonly PlaylistItem itemToEdit; private readonly long? itemToEdit;
private LoadingLayer loadingLayer; private LoadingLayer loadingLayer;
@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
/// <param name="itemToEdit">The item to be edited. May be null, in which case a new item will be added to the playlist.</param> /// <param name="itemToEdit">The item to be edited. May be null, in which case a new item will be added to the playlist.</param>
/// <param name="beatmap">An optional initial beatmap selection to perform.</param> /// <param name="beatmap">An optional initial beatmap selection to perform.</param>
/// <param name="ruleset">An optional initial ruleset selection to perform.</param> /// <param name="ruleset">An optional initial ruleset selection to perform.</param>
public MultiplayerMatchSongSelect(Room room, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
: base(room) : base(room)
{ {
this.itemToEdit = itemToEdit; this.itemToEdit = itemToEdit;
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
var multiplayerItem = new MultiplayerPlaylistItem var multiplayerItem = new MultiplayerPlaylistItem
{ {
ID = itemToEdit?.ID ?? 0, ID = itemToEdit ?? 0,
BeatmapID = item.BeatmapID, BeatmapID = item.BeatmapID,
BeatmapChecksum = item.Beatmap.Value.MD5Hash, BeatmapChecksum = item.Beatmap.Value.MD5Hash,
RulesetID = item.RulesetID, RulesetID = item.RulesetID,

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -67,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
base.LoadComplete(); base.LoadComplete();
SelectedItem.BindTo(client.CurrentMatchPlayingItem);
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
UserMods.BindValueChanged(onUserModsChanged); UserMods.BindValueChanged(onUserModsChanged);
@ -147,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
new MultiplayerPlaylist new MultiplayerPlaylist
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
RequestEdit = OpenSongSelection RequestEdit = item => OpenSongSelection(item.ID)
} }
}, },
new[] new[]
@ -224,7 +223,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
/// Opens the song selection screen to add or edit an item. /// Opens the song selection screen to add or edit an item.
/// </summary> /// </summary>
/// <param name="itemToEdit">An optional playlist item to edit. If null, a new item will be added instead.</param> /// <param name="itemToEdit">An optional playlist item to edit. If null, a new item will be added instead.</param>
internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null) internal void OpenSongSelection(long? itemToEdit = null)
{ {
if (!this.IsCurrentScreen()) if (!this.IsCurrentScreen())
return; return;
@ -327,10 +326,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client.LocalUser?.State == MultiplayerUserState.Ready) if (client.LocalUser?.State == MultiplayerUserState.Ready)
client.ChangeState(MultiplayerUserState.Idle); client.ChangeState(MultiplayerUserState.Idle);
} }
else else if (client.LocalUser?.State == MultiplayerUserState.Spectating
&& (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
{ {
if (client.LocalUser?.State == MultiplayerUserState.Spectating && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) onLoadRequested();
onLoadRequested();
} }
} }
@ -389,11 +388,50 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return; return;
} }
updateCurrentItem();
addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0; addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0;
Scheduler.AddOnce(UpdateMods); Scheduler.AddOnce(UpdateMods);
} }
private void updateCurrentItem()
{
Debug.Assert(client.Room != null);
var expectedSelectedItem = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId);
if (expectedSelectedItem == null)
return;
// There's no reason to renew the selected item if its content hasn't changed.
if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null)
return;
// Clear the selected item while the lookup is performed, so components like the ready button can enter their disabled states.
SelectedItem.Value = null;
if (expectedSelectedItem.Beatmap.Value == null)
{
Task.Run(async () =>
{
var beatmap = await client.GetAPIBeatmap(expectedSelectedItem.BeatmapID).ConfigureAwait(false);
Schedule(() =>
{
expectedSelectedItem.Beatmap.Value = beatmap;
if (Room.Playlist.SingleOrDefault(i => i.ID == client.Room?.Settings.PlaylistItemId)?.Equals(expectedSelectedItem) == true)
applyCurrentItem();
});
});
}
else
applyCurrentItem();
void applyCurrentItem() => SelectedItem.Value = expectedSelectedItem;
}
private void handleRoomLost() => Schedule(() => private void handleRoomLost() => Schedule(() =>
{ {
if (this.IsCurrentScreen()) if (this.IsCurrentScreen())
@ -446,6 +484,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (!this.IsCurrentScreen()) if (!this.IsCurrentScreen())
return; return;
if (client.Room == null)
return;
if (!client.IsHost) if (!client.IsHost)
{ {
// todo: should handle this when the request queue is implemented. // todo: should handle this when the request queue is implemented.
@ -454,7 +495,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return; return;
} }
this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset)); this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset));
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -87,6 +87,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{ {
var allScores = new List<MultiplayerScore> { userScore }; var allScores = new List<MultiplayerScore> { userScore };
// Other scores could have arrived between score submission and entering the results screen. Ensure the local player score position is up to date.
if (Score != null)
{
Score.Position = userScore.Position;
ScorePanelList.GetPanelForScore(Score).ScorePosition.Value = userScore.Position;
}
if (userScore.ScoresAround?.Higher != null) if (userScore.ScoresAround?.Higher != null)
{ {
allScores.AddRange(userScore.ScoresAround.Higher.Scores); allScores.AddRange(userScore.ScoresAround.Higher.Scores);
@ -186,12 +193,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Schedule(() => Schedule(() =>
{ {
// Prefer selecting the local user's score, or otherwise default to the first visible score. // Prefer selecting the local user's score, or otherwise default to the first visible score.
SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault();
}); });
} }
// Invoke callback to add the scores. Exclude the user's current score which was added previously. // Invoke callback to add the scores. Exclude the user's current score which was added previously.
callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID));
hideLoadingSpinners(pivot); hideLoadingSpinners(pivot);
})); }));

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
using ManagedBass.Fx; using ManagedBass.Fx;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -18,6 +19,7 @@ using osu.Framework.Utils;
using osu.Game.Audio.Effects; using osu.Game.Audio.Effects;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -58,6 +60,12 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}; };
/// <summary>
/// The player screen background, used to adjust appearance on failing.
/// </summary>
[CanBeNull]
public BackgroundScreen Background { private get; set; }
public FailAnimation(DrawableRuleset drawableRuleset) public FailAnimation(DrawableRuleset drawableRuleset)
{ {
this.drawableRuleset = drawableRuleset; this.drawableRuleset = drawableRuleset;
@ -136,6 +144,9 @@ namespace osu.Game.Screens.Play
Content.ScaleTo(0.85f, duration, Easing.OutQuart); Content.ScaleTo(0.85f, duration, Easing.OutQuart);
Content.RotateTo(1, duration, Easing.OutQuart); Content.RotateTo(1, duration, Easing.OutQuart);
Content.FadeColour(Color4.Gray, duration); Content.FadeColour(Color4.Gray, duration);
// Will be restored by `ApplyToBackground` logic in `SongSelect`.
Background?.FadeColour(OsuColour.Gray(0.3f), 60);
} }
public void RemoveFilters(bool resetTrackFrequency = true) public void RemoveFilters(bool resetTrackFrequency = true)

View File

@ -921,6 +921,8 @@ namespace osu.Game.Screens.Play
b.IsBreakTime.BindTo(breakTracker.IsBreakTime); b.IsBreakTime.BindTo(breakTracker.IsBreakTime);
b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
failAnimationLayer.Background = b;
}); });
HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime); HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime);
@ -1031,13 +1033,13 @@ namespace osu.Game.Screens.Play
// //
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
// conflicts across various systems (ie. solo and multiplayer). // conflicts across various systems (ie. solo and multiplayer).
long? onlineScoreId = score.ScoreInfo.OnlineScoreID; long? onlineScoreId = score.ScoreInfo.OnlineID;
score.ScoreInfo.OnlineScoreID = null; score.ScoreInfo.OnlineID = -1;
await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false);
// ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen). // ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen).
score.ScoreInfo.OnlineScoreID = onlineScoreId; score.ScoreInfo.OnlineID = onlineScoreId;
} }
/// <summary> /// <summary>

View File

@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play
private void userSentFrames(int userId, FrameDataBundle bundle) private void userSentFrames(int userId, FrameDataBundle bundle)
{ {
if (userId != score.ScoreInfo.User.Id) if (userId != score.ScoreInfo.User.OnlineID)
return; return;
if (!LoadedBeatmapSuccessfully) if (!LoadedBeatmapSuccessfully)

View File

@ -156,7 +156,7 @@ namespace osu.Game.Screens.Play
request.Success += s => request.Success += s =>
{ {
score.ScoreInfo.OnlineScoreID = s.ID; score.ScoreInfo.OnlineID = s.ID;
score.ScoreInfo.Position = s.Position; score.ScoreInfo.Position = s.Position;
scoreSubmissionSource.SetResult(true); scoreSubmissionSource.SetResult(true);

View File

@ -2,36 +2,42 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Scoring;
namespace osu.Game.Screens.Ranking.Contracted namespace osu.Game.Screens.Ranking.Contracted
{ {
public class ContractedPanelTopContent : CompositeDrawable public class ContractedPanelTopContent : CompositeDrawable
{ {
private readonly ScoreInfo score; public readonly Bindable<int?> ScorePosition = new Bindable<int?>();
public ContractedPanelTopContent(ScoreInfo score) private OsuSpriteText text;
public ContractedPanelTopContent()
{ {
this.score = score;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = new OsuSpriteText InternalChild = text = new OsuSpriteText
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Y = 6, Y = 6,
Text = score.Position != null ? $"#{score.Position}" : string.Empty,
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold)
}; };
} }
protected override void LoadComplete()
{
base.LoadComplete();
ScorePosition.BindValueChanged(pos => text.Text = pos.NewValue != null ? $"#{pos.NewValue}" : string.Empty, true);
}
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
@ -78,6 +79,11 @@ namespace osu.Game.Screens.Ranking
public event Action<PanelState> StateChanged; public event Action<PanelState> StateChanged;
/// <summary>
/// The position of the score in the rankings.
/// </summary>
public readonly Bindable<int?> ScorePosition = new Bindable<int?>();
/// <summary> /// <summary>
/// An action to be invoked if this <see cref="ScorePanel"/> is clicked while in an expanded state. /// An action to be invoked if this <see cref="ScorePanel"/> is clicked while in an expanded state.
/// </summary> /// </summary>
@ -103,6 +109,8 @@ namespace osu.Game.Screens.Ranking
{ {
Score = score; Score = score;
displayWithFlair = isNewLocalScore; displayWithFlair = isNewLocalScore;
ScorePosition.Value = score.Position;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -211,8 +219,8 @@ namespace osu.Game.Screens.Ranking
topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 });
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 });
// only the first expanded display should happen with flair. // only the first expanded display should happen with flair.
displayWithFlair = false; displayWithFlair = false;
@ -224,8 +232,13 @@ namespace osu.Game.Screens.Ranking
topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); {
ScorePosition = { BindTarget = ScorePosition },
Alpha = 0
});
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score) { Alpha = 0 });
break; break;
} }

View File

@ -341,7 +341,7 @@ namespace osu.Game.Screens.Ranking
private IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>() private IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>()
.OrderByDescending(GetLayoutPosition) .OrderByDescending(GetLayoutPosition)
.ThenBy(s => s.Panel.Score.OnlineScoreID); .ThenBy(s => s.Panel.Score.OnlineID);
} }
private class Scroll : OsuScrollContainer private class Scroll : OsuScrollContainer

View File

@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking
return null; return null;
getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset);
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo)));
return getScoreRequest; return getScoreRequest;
} }

View File

@ -33,10 +33,8 @@ namespace osu.Game.Storyboards
foreach (var l in loops) foreach (var l in loops)
{ {
if (!(l.EarliestDisplayedTime is double lEarliest)) if (l.EarliestDisplayedTime is double loopEarliestDisplayTime)
continue; earliestStartTime = Math.Min(earliestStartTime, l.LoopStartTime + loopEarliestDisplayTime);
earliestStartTime = Math.Min(earliestStartTime, lEarliest);
} }
if (earliestStartTime < double.MaxValue) if (earliestStartTime < double.MaxValue)

View File

@ -1,66 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests
{
public class TestScoreInfo : ScoreInfo
{
public TestScoreInfo(RulesetInfo ruleset, bool excessMods = false)
{
User = new APIUser
{
Id = 2,
Username = "peppy",
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
};
BeatmapInfo = new TestBeatmap(ruleset).BeatmapInfo;
Ruleset = ruleset;
RulesetID = ruleset.ID ?? 0;
Mods = excessMods
? ruleset.CreateInstance().CreateAllMods().ToArray()
: new Mod[] { new TestModHardRock(), new TestModDoubleTime() };
TotalScore = 2845370;
Accuracy = 0.95;
MaxCombo = 999;
Rank = ScoreRank.S;
Date = DateTimeOffset.Now;
Statistics[HitResult.Miss] = 1;
Statistics[HitResult.Meh] = 50;
Statistics[HitResult.Ok] = 100;
Statistics[HitResult.Good] = 200;
Statistics[HitResult.Great] = 300;
Statistics[HitResult.Perfect] = 320;
Statistics[HitResult.SmallTickHit] = 50;
Statistics[HitResult.SmallTickMiss] = 25;
Statistics[HitResult.LargeTickHit] = 100;
Statistics[HitResult.LargeTickMiss] = 50;
Statistics[HitResult.SmallBonus] = 10;
Statistics[HitResult.SmallBonus] = 50;
Position = 1;
}
private class TestModHardRock : ModHardRock
{
public override double ScoreMultiplier => 1;
}
private class TestModDoubleTime : ModDoubleTime
{
public override double ScoreMultiplier => 1;
}
}
}

View File

@ -376,7 +376,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId);
protected override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) public override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
{ {
IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist)
.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet

View File

@ -20,7 +20,7 @@
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="AutoMapper" Version="10.1.1" /> <PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="DiffPlex" Version="1.7.0" /> <PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.38" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.39" />
<PackageReference Include="Humanizer" Version="2.13.14" /> <PackageReference Include="Humanizer" Version="2.13.14" />
<PackageReference Include="MessagePack" Version="2.3.85" /> <PackageReference Include="MessagePack" Version="2.3.85" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.11" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.11" />
@ -31,15 +31,15 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.LocalisationAnalyser" Version="2021.725.0"> <PackageReference Include="ppy.LocalisationAnalyser" Version="2021.1210.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.7.1" /> <PackageReference Include="Realm" Version="10.7.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1207.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1210.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" />
<PackageReference Include="Sentry" Version="3.11.1" /> <PackageReference Include="Sentry" Version="3.12.1" />
<PackageReference Include="SharpCompress" Version="0.30.0" /> <PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="TagLibSharp" Version="2.2.0" /> <PackageReference Include="TagLibSharp" Version="2.2.0" />

View File

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