Merge pull request #3786 from smoogipoo/local-score-display

Display local scores in the leaderboard
This commit is contained in:
Dean Herbert 2018-11-30 19:47:25 +09:00 committed by GitHub
commit 397814698e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 118 additions and 75 deletions

View File

@ -9,7 +9,6 @@
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Catch.Replays
{
@ -20,7 +19,7 @@ internal class CatchAutoGenerator : AutoGenerator<CatchHitObject>
public CatchAutoGenerator(Beatmap<CatchHitObject> beatmap)
: base(beatmap)
{
Replay = new Replay { User = new User { Username = @"Autoplay" } };
Replay = new Replay();
}
protected Replay Replay;

View File

@ -8,7 +8,6 @@
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Mania.Replays
{
@ -23,7 +22,7 @@ internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject>
public ManiaAutoGenerator(ManiaBeatmap beatmap)
: base(beatmap)
{
Replay = new Replay { User = new User { Username = @"Autoplay" } };
Replay = new Replay();
columnActions = new ManiaAction[Beatmap.TotalColumns];

View File

@ -8,6 +8,7 @@
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Scoring;
using osu.Game.Users;
namespace osu.Game.Rulesets.Osu.Mods
{
@ -17,6 +18,7 @@ public class OsuModAutoplay : ModAutoplay<OsuHitObject>
protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
Replay = new OsuAutoGenerator(beatmap).Generate()
};
}

View File

@ -9,7 +9,6 @@
using osu.Game.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Osu.Replays
{
@ -38,13 +37,7 @@ public abstract class OsuAutoGeneratorBase : AutoGenerator<OsuHitObject>
protected OsuAutoGeneratorBase(Beatmap<OsuHitObject> beatmap)
: base(beatmap)
{
Replay = new Replay
{
User = new User
{
Username = @"Autoplay",
}
};
Replay = new Replay();
// We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps.
FrameDelay = ApplyModsToRate(1000.0 / 60.0);

View File

@ -9,7 +9,6 @@
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Taiko.Replays
{
@ -20,13 +19,7 @@ public class TaikoAutoGenerator : AutoGenerator<TaikoHitObject>
public TaikoAutoGenerator(Beatmap<TaikoHitObject> beatmap)
: base(beatmap)
{
Replay = new Replay
{
User = new User
{
Username = @"Autoplay",
}
};
Replay = new Replay();
}
protected Replay Replay;

View File

@ -102,7 +102,7 @@ public void TestRollbackOnFailure()
int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += (_, __) => fireCount++;
manager.ItemAdded += (_, __, ___) => fireCount++;
manager.ItemRemoved += _ => fireCount++;
var imported = loadOszIntoOsu(osu);

View File

@ -5,6 +5,7 @@
using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
@ -19,13 +20,10 @@ protected override Player CreatePlayer(Ruleset ruleset)
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value);
// We have the replay
var replay = dummyRulesetContainer.Replay;
// Reset the mods
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay));
return new ReplayPlayer(replay);
return new ReplayPlayer(new Score { Replay = dummyRulesetContainer.Replay });
}
}
}

View File

@ -78,7 +78,7 @@ public void Download()
}
}
private void setAdded(BeatmapSetInfo s, bool existing) => Schedule(() =>
private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() =>
{
if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
DownloadState.Value = DownloadStatus.Downloaded;

View File

@ -32,7 +32,7 @@ public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new()
{
public delegate void ItemAddedDelegate(TModel model, bool existing);
public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent);
/// <summary>
/// Set an endpoint for notifications to be posted to.
@ -110,7 +110,7 @@ protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFa
ContextFactory = contextFactory;
ModelStore = modelStore;
ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s, false));
ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent));
ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage);
@ -211,7 +211,7 @@ public TModel Import(ArchiveReader archive)
model.Hash = computeHash(archive);
return Import(model, archive);
return Import(model, false, archive);
}
catch (Exception e)
{
@ -245,8 +245,9 @@ private string computeHash(ArchiveReader reader)
/// Import an item from a <see cref="TModel"/>.
/// </summary>
/// <param name="item">The model to be imported.</param>
/// <param name="silent">Whether the user should be notified fo the import.</param>
/// <param name="archive">An optional archive to use for model population.</param>
public TModel Import(TModel item, ArchiveReader archive = null)
public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null)
{
delayEvents();
@ -266,7 +267,7 @@ public TModel Import(TModel item, ArchiveReader archive = null)
{
Undelete(existing);
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
handleEvent(() => ItemAdded?.Invoke(existing, true));
handleEvent(() => ItemAdded?.Invoke(existing, true, silent));
return existing;
}
@ -276,7 +277,7 @@ public TModel Import(TModel item, ArchiveReader archive = null)
Populate(item, archive);
// import to store
ModelStore.Add(item);
ModelStore.Add(item, silent);
}
catch (Exception e)
{
@ -507,7 +508,7 @@ protected virtual void Populate(TModel model, [CanBeNull] ArchiveReader archive)
/// </summary>
/// <param name="model">The new model proposed for import. Note that <see cref="Populate"/> has not yet been run on this model.</param>
/// <returns>An existing model which matches the criteria to skip importing, else null.</returns>
protected virtual TModel CheckForExisting(TModel model) => ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
protected virtual TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();

View File

@ -16,7 +16,9 @@ namespace osu.Game.Database
public abstract class MutableDatabaseBackedStore<T> : DatabaseBackedStore
where T : class, IHasPrimaryKey, ISoftDelete
{
public event Action<T> ItemAdded;
public delegate void ItemAddedDelegate(T model, bool silent);
public event ItemAddedDelegate ItemAdded;
public event Action<T> ItemRemoved;
protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null)
@ -33,7 +35,8 @@ protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Sto
/// Add a <see cref="T"/> to the database.
/// </summary>
/// <param name="item">The item to add.</param>
public void Add(T item)
/// <param name="silent">Whether the user should be notified of the addition.</param>
public void Add(T item, bool silent)
{
using (var usage = ContextFactory.GetForWrite())
{
@ -41,7 +44,7 @@ public void Add(T item)
context.Attach(item);
}
ItemAdded?.Invoke(item);
ItemAdded?.Invoke(item, silent);
}
/// <summary>
@ -54,7 +57,7 @@ public void Update(T item)
usage.Context.Update(item);
ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item);
ItemAdded?.Invoke(item, true);
}
/// <summary>
@ -89,7 +92,7 @@ public bool Undelete(T item)
item.DeletePending = false;
}
ItemAdded?.Invoke(item);
ItemAdded?.Invoke(item, true);
return true;
}

View File

@ -148,7 +148,7 @@ private void load(FrameworkConfigManager frameworkConfig)
{
this.frameworkConfig = frameworkConfig;
ScoreManager.ItemAdded += (score, _) => Schedule(() => LoadScore(score));
ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent));
if (!Host.IsPrimaryInstance)
{
@ -248,43 +248,69 @@ void setBeatmap()
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
protected void LoadScore(ScoreInfo score)
protected void LoadScore(ScoreInfo score, bool silent)
{
if (silent)
return;
scoreLoad?.Cancel();
var menu = intro.ChildScreen;
if (menu == null)
{
scoreLoad = Schedule(() => LoadScore(score));
scoreLoad = Schedule(() => LoadScore(score, false));
return;
}
if (!menu.IsCurrentScreen)
var databasedScore = ScoreManager.GetScore(score);
var databasedScoreInfo = databasedScore.ScoreInfo;
if (databasedScore.Replay == null)
{
menu.MakeCurrent();
this.Delay(500).Schedule(() => LoadScore(score), out scoreLoad);
Logger.Log("The loaded score has no replay data.", LoggingTarget.Information);
return;
}
var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == score.Beatmap.ID);
var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID);
if (databasedBeatmap == null)
{
Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information);
return;
}
if (!currentScreen.AllowExternalScreenChange)
{
notifications.Post(new SimpleNotification
{
Text = @"Tried to load a score for a beatmap we don't have!",
Icon = FontAwesome.fa_life_saver,
Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}",
Activated = () =>
{
loadScore();
return true;
}
});
return;
}
ruleset.Value = score.Ruleset;
loadScore();
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = score.Mods;
void loadScore()
{
if (!menu.IsCurrentScreen)
{
menu.MakeCurrent();
this.Delay(500).Schedule(loadScore, out scoreLoad);
return;
}
menu.Push(new PlayerLoader(new ReplayPlayer(ScoreManager.GetScore(score).Replay)));
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
currentScreen.Push(new PlayerLoader(new ReplayPlayer(databasedScore)));
}
}
protected override void Dispose(bool isDisposing)

View File

@ -174,7 +174,7 @@ private void attachDownload(DownloadBeatmapSetRequest request)
};
}
private void setAdded(BeatmapSetInfo s, bool existing) => Schedule(() =>
private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() =>
{
if (s.OnlineBeatmapSetID == SetInfo.OnlineBeatmapSetID)
progressBar.FadeOut(500);

View File

@ -75,7 +75,7 @@ public ItemsScrollContainer()
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap)
{
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false));
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false));
beatmaps.ItemAdded += addBeatmapSet;
beatmaps.ItemRemoved += removeBeatmapSet;
@ -83,7 +83,7 @@ private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap)
beatmapBacking.ValueChanged += _ => updateSelectedSet();
}
private void addBeatmapSet(BeatmapSetInfo obj, bool existing) => Schedule(() =>
private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() =>
{
if (existing)
return;

View File

@ -214,7 +214,7 @@ private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index)
beatmapSets.Insert(index, beatmapSetInfo);
}
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing)
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent)
{
if (existing)
return;

View File

@ -72,7 +72,7 @@ private void load(OsuConfigManager config, SkinManager skins)
private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray());
private void itemAdded(SkinInfo s, bool existing)
private void itemAdded(SkinInfo s, bool existing, bool silent)
{
if (existing)
return;

View File

@ -3,13 +3,11 @@
using System.Collections.Generic;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Replays
{
public class Replay
{
public User User;
public List<ReplayFrame> Frames = new List<ReplayFrame>();
}
}

View File

@ -45,7 +45,7 @@ public Score Parse(Stream stream)
currentBeatmap = workingBeatmap.Beatmap;
score.ScoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
score.ScoreInfo.User = score.Replay.User = new User { Username = sr.ReadString() };
score.ScoreInfo.User = new User { Username = sr.ReadString() };
// MD5Hash
sr.ReadString();

View File

@ -57,6 +57,8 @@ protected override ScoreInfo CreateModel(ArchiveReader archive)
public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
public IEnumerable<ScoreInfo> QueryScores(Expression<Func<ScoreInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().Where(query);
public ScoreInfo Query(Expression<Func<ScoreInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
}
}

View File

@ -29,6 +29,8 @@ public class MainMenu : OsuScreen
protected override bool AllowBackButton => buttons.State != ButtonSystemState.Initial;
public override bool AllowExternalScreenChange => true;
private readonly BackgroundScreenDefault background;
private Screen songSelect;

View File

@ -34,6 +34,8 @@ public abstract class OsuScreen : Screen, IKeyBindingHandler<GlobalAction>, IHas
protected virtual bool AllowBackButton => true;
public virtual bool AllowExternalScreenChange => false;
/// <summary>
/// Override to create a BackgroundMode for the current screen.
/// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause.

View File

@ -67,6 +67,9 @@ public class Player : ScreenWithBeatmapBackground, IProvideCursor
/// </summary>
private DecoupleableInterpolatingFramedClock adjustableClock;
[Resolved]
private ScoreManager scoreManager { get; set; }
private PauseContainer pauseContainer;
private RulesetInfo ruleset;
@ -273,13 +276,10 @@ private void onCompletion()
{
if (!IsCurrentScreen) return;
var score = new ScoreInfo
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = ruleset
};
ScoreProcessor.PopulateScore(score);
score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value;
var score = CreateScore();
if (RulesetContainer.Replay == null)
scoreManager.Import(score, true);
Push(new Results(score));
onCompletionEvent = null;
@ -287,6 +287,20 @@ private void onCompletion()
}
}
protected virtual ScoreInfo CreateScore()
{
var score = new ScoreInfo
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = ruleset,
User = api.LocalUser.Value
};
ScoreProcessor.PopulateScore(score);
return score;
}
private bool onFail()
{
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))

View File

@ -1,23 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Replays;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
{
public class ReplayPlayer : Player
{
public Replay Replay;
private readonly Score score;
public ReplayPlayer(Replay replay)
public ReplayPlayer(Score score)
{
Replay = replay;
this.score = score;
}
protected override void LoadComplete()
{
base.LoadComplete();
RulesetContainer.SetReplay(Replay);
RulesetContainer.SetReplay(score.Replay);
}
protected override ScoreInfo CreateScore() => score.ScoreInfo;
}
}

View File

@ -179,6 +179,9 @@ public Leaderboard()
private APIAccess api;
private BeatmapInfo beatmap;
[Resolved]
private ScoreManager scoreManager { get; set; }
private ScheduledDelegate pendingUpdateScores;
public BeatmapInfo Beatmap
@ -216,6 +219,8 @@ protected override void Dispose(bool isDisposing)
api.OnStateChange -= handleApiStateChange;
}
public void RefreshScores() => updateScores();
private GetScoresRequest getScoresRequest;
private void handleApiStateChange(APIState oldState, APIState newState)
@ -242,8 +247,8 @@ private void updateScores()
{
if (Scope == LeaderboardScope.Local)
{
// TODO: get local scores from wherever here.
PlaceholderState = PlaceholderState.NoScores;
Scores = scoreManager.QueryScores(s => s.Beatmap.ID == Beatmap.ID).ToArray();
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
return;
}

View File

@ -108,6 +108,8 @@ protected override void OnResuming(Screen last)
removeAutoModOnResume = false;
}
BeatmapDetails.Leaderboard.RefreshScores();
Beatmap.Value.Track.Looping = true;
base.OnResuming(last);

View File

@ -40,6 +40,8 @@ public abstract class SongSelect : OsuScreen
protected virtual bool ShowFooter => true;
public override bool AllowExternalScreenChange => true;
/// <summary>
/// Can be null if <see cref="ShowFooter"/> is false.
/// </summary>
@ -503,7 +505,7 @@ private void ensurePlayingSelected(bool preview = false)
}
}
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s);
private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));