mirror of
https://github.com/ppy/osu.git
synced 2025-01-07 22:22:59 +08:00
Merge pull request #27128 from frenzibyte/user-statistics-provider
Introduce `UserStatisticsProvider` component and add support for respecting selected ruleset
This commit is contained in:
commit
573aaf6637
@ -15,6 +15,7 @@ using osu.Framework.Threading;
|
|||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
@ -47,6 +48,9 @@ namespace osu.Desktop
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; } = null!;
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
@ -117,7 +121,9 @@ namespace osu.Desktop
|
|||||||
status.BindValueChanged(_ => schedulePresenceUpdate());
|
status.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
activity.BindValueChanged(_ => schedulePresenceUpdate());
|
activity.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
|
privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
|
|
||||||
multiplayerClient.RoomUpdated += onRoomUpdated;
|
multiplayerClient.RoomUpdated += onRoomUpdated;
|
||||||
|
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReady(object _, ReadyMessage __)
|
private void onReady(object _, ReadyMessage __)
|
||||||
@ -133,6 +139,8 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
private void onRoomUpdated() => schedulePresenceUpdate();
|
private void onRoomUpdated() => schedulePresenceUpdate();
|
||||||
|
|
||||||
|
private void onStatisticsUpdated(UserStatisticsUpdate _) => schedulePresenceUpdate();
|
||||||
|
|
||||||
private ScheduledDelegate? presenceUpdateDelegate;
|
private ScheduledDelegate? presenceUpdateDelegate;
|
||||||
|
|
||||||
private void schedulePresenceUpdate()
|
private void schedulePresenceUpdate()
|
||||||
@ -229,10 +237,8 @@ namespace osu.Desktop
|
|||||||
presence.Assets.LargeImageText = string.Empty;
|
presence.Assets.LargeImageText = string.Empty;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics? statistics))
|
var statistics = statisticsProvider.GetStatisticsFor(ruleset.Value);
|
||||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics?.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
||||||
else
|
|
||||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// small image
|
// small image
|
||||||
@ -346,6 +352,9 @@ namespace osu.Desktop
|
|||||||
if (multiplayerClient.IsNotNull())
|
if (multiplayerClient.IsNotNull())
|
||||||
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
||||||
|
|
||||||
|
if (statisticsProvider.IsNotNull())
|
||||||
|
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||||
|
|
||||||
client.Dispose();
|
client.Dispose();
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -64,6 +65,10 @@ namespace osu.Game.Tests
|
|||||||
// Beatmap must be imported before the collection manager is loaded.
|
// Beatmap must be imported before the collection manager is loaded.
|
||||||
if (withBeatmap)
|
if (withBeatmap)
|
||||||
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
|
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
|
||||||
|
|
||||||
|
// the logic for setting the initial ruleset exists in OsuGame rather than OsuGameBase.
|
||||||
|
// the ruleset bindable is not meant to be nullable, so assign any ruleset in here.
|
||||||
|
Ruleset.Value = RulesetStore.AvailableRulesets.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,13 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Login;
|
using osu.Game.Overlays.Login;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Tests.Visual.Online;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -31,6 +33,9 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager configManager { get; set; } = null!;
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
|
[Cached(typeof(LocalUserStatisticsProvider))]
|
||||||
|
private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -170,6 +175,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("enter code", () => loginOverlay.ChildrenOfType<OsuTextBox>().First().Text = "88800088");
|
AddStep("enter code", () => loginOverlay.ChildrenOfType<OsuTextBox>().First().Text = "88800088");
|
||||||
assertAPIState(APIState.Online);
|
assertAPIState(APIState.Online);
|
||||||
|
|
||||||
|
AddStep("feed statistics", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||||
AddStep("click on flag", () =>
|
AddStep("click on flag", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<UpdateableFlag>().First());
|
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<UpdateableFlag>().First());
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("Gain", () =>
|
AddStep("Gain", () =>
|
||||||
{
|
{
|
||||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||||
new ScoreInfo(),
|
new ScoreInfo(),
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("Loss", () =>
|
AddStep("Loss", () =>
|
||||||
{
|
{
|
||||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||||
new ScoreInfo(),
|
new ScoreInfo(),
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("Tiny increase in PP", () =>
|
AddStep("Tiny increase in PP", () =>
|
||||||
{
|
{
|
||||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||||
new ScoreInfo(),
|
new ScoreInfo(),
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("No change 1", () =>
|
AddStep("No change 1", () =>
|
||||||
{
|
{
|
||||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||||
new ScoreInfo(),
|
new ScoreInfo(),
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("Was null", () =>
|
AddStep("Was null", () =>
|
||||||
{
|
{
|
||||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||||
new ScoreInfo(),
|
new ScoreInfo(),
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddStep("Became null", () =>
|
AddStep("Became null", () =>
|
||||||
{
|
{
|
||||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||||
new ScoreInfo(),
|
new ScoreInfo(),
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Online;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Taiko;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public partial class TestSceneLocalUserStatisticsProvider : OsuTestScene
|
||||||
|
{
|
||||||
|
private LocalUserStatisticsProvider statisticsProvider = null!;
|
||||||
|
|
||||||
|
private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>();
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("clear statistics", () => serverSideStatistics.Clear());
|
||||||
|
|
||||||
|
setUser(1000);
|
||||||
|
|
||||||
|
AddStep("setup provider", () =>
|
||||||
|
{
|
||||||
|
OsuTextFlowContainer text;
|
||||||
|
|
||||||
|
((DummyAPIAccess)API).HandleRequest = r =>
|
||||||
|
{
|
||||||
|
switch (r)
|
||||||
|
{
|
||||||
|
case GetUserRequest userRequest:
|
||||||
|
int userId = int.Parse(userRequest.Lookup);
|
||||||
|
string rulesetName = userRequest.Ruleset!.ShortName;
|
||||||
|
var response = new APIUser
|
||||||
|
{
|
||||||
|
Id = userId,
|
||||||
|
Statistics = tryGetStatistics(userId, rulesetName)
|
||||||
|
};
|
||||||
|
|
||||||
|
userRequest.TriggerSuccess(response);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
Add(statisticsProvider = new LocalUserStatisticsProvider());
|
||||||
|
Add(text = new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
|
||||||
|
statisticsProvider.StatisticsUpdated += update =>
|
||||||
|
{
|
||||||
|
text.Clear();
|
||||||
|
|
||||||
|
foreach (var ruleset in Dependencies.Get<RulesetStore>().AvailableRulesets)
|
||||||
|
{
|
||||||
|
text.AddText(statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics
|
||||||
|
? $"{ruleset.Name} statistics: (total score: {statistics.TotalScore})"
|
||||||
|
: $"{ruleset.Name} statistics: (null)");
|
||||||
|
text.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
text.AddText($"latest update: {update.Ruleset}"
|
||||||
|
+ $" ({(update.OldStatistics?.TotalScore.ToString() ?? "null")} -> {update.NewStatistics.TotalScore})");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInitialStatistics()
|
||||||
|
{
|
||||||
|
AddAssert("osu statistics populated", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000));
|
||||||
|
AddAssert("taiko statistics populated", () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(3_000_000));
|
||||||
|
AddAssert("catch statistics populated", () => statisticsProvider.GetStatisticsFor(new CatchRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(2_000_000));
|
||||||
|
AddAssert("mania statistics populated", () => statisticsProvider.GetStatisticsFor(new ManiaRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(1_000_000));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserChanges()
|
||||||
|
{
|
||||||
|
setUser(1001);
|
||||||
|
|
||||||
|
AddStep("update statistics for user 1000", () =>
|
||||||
|
{
|
||||||
|
serverSideStatistics[(1000, "osu")] = new UserStatistics { TotalScore = 5_000_000 };
|
||||||
|
serverSideStatistics[(1000, "taiko")] = new UserStatistics { TotalScore = 6_000_000 };
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("statistics matches user 1001 in osu",
|
||||||
|
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||||
|
() => Is.EqualTo(4_000_000));
|
||||||
|
|
||||||
|
AddAssert("statistics matches user 1001 in taiko",
|
||||||
|
() => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
|
||||||
|
() => Is.EqualTo(3_000_000));
|
||||||
|
|
||||||
|
setUser(1000, false);
|
||||||
|
|
||||||
|
AddAssert("statistics matches user 1000 in osu",
|
||||||
|
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||||
|
() => Is.EqualTo(5_000_000));
|
||||||
|
|
||||||
|
AddAssert("statistics matches user 1000 in taiko",
|
||||||
|
() => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
|
||||||
|
() => Is.EqualTo(6_000_000));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRefetchStatistics()
|
||||||
|
{
|
||||||
|
UserStatisticsUpdate? update = null;
|
||||||
|
|
||||||
|
setUser(1001);
|
||||||
|
|
||||||
|
AddStep("update statistics server side",
|
||||||
|
() => serverSideStatistics[(1001, "osu")] = new UserStatistics { TotalScore = 9_000_000 });
|
||||||
|
|
||||||
|
AddAssert("statistics match old score",
|
||||||
|
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||||
|
() => Is.EqualTo(4_000_000));
|
||||||
|
|
||||||
|
AddStep("setup event", () =>
|
||||||
|
{
|
||||||
|
update = null;
|
||||||
|
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||||
|
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo));
|
||||||
|
AddUntilStep("statistics update raised",
|
||||||
|
() => update?.NewStatistics.TotalScore,
|
||||||
|
() => Is.EqualTo(9_000_000));
|
||||||
|
AddAssert("statistics match new score",
|
||||||
|
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||||
|
() => Is.EqualTo(9_000_000));
|
||||||
|
|
||||||
|
void onStatisticsUpdated(UserStatisticsUpdate u) => update = u;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserStatistics tryGetStatistics(int userId, string rulesetName)
|
||||||
|
=> serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics();
|
||||||
|
|
||||||
|
private void setUser(int userId, bool generateStatistics = true)
|
||||||
|
{
|
||||||
|
AddStep($"set local user to {userId}", () =>
|
||||||
|
{
|
||||||
|
if (generateStatistics)
|
||||||
|
{
|
||||||
|
serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 };
|
||||||
|
serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 };
|
||||||
|
serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 };
|
||||||
|
serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 };
|
||||||
|
}
|
||||||
|
|
||||||
|
((DummyAPIAccess)API).LocalUser.Value = new APIUser { Id = userId };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -11,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -24,17 +23,20 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneUserPanel : OsuTestScene
|
public partial class TestSceneUserPanel : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly Bindable<UserActivity> activity = new Bindable<UserActivity>();
|
private readonly Bindable<UserActivity?> activity = new Bindable<UserActivity?>();
|
||||||
private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();
|
private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();
|
||||||
|
|
||||||
private UserGridPanel boundPanel1;
|
private UserGridPanel boundPanel1 = null!;
|
||||||
private TestUserListPanel boundPanel2;
|
private TestUserListPanel boundPanel2 = null!;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
[Cached(typeof(LocalUserStatisticsProvider))]
|
||||||
|
private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IRulesetStore rulesetStore { get; set; }
|
private IRulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
@ -42,7 +44,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
activity.Value = null;
|
activity.Value = null;
|
||||||
status.Value = null;
|
status.Value = null;
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Remove(statisticsProvider, false);
|
||||||
|
Clear();
|
||||||
|
Add(statisticsProvider);
|
||||||
|
|
||||||
|
Add(new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -108,7 +114,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||||
}) { Width = 300 }
|
}) { Width = 300 }
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
boundPanel1.Status.BindTo(status);
|
boundPanel1.Status.BindTo(status);
|
||||||
boundPanel1.Activity.BindTo(activity);
|
boundPanel1.Activity.BindTo(activity);
|
||||||
@ -162,24 +168,21 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
AddStep("update statistics", () =>
|
AddStep("update statistics", () =>
|
||||||
{
|
{
|
||||||
API.UpdateStatistics(new UserStatistics
|
statisticsProvider.UpdateStatistics(new UserStatistics
|
||||||
{
|
{
|
||||||
GlobalRank = RNG.Next(100000),
|
GlobalRank = RNG.Next(100000),
|
||||||
CountryRank = RNG.Next(100000)
|
CountryRank = RNG.Next(100000)
|
||||||
});
|
}, Ruleset.Value);
|
||||||
});
|
});
|
||||||
AddStep("set statistics to something big", () =>
|
AddStep("set statistics to something big", () =>
|
||||||
{
|
{
|
||||||
API.UpdateStatistics(new UserStatistics
|
statisticsProvider.UpdateStatistics(new UserStatistics
|
||||||
{
|
{
|
||||||
GlobalRank = RNG.Next(1_000_000, 100_000_000),
|
GlobalRank = RNG.Next(1_000_000, 100_000_000),
|
||||||
CountryRank = RNG.Next(1_000_000, 100_000_000)
|
CountryRank = RNG.Next(1_000_000, 100_000_000)
|
||||||
});
|
}, Ruleset.Value);
|
||||||
});
|
|
||||||
AddStep("set statistics to empty", () =>
|
|
||||||
{
|
|
||||||
API.UpdateStatistics(new UserStatistics());
|
|
||||||
});
|
});
|
||||||
|
AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!);
|
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!);
|
||||||
@ -201,5 +204,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
|
public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public partial class TestUserStatisticsProvider : LocalUserStatisticsProvider
|
||||||
|
{
|
||||||
|
public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action<UserStatisticsUpdate>? callback = null)
|
||||||
|
=> base.UpdateStatistics(newStatistics, ruleset, callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
protected override bool UseOnlineAPI => false;
|
protected override bool UseOnlineAPI => false;
|
||||||
|
|
||||||
|
private LocalUserStatisticsProvider statisticsProvider = null!;
|
||||||
private UserStatisticsWatcher watcher = null!;
|
private UserStatisticsWatcher watcher = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -107,7 +108,9 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
AddStep("create watcher", () =>
|
AddStep("create watcher", () =>
|
||||||
{
|
{
|
||||||
Child = watcher = new UserStatisticsWatcher();
|
Clear();
|
||||||
|
Add(statisticsProvider = new LocalUserStatisticsProvider());
|
||||||
|
Add(watcher = new UserStatisticsWatcher(statisticsProvider));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
@ -146,7 +149,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
// note ordering - in this test processing completes *before* the registration is added.
|
// note ordering - in this test processing completes *before* the registration is added.
|
||||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||||
@ -164,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
long scoreId = getScoreId();
|
long scoreId = getScoreId();
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
@ -191,7 +194,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
long scoreId = getScoreId();
|
long scoreId = getScoreId();
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
@ -212,7 +215,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
long scoreId = getScoreId();
|
long scoreId = getScoreId();
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
@ -241,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
feignScoreProcessing(userId, ruleset, 6_000_000);
|
feignScoreProcessing(userId, ruleset, 6_000_000);
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
|
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
|
||||||
@ -259,15 +262,14 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
UserStatisticsUpdate? update = null;
|
ScoreBasedUserStatisticsUpdate? update = null;
|
||||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
|
|
||||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||||
AddUntilStep("update received", () => update != null);
|
AddUntilStep("update received", () => update != null);
|
||||||
AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
|
AddAssert("statistics values are correct", () => statisticsProvider.GetStatisticsFor(ruleset)!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||||
AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int nextUserId = 2000;
|
private int nextUserId = 2000;
|
||||||
@ -289,7 +291,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<UserStatisticsUpdate> onUpdateReady) =>
|
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<ScoreBasedUserStatisticsUpdate> onUpdateReady) =>
|
||||||
AddStep("register for updates", () =>
|
AddStep("register for updates", () =>
|
||||||
{
|
{
|
||||||
watcher.RegisterForStatisticsUpdateAfter(
|
watcher.RegisterForStatisticsUpdateAfter(
|
||||||
|
@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
});
|
});
|
||||||
|
|
||||||
private void displayUpdate(UserStatistics before, UserStatistics after) =>
|
private void displayUpdate(UserStatistics before, UserStatistics after) =>
|
||||||
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new UserStatisticsUpdate(new ScoreInfo(), before, after));
|
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,12 +91,12 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
UserStatisticsWatcher userStatisticsWatcher = null!;
|
UserStatisticsWatcher userStatisticsWatcher = null!;
|
||||||
ScoreInfo score = null!;
|
ScoreInfo score = null!;
|
||||||
|
|
||||||
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher()));
|
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher(new LocalUserStatisticsProvider())));
|
||||||
AddStep("set user statistics update", () =>
|
AddStep("set user statistics update", () =>
|
||||||
{
|
{
|
||||||
score = TestResources.CreateTestScoreInfo();
|
score = TestResources.CreateTestScoreInfo();
|
||||||
score.OnlineID = 1234;
|
score.OnlineID = 1234;
|
||||||
((Bindable<UserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
|
((Bindable<ScoreBasedUserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new ScoreBasedUserStatisticsUpdate(score,
|
||||||
new UserStatistics
|
new UserStatistics
|
||||||
{
|
{
|
||||||
Level = new UserStatistics.LevelInfo
|
Level = new UserStatistics.LevelInfo
|
||||||
@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Score = { Value = score },
|
Score = { Value = score },
|
||||||
DisplayedUserStatisticsUpdate =
|
DisplayedUserStatisticsUpdate =
|
||||||
{
|
{
|
||||||
Value = new UserStatisticsUpdate(score, new UserStatistics
|
Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
|
||||||
{
|
{
|
||||||
Level = new UserStatistics.LevelInfo
|
Level = new UserStatistics.LevelInfo
|
||||||
{
|
{
|
||||||
|
@ -8,14 +8,14 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
@ -28,25 +28,31 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapRecommendations : OsuGameTestScene
|
public partial class TestSceneBeatmapRecommendations : OsuGameTestScene
|
||||||
{
|
{
|
||||||
[Resolved]
|
|
||||||
private IRulesetStore rulesetStore { get; set; }
|
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("populate ruleset statistics", () =>
|
AddStep("populate ruleset statistics", () =>
|
||||||
{
|
{
|
||||||
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
((DummyAPIAccess)API).HandleRequest = r =>
|
||||||
|
|
||||||
rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
|
|
||||||
{
|
{
|
||||||
rulesetStatistics[rulesetInfo.ShortName] = new UserStatistics
|
switch (r)
|
||||||
{
|
{
|
||||||
PP = getNecessaryPP(rulesetInfo.OnlineID)
|
case GetUserRequest userRequest:
|
||||||
};
|
userRequest.TriggerSuccess(new APIUser
|
||||||
});
|
{
|
||||||
|
Id = 99,
|
||||||
|
Statistics = new UserStatistics
|
||||||
|
{
|
||||||
|
PP = getNecessaryPP(userRequest.Ruleset?.OnlineID ?? 0)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
API.LocalUser.Value.RulesetsStatistics = rulesetStatistics;
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
decimal getNecessaryPP(int? rulesetID)
|
decimal getNecessaryPP(int? rulesetID)
|
||||||
|
@ -9,9 +9,11 @@ using System.Linq;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -21,18 +23,63 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class DifficultyRecommender : Component
|
public partial class DifficultyRecommender : Component
|
||||||
{
|
{
|
||||||
[Resolved]
|
private readonly LocalUserStatisticsProvider statisticsProvider;
|
||||||
private IAPIProvider api { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
private Bindable<RulesetInfo> gameRuleset { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RulesetStore rulesets { get; set; } = null!;
|
||||||
|
|
||||||
private readonly Dictionary<string, double> recommendedDifficultyMapping = new Dictionary<string, double>();
|
private readonly Dictionary<string, double> recommendedDifficultyMapping = new Dictionary<string, double>();
|
||||||
|
|
||||||
|
/// <returns>
|
||||||
|
/// Rulesets ordered descending by their respective recommended difficulties.
|
||||||
|
/// The currently selected ruleset will always be first.
|
||||||
|
/// </returns>
|
||||||
|
private IEnumerable<string> orderedRulesets
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (LoadState < LoadState.Ready || gameRuleset.Value == null)
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
|
||||||
|
return recommendedDifficultyMapping
|
||||||
|
.OrderByDescending(pair => pair.Value)
|
||||||
|
.Select(pair => pair.Key)
|
||||||
|
.Where(r => !r.Equals(gameRuleset.Value.ShortName, StringComparison.Ordinal))
|
||||||
|
.Prepend(gameRuleset.Value.ShortName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DifficultyRecommender(LocalUserStatisticsProvider statisticsProvider)
|
||||||
|
{
|
||||||
|
this.statisticsProvider = statisticsProvider;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
api.LocalUser.BindValueChanged(_ => populateValues(), true);
|
foreach (var ruleset in rulesets.AvailableRulesets)
|
||||||
|
{
|
||||||
|
if (statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics)
|
||||||
|
updateMapping(ruleset, statistics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStatisticsUpdated(UserStatisticsUpdate update) => updateMapping(update.Ruleset, update.NewStatistics);
|
||||||
|
|
||||||
|
private void updateMapping(RulesetInfo ruleset, UserStatistics statistics)
|
||||||
|
{
|
||||||
|
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
||||||
|
recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -64,35 +111,12 @@ namespace osu.Game.Beatmaps
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateValues()
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
if (api.LocalUser.Value.RulesetsStatistics == null)
|
if (statisticsProvider.IsNotNull())
|
||||||
return;
|
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||||
|
|
||||||
foreach (var kvp in api.LocalUser.Value.RulesetsStatistics)
|
base.Dispose(isDisposing);
|
||||||
{
|
|
||||||
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
|
||||||
recommendedDifficultyMapping[kvp.Key] = Math.Pow((double)(kvp.Value.PP ?? 0), 0.4) * 0.195;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <returns>
|
|
||||||
/// Rulesets ordered descending by their respective recommended difficulties.
|
|
||||||
/// The currently selected ruleset will always be first.
|
|
||||||
/// </returns>
|
|
||||||
private IEnumerable<string> orderedRulesets
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (LoadState < LoadState.Ready || ruleset.Value == null)
|
|
||||||
return Enumerable.Empty<string>();
|
|
||||||
|
|
||||||
return recommendedDifficultyMapping
|
|
||||||
.OrderByDescending(pair => pair.Value)
|
|
||||||
.Select(pair => pair.Key)
|
|
||||||
.Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal))
|
|
||||||
.Prepend(ruleset.Value.ShortName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,6 @@ namespace osu.Game.Online.API
|
|||||||
public IBindable<APIUser> LocalUser => localUser;
|
public IBindable<APIUser> LocalUser => localUser;
|
||||||
public IBindableList<APIRelation> Friends => friends;
|
public IBindableList<APIRelation> Friends => friends;
|
||||||
public IBindable<UserActivity> Activity => activity;
|
public IBindable<UserActivity> Activity => activity;
|
||||||
public IBindable<UserStatistics> Statistics => statistics;
|
|
||||||
|
|
||||||
public INotificationsClient NotificationsClient { get; }
|
public INotificationsClient NotificationsClient { get; }
|
||||||
|
|
||||||
@ -74,8 +73,6 @@ namespace osu.Game.Online.API
|
|||||||
private Bindable<UserStatus?> configStatus { get; } = new Bindable<UserStatus?>();
|
private Bindable<UserStatus?> configStatus { get; } = new Bindable<UserStatus?>();
|
||||||
private Bindable<UserStatus?> localUserStatus { get; } = new Bindable<UserStatus?>();
|
private Bindable<UserStatus?> localUserStatus { get; } = new Bindable<UserStatus?>();
|
||||||
|
|
||||||
private Bindable<UserStatistics> statistics { get; } = new Bindable<UserStatistics>();
|
|
||||||
|
|
||||||
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
||||||
|
|
||||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||||
@ -604,14 +601,6 @@ namespace osu.Game.Online.API
|
|||||||
flushQueue();
|
flushQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatistics(UserStatistics newStatistics)
|
|
||||||
{
|
|
||||||
statistics.Value = newStatistics;
|
|
||||||
|
|
||||||
if (IsLoggedIn)
|
|
||||||
localUser.Value.Statistics = newStatistics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateLocalFriends()
|
public void UpdateLocalFriends()
|
||||||
{
|
{
|
||||||
if (!IsLoggedIn)
|
if (!IsLoggedIn)
|
||||||
@ -630,11 +619,7 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private static APIUser createGuestUser() => new GuestUser();
|
private static APIUser createGuestUser() => new GuestUser();
|
||||||
|
|
||||||
private void setLocalUser(APIUser user) => Scheduler.Add(() =>
|
private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
|
||||||
{
|
|
||||||
localUser.Value = user;
|
|
||||||
statistics.Value = user.Statistics;
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
|
@ -30,8 +30,6 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
public Bindable<UserStatistics?> Statistics { get; } = new Bindable<UserStatistics?>();
|
|
||||||
|
|
||||||
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
|
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
|
||||||
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
|
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
|
||||||
|
|
||||||
@ -178,11 +176,6 @@ namespace osu.Game.Online.API
|
|||||||
private void onSuccessfulLogin()
|
private void onSuccessfulLogin()
|
||||||
{
|
{
|
||||||
state.Value = APIState.Online;
|
state.Value = APIState.Online;
|
||||||
Statistics.Value = new UserStatistics
|
|
||||||
{
|
|
||||||
GlobalRank = 1,
|
|
||||||
CountryRank = 1
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Logout()
|
public void Logout()
|
||||||
@ -193,14 +186,6 @@ namespace osu.Game.Online.API
|
|||||||
LocalUser.Value = new GuestUser();
|
LocalUser.Value = new GuestUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatistics(UserStatistics newStatistics)
|
|
||||||
{
|
|
||||||
Statistics.Value = newStatistics;
|
|
||||||
|
|
||||||
if (IsLoggedIn)
|
|
||||||
LocalUser.Value.Statistics = newStatistics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateLocalFriends()
|
public void UpdateLocalFriends()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -220,7 +205,6 @@ namespace osu.Game.Online.API
|
|||||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||||
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
|
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
|
||||||
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
||||||
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skip 2FA requirement for next login.
|
/// Skip 2FA requirement for next login.
|
||||||
|
@ -29,11 +29,6 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IBindable<UserActivity> Activity { get; }
|
IBindable<UserActivity> Activity { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current user's online statistics.
|
|
||||||
/// </summary>
|
|
||||||
IBindable<UserStatistics?> Statistics { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The language supplied by this provider to API requests.
|
/// The language supplied by this provider to API requests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -129,11 +124,6 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Logout();
|
void Logout();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets Statistics bindable.
|
|
||||||
/// </summary>
|
|
||||||
void UpdateStatistics(UserStatistics newStatistics);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the friends status of the current user.
|
/// Update the friends status of the current user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -223,8 +223,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User statistics for the requested ruleset (in the case of a <see cref="GetUserRequest"/> or <see cref="GetFriendsRequest"/> response).
|
/// User statistics for the requested ruleset (in the case of a <see cref="GetUserRequest"/> or <see cref="GetFriendsRequest"/> response).
|
||||||
/// Otherwise empty.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This returns null when accessed from <see cref="IAPIProvider.LocalUser"/>. Use <see cref="LocalUserStatisticsProvider"/> instead.
|
||||||
|
/// </remarks>
|
||||||
[JsonProperty(@"statistics")]
|
[JsonProperty(@"statistics")]
|
||||||
public UserStatistics Statistics
|
public UserStatistics Statistics
|
||||||
{
|
{
|
||||||
|
92
osu.Game/Online/LocalUserStatisticsProvider.cs
Normal file
92
osu.Game/Online/LocalUserStatisticsProvider.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Online
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A component that keeps track of the latest statistics for the local user.
|
||||||
|
/// </summary>
|
||||||
|
public partial class LocalUserStatisticsProvider : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked whenever a change occured to the statistics of any ruleset,
|
||||||
|
/// either due to change in local user (log out and log in) or as a result of score submission.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This does not guarantee the presence of the old statistics,
|
||||||
|
/// specifically in the case of initial population or change in local user.
|
||||||
|
/// </remarks>
|
||||||
|
public event Action<UserStatisticsUpdate>? StatisticsUpdated;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RulesetStore rulesets { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, UserStatistics> statisticsCache = new Dictionary<string, UserStatistics>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the <see cref="UserStatistics"/> currently available for the given ruleset.
|
||||||
|
/// This may return null if the requested statistics has not been fetched before yet.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ruleset">The ruleset to return the corresponding <see cref="UserStatistics"/> for.</param>
|
||||||
|
public UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => statisticsCache.GetValueOrDefault(ruleset.ShortName);
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
api.LocalUser.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
// queuing up requests directly on user change is unsafe, as the API status may have not been updated yet.
|
||||||
|
// schedule a frame to allow the API to be in its correct state sending requests.
|
||||||
|
Schedule(initialiseStatistics);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialiseStatistics()
|
||||||
|
{
|
||||||
|
statisticsCache.Clear();
|
||||||
|
|
||||||
|
if (api.LocalUser.Value == null || api.LocalUser.Value.Id <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset()))
|
||||||
|
RefetchStatistics(ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefetchStatistics(RulesetInfo ruleset, Action<UserStatisticsUpdate>? callback = null)
|
||||||
|
{
|
||||||
|
if (!ruleset.IsLegacyRuleset())
|
||||||
|
throw new InvalidOperationException($@"Retrieving statistics is not supported for ruleset {ruleset.ShortName}");
|
||||||
|
|
||||||
|
var request = new GetUserRequest(api.LocalUser.Value.Id, ruleset);
|
||||||
|
request.Success += u => UpdateStatistics(u.Statistics, ruleset, callback);
|
||||||
|
api.Queue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action<UserStatisticsUpdate>? callback = null)
|
||||||
|
{
|
||||||
|
var oldStatistics = statisticsCache.GetValueOrDefault(ruleset.ShortName);
|
||||||
|
statisticsCache[ruleset.ShortName] = newStatistics;
|
||||||
|
|
||||||
|
var update = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics);
|
||||||
|
callback?.Invoke(update);
|
||||||
|
StatisticsUpdated?.Invoke(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UserStatisticsUpdate(RulesetInfo Ruleset, UserStatistics? OldStatistics, UserStatistics NewStatistics);
|
||||||
|
}
|
@ -9,7 +9,7 @@ namespace osu.Game.Online
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains data about the change in a user's profile statistics after completing a score.
|
/// Contains data about the change in a user's profile statistics after completing a score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserStatisticsUpdate
|
public class ScoreBasedUserStatisticsUpdate
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The score set by the user that triggered the update.
|
/// The score set by the user that triggered the update.
|
||||||
@ -27,12 +27,12 @@ namespace osu.Game.Online
|
|||||||
public UserStatistics After { get; }
|
public UserStatistics After { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="UserStatisticsUpdate"/>.
|
/// Creates a new <see cref="ScoreBasedUserStatisticsUpdate"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The score set by the user that triggered the update.</param>
|
/// <param name="score">The score set by the user that triggered the update.</param>
|
||||||
/// <param name="before">The user's profile statistics prior to the score being set.</param>
|
/// <param name="before">The user's profile statistics prior to the score being set.</param>
|
||||||
/// <param name="after">The user's profile statistics after the score was set.</param>
|
/// <param name="after">The user's profile statistics after the score was set.</param>
|
||||||
public UserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
|
public ScoreBasedUserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
|
||||||
{
|
{
|
||||||
Score = score;
|
Score = score;
|
||||||
Before = before;
|
Before = before;
|
@ -2,18 +2,14 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Online
|
namespace osu.Game.Online
|
||||||
{
|
{
|
||||||
@ -22,8 +18,10 @@ namespace osu.Game.Online
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class UserStatisticsWatcher : Component
|
public partial class UserStatisticsWatcher : Component
|
||||||
{
|
{
|
||||||
public IBindable<UserStatisticsUpdate?> LatestUpdate => latestUpdate;
|
private readonly LocalUserStatisticsProvider statisticsProvider;
|
||||||
private readonly Bindable<UserStatisticsUpdate?> latestUpdate = new Bindable<UserStatisticsUpdate?>();
|
|
||||||
|
public IBindable<ScoreBasedUserStatisticsUpdate?> LatestUpdate => latestUpdate;
|
||||||
|
private readonly Bindable<ScoreBasedUserStatisticsUpdate?> latestUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; } = null!;
|
private SpectatorClient spectatorClient { get; set; } = null!;
|
||||||
@ -33,13 +31,15 @@ namespace osu.Game.Online
|
|||||||
|
|
||||||
private readonly Dictionary<long, ScoreInfo> watchedScores = new Dictionary<long, ScoreInfo>();
|
private readonly Dictionary<long, ScoreInfo> watchedScores = new Dictionary<long, ScoreInfo>();
|
||||||
|
|
||||||
private Dictionary<string, UserStatistics>? latestStatistics;
|
public UserStatisticsWatcher(LocalUserStatisticsProvider statisticsProvider)
|
||||||
|
{
|
||||||
|
this.statisticsProvider = statisticsProvider;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true);
|
|
||||||
spectatorClient.OnUserScoreProcessed += userScoreProcessed;
|
spectatorClient.OnUserScoreProcessed += userScoreProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,35 +61,6 @@ namespace osu.Game.Online
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUserChanged(APIUser? localUser) => Schedule(() =>
|
|
||||||
{
|
|
||||||
latestStatistics = null;
|
|
||||||
|
|
||||||
if (localUser == null || localUser.OnlineID <= 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var userRequest = new GetUsersRequest(new[] { localUser.OnlineID });
|
|
||||||
userRequest.Success += initialiseUserStatistics;
|
|
||||||
api.Queue(userRequest);
|
|
||||||
});
|
|
||||||
|
|
||||||
private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() =>
|
|
||||||
{
|
|
||||||
var user = response.Users.SingleOrDefault();
|
|
||||||
|
|
||||||
// possible if the user is restricted or similar.
|
|
||||||
if (user == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
latestStatistics = new Dictionary<string, UserStatistics>();
|
|
||||||
|
|
||||||
if (user.RulesetsStatistics != null)
|
|
||||||
{
|
|
||||||
foreach (var rulesetStats in user.RulesetsStatistics)
|
|
||||||
latestStatistics.Add(rulesetStats.Key, rulesetStats.Value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
private void userScoreProcessed(int userId, long scoreId)
|
private void userScoreProcessed(int userId, long scoreId)
|
||||||
{
|
{
|
||||||
if (userId != api.LocalUser.Value?.OnlineID)
|
if (userId != api.LocalUser.Value?.OnlineID)
|
||||||
@ -98,30 +69,11 @@ namespace osu.Game.Online
|
|||||||
if (!watchedScores.Remove(scoreId, out var scoreInfo))
|
if (!watchedScores.Remove(scoreId, out var scoreInfo))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
requestStatisticsUpdate(userId, scoreInfo);
|
statisticsProvider.RefetchStatistics(scoreInfo.Ruleset, u => Schedule(() =>
|
||||||
}
|
{
|
||||||
|
if (u.OldStatistics != null)
|
||||||
private void requestStatisticsUpdate(int userId, ScoreInfo scoreInfo)
|
latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scoreInfo, u.OldStatistics, u.NewStatistics);
|
||||||
{
|
}));
|
||||||
var request = new GetUserRequest(userId, scoreInfo.Ruleset);
|
|
||||||
request.Success += user => Schedule(() => dispatchStatisticsUpdate(scoreInfo, user.Statistics));
|
|
||||||
api.Queue(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchStatisticsUpdate(ScoreInfo scoreInfo, UserStatistics updatedStatistics)
|
|
||||||
{
|
|
||||||
string rulesetName = scoreInfo.Ruleset.ShortName;
|
|
||||||
|
|
||||||
api.UpdateStatistics(updatedStatistics);
|
|
||||||
|
|
||||||
if (latestStatistics == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics);
|
|
||||||
latestRulesetStatistics ??= new UserStatistics();
|
|
||||||
|
|
||||||
latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics);
|
|
||||||
latestStatistics[rulesetName] = updatedStatistics;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -148,8 +148,7 @@ namespace osu.Game
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private FrameworkConfigManager frameworkConfig { get; set; }
|
private FrameworkConfigManager frameworkConfig { get; set; }
|
||||||
|
|
||||||
[Cached]
|
private DifficultyRecommender difficultyRecommender;
|
||||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly LegacyImportManager legacyImportManager = new LegacyImportManager();
|
private readonly LegacyImportManager legacyImportManager = new LegacyImportManager();
|
||||||
@ -1069,7 +1068,11 @@ namespace osu.Game
|
|||||||
ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both));
|
ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both));
|
||||||
});
|
});
|
||||||
|
|
||||||
loadComponentSingleFile(new UserStatisticsWatcher(), Add, true);
|
LocalUserStatisticsProvider statisticsProvider;
|
||||||
|
|
||||||
|
loadComponentSingleFile(statisticsProvider = new LocalUserStatisticsProvider(), Add, true);
|
||||||
|
loadComponentSingleFile(difficultyRecommender = new DifficultyRecommender(statisticsProvider), Add, true);
|
||||||
|
loadComponentSingleFile(new UserStatisticsWatcher(statisticsProvider), Add, true);
|
||||||
loadComponentSingleFile(Toolbar = new Toolbar
|
loadComponentSingleFile(Toolbar = new Toolbar
|
||||||
{
|
{
|
||||||
OnHome = delegate
|
OnHome = delegate
|
||||||
@ -1139,7 +1142,6 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
|
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
|
||||||
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
|
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
|
||||||
|
|
||||||
Add(difficultyRecommender);
|
|
||||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||||
Add(new MusicKeyBindingHandler());
|
Add(new MusicKeyBindingHandler());
|
||||||
Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen));
|
Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen));
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable
|
public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable
|
||||||
{
|
{
|
||||||
public Bindable<UserStatisticsUpdate?> LatestUpdate { get; } = new Bindable<UserStatisticsUpdate?>();
|
public Bindable<ScoreBasedUserStatisticsUpdate?> LatestUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||||
|
|
||||||
private Statistic<int> globalRank = null!;
|
private Statistic<int> globalRank = null!;
|
||||||
private Statistic<int> pp = null!;
|
private Statistic<int> pp = null!;
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (userStatisticsWatcher != null)
|
if (userStatisticsWatcher != null)
|
||||||
((IBindable<UserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
|
((IBindable<ScoreBasedUserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
|||||||
{
|
{
|
||||||
private const float transition_duration = 300;
|
private const float transition_duration = 300;
|
||||||
|
|
||||||
public Bindable<UserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<UserStatisticsUpdate?>();
|
public Bindable<ScoreBasedUserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||||
|
|
||||||
private LoadingLayer loadingLayer = null!;
|
private LoadingLayer loadingLayer = null!;
|
||||||
private GridContainer content = null!;
|
private GridContainer content = null!;
|
||||||
@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
|||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUpdateReceived(ValueChangedEvent<UserStatisticsUpdate?> update)
|
private void onUpdateReceived(ValueChangedEvent<ScoreBasedUserStatisticsUpdate?> update)
|
||||||
{
|
{
|
||||||
if (update.NewValue == null)
|
if (update.NewValue == null)
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
|||||||
{
|
{
|
||||||
public abstract partial class RankingChangeRow<T> : CompositeDrawable
|
public abstract partial class RankingChangeRow<T> : CompositeDrawable
|
||||||
{
|
{
|
||||||
public Bindable<UserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<UserStatisticsUpdate?>();
|
public Bindable<ScoreBasedUserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||||
|
|
||||||
private readonly Func<UserStatistics, T> accessor;
|
private readonly Func<UserStatistics, T> accessor;
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
|||||||
StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true);
|
StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStatisticsUpdate(ValueChangedEvent<UserStatisticsUpdate?> statisticsUpdate)
|
private void onStatisticsUpdate(ValueChangedEvent<ScoreBasedUserStatisticsUpdate?> statisticsUpdate)
|
||||||
{
|
{
|
||||||
var update = statisticsUpdate.NewValue;
|
var update = statisticsUpdate.NewValue;
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
{
|
{
|
||||||
private readonly ScoreInfo achievedScore;
|
private readonly ScoreInfo achievedScore;
|
||||||
|
|
||||||
internal readonly Bindable<UserStatisticsUpdate?> DisplayedUserStatisticsUpdate = new Bindable<UserStatisticsUpdate?>();
|
internal readonly Bindable<ScoreBasedUserStatisticsUpdate?> DisplayedUserStatisticsUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||||
|
|
||||||
private IBindable<UserStatisticsUpdate?> latestGlobalStatisticsUpdate = null!;
|
private IBindable<ScoreBasedUserStatisticsUpdate?> latestGlobalStatisticsUpdate = null!;
|
||||||
|
|
||||||
public UserStatisticsPanel(ScoreInfo achievedScore)
|
public UserStatisticsPanel(ScoreInfo achievedScore)
|
||||||
{
|
{
|
||||||
|
@ -4,13 +4,16 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.Profile.Header.Components;
|
using osu.Game.Overlays.Profile.Header.Components;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Users
|
namespace osu.Game.Users
|
||||||
@ -24,13 +27,9 @@ namespace osu.Game.Users
|
|||||||
private const int padding = 10;
|
private const int padding = 10;
|
||||||
private const int main_content_height = 80;
|
private const int main_content_height = 80;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IAPIProvider api { get; set; } = null!;
|
|
||||||
|
|
||||||
private ProfileValueDisplay globalRankDisplay = null!;
|
private ProfileValueDisplay globalRankDisplay = null!;
|
||||||
private ProfileValueDisplay countryRankDisplay = null!;
|
private ProfileValueDisplay countryRankDisplay = null!;
|
||||||
|
private LoadingLayer loadingLayer = null!;
|
||||||
private readonly IBindable<UserStatistics?> statistics = new Bindable<UserStatistics?>();
|
|
||||||
|
|
||||||
public UserRankPanel(APIUser user)
|
public UserRankPanel(APIUser user)
|
||||||
: base(user)
|
: base(user)
|
||||||
@ -43,13 +42,37 @@ namespace osu.Game.Users
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter;
|
BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter;
|
||||||
|
}
|
||||||
|
|
||||||
statistics.BindTo(api.Statistics);
|
[Resolved]
|
||||||
statistics.BindValueChanged(stats =>
|
private LocalUserStatisticsProvider? statisticsProvider { get; set; }
|
||||||
{
|
|
||||||
globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-";
|
[Resolved]
|
||||||
countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-";
|
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||||
}, true);
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (statisticsProvider != null)
|
||||||
|
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||||
|
|
||||||
|
ruleset.BindValueChanged(_ => updateDisplay(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStatisticsUpdated(UserStatisticsUpdate update)
|
||||||
|
{
|
||||||
|
if (update.Ruleset.Equals(ruleset.Value))
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
|
var statistics = statisticsProvider?.GetStatisticsFor(ruleset.Value);
|
||||||
|
|
||||||
|
loadingLayer.State.Value = statistics == null ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
globalRankDisplay.Content = statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-";
|
||||||
|
countryRankDisplay.Content = statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateLayout()
|
protected override Drawable CreateLayout()
|
||||||
@ -176,7 +199,8 @@ namespace osu.Game.Users
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
loadingLayer = new LoadingLayer(true),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -205,5 +229,13 @@ namespace osu.Game.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable? CreateBackground() => null;
|
protected override Drawable? CreateBackground() => null;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (statisticsProvider.IsNotNull())
|
||||||
|
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||||
|
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user