mirror of
https://github.com/ppy/osu.git
synced 2025-01-06 09:03:01 +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.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -47,6 +48,9 @@ namespace osu.Desktop
|
||||
[Resolved]
|
||||
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
@ -117,7 +121,9 @@ namespace osu.Desktop
|
||||
status.BindValueChanged(_ => schedulePresenceUpdate());
|
||||
activity.BindValueChanged(_ => schedulePresenceUpdate());
|
||||
privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
|
||||
|
||||
multiplayerClient.RoomUpdated += onRoomUpdated;
|
||||
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||
}
|
||||
|
||||
private void onReady(object _, ReadyMessage __)
|
||||
@ -133,6 +139,8 @@ namespace osu.Desktop
|
||||
|
||||
private void onRoomUpdated() => schedulePresenceUpdate();
|
||||
|
||||
private void onStatisticsUpdated(UserStatisticsUpdate _) => schedulePresenceUpdate();
|
||||
|
||||
private ScheduledDelegate? presenceUpdateDelegate;
|
||||
|
||||
private void schedulePresenceUpdate()
|
||||
@ -229,10 +237,8 @@ namespace osu.Desktop
|
||||
presence.Assets.LargeImageText = string.Empty;
|
||||
else
|
||||
{
|
||||
if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics? statistics))
|
||||
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);
|
||||
var statistics = statisticsProvider.GetStatisticsFor(ruleset.Value);
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics?.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
||||
}
|
||||
|
||||
// small image
|
||||
@ -346,6 +352,9 @@ namespace osu.Desktop
|
||||
if (multiplayerClient.IsNotNull())
|
||||
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
||||
|
||||
if (statisticsProvider.IsNotNull())
|
||||
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||
|
||||
client.Dispose();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
@ -64,6 +65,10 @@ namespace osu.Game.Tests
|
||||
// Beatmap must be imported before the collection manager is loaded.
|
||||
if (withBeatmap)
|
||||
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.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Login;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Tests.Visual.Online;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK.Input;
|
||||
@ -31,6 +33,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[Cached(typeof(LocalUserStatisticsProvider))]
|
||||
private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -170,6 +175,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("enter code", () => loginOverlay.ChildrenOfType<OsuTextBox>().First().Text = "88800088");
|
||||
assertAPIState(APIState.Online);
|
||||
|
||||
AddStep("feed statistics", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||
AddStep("click on flag", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<UpdateableFlag>().First());
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Gain", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Loss", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Tiny increase in PP", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("No change 1", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Was null", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Became null", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -11,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
@ -24,17 +23,20 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[TestFixture]
|
||||
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 UserGridPanel boundPanel1;
|
||||
private TestUserListPanel boundPanel2;
|
||||
private UserGridPanel boundPanel1 = null!;
|
||||
private TestUserListPanel boundPanel2 = null!;
|
||||
|
||||
[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]
|
||||
private IRulesetStore rulesetStore { get; set; }
|
||||
private IRulesetStore rulesetStore { get; set; } = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
@ -42,7 +44,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
activity.Value = null;
|
||||
status.Value = null;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
Remove(statisticsProvider, false);
|
||||
Clear();
|
||||
Add(statisticsProvider);
|
||||
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -108,7 +114,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||
}) { Width = 300 }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
boundPanel1.Status.BindTo(status);
|
||||
boundPanel1.Activity.BindTo(activity);
|
||||
@ -162,24 +168,21 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("update statistics", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics
|
||||
statisticsProvider.UpdateStatistics(new UserStatistics
|
||||
{
|
||||
GlobalRank = RNG.Next(100000),
|
||||
CountryRank = RNG.Next(100000)
|
||||
});
|
||||
}, Ruleset.Value);
|
||||
});
|
||||
AddStep("set statistics to something big", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics
|
||||
statisticsProvider.UpdateStatistics(new UserStatistics
|
||||
{
|
||||
GlobalRank = RNG.Next(1_000_000, 100_000_000),
|
||||
CountryRank = RNG.Next(1_000_000, 100_000_000)
|
||||
});
|
||||
});
|
||||
AddStep("set statistics to empty", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics());
|
||||
}, Ruleset.Value);
|
||||
});
|
||||
AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||
}
|
||||
|
||||
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 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;
|
||||
|
||||
private LocalUserStatisticsProvider statisticsProvider = null!;
|
||||
private UserStatisticsWatcher watcher = null!;
|
||||
|
||||
[Resolved]
|
||||
@ -107,7 +108,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
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;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
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.
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
@ -164,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -191,7 +194,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -212,7 +215,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -241,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 6_000_000);
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
|
||||
@ -259,15 +262,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
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", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
AddAssert("statistics values are correct", () => statisticsProvider.GetStatisticsFor(ruleset)!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
}
|
||||
|
||||
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", () =>
|
||||
{
|
||||
watcher.RegisterForStatisticsUpdateAfter(
|
||||
|
@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
|
||||
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!;
|
||||
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", () =>
|
||||
{
|
||||
score = TestResources.CreateTestScoreInfo();
|
||||
score.OnlineID = 1234;
|
||||
((Bindable<UserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
|
||||
((Bindable<ScoreBasedUserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new ScoreBasedUserStatisticsUpdate(score,
|
||||
new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Score = { Value = score },
|
||||
DisplayedUserStatisticsUpdate =
|
||||
{
|
||||
Value = new UserStatisticsUpdate(score, new UserStatistics
|
||||
Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
|
@ -8,14 +8,14 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
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;
|
||||
@ -28,25 +28,31 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneBeatmapRecommendations : OsuGameTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private IRulesetStore rulesetStore { get; set; }
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("populate ruleset statistics", () =>
|
||||
{
|
||||
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
||||
|
||||
rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
|
||||
((DummyAPIAccess)API).HandleRequest = r =>
|
||||
{
|
||||
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)
|
||||
|
@ -9,9 +9,11 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -21,18 +23,63 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public partial class DifficultyRecommender : Component
|
||||
{
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private readonly LocalUserStatisticsProvider statisticsProvider;
|
||||
|
||||
[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>();
|
||||
|
||||
/// <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]
|
||||
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>
|
||||
@ -64,35 +111,12 @@ namespace osu.Game.Beatmaps
|
||||
return null;
|
||||
}
|
||||
|
||||
private void populateValues()
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (api.LocalUser.Value.RulesetsStatistics == null)
|
||||
return;
|
||||
if (statisticsProvider.IsNotNull())
|
||||
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||
|
||||
foreach (var kvp in api.LocalUser.Value.RulesetsStatistics)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ namespace osu.Game.Online.API
|
||||
public IBindable<APIUser> LocalUser => localUser;
|
||||
public IBindableList<APIRelation> Friends => friends;
|
||||
public IBindable<UserActivity> Activity => activity;
|
||||
public IBindable<UserStatistics> Statistics => statistics;
|
||||
|
||||
public INotificationsClient NotificationsClient { get; }
|
||||
|
||||
@ -74,8 +73,6 @@ namespace osu.Game.Online.API
|
||||
private Bindable<UserStatus?> configStatus { 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));
|
||||
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
@ -604,14 +601,6 @@ namespace osu.Game.Online.API
|
||||
flushQueue();
|
||||
}
|
||||
|
||||
public void UpdateStatistics(UserStatistics newStatistics)
|
||||
{
|
||||
statistics.Value = newStatistics;
|
||||
|
||||
if (IsLoggedIn)
|
||||
localUser.Value.Statistics = newStatistics;
|
||||
}
|
||||
|
||||
public void UpdateLocalFriends()
|
||||
{
|
||||
if (!IsLoggedIn)
|
||||
@ -630,11 +619,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
private static APIUser createGuestUser() => new GuestUser();
|
||||
|
||||
private void setLocalUser(APIUser user) => Scheduler.Add(() =>
|
||||
{
|
||||
localUser.Value = user;
|
||||
statistics.Value = user.Statistics;
|
||||
}, false);
|
||||
private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
|
||||
|
||||
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<UserStatistics?> Statistics { get; } = new Bindable<UserStatistics?>();
|
||||
|
||||
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
|
||||
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
|
||||
|
||||
@ -178,11 +176,6 @@ namespace osu.Game.Online.API
|
||||
private void onSuccessfulLogin()
|
||||
{
|
||||
state.Value = APIState.Online;
|
||||
Statistics.Value = new UserStatistics
|
||||
{
|
||||
GlobalRank = 1,
|
||||
CountryRank = 1
|
||||
};
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
@ -193,14 +186,6 @@ namespace osu.Game.Online.API
|
||||
LocalUser.Value = new GuestUser();
|
||||
}
|
||||
|
||||
public void UpdateStatistics(UserStatistics newStatistics)
|
||||
{
|
||||
Statistics.Value = newStatistics;
|
||||
|
||||
if (IsLoggedIn)
|
||||
LocalUser.Value.Statistics = newStatistics;
|
||||
}
|
||||
|
||||
public void UpdateLocalFriends()
|
||||
{
|
||||
}
|
||||
@ -220,7 +205,6 @@ namespace osu.Game.Online.API
|
||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
|
||||
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
||||
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;
|
||||
|
||||
/// <summary>
|
||||
/// Skip 2FA requirement for next login.
|
||||
|
@ -29,11 +29,6 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
IBindable<UserActivity> Activity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current user's online statistics.
|
||||
/// </summary>
|
||||
IBindable<UserStatistics?> Statistics { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The language supplied by this provider to API requests.
|
||||
/// </summary>
|
||||
@ -129,11 +124,6 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
void Logout();
|
||||
|
||||
/// <summary>
|
||||
/// Sets Statistics bindable.
|
||||
/// </summary>
|
||||
void UpdateStatistics(UserStatistics newStatistics);
|
||||
|
||||
/// <summary>
|
||||
/// Update the friends status of the current user.
|
||||
/// </summary>
|
||||
|
@ -223,8 +223,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
|
||||
/// <summary>
|
||||
/// User statistics for the requested ruleset (in the case of a <see cref="GetUserRequest"/> or <see cref="GetFriendsRequest"/> response).
|
||||
/// Otherwise empty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This returns null when accessed from <see cref="IAPIProvider.LocalUser"/>. Use <see cref="LocalUserStatisticsProvider"/> instead.
|
||||
/// </remarks>
|
||||
[JsonProperty(@"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>
|
||||
/// Contains data about the change in a user's profile statistics after completing a score.
|
||||
/// </summary>
|
||||
public class UserStatisticsUpdate
|
||||
public class ScoreBasedUserStatisticsUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// The score set by the user that triggered the update.
|
||||
@ -27,12 +27,12 @@ namespace osu.Game.Online
|
||||
public UserStatistics After { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UserStatisticsUpdate"/>.
|
||||
/// Creates a new <see cref="ScoreBasedUserStatisticsUpdate"/>.
|
||||
/// </summary>
|
||||
/// <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="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;
|
||||
Before = before;
|
@ -2,18 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Extensions;
|
||||
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.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
@ -22,8 +18,10 @@ namespace osu.Game.Online
|
||||
/// </summary>
|
||||
public partial class UserStatisticsWatcher : Component
|
||||
{
|
||||
public IBindable<UserStatisticsUpdate?> LatestUpdate => latestUpdate;
|
||||
private readonly Bindable<UserStatisticsUpdate?> latestUpdate = new Bindable<UserStatisticsUpdate?>();
|
||||
private readonly LocalUserStatisticsProvider statisticsProvider;
|
||||
|
||||
public IBindable<ScoreBasedUserStatisticsUpdate?> LatestUpdate => latestUpdate;
|
||||
private readonly Bindable<ScoreBasedUserStatisticsUpdate?> latestUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||
|
||||
[Resolved]
|
||||
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 Dictionary<string, UserStatistics>? latestStatistics;
|
||||
public UserStatisticsWatcher(LocalUserStatisticsProvider statisticsProvider)
|
||||
{
|
||||
this.statisticsProvider = statisticsProvider;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true);
|
||||
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)
|
||||
{
|
||||
if (userId != api.LocalUser.Value?.OnlineID)
|
||||
@ -98,30 +69,11 @@ namespace osu.Game.Online
|
||||
if (!watchedScores.Remove(scoreId, out var scoreInfo))
|
||||
return;
|
||||
|
||||
requestStatisticsUpdate(userId, scoreInfo);
|
||||
}
|
||||
|
||||
private void requestStatisticsUpdate(int userId, ScoreInfo scoreInfo)
|
||||
{
|
||||
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;
|
||||
statisticsProvider.RefetchStatistics(scoreInfo.Ruleset, u => Schedule(() =>
|
||||
{
|
||||
if (u.OldStatistics != null)
|
||||
latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scoreInfo, u.OldStatistics, u.NewStatistics);
|
||||
}));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -148,8 +148,7 @@ namespace osu.Game
|
||||
[Resolved]
|
||||
private FrameworkConfigManager frameworkConfig { get; set; }
|
||||
|
||||
[Cached]
|
||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
||||
private DifficultyRecommender difficultyRecommender;
|
||||
|
||||
[Cached]
|
||||
private readonly LegacyImportManager legacyImportManager = new LegacyImportManager();
|
||||
@ -1069,7 +1068,11 @@ namespace osu.Game
|
||||
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
|
||||
{
|
||||
OnHome = delegate
|
||||
@ -1139,7 +1142,6 @@ namespace osu.Game
|
||||
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
|
||||
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
|
||||
|
||||
Add(difficultyRecommender);
|
||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||
Add(new MusicKeyBindingHandler());
|
||||
Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen));
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
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> pp = null!;
|
||||
@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
};
|
||||
|
||||
if (userStatisticsWatcher != null)
|
||||
((IBindable<UserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
|
||||
((IBindable<ScoreBasedUserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
{
|
||||
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 GridContainer content = null!;
|
||||
@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void onUpdateReceived(ValueChangedEvent<UserStatisticsUpdate?> update)
|
||||
private void onUpdateReceived(ValueChangedEvent<ScoreBasedUserStatisticsUpdate?> update)
|
||||
{
|
||||
if (update.NewValue == null)
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
{
|
||||
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;
|
||||
|
||||
@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true);
|
||||
}
|
||||
|
||||
private void onStatisticsUpdate(ValueChangedEvent<UserStatisticsUpdate?> statisticsUpdate)
|
||||
private void onStatisticsUpdate(ValueChangedEvent<ScoreBasedUserStatisticsUpdate?> statisticsUpdate)
|
||||
{
|
||||
var update = statisticsUpdate.NewValue;
|
||||
|
||||
|
@ -18,9 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -4,13 +4,16 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
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.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Users
|
||||
@ -24,13 +27,9 @@ namespace osu.Game.Users
|
||||
private const int padding = 10;
|
||||
private const int main_content_height = 80;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private ProfileValueDisplay globalRankDisplay = null!;
|
||||
private ProfileValueDisplay countryRankDisplay = null!;
|
||||
|
||||
private readonly IBindable<UserStatistics?> statistics = new Bindable<UserStatistics?>();
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
|
||||
public UserRankPanel(APIUser user)
|
||||
: base(user)
|
||||
@ -43,13 +42,37 @@ namespace osu.Game.Users
|
||||
private void load()
|
||||
{
|
||||
BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter;
|
||||
}
|
||||
|
||||
statistics.BindTo(api.Statistics);
|
||||
statistics.BindValueChanged(stats =>
|
||||
{
|
||||
globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-";
|
||||
countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-";
|
||||
}, true);
|
||||
[Resolved]
|
||||
private LocalUserStatisticsProvider? statisticsProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
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()
|
||||
@ -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 void Dispose(bool isDisposing)
|
||||
{
|
||||
if (statisticsProvider.IsNotNull())
|
||||
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user