mirror of
https://github.com/ppy/osu.git
synced 2026-06-08 17:03:58 +08:00
Compare commits
21 Commits
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -30,7 +31,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
WaitForFiltering();
|
||||
AddBeatmaps(1, 3);
|
||||
AddStep("add beatmap", () =>
|
||||
{
|
||||
var beatmap = CreateTestBeatmapSetInfo(3, false);
|
||||
Realm.Write(r => r.Add(beatmap, update: true));
|
||||
BeatmapSets.Add(beatmap.Detach());
|
||||
});
|
||||
WaitForFiltering();
|
||||
AddStep("generate and add test beatmap", () =>
|
||||
{
|
||||
@@ -44,7 +50,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
foreach (var b in baseTestBeatmap.Beatmaps)
|
||||
b.Metadata = metadata;
|
||||
BeatmapSets.Add(baseTestBeatmap);
|
||||
|
||||
Realm.Write(r => r.Add(baseTestBeatmap, update: true));
|
||||
BeatmapSets.Add(baseTestBeatmap.Detach());
|
||||
});
|
||||
WaitForFiltering();
|
||||
|
||||
@@ -269,14 +277,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
updateBeatmap(null, bs =>
|
||||
{
|
||||
string selectedName = bs.Beatmaps[0].DifficultyName;
|
||||
Realm.Write(r => r.Remove(r.Find<BeatmapInfo>(bs.Beatmaps[0].ID)!));
|
||||
bs.Beatmaps.RemoveAt(0);
|
||||
|
||||
var newBeatmap = createBeatmap(bs);
|
||||
newBeatmap.ID = Guid.NewGuid();
|
||||
newBeatmap.DifficultyName = selectedName;
|
||||
newBeatmap.OnlineID = -1;
|
||||
bs.Beatmaps.Add(newBeatmap);
|
||||
|
||||
newBeatmap = createBeatmap(bs);
|
||||
newBeatmap.ID = Guid.NewGuid();
|
||||
newBeatmap.DifficultyName = selectedName;
|
||||
newBeatmap.OnlineID = -1;
|
||||
bs.Beatmaps.Add(newBeatmap);
|
||||
@@ -284,8 +295,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentBeatmap, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => (GetSelectedPanel()?.Item?.Model as GroupedBeatmap)?.Beatmap, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentBeatmap, () => Is.EqualTo(BeatmapSets[1].Beatmaps[2]));
|
||||
AddAssert("visible panel is updateable beatmap", () => (GetSelectedPanel()?.Item?.Model as GroupedBeatmap)?.Beatmap, () => Is.EqualTo(BeatmapSets[1].Beatmaps[2]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -439,7 +450,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
int originalIndex = BeatmapSets.IndexOf(baseTestBeatmap);
|
||||
|
||||
BeatmapSets.ReplaceRange(originalIndex, 1, [updatedSet]);
|
||||
Realm.Write(r => r.Add(updatedSet, update: true));
|
||||
BeatmapSets.ReplaceRange(originalIndex, 1, [updatedSet.Detach()]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@ namespace osu.Game.Online.Matchmaking
|
||||
{
|
||||
public interface IMatchmakingServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves all active matchmaking pools.
|
||||
/// </summary>
|
||||
Task<MatchmakingPool[]> GetMatchmakingPools();
|
||||
|
||||
/// <summary>
|
||||
/// Joins the matchmaking lobby, allowing the local user to receive status updates.
|
||||
/// </summary>
|
||||
@@ -20,7 +25,7 @@ namespace osu.Game.Online.Matchmaking
|
||||
/// <summary>
|
||||
/// Joins the matchmaking queue, allowing the local user to get matched up with others.
|
||||
/// </summary>
|
||||
Task MatchmakingJoinQueue(MatchmakingSettings settings);
|
||||
Task MatchmakingJoinQueue(int poolId);
|
||||
|
||||
/// <summary>
|
||||
/// Leaves the matchmaking queue.
|
||||
|
||||
+14
-6
@@ -9,23 +9,31 @@ namespace osu.Game.Online.Matchmaking
|
||||
{
|
||||
[MessagePackObject]
|
||||
[Serializable]
|
||||
public class MatchmakingSettings : IEquatable<MatchmakingSettings>
|
||||
public class MatchmakingPool : IEquatable<MatchmakingPool>
|
||||
{
|
||||
[Key(0)]
|
||||
public int RulesetId { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
[Key(1)]
|
||||
public int RulesetId { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public int Variant { get; set; }
|
||||
|
||||
public bool Equals(MatchmakingSettings? other)
|
||||
[Key(3)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public bool Equals(MatchmakingPool? other)
|
||||
=> other != null
|
||||
&& Id == other.Id
|
||||
&& RulesetId == other.RulesetId
|
||||
&& Variant == other.Variant;
|
||||
&& Variant == other.Variant
|
||||
&& Name == other.Name;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is MatchmakingSettings other && Equals(other);
|
||||
=> obj is MatchmakingPool other && Equals(other);
|
||||
|
||||
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
|
||||
public override int GetHashCode() => HashCode.Combine(RulesetId, Variant);
|
||||
public override int GetHashCode() => HashCode.Combine(Id, RulesetId, Variant, Name);
|
||||
}
|
||||
}
|
||||
@@ -19,22 +19,25 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class DefaultRankDisplay : CompositeDrawable, ISerialisableDrawable
|
||||
{
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
||||
|
||||
[SettingSource(typeof(DefaultRankDisplayStrings), nameof(DefaultRankDisplayStrings.PlaySamplesOnRankChange))]
|
||||
public BindableBool PlaySamples { get; set; } = new BindableBool(true);
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
private UpdateableRank rankDisplay = null!;
|
||||
|
||||
private SkinnableSound rankDownSample = null!;
|
||||
private SkinnableSound rankUpSample = null!;
|
||||
|
||||
private Bindable<double?> lastSamplePlaybackTime = null!;
|
||||
private Bindable<double?> lastSamplePlayback = null!;
|
||||
private double timeSinceChange;
|
||||
|
||||
private IBindable<ScoreRank> rank = null!;
|
||||
private ScoreRank? displayedRank;
|
||||
|
||||
private const int time_before_commit = 1500;
|
||||
|
||||
public DefaultRankDisplay()
|
||||
{
|
||||
@@ -48,7 +51,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
rankDownSample = new SkinnableSound(new SampleInfo("Gameplay/rank-down")),
|
||||
rankUpSample = new SkinnableSound(new SampleInfo("Gameplay/rank-up")),
|
||||
rankDisplay = new UpdateableRank(ScoreRank.X)
|
||||
rankDisplay = new UpdateableRank
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
@@ -57,31 +60,52 @@ namespace osu.Game.Screens.Play.HUD
|
||||
if (skinEditor != null)
|
||||
PlaySamples.Value = false;
|
||||
|
||||
lastSamplePlaybackTime = statics.GetBindable<double?>(Static.LastRankChangeSamplePlaybackTime);
|
||||
lastSamplePlayback = statics.GetBindable<double?>(Static.LastRankChangeSamplePlaybackTime);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
rank = scoreProcessor.Rank.GetBoundCopy();
|
||||
rank.BindValueChanged(r =>
|
||||
updateRank(scoreProcessor.Rank.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var currentRank = scoreProcessor.Rank.Value;
|
||||
|
||||
if (currentRank == displayedRank)
|
||||
{
|
||||
bool enoughTimeElapsed = !lastSamplePlaybackTime.Value.HasValue || Time.Current - lastSamplePlaybackTime.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME;
|
||||
timeSinceChange = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't play rank-down sfx on quit/retry
|
||||
if (r.NewValue != r.OldValue && r.NewValue > ScoreRank.F && PlaySamples.Value && enoughTimeElapsed)
|
||||
{
|
||||
if (r.NewValue > rankDisplay.Rank)
|
||||
rankUpSample.Play();
|
||||
else
|
||||
rankDownSample.Play();
|
||||
if ((timeSinceChange += Time.Elapsed) >= time_before_commit || scoreProcessor.HasCompleted.Value || currentRank == ScoreRank.F)
|
||||
updateRank(currentRank);
|
||||
}
|
||||
|
||||
lastSamplePlaybackTime.Value = Time.Current;
|
||||
}
|
||||
private void updateRank(ScoreRank rank)
|
||||
{
|
||||
rankDisplay.Rank = rank;
|
||||
|
||||
rankDisplay.Rank = r.NewValue;
|
||||
}, true);
|
||||
// Check sample time separately to ensure two copies of the rank display don't both play samples on a change.
|
||||
bool enoughSampleTimeElapsed = !lastSamplePlayback.Value.HasValue || Time.Current - lastSamplePlayback.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME;
|
||||
|
||||
// Also don't play rank-down sfx on quit/retry/initial update.
|
||||
if (displayedRank != null && rank > ScoreRank.F && PlaySamples.Value && enoughSampleTimeElapsed)
|
||||
{
|
||||
if (rank > displayedRank)
|
||||
rankUpSample.Play();
|
||||
else
|
||||
rankDownSample.Play();
|
||||
|
||||
lastSamplePlayback.Value = Time.Current;
|
||||
}
|
||||
|
||||
displayedRank = rank;
|
||||
timeSinceChange = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,16 +251,19 @@ namespace osu.Game.Screens.SelectV2
|
||||
newSetBeatmaps.FirstOrDefault(b => b.OnlineID > 0 && b.OnlineID == beatmap.OnlineID) ??
|
||||
newSetBeatmaps.FirstOrDefault(b => b.DifficultyName == beatmap.DifficultyName && b.Ruleset.Equals(beatmap.Ruleset));
|
||||
|
||||
// The matching beatmap may have been deleted or invalidated in some way since this event was fired.
|
||||
// Let's make sure we have the most up-to-date realm state.
|
||||
if (matchingNewBeatmap?.ID is Guid matchingID)
|
||||
matchingNewBeatmap = realm.Run(r => r.FindWithRefresh<BeatmapInfo>(matchingID)?.Detach());
|
||||
|
||||
if (matchingNewBeatmap != null)
|
||||
{
|
||||
// TODO: should this exist in song select instead of here?
|
||||
// we need to ensure the global beatmap is also updated alongside changes.
|
||||
if (CurrentSelection is GroupedBeatmap currentBeatmapUnderGrouping)
|
||||
{
|
||||
var candidateSelection = currentBeatmapUnderGrouping with { Beatmap = beatmap };
|
||||
if (CheckModelEquality(candidateSelection, CurrentSelection))
|
||||
RequestSelection(candidateSelection);
|
||||
}
|
||||
if (CurrentBeatmap != null && beatmap.Equals(CurrentBeatmap))
|
||||
// we don't know in which group the matching new beatmap is, but that's fine - we can leave it null for now.
|
||||
// we are about to modify `Items`, which will trigger a re-filter, which will pick a correct group - if one is present - via `HandleFilterCompleted()`.
|
||||
RequestSelection(new GroupedBeatmap(null, matchingNewBeatmap));
|
||||
|
||||
Items.ReplaceRange(previousIndex, 1, [matchingNewBeatmap]);
|
||||
newSetBeatmaps.Remove(matchingNewBeatmap);
|
||||
|
||||
@@ -249,7 +249,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
mapperText.Text = beatmap.Value.Metadata.Author.Username;
|
||||
}
|
||||
|
||||
starRatingDisplay.Current = (Bindable<StarDifficulty>)difficultyCache.GetBindableDifficulty(beatmap.Value.BeatmapInfo, cancellationSource.Token, SongSelect.SELECTION_DEBOUNCE);
|
||||
starRatingDisplay.Current = (Bindable<StarDifficulty>)difficultyCache.GetBindableDifficulty(beatmap.Value.BeatmapInfo, cancellationSource.Token, SongSelect.DIFFICULTY_CALCULATION_DEBOUNCE);
|
||||
|
||||
updateCountStatistics(cancellationSource.Token);
|
||||
updateDifficultyStatistics();
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE);
|
||||
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.DIFFICULTY_CALCULATION_DEBOUNCE);
|
||||
starDifficultyBindable.BindValueChanged(starDifficulty =>
|
||||
{
|
||||
starRatingDisplay.Current.Value = starDifficulty.NewValue;
|
||||
|
||||
@@ -260,7 +260,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE);
|
||||
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.DIFFICULTY_CALCULATION_DEBOUNCE);
|
||||
starDifficultyBindable.BindValueChanged(starDifficulty =>
|
||||
{
|
||||
starRatingDisplay.Current.Value = starDifficulty.NewValue;
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -63,9 +64,20 @@ namespace osu.Game.Screens.SelectV2
|
||||
[Cached(typeof(ISongSelect))]
|
||||
public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, ISongSelect
|
||||
{
|
||||
// this is intentionally slightly higher than key repeat, but low enough to not impede user experience.
|
||||
// this avoids rapid churn loading when iterating the carousel using keyboard.
|
||||
public const int SELECTION_DEBOUNCE = 100;
|
||||
/// <summary>
|
||||
/// A debounce that governs how long after a panel is selected before the rest of song select (and the game at large)
|
||||
/// updates to show that selection.
|
||||
///
|
||||
/// This is intentionally slightly higher than key repeat, but low enough to not impede user experience.
|
||||
/// </summary>
|
||||
public const int SELECTION_DEBOUNCE = 150;
|
||||
|
||||
/// <summary>
|
||||
/// A general "global" debounce to be applied to anything aggressive difficulty calculation at song select,
|
||||
/// either after selection or after a panel comes on screen. Value should be low enough that users don't complain,
|
||||
/// but otherwise as high as possible to reduce overheads.
|
||||
/// </summary>
|
||||
public const int DIFFICULTY_CALCULATION_DEBOUNCE = 150;
|
||||
|
||||
private const float logo_scale = 0.4f;
|
||||
private const double fade_duration = 300;
|
||||
@@ -371,8 +383,63 @@ namespace osu.Game.Screens.SelectV2
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Relative, 0.5f, minSize: 500, maxSize: 700 + widescreenBonusWidth * 300),
|
||||
};
|
||||
|
||||
if (this.IsCurrentScreen())
|
||||
updateDebounce();
|
||||
}
|
||||
|
||||
#region Selection debounce
|
||||
|
||||
private BeatmapInfo? debounceQueuedSelection;
|
||||
private double debounceElapsedTime;
|
||||
|
||||
private void debounceQueueSelection(BeatmapInfo beatmap)
|
||||
{
|
||||
debounceQueuedSelection = beatmap;
|
||||
debounceElapsedTime = 0;
|
||||
}
|
||||
|
||||
private void updateDebounce()
|
||||
{
|
||||
if (debounceQueuedSelection == null) return;
|
||||
|
||||
double elapsed = Clock.ElapsedFrameTime;
|
||||
|
||||
// avoid debounce running early if there's a single long frame.
|
||||
if (!DebugUtils.IsNUnitRunning && Clock.FramesPerSecond > 0)
|
||||
elapsed = Math.Min(1000 / Clock.FramesPerSecond, elapsed);
|
||||
|
||||
debounceElapsedTime += elapsed;
|
||||
|
||||
if (debounceElapsedTime >= SELECTION_DEBOUNCE)
|
||||
performDebounceSelection();
|
||||
}
|
||||
|
||||
private void performDebounceSelection()
|
||||
{
|
||||
if (debounceQueuedSelection == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (Beatmap.Value.BeatmapInfo.Equals(debounceQueuedSelection))
|
||||
return;
|
||||
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(debounceQueuedSelection);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancelDebounceSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelDebounceSelection()
|
||||
{
|
||||
debounceQueuedSelection = null;
|
||||
debounceElapsedTime = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audio
|
||||
|
||||
[Resolved]
|
||||
@@ -436,8 +503,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
#region Selection handling
|
||||
|
||||
private ScheduledDelegate? selectionDebounce;
|
||||
|
||||
/// <summary>
|
||||
/// Finalises selection on the given <see cref="BeatmapInfo"/> and runs the provided action if possible.
|
||||
/// </summary>
|
||||
@@ -453,7 +518,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
// To ensure sanity, cancel any pending selection as we are about to force a selection.
|
||||
// Carousel selection will update to the forced selection via a call of `ensureGlobalBeatmapValid` below, or when song select becomes current again.
|
||||
selectionDebounce?.Cancel();
|
||||
cancelDebounceSelection();
|
||||
|
||||
// Forced refetch is important here to guarantee correct invalidation across all difficulties (editor specific).
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, true);
|
||||
@@ -482,16 +547,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
carousel.CurrentGroupedBeatmap = groupedBeatmap;
|
||||
|
||||
// Debounce consideration is to avoid beatmap churn on key repeat selection.
|
||||
selectionDebounce?.Cancel();
|
||||
selectionDebounce = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
var beatmapInfo = groupedBeatmap.Beatmap;
|
||||
|
||||
if (Beatmap.Value.BeatmapInfo.Equals(beatmapInfo))
|
||||
return;
|
||||
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||
}, SELECTION_DEBOUNCE);
|
||||
debounceQueueSelection(groupedBeatmap.Beatmap);
|
||||
}
|
||||
|
||||
private bool ensureGlobalBeatmapValid()
|
||||
@@ -499,7 +555,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (!this.IsCurrentScreen())
|
||||
return false;
|
||||
|
||||
finaliseBeatmapSelection();
|
||||
performDebounceSelection();
|
||||
|
||||
// While filtering, let's not ever attempt to change selection.
|
||||
// This will be resolved after the filter completes, see `newItemsPresented`.
|
||||
@@ -520,7 +576,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (Beatmap.IsDefault)
|
||||
{
|
||||
validSelection = carousel.NextRandom();
|
||||
finaliseBeatmapSelection();
|
||||
performDebounceSelection();
|
||||
return validSelection;
|
||||
}
|
||||
|
||||
@@ -542,15 +598,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
// If all else fails, use the default beatmap.
|
||||
Beatmap.SetDefault();
|
||||
finaliseBeatmapSelection();
|
||||
performDebounceSelection();
|
||||
|
||||
return validSelection;
|
||||
|
||||
void finaliseBeatmapSelection()
|
||||
{
|
||||
if (selectionDebounce?.State == ScheduledDelegate.RunState.Waiting)
|
||||
selectionDebounce?.RunTask();
|
||||
}
|
||||
}
|
||||
|
||||
private bool checkBeatmapValidForSelection(BeatmapInfo beatmap, FilterCriteria? criteria)
|
||||
@@ -797,7 +847,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
// Interrupting could cause the debounce interval to be reduced.
|
||||
//
|
||||
// `ensureGlobalBeatmapValid` is run post-selection which will resolve any pending incompatibilities (see `Beatmap` bindable callback).
|
||||
if (selectionDebounce?.State != ScheduledDelegate.RunState.Waiting)
|
||||
if (debounceQueuedSelection == null)
|
||||
ensureGlobalBeatmapValid();
|
||||
|
||||
updateWedgeVisibility();
|
||||
@@ -1005,6 +1055,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
return;
|
||||
|
||||
onlineLookupCancellation?.Cancel();
|
||||
onlineLookupCancellation = null;
|
||||
|
||||
if (beatmapSetInfo.OnlineID < 0)
|
||||
{
|
||||
|
||||
@@ -34,10 +34,12 @@ namespace osu.Game.Skinning
|
||||
private SkinnableSound rankDownSample = null!;
|
||||
private SkinnableSound rankUpSample = null!;
|
||||
|
||||
private Bindable<double?> lastSamplePlaybackTime = null!;
|
||||
private Bindable<double?> lastSamplePlayback = null!;
|
||||
private double timeSinceChange;
|
||||
|
||||
private IBindable<ScoreRank> rank = null!;
|
||||
private ScoreRank lastRank;
|
||||
private ScoreRank? displayedRank;
|
||||
|
||||
private const int time_before_commit = 1500;
|
||||
|
||||
public LegacyRankDisplay()
|
||||
{
|
||||
@@ -62,51 +64,72 @@ namespace osu.Game.Skinning
|
||||
if (skinEditor != null)
|
||||
PlaySamples.Value = false;
|
||||
|
||||
lastSamplePlaybackTime = statics.GetBindable<double?>(Static.LastRankChangeSamplePlaybackTime);
|
||||
lastSamplePlayback = statics.GetBindable<double?>(Static.LastRankChangeSamplePlaybackTime);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
rank = scoreProcessor.Rank.GetBoundCopy();
|
||||
rank.BindValueChanged(r =>
|
||||
base.LoadComplete();
|
||||
|
||||
updateRank(scoreProcessor.Rank.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var currentRank = scoreProcessor.Rank.Value;
|
||||
|
||||
if (currentRank == displayedRank)
|
||||
{
|
||||
var texture = source.GetTexture($"ranking-{r.NewValue}-small");
|
||||
timeSinceChange = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
rankDisplay.Texture = texture;
|
||||
if ((timeSinceChange += Time.Elapsed) >= time_before_commit || scoreProcessor.HasCompleted.Value || currentRank == ScoreRank.F)
|
||||
updateRank(currentRank);
|
||||
}
|
||||
|
||||
if (texture != null)
|
||||
private void updateRank(ScoreRank rank)
|
||||
{
|
||||
var texture = source.GetTexture($"ranking-{rank}-small");
|
||||
|
||||
rankDisplay.Texture = texture;
|
||||
|
||||
if (texture != null && displayedRank != null)
|
||||
{
|
||||
var transientRank = new Sprite
|
||||
{
|
||||
var transientRank = new Sprite
|
||||
{
|
||||
Texture = texture,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
};
|
||||
AddInternal(transientRank);
|
||||
transientRank.FadeOutFromOne(500, Easing.Out)
|
||||
.ScaleTo(new Vector2(1.625f), 500, Easing.Out)
|
||||
.Expire();
|
||||
}
|
||||
Texture = texture,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
bool enoughTimeElapsed = !lastSamplePlaybackTime.Value.HasValue || Time.Current - lastSamplePlaybackTime.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME;
|
||||
AddInternal(transientRank);
|
||||
|
||||
// Don't play rank-down sfx on quit/retry
|
||||
if (r.NewValue != r.OldValue && r.NewValue > ScoreRank.F && PlaySamples.Value && enoughTimeElapsed)
|
||||
{
|
||||
if (r.NewValue > lastRank)
|
||||
rankUpSample.Play();
|
||||
else
|
||||
rankDownSample.Play();
|
||||
transientRank.FadeOutFromOne(500, Easing.Out)
|
||||
.ScaleTo(new Vector2(1.625f), 500, Easing.Out)
|
||||
.Expire();
|
||||
}
|
||||
|
||||
lastSamplePlaybackTime.Value = Time.Current;
|
||||
}
|
||||
// Check sample time separately to ensure two copies of the rank display don't both play samples on a change.
|
||||
bool enoughSampleTimeElapsed = !lastSamplePlayback.Value.HasValue || Time.Current - lastSamplePlayback.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME;
|
||||
|
||||
lastRank = r.NewValue;
|
||||
}, true);
|
||||
// Also don't play rank-down sfx on quit/retry/initial update.
|
||||
if (displayedRank != null && rank > ScoreRank.F && PlaySamples.Value && enoughSampleTimeElapsed)
|
||||
{
|
||||
if (rank > displayedRank)
|
||||
rankUpSample.Play();
|
||||
else
|
||||
rankDownSample.Play();
|
||||
|
||||
FinishTransforms(true);
|
||||
lastSamplePlayback.Value = Time.Current;
|
||||
}
|
||||
|
||||
displayedRank = rank;
|
||||
timeSinceChange = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user