1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 12:17:26 +08:00

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": {
"version": "2021.725.0",
"version": "2021.1210.0",
"commands": [
"localisation"
]
}
}
}
}

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<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 Label="Transitive Dependencies">
<!-- 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))
return stableInstallPath;
}
catch { }
catch
{
}
}
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
@ -113,7 +115,7 @@ namespace osu.Desktop
base.LoadComplete();
if (!noVersionOverlay)
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
LoadComponentAsync(new DiscordRichPresence(), Add);

View File

@ -55,73 +55,75 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
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 prevObj = (OsuDifficultyHitObject)Previous[i];
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.
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 (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
{
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;
}
if (islandSize < 7)
islandSize++; // island is still progressing, count size.
}
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
else
{
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
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.
{
// 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)

View File

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

View File

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

View File

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

View File

@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database
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)
{
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);
checkPlayingUserCount(0);
AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
@ -64,8 +62,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
}
[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.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
@ -94,7 +93,7 @@ namespace osu.Game.Tests.Online
[Test]
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));
@ -106,7 +105,6 @@ namespace osu.Game.Tests.Online
{
var score = new SubmittableScore(new ScoreInfo
{
Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
});

View File

@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
AddStep("import altered beatmap", () =>
{
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
{
SelectedItem = { BindTarget = selectedItem }
});
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)

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;
@ -12,8 +13,12 @@ using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
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,
User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineScoreID = 12345,
OnlineID = 12345,
};
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.User.Username, imported.User.Username);
Assert.AreEqual(toImport.Date, imported.Date);
Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID);
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
}
finally
{
@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO
{
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>();
// 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
{

View File

@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO
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]
public void TestNonMatchingByNull()
{

View File

@ -5,8 +5,11 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.Backgrounds;
@ -15,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Background
{
@ -22,8 +26,7 @@ namespace osu.Game.Tests.Visual.Background
public class TestSceneBackgroundScreenDefault : OsuTestScene
{
private BackgroundScreenStack stack;
private BackgroundScreenDefault screen;
private TestBackgroundScreenDefault screen;
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault();
[Resolved]
@ -36,10 +39,95 @@ namespace osu.Game.Tests.Visual.Background
public void SetUpSteps()
{
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());
}
[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]
public void TestBackgroundTypeSwitch()
{
@ -78,36 +166,24 @@ namespace osu.Game.Tests.Visual.Background
[TestCase(BackgroundSource.Skin, typeof(SkinBackground))]
public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType)
{
Graphics.Backgrounds.Background last = null;
setSourceMode(source);
setSupporter(true);
if (source == BackgroundSource.Skin)
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);
// doesn't really need to be checked but might as well.
AddWaitStep("wait a bit", 5);
AddUntilStep("ensure same background instance", () => last == getCurrentBackground());
}
[Test]
public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter)
{
Graphics.Backgrounds.Background last = null;
setSourceMode(BackgroundSource.Skin);
setSupporter(supporter);
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());
// 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) =>
@ -120,6 +196,42 @@ namespace osu.Game.Tests.Visual.Background
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()
{
// 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.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
@ -28,7 +27,6 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Graphics;
@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background
FadeAccessibleResults results = null;
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
{
User = new APIUser { Username = "osu!" },
BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo,
Ruleset = Ruleset.Value,
})));
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());

View File

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

View File

@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("total number of results == 1", () =>
{
var score = new ScoreInfo();
((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
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);
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.
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);

View File

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

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
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]
@ -27,13 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
addItem(() => OtherBeatmap);
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);
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]
@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1);
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]
@ -55,12 +53,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
RunGameplay();
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();
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]
@ -74,15 +72,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly));
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID);
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2);
}
[Test]
public void TestCorrectItemSelectedAfterNewItemAdded()
{
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)

View File

@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
@ -20,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
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]
@ -28,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
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]
@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
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]
@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
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 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]
@ -85,6 +86,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
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", () =>
{
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()));
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)

View File

@ -391,9 +391,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready));
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
pressReadyButton();
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
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", () =>
{
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);
@ -592,19 +594,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
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));
pressReadyButton();
pressReadyButton();
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.
@ -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)
{

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@ -27,7 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
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);

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
@ -27,11 +28,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerQueueList : MultiplayerTestScene
{
private MultiplayerQueueList playlist;
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
[Cached(typeof(UserLookupCache))]
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
private MultiplayerQueueList playlist;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
@ -50,12 +52,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create playlist", () =>
{
selectedItem.Value = null;
Child = playlist = new MultiplayerQueueList
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300),
SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem },
SelectedItem = { BindTarget = selectedItem },
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 }));
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
assertDeleteButtonVisibility(0, false);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
AddStep("select item 0", () => selectedItem.Value = playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().ElementAt(0).Model);
assertDeleteButtonVisibility(0, false);
assertDeleteButtonVisibility(1, true);
// Run through gameplay.
AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready));
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);
AddStep("select item 1", () => selectedItem.Value = playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().ElementAt(1).Model);
assertDeleteButtonVisibility(0, true);
assertDeleteButtonVisibility(1, false);
}

View File

@ -1,13 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -22,20 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
var rulesetInfo = new OsuRuleset().RulesetInfo;
var beatmapInfo = CreateBeatmap(rulesetInfo).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,
};
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
PlaylistItem playlistItem = new PlaylistItem
{

View File

@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -26,20 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
var rulesetInfo = new OsuRuleset().RulesetInfo;
var beatmapInfo = CreateBeatmap(rulesetInfo).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,
};
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
PlaylistItem playlistItem = new PlaylistItem
{

View File

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

View File

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

View File

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

View File

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

View File

@ -15,9 +15,11 @@ using osu.Game.Database;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
@ -112,37 +114,80 @@ namespace osu.Game.Tests.Visual.Playlists
[Test]
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);
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 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;
realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash;
realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID ?? -1;
realOnlineSetId = importedBeatmap.Value.OnlineID ?? -1;
});
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 =>
{
room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value;
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 }
});
});
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)

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -13,6 +14,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Contracted;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
@ -22,13 +24,18 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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]
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)

View File

@ -20,6 +20,7 @@ using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
@ -34,21 +35,21 @@ namespace osu.Game.Tests.Visual.Ranking
{
var author = new APIUser { Username = "mapper_name" };
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = createTestBeatmap(author)
}));
AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author))));
}
[Test]
public void TestExcessMods()
{
var author = new APIUser { Username = "mapper_name" };
AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true)
AddStep("show excess mods score", () =>
{
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"));
}
@ -56,10 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestMapWithUnknownMapper()
{
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = createTestBeatmap(new APIUser())
}));
AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new APIUser()))));
AddAssert("mapped by text not present", () =>
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 beatmap = createTestBeatmap(new APIUser());
showPanel(new TestScoreInfo(ruleset.RulesetInfo)
{
Mods = mods,
BeatmapInfo = beatmap,
Date = default,
});
var score = TestResources.CreateTestScoreInfo(beatmap);
score.Mods = mods;
score.Date = default;
showPanel(score);
});
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.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking
RelativeSizeAxes = Axes.Both,
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.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
@ -72,11 +72,10 @@ namespace osu.Game.Tests.Visual.Ranking
{
TestResultsScreen screen = null;
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
{
Accuracy = accuracy,
Rank = rank
};
var score = TestResources.CreateTestScoreInfo();
score.Accuracy = accuracy;
score.Rank = rank;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score)));
AddUntilStep("wait for loaded", () => screen.IsLoaded);
@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking
{
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);
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);
}
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
{
@ -282,7 +281,7 @@ namespace osu.Game.Tests.Visual.Ranking
for (int i = 0; i < 20; i++)
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i;
score.Hash = $"test{i}";
scores.Add(score);
@ -316,7 +315,7 @@ namespace osu.Game.Tests.Visual.Ranking
for (int i = 0; i < 20; i++)
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i;
scores.Add(score);
}

View File

@ -3,10 +3,10 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Ranking
{
@ -17,7 +17,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -25,7 +27,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -33,7 +37,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -41,7 +47,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -49,7 +57,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -57,7 +67,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -65,7 +77,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -73,7 +87,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -81,7 +97,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
}
@ -89,7 +107,9 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
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);
AddWaitStep("wait for transition", 10);

View File

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

View File

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

View File

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

View File

@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
var score = new ScoreInfo
{
OnlineScoreID = i,
OnlineID = i,
BeatmapInfo = beatmapInfo,
BeatmapInfoID = beatmapInfo.ID,
Accuracy = RNG.NextDouble(),
@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface
});
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]
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
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<ScoreInfo>().HasIndex(b => b.OnlineScoreID).IsUnique();
modelBuilder.Entity<ScoreInfo>().HasIndex(b => b.OnlineID).IsUnique();
}
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>
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)
{
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.
foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
{
// avoid performing redundant queries when the database is empty and needs to be re-filled.
int existingCount = existingBindings.Count(k => k.RulesetName == rulesetName && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key);
IEnumerable<RealmKeyBinding> existing = existingBindings.Where(k =>
k.RulesetName == rulesetName
&& k.Variant == variant
&& k.ActionInt == (int)defaultsForAction.Key);
if (defaultsForAction.Count() <= existingCount)
continue;
int defaultsCount = defaultsForAction.Count();
int existingCount = existing.Count();
// insert any defaults which are missing.
realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
if (defaultsCount > existingCount)
{
KeyCombinationString = k.KeyCombination.ToString(),
ActionInt = (int)k.Action,
RulesetName = rulesetName,
Variant = variant
}));
// insert any defaults which are missing.
realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
{
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
{
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScoreInfo>>
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScore>>
{
private readonly long userId;
private readonly ScoreType type;

View File

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

View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -120,16 +120,21 @@ namespace osu.Game.Online.Chat
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));
}
/// <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).
/// </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
{

View File

@ -111,7 +111,7 @@ namespace osu.Game.Online.Leaderboards
background = new Box
{
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,
},
},

View File

@ -95,8 +95,6 @@ namespace osu.Game.Online.Multiplayer
protected readonly BindableList<int> PlayingUserIds = new BindableList<int>();
public readonly Bindable<PlaylistItem?> CurrentMatchPlayingItem = new Bindable<PlaylistItem?>();
/// <summary>
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
/// </summary>
@ -162,9 +160,6 @@ namespace osu.Game.Online.Multiplayer
var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
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.
Debug.Assert(joinedRoom.Users != null);
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false);
@ -176,7 +171,7 @@ namespace osu.Game.Online.Multiplayer
APIRoom = room;
APIRoom.Playlist.Clear();
APIRoom.Playlist.AddRange(playlistItems);
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
Debug.Assert(LocalUser != null);
addUserToAPIRoom(LocalUser);
@ -219,7 +214,6 @@ namespace osu.Game.Online.Multiplayer
{
APIRoom = null;
Room = null;
CurrentMatchPlayingItem.Value = null;
PlayingUserIds.Clear();
RoomUpdated?.Invoke();
@ -477,28 +471,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null);
Debug.Assert(Room != null);
Scheduler.Add(() =>
{
// 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);
});
});
}
});
Scheduler.Add(() => updateLocalRoomSettings(newSettings));
return Task.CompletedTask;
}
@ -653,12 +626,10 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask;
}
public async Task PlaylistItemAdded(MultiplayerPlaylistItem item)
public Task PlaylistItemAdded(MultiplayerPlaylistItem item)
{
if (Room == null)
return;
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
return Task.CompletedTask;
Scheduler.Add(() =>
{
@ -668,11 +639,13 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null);
Room.Playlist.Add(item);
APIRoom.Playlist.Add(playlistItem);
APIRoom.Playlist.Add(createPlaylistItem(item));
ItemAdded?.Invoke(item);
RoomUpdated?.Invoke();
});
return Task.CompletedTask;
}
public Task PlaylistItemRemoved(long playlistItemId)
@ -697,12 +670,10 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask;
}
public async Task PlaylistItemChanged(MultiplayerPlaylistItem item)
public Task PlaylistItemChanged(MultiplayerPlaylistItem item)
{
if (Room == null)
return;
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
return Task.CompletedTask;
Scheduler.Add(() =>
{
@ -715,15 +686,13 @@ namespace osu.Game.Online.Multiplayer
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
APIRoom.Playlist.RemoveAt(existingIndex);
APIRoom.Playlist.Insert(existingIndex, playlistItem);
// 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;
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
ItemChanged?.Invoke(item);
RoomUpdated?.Invoke();
});
return Task.CompletedTask;
}
/// <summary>
@ -752,12 +721,11 @@ namespace osu.Game.Online.Multiplayer
APIRoom.Password.Value = Room.Settings.Password;
APIRoom.Type.Value = Room.Settings.MatchType;
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);
@ -779,9 +747,6 @@ namespace osu.Game.Online.Multiplayer
playlistItem.RequiredMods.AddRange(item.RequiredMods.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;
}
@ -791,7 +756,7 @@ namespace osu.Game.Online.Multiplayer
/// <param name="beatmapId">The beatmap ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param>
/// <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>
/// 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);
}
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);
}

View File

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

View File

@ -40,6 +40,11 @@ namespace osu.Game.Online.Rooms
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()
{
base.LoadComplete();
@ -71,13 +76,34 @@ namespace osu.Game.Online.Rooms
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
}, 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()
{
if (downloadTracker == null)
return;
// will be repopulated below if still valid.
matchingHash = null;
switch (downloadTracker.State.Value)
{
case DownloadState.NotDownloaded:
@ -93,7 +119,9 @@ namespace osu.Game.Online.Rooms
break;
case DownloadState.LocallyAvailable:
bool hashMatches = checkHashValidity();
matchingHash = findMatchingHash();
bool hashMatches = matchingHash != null;
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;
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.Method = HttpMethod.Put;
req.Timeout = 30000;
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
{

View File

@ -3,6 +3,7 @@
using System;
using osu.Framework.Allocation;
using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Scoring;
@ -35,7 +36,7 @@ namespace osu.Game.Online
var scoreInfo = new ScoreInfo
{
ID = TrackedItem.ID,
OnlineScoreID = TrackedItem.OnlineScoreID
OnlineID = TrackedItem.OnlineID
};
if (Manager.IsAvailableLocally(scoreInfo))
@ -113,7 +114,7 @@ namespace osu.Game.Online
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

View File

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

View File

@ -16,7 +16,7 @@ namespace osu.Game.Online.Solo
{
/// <summary>
/// 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>
[Serializable]
public class SubmittableScore

View File

@ -103,7 +103,7 @@ namespace osu.Game
private Container topMostOverlayContent;
private ScalingContainer screenContainer;
protected ScalingContainer ScreenContainer { get; private set; }
protected Container ScreenOffsetContainer { get; private set; }
@ -179,7 +179,7 @@ namespace osu.Game
}
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)
{
@ -487,8 +487,8 @@ namespace osu.Game
// to ensure all the required data for presenting a replay are present.
ScoreInfo databasedScoreInfo = null;
if (score.OnlineScoreID != null)
databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID);
if (score.OnlineID > 0)
databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID);
databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash);
@ -698,7 +698,7 @@ namespace osu.Game
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
ScreenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
@ -801,7 +801,7 @@ namespace osu.Game
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), 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
{

View File

@ -94,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
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));
}), TaskContinuationOptions.OnlyOnRanToCompletion);
});

View File

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

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
private readonly double weight;
public DrawableProfileWeightedScore(APIScoreInfo score, double weight)
public DrawableProfileWeightedScore(APIScore score, double weight)
: base(score)
{
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
{
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScoreInfo>
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScore>
{
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)
drawableItemIndex = 0;
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
base.OnItemsReceived(items);
}
protected override APIRequest<List<APIScoreInfo>> CreateRequest() =>
protected override APIRequest<List<APIScore>> CreateRequest() =>
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
private int drawableItemIndex;
protected override Drawable CreateDrawableItem(APIScoreInfo model)
protected override Drawable CreateDrawableItem(APIScore model)
{
switch (type)
{

View File

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

View File

@ -80,12 +80,9 @@ namespace osu.Game.Scoring.Legacy
byte[] compressedReplay = sr.ReadByteArray();
if (version >= 20140721)
scoreInfo.OnlineScoreID = sr.ReadInt64();
scoreInfo.OnlineID = sr.ReadInt64();
else if (version >= 20121008)
scoreInfo.OnlineScoreID = sr.ReadInt32();
if (scoreInfo.OnlineScoreID <= 0)
scoreInfo.OnlineScoreID = null;
scoreInfo.OnlineID = sr.ReadInt32();
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.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
using osu.Game.Utils;
namespace osu.Game.Scoring
@ -136,7 +137,14 @@ namespace osu.Game.Scoring
[Column("Beatmap")]
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; }
@ -231,24 +239,18 @@ namespace osu.Game.Scoring
public bool Equals(ScoreInfo other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other)) return true;
if (other == null) return false;
if (ID != 0 && other.ID != 0)
return ID == other.ID;
if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue)
return OnlineScoreID == other.OnlineScoreID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash;
return ReferenceEquals(this, other);
return false;
}
#region Implementation of IHasOnlineID
public long OnlineID => OnlineScoreID ?? -1;
long IHasOnlineID<long>.OnlineID => OnlineID ?? -1;
#endregion
@ -256,6 +258,7 @@ namespace osu.Game.Scoring
IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo;
IRulesetInfo IScoreInfo.Ruleset => Ruleset;
IUser IScoreInfo.User => User;
bool IScoreInfo.HasReplay => Files.Any();
#endregion

View File

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

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Online.API;
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);
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)
=> 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);
user.ValueChanged += _ => Next();
skin.ValueChanged += _ => Next();
mode.ValueChanged += _ => Next();
beatmap.ValueChanged += _ => Next();
introSequence.ValueChanged += _ => Next();
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Next();
user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired);
currentDisplay = RNG.Next(0, background_count);
Next();
// helper function required for AddOnce usage.
void loadNextIfRequired() => Next();
}
private ScheduledDelegate nextTask;
@ -67,7 +70,7 @@ namespace osu.Game.Screens.Backgrounds
/// Request loading the next background.
/// </summary>
/// <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();

View File

@ -1,6 +1,7 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -9,6 +10,7 @@ using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu;
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 override void LogoArriving(OsuLogo logo, bool resuming)

View File

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

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -24,6 +25,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
@ -90,6 +92,10 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved]
private UserLookupCache userLookupCache { get; set; }
[CanBeNull]
[Resolved(CanBeNull = true)]
private MultiplayerClient multiplayerClient { get; set; }
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; }
@ -157,7 +163,15 @@ namespace osu.Game.Screens.OnlinePlay
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);
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
{
[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;
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected OnlinePlayScreen ParentScreen { get; private set; }
[Cached]
private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; }
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
protected IBindable<BeatmapAvailability> BeatmapAvailability => beatmapAvailabilityTracker.Availability;
@ -90,11 +90,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
Padding = new MarginPadding { Top = Header.HEIGHT };
beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = SelectedItem }
};
RoomId.BindTo(room.RoomID);
}
@ -247,10 +242,10 @@ namespace osu.Game.Screens.OnlinePlay.Match
}, true);
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
beatmapManager.ItemUpdated += beatmapUpdated;
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
}
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()
{
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>
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
{
}

View File

@ -63,6 +63,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready");
}
protected override void LoadComplete()
{
base.LoadComplete();
SelectedItem.BindValueChanged(_ => updateState());
}
protected override void OnRoomUpdated()
{
base.OnRoomUpdated();
@ -104,7 +111,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
bool enableButton =
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;
// 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]
private MultiplayerClient client { get; set; }
private readonly PlaylistItem itemToEdit;
private readonly long? itemToEdit;
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="beatmap">An optional initial beatmap 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)
{
this.itemToEdit = itemToEdit;
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
var multiplayerItem = new MultiplayerPlaylistItem
{
ID = itemToEdit?.ID ?? 0,
ID = itemToEdit ?? 0,
BeatmapID = item.BeatmapID,
BeatmapChecksum = item.Beatmap.Value.MD5Hash,
RulesetID = item.RulesetID,

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -67,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.LoadComplete();
SelectedItem.BindTo(client.CurrentMatchPlayingItem);
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
UserMods.BindValueChanged(onUserModsChanged);
@ -147,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
new MultiplayerPlaylist
{
RelativeSizeAxes = Axes.Both,
RequestEdit = OpenSongSelection
RequestEdit = item => OpenSongSelection(item.ID)
}
},
new[]
@ -224,7 +223,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
/// Opens the song selection screen to add or edit an item.
/// </summary>
/// <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())
return;
@ -327,10 +326,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client.LocalUser?.State == MultiplayerUserState.Ready)
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;
}
updateCurrentItem();
addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0;
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(() =>
{
if (this.IsCurrentScreen())
@ -446,6 +484,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (!this.IsCurrentScreen())
return;
if (client.Room == null)
return;
if (!client.IsHost)
{
// todo: should handle this when the request queue is implemented.
@ -454,7 +495,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
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)

View File

@ -87,6 +87,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
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)
{
allScores.AddRange(userScore.ScoresAround.Higher.Scores);
@ -186,12 +193,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Schedule(() =>
{
// 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.
callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID));
callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID));
hideLoadingSpinners(pivot);
}));

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using ManagedBass.Fx;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@ -18,6 +19,7 @@ using osu.Framework.Utils;
using osu.Game.Audio.Effects;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
@ -58,6 +60,12 @@ namespace osu.Game.Screens.Play
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)
{
this.drawableRuleset = drawableRuleset;
@ -136,6 +144,9 @@ namespace osu.Game.Screens.Play
Content.ScaleTo(0.85f, duration, Easing.OutQuart);
Content.RotateTo(1, duration, Easing.OutQuart);
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)

View File

@ -921,6 +921,8 @@ namespace osu.Game.Screens.Play
b.IsBreakTime.BindTo(breakTracker.IsBreakTime);
b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
failAnimationLayer.Background = b;
});
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
// conflicts across various systems (ie. solo and multiplayer).
long? onlineScoreId = score.ScoreInfo.OnlineScoreID;
score.ScoreInfo.OnlineScoreID = null;
long? onlineScoreId = score.ScoreInfo.OnlineID;
score.ScoreInfo.OnlineID = -1;
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).
score.ScoreInfo.OnlineScoreID = onlineScoreId;
score.ScoreInfo.OnlineID = onlineScoreId;
}
/// <summary>

View File

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

View File

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

View File

@ -2,36 +2,42 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Scoring;
namespace osu.Game.Screens.Ranking.Contracted
{
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;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new OsuSpriteText
InternalChild = text = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Y = 6,
Text = score.Position != null ? $"#{score.Position}" : string.Empty,
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 osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@ -78,6 +79,11 @@ namespace osu.Game.Screens.Ranking
public event Action<PanelState> StateChanged;
/// <summary>
/// The position of the score in the rankings.
/// </summary>
public readonly Bindable<int?> ScorePosition = new Bindable<int?>();
/// <summary>
/// An action to be invoked if this <see cref="ScorePanel"/> is clicked while in an expanded state.
/// </summary>
@ -103,6 +109,8 @@ namespace osu.Game.Screens.Ranking
{
Score = score;
displayWithFlair = isNewLocalScore;
ScorePosition.Value = score.Position;
}
[BackgroundDependencyLoader]
@ -211,8 +219,8 @@ namespace osu.Game.Screens.Ranking
topLayerBackground.FadeColour(expanded_top_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));
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0));
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 });
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 });
// only the first expanded display should happen with flair.
displayWithFlair = false;
@ -224,8 +232,13 @@ namespace osu.Game.Screens.Ranking
topLayerBackground.FadeColour(contracted_top_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));
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0));
topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent
{
ScorePosition = { BindTarget = ScorePosition },
Alpha = 0
});
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score) { Alpha = 0 });
break;
}

View File

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

View File

@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking
return null;
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;
}

View File

@ -33,10 +33,8 @@ namespace osu.Game.Storyboards
foreach (var l in loops)
{
if (!(l.EarliestDisplayedTime is double lEarliest))
continue;
earliestStartTime = Math.Min(earliestStartTime, lEarliest);
if (l.EarliestDisplayedTime is double loopEarliestDisplayTime)
earliestStartTime = Math.Min(earliestStartTime, l.LoopStartTime + loopEarliestDisplayTime);
}
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);
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)
.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet

View File

@ -20,7 +20,7 @@
<ItemGroup Label="Package References">
<PackageReference Include="AutoMapper" Version="10.1.1" />
<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="MessagePack" Version="2.3.85" />
<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.NETCore.Targets" Version="3.1.0" />
<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>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<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="Sentry" Version="3.11.1" />
<PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="Sentry" Version="3.12.1" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="TagLibSharp" Version="2.2.0" />

View File

@ -60,7 +60,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<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" />
</ItemGroup>
<!-- 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.Core" Version="2.2.6" />
<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="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />