1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 06:03:08 +08:00

Merge pull request #1599 from naoey/leaderboard-scopes

Make leaderboard tabs and active ruleset filter scores
This commit is contained in:
Dean Herbert 2017-12-22 01:23:24 +09:00 committed by GitHub
commit be526d68e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 371 additions and 25 deletions

View File

@ -6,14 +6,44 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
using osu.Framework.Allocation;
using OpenTK;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual
{
[Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase
{
private readonly Leaderboard leaderboard;
private RulesetStore rulesets;
private readonly FailableLeaderboard leaderboard;
public TestCaseLeaderboard()
{
Add(leaderboard = new FailableLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = LeaderboardScope.Global,
});
AddStep(@"New Scores", newScores);
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
AddStep(@"Real beatmap", realBeatmap);
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}
private void newScores()
{
@ -204,17 +234,44 @@ namespace osu.Game.Tests.Visual
leaderboard.Scores = scores;
}
public TestCaseLeaderboard()
private void realBeatmap()
{
Add(leaderboard = new Leaderboard
leaderboard.Beatmap = new BeatmapInfo
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
});
StarDifficulty = 1.36,
Version = @"BASIC",
OnlineBeatmapID = 1113057,
Ruleset = rulesets.GetRuleset(0),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 6.5f,
OverallDifficulty = 6.5f,
ApproachRate = 5,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 115000,
CircleCount = 265,
SliderCount = 71,
PlayCount = 47906,
PassCount = 19899,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
};
}
AddStep(@"New Scores", newScores);
newScores();
private class FailableLeaderboard : Leaderboard
{
public void SetRetrievalState(PlaceholderState state)
{
PlaceholderState = state;
}
}
}
}

View File

@ -10,19 +10,28 @@ using osu.Game.Rulesets;
using osu.Game.Users;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.IO.Network;
namespace osu.Game.Online.API.Requests
{
public class GetScoresRequest : APIRequest<GetScoresResponse>
{
private readonly BeatmapInfo beatmap;
private readonly LeaderboardScope scope;
private readonly RulesetInfo ruleset;
public GetScoresRequest(BeatmapInfo beatmap)
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global)
{
if (!beatmap.OnlineBeatmapID.HasValue)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
if (scope == LeaderboardScope.Local)
throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard");
this.beatmap = beatmap;
this.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
Success += onSuccess;
}
@ -33,6 +42,17 @@ namespace osu.Game.Online.API.Requests
score.ApplyBeatmap(beatmap);
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Timeout = 30000;
req.AddParameter(@"type", scope.ToString().ToLowerInvariant());
req.AddParameter(@"mode", ruleset.ShortName);
return req;
}
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores";
}

View File

@ -104,7 +104,7 @@ namespace osu.Game.Overlays
scores.IsLoading = true;
getScoresRequest = new GetScoresRequest(beatmap);
getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset);
getScoresRequest.Success += r =>
{
scores.Scores = r.Scores;

View File

@ -52,6 +52,7 @@ namespace osu.Game.Screens.Select
default:
Details.Hide();
Leaderboard.Scope = (LeaderboardScope)tab - 1;
Leaderboard.Show();
break;
}

View File

@ -18,14 +18,26 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using System.Linq;
using osu.Framework.Configuration;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Framework.Logging;
using osu.Game.Rulesets;
using osu.Framework.Input;
namespace osu.Game.Screens.Select.Leaderboards
{
public class Leaderboard : Container
{
private const double fade_duration = 200;
private readonly ScrollContainer scrollContainer;
private readonly Container placeholderContainer;
private FillFlowContainer<LeaderboardScore> scrollFlow;
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
public Action<Score> ScoreSelected;
private readonly LoadingAnimation loading;
@ -38,15 +50,18 @@ namespace osu.Game.Screens.Select.Leaderboards
set
{
scores = value;
getScoresRequest?.Cancel();
scrollFlow?.FadeOut(200);
scrollFlow?.Expire();
scrollFlow?.FadeOut(fade_duration).Expire();
scrollFlow = null;
if (scores == null)
loading.Hide();
if (scores == null || !scores.Any())
return;
// ensure placeholder is hidden when displaying scores
PlaceholderState = PlaceholderState.Successful;
// schedule because we may not be loaded yet (LoadComponentAsync complains).
Schedule(() =>
{
@ -74,6 +89,55 @@ namespace osu.Game.Screens.Select.Leaderboards
}
}
private LeaderboardScope scope;
public LeaderboardScope Scope
{
get { return scope; }
set
{
if (value == scope) return;
scope = value;
updateScores();
}
}
private PlaceholderState placeholderState;
protected PlaceholderState PlaceholderState
{
get { return placeholderState; }
set
{
if (value == placeholderState) return;
switch (placeholderState = value)
{
case PlaceholderState.NetworkFailure:
replacePlaceholder(new RetrievalFailurePlaceholder
{
OnRetry = updateScores,
});
break;
case PlaceholderState.NoScores:
replacePlaceholder(new MessagePlaceholder(@"No records yet!"));
break;
case PlaceholderState.NotLoggedIn:
replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!"));
break;
case PlaceholderState.NotSupporter:
replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!"));
break;
default:
replacePlaceholder(null);
break;
}
}
}
public Leaderboard()
{
Children = new Drawable[]
@ -83,7 +147,14 @@ namespace osu.Game.Screens.Select.Leaderboards
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
},
loading = new LoadingAnimation()
loading = new LoadingAnimation(),
placeholderContainer = new Container
{
Alpha = 0,
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
@ -91,6 +162,8 @@ namespace osu.Game.Screens.Select.Leaderboards
private BeatmapInfo beatmap;
private OsuGame osuGame;
private ScheduledDelegate pendingBeatmapSwitch;
public BeatmapInfo Beatmap
@ -109,33 +182,115 @@ namespace osu.Game.Screens.Select.Leaderboards
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(APIAccess api)
private void load(APIAccess api, OsuGame osuGame)
{
this.api = api;
this.osuGame = osuGame;
if (osuGame != null)
ruleset.BindTo(osuGame.Ruleset);
ruleset.ValueChanged += r => updateScores();
if (api != null)
api.OnStateChange += handleApiStateChange;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (api != null)
api.OnStateChange -= handleApiStateChange;
}
private GetScoresRequest getScoresRequest;
private void handleApiStateChange(APIState oldState, APIState newState)
{
if (Scope == LeaderboardScope.Local)
// No need to respond to API state change while current scope is local
return;
if (newState == APIState.Online)
updateScores();
}
private void updateScores()
{
if (!IsLoaded) return;
Scores = null;
getScoresRequest?.Cancel();
getScoresRequest = null;
Scores = null;
if (api == null || Beatmap?.OnlineBeatmapID == null) return;
if (Scope == LeaderboardScope.Local)
{
// TODO: get local scores from wherever here.
PlaceholderState = PlaceholderState.NoScores;
return;
}
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
if (Beatmap?.OnlineBeatmapID == null)
{
PlaceholderState = PlaceholderState.NetworkFailure;
return;
}
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
getScoresRequest = new GetScoresRequest(Beatmap);
if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter)
{
loading.Hide();
PlaceholderState = PlaceholderState.NotSupporter;
return;
}
getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope);
getScoresRequest.Success += r =>
{
Scores = r.Scores;
loading.Hide();
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
};
getScoresRequest.Failure += onUpdateFailed;
api.Queue(getScoresRequest);
}
private void onUpdateFailed(Exception e)
{
if (e is OperationCanceledException) return;
PlaceholderState = PlaceholderState.NetworkFailure;
Logger.Error(e, @"Couldn't fetch beatmap scores!");
}
private void replacePlaceholder(Placeholder placeholder)
{
if (placeholder == null)
{
placeholderContainer.FadeOutFromOne(fade_duration, Easing.OutQuint);
placeholderContainer.Clear(true);
return;
}
var existingPlaceholder = placeholderContainer.Children.FirstOrDefault() as Placeholder;
if (placeholder.Equals(existingPlaceholder))
return;
Scores = null;
placeholderContainer.Clear(true);
placeholderContainer.Child = placeholder;
placeholderContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
}
protected override void Update()
{
base.Update();
@ -164,5 +319,118 @@ namespace osu.Game.Screens.Select.Leaderboards
}
}
}
private abstract class Placeholder : FillFlowContainer, IEquatable<Placeholder>
{
public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();
}
private class MessagePlaceholder : Placeholder
{
private readonly string message;
public MessagePlaceholder(string message)
{
Direction = FillDirection.Horizontal;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.fa_exclamation_circle,
Size = new Vector2(26),
Margin = new MarginPadding { Right = 10 },
},
new OsuSpriteText
{
Text = this.message = message,
TextSize = 22,
},
};
}
public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;
}
private class RetrievalFailurePlaceholder : Placeholder
{
public Action OnRetry;
public RetrievalFailurePlaceholder()
{
Direction = FillDirection.Horizontal;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new RetryButton
{
Action = () => OnRetry?.Invoke(),
Margin = new MarginPadding { Right = 10 },
},
new OsuSpriteText
{
Anchor = Anchor.TopLeft,
Text = @"Couldn't retrieve scores!",
TextSize = 22,
},
};
}
private class RetryButton : OsuHoverContainer
{
private readonly SpriteIcon icon;
public Action Action;
public RetryButton()
{
Height = 26;
Width = 26;
Child = new OsuClickableContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => Action?.Invoke(),
Child = icon = new SpriteIcon
{
Icon = FontAwesome.fa_refresh,
Size = new Vector2(26),
Shadow = true,
},
};
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
icon.ScaleTo(0.8f, 4000, Easing.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
icon.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
}
}
}
public enum LeaderboardScope
{
Local,
Country,
Global,
Friend,
}
public enum PlaceholderState
{
Successful,
Retrieving,
NetworkFailure,
NoScores,
NotLoggedIn,
NotSupporter,
}
}

View File

@ -275,7 +275,7 @@ namespace osu.Game.Screens.Select
if (beatmap == Beatmap.Value.BeatmapInfo)
performLoad();
else
selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 100);
selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 200);
}
}

View File

@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual
if (!api.IsLoggedIn)
return;
lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo);
lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset);
lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
api.Queue(lastRequest);
}