同步更新,优化ezmode逻辑

This commit is contained in:
LA
2025-08-28 23:13:06 +08:00
parent 673b302f1c
commit 493684465e
13 changed files with 418 additions and 39 deletions

View File

@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
[TestCase]
public void TestFilterIntersection()
public void TestKeysFilterIntersection()
{
var criteria = new ManiaFilterCriteria();
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
[TestCase]
public void TestInvalidFilters()
public void TestInvalidKeysFilters()
{
var criteria = new ManiaFilterCriteria();
@@ -183,5 +183,132 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6"));
}
[TestCase]
public void TestLnsEqual()
{
var criteria = new ManiaFilterCriteria();
var filterCriteria = new FilterCriteria
{
Ruleset = new ManiaRuleset().RulesetInfo
};
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
BeatmapInfo beatmapInfo1 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 0,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "100");
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 100
};
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "1");
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0.1");
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 1000,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
}
[TestCase]
public void TestLnsGreaterOrEqual()
{
var criteria = new ManiaFilterCriteria();
var filterCriteria = new FilterCriteria
{
Ruleset = new ManiaRuleset().RulesetInfo
};
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
BeatmapInfo beatmapInfo1 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 0,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "100");
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 100
};
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1");
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0.1");
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 1000,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
}
[TestCase]
public void TestLnsNotManiaRuleset()
{
var criteria = new ManiaFilterCriteria();
var filterCriteria = new FilterCriteria
{
Ruleset = new ManiaRuleset().RulesetInfo
};
criteria.TryParseCustomKeywordCriteria("lns", Operator.LessOrEqual, "100");
BeatmapInfo beatmapInfo = new BeatmapInfo
{
TotalObjectCount = 100,
EndTimeObjectCount = 50
};
Assert.False(criteria.Matches(beatmapInfo, filterCriteria));
}
[TestCase]
public void TestInvalidLnsFilters()
{
var criteria = new ManiaFilterCriteria();
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "some text"));
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1some text"));
}
}
}

View File

@@ -1,6 +1,7 @@
// 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.Bindables;
@@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mania
public class ManiaFilterCriteria : IRulesetFilterCriteria
{
private readonly HashSet<int> includedKeyCounts = Enumerable.Range(1, LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT).ToHashSet();
private FilterCriteria.OptionalRange<float> longNotePercentage;
public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria)
{
@@ -40,7 +42,10 @@ namespace osu.Game.Rulesets.Mania
}
}
return includedKeyCounts.Contains(keyCount);
bool keyCountMatch = includedKeyCounts.Contains(keyCount);
bool longNotePercentageMatch = !longNotePercentage.HasFilter || (!isConvertedBeatmap(beatmapInfo) && longNotePercentage.IsInRange(calculateLongNotePercentage(beatmapInfo)));
return keyCountMatch && longNotePercentageMatch;
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string strValues)
@@ -100,6 +105,10 @@ namespace osu.Game.Rulesets.Mania
return false;
}
}
case "ln":
case "lns":
return FilterQueryParser.TryUpdateCriteriaRange(ref longNotePercentage, op, strValues);
}
return false;
@@ -119,5 +128,18 @@ namespace osu.Game.Rulesets.Mania
return false;
}
private static bool isConvertedBeatmap(BeatmapInfo beatmapInfo)
{
return !beatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
}
private static float calculateLongNotePercentage(BeatmapInfo beatmapInfo)
{
int holdNotes = beatmapInfo.EndTimeObjectCount;
int totalNotes = Math.Max(1, beatmapInfo.TotalObjectCount);
return holdNotes / (float)totalNotes * 100;
}
}
}

View File

@@ -28,6 +28,7 @@ using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -302,9 +303,69 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 10);
}
[Test]
[Explicit("Test relies on timing of arriving frames to exercise assertions which doesn't work headless.")]
public void TestMaximisedUserIsAudioSource()
{
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();
// With no frames, the synchronisation state will be TooFarAhead.
// In this state, all players should be muted.
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, true);
// Send frames for both players.
sendFrames(PLAYER_1_ID, 20);
sendFrames(PLAYER_2_ID, 40);
waitUntilRunning(PLAYER_1_ID);
AddStep("maximise player 1", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
waitUntilPaused(PLAYER_1_ID);
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
AddStep("minimise player 1", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
AddStep("maximise player 2", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
waitUntilPaused(PLAYER_2_ID);
sendFrames(PLAYER_1_ID, 60);
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
AddStep("minimise player 2", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
}
[Test]
[FlakyTest]
public void TestMostInSyncUserIsAudioSource()
public void TestMostInSyncUserIsAudioSourceIfNoneMaximised()
{
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();

View File

@@ -22,12 +22,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
private BeatmapSetInfo baseTestBeatmap = null!;
private const int initial_filter_count = 3;
[SetUpSteps]
public void SetUpSteps()
{
RemoveAllBeatmaps();
CreateCarousel();
WaitForFiltering();
AddBeatmaps(1, 3);
WaitForFiltering();
AddStep("generate and add test beatmap", () =>
{
baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3);
@@ -42,8 +46,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
b.Metadata = metadata;
BeatmapSets.Add(baseTestBeatmap);
});
WaitForFiltering();
AddAssert("filter count correct", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
}
[Test]
@@ -81,12 +86,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddUntilStep("is scrolled to end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
updateBeatmap(b => b.Metadata = new BeatmapMetadata
updateBeatmap(b =>
{
Artist = "updated test",
Title = $"beatmap {RNG.Next().ToString()}"
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
b.Hash = "new hash";
b.Metadata = new BeatmapMetadata
{
Artist = "updated test",
Title = $"beatmap {RNG.Next().ToString()}"
};
});
assertDidFilter();
WaitForFiltering();
AddAssert("scroll is still at end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
@@ -113,8 +124,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddStep("find panel", () => panel = Carousel.ChildrenOfType<PanelBeatmapSet>().Single(p => p.ChildrenOfType<OsuSpriteText>().Any(t => t.Text.ToString() == "beatmap")));
updateBeatmap(b => b.Metadata = metadata);
updateBeatmap(b =>
{
b.Metadata = metadata;
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
b.Hash = "new hash";
});
assertDidFilter();
WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
@@ -123,7 +140,41 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}
[Test]
public void TestSelectionHeld()
public void TestOnlineStatusUpdated()
{
List<Panel> originalDrawables = new List<Panel>();
AddStep("store drawable references", () =>
{
originalDrawables.Clear();
originalDrawables.AddRange(Carousel.ChildrenOfType<Panel>().ToList());
});
updateBeatmap(b => b.Status = BeatmapOnlineStatus.Graveyard);
assertDidFilter();
WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
}
[Test]
public void TestNoUpdateTriggeredOnUserTagsChange()
{
var metadata = new BeatmapMetadata
{
Artist = "updated test",
Title = "new beatmap title",
UserTags = { "hi" }
};
updateBeatmap(b => b.Metadata = metadata);
assertDidNotFilter();
}
[TestCase(false)]
[TestCase(true)]
public void TestSelectionHeld(bool hashChanged)
{
SelectNextSet();
@@ -131,7 +182,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap();
updateBeatmap(b =>
{
if (hashChanged)
b.Hash = "new hash";
});
if (hashChanged)
assertDidFilter();
else
assertDidNotFilter();
WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -148,6 +209,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => b.DifficultyName = "new name");
assertDidFilter();
WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -164,6 +226,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => b.OnlineID = b.OnlineID + 1);
assertDidFilter();
WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -339,6 +402,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("Order didn't change", () => Carousel.PostFilterBeatmaps.Select(b => b.ID), () => Is.EqualTo(originalOrder));
}
private void assertDidFilter() => AddAssert("did filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count + 1));
private void assertDidNotFilter() => AddAssert("did not filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
private void updateBeatmap(Action<BeatmapInfo>? updateBeatmap = null, Action<BeatmapSetInfo>? updateSet = null)
{
AddStep("update beatmap with different reference", () =>

View File

@@ -23,7 +23,9 @@ using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Screens.SelectV2;
using osu.Game.Tests.Resources;
using osuTK.Input;
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods;
using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions;
using FooterButtonRandom = osu.Game.Screens.SelectV2.FooterButtonRandom;
@@ -302,6 +304,28 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
}
/// <summary>
/// Last played and rank achieved may have changed, so we want to make sure filtering runs on resume to song select.
/// </summary>
[Test]
public void TestFilteringRunsAfterReturningFromGameplay()
{
AddStep("import actual beatmap", () => Beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
LoadSongSelect();
AddUntilStep("wait for filtered", () => SongSelect.ChildrenOfType<BeatmapCarousel>().Single().FilterCount, () => Is.EqualTo(1));
AddStep("enter gameplay", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
AddUntilStep("wait for fail", () => ((Player)Stack.CurrentScreen).GameplayState.HasFailed);
AddStep("exit gameplay", () => InputManager.Key(Key.Escape));
AddStep("exit gameplay", () => InputManager.Key(Key.Escape));
AddUntilStep("wait for filtered", () => SongSelect.ChildrenOfType<BeatmapCarousel>().Single().FilterCount, () => Is.EqualTo(2));
}
[Test]
public void TestAutoplayShortcut()
{

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -79,7 +80,7 @@ namespace osu.Game.Graphics.Carousel
/// <summary>
/// The number of times filter operations have been triggered.
/// </summary>
internal int FilterCount { get; private set; }
public int FilterCount { get; private set; }
/// <summary>
/// The number of displayable items currently being tracked (before filtering).
@@ -210,6 +211,12 @@ namespace osu.Game.Graphics.Carousel
return filterTask;
}
/// <summary>
/// Called when <see cref="Items"/> changes in any way.
/// </summary>
/// <returns>Whether a re-filter is required.</returns>
protected virtual bool HandleItemsChanged(NotifyCollectionChangedEventArgs args) => true;
/// <summary>
/// Fired after a filter operation completed.
/// </summary>
@@ -301,7 +308,11 @@ namespace osu.Game.Graphics.Carousel
RelativeSizeAxes = Axes.Both,
};
Items.BindCollectionChanged((_, _) => filterAfterItemsChanged.Invalidate());
Items.BindCollectionChanged((_, args) =>
{
if (HandleItemsChanged(args))
filterAfterItemsChanged.Invalidate();
});
}
[BackgroundDependencyLoader]

View File

@@ -180,7 +180,7 @@ namespace osu.Game.Overlays
notification.Closed += () => notificationClosed(notification);
if (notification is IHasCompletionTarget hasCompletionTarget)
hasCompletionTarget.CompletionTarget = Post;
hasCompletionTarget.CompletionTarget ??= Post;
playDebouncedSample(notification.PopInSampleName);

View File

@@ -59,11 +59,7 @@ namespace osu.Game.Rulesets.Scoring
protected override void RevertResultInternal(JudgementResult result)
{
// TODO: this is rudimentary as to make rewinding failed replays work,
// but it also acts up (sometimes rewinding a replay several times around the fail boundary moves the point of fail forward).
// needs further investigation.
if (result.FailedAtJudgement)
HasFailed = false;
HasFailed = result.FailedAtJudgement;
if (HasFailed)
return;

View File

@@ -178,17 +178,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
base.Update();
if (!isCandidateAudioSource(currentAudioSource?.SpectatorPlayerClock))
{
currentAudioSource = instances.Where(i => isCandidateAudioSource(i.SpectatorPlayerClock)).MinBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime));
checkAudioSource();
}
// Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio.
if (currentAudioSource != null)
bindAudioAdjustments(currentAudioSource);
private void checkAudioSource()
{
// always use the maximised player instance as the current audio source if there is one
if (grid.MaximisedCell?.Content is PlayerArea maximisedPlayer && maximisedPlayer == currentAudioSource)
return;
foreach (var instance in instances)
instance.Mute = instance != currentAudioSource;
}
// if there is no maximised player instance and the previous audio source is still good to use, keep using it
if (grid.MaximisedCell == null && isCandidateAudioSource(currentAudioSource?.SpectatorPlayerClock))
return;
// at this point we're in one of the following scenarios:
// - the maximised player instance is not the current audio source => we want to switch to the maximised player instance
// - there is no maximised player instance, and the previous audio source is stopped => find another running audio source
currentAudioSource = grid.MaximisedCell?.Content as PlayerArea
?? instances.Where(i => isCandidateAudioSource(i.SpectatorPlayerClock)).MinBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime));
// Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio.
if (currentAudioSource != null)
bindAudioAdjustments(currentAudioSource);
foreach (var instance in instances)
instance.Mute = instance != currentAudioSource;
}
private void bindAudioAdjustments(PlayerArea first)

View File

@@ -31,6 +31,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary>
public Facade MaximisedFacade { get; }
/// <summary>
/// The currently-maximised cell.
/// </summary>
public Cell? MaximisedCell { get; private set; }
private readonly Container paddingContainer;
private readonly FillFlowContainer<Facade> facadeContainer;
private readonly Container<Cell> cellContainer;
@@ -99,7 +104,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private void toggleMaximisationState(Cell target)
{
// in the case the target is the already maximised cell (or there is only one cell), no cell should be maximised.
bool hasMaximised = !target.IsMaximised && cellContainer.Count > 1;
bool hasMaximised = target != MaximisedCell && cellContainer.Count > 1;
MaximisedCell = hasMaximised ? target : null;
// Iterate through all cells to ensure only one is maximised at any time.
foreach (var cell in cellContainer.ToList())

View File

@@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// <summary>
/// A cell of the grid. Contains the content and tracks to the linked facade.
/// </summary>
private partial class Cell : CompositeDrawable
public partial class Cell : CompositeDrawable
{
/// <summary>
/// The index of the original facade of this cell.
@@ -33,11 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary>
public Action<Cell>? ToggleMaximisationState;
/// <summary>
/// Whether this cell is currently maximised.
/// </summary>
public bool IsMaximised { get; private set; }
private Facade facade;
private bool isAnimating;
@@ -83,7 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public void SetFacade(Facade newFacade, bool isMaximised)
{
facade = newFacade;
IsMaximised = isMaximised;
isAnimating = true;
TweenEdgeEffectTo(new EdgeEffectParameters

View File

@@ -143,6 +143,9 @@ namespace osu.Game.Screens.SelectV2
switch (changed.Action)
{
case NotifyCollectionChangedAction.Add:
if (!newItems!.Any())
return;
Items.AddRange(newItems!.SelectMany(s => s.Beatmaps));
break;
@@ -353,6 +356,57 @@ namespace osu.Game.Screens.SelectV2
}
}
protected override bool HandleItemsChanged(NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Reset:
return true;
case NotifyCollectionChangedAction.Replace:
var oldBeatmaps = args.OldItems!.OfType<BeatmapInfo>().ToList();
var newBeatmaps = args.NewItems!.OfType<BeatmapInfo>().ToList();
for (int i = 0; i < oldBeatmaps.Count; i++)
{
var oldBeatmap = oldBeatmaps[i];
var newBeatmap = newBeatmaps[i];
// Ignore changes which don't concern us.
//
// Here are some examples of things that can go wrong:
// - Background difficulty calculation runs and causes a realm update.
// We use `BeatmapDifficultyCache` and don't want to know about these.
// - Background user tag population runs and causes a realm update.
// We don't display user tags so want to ignore this.
bool equalForDisplayPurposes =
// covers metadata changes
oldBeatmap.Hash == newBeatmap.Hash &&
// sanity check
oldBeatmap.OnlineID == newBeatmap.OnlineID &&
// displayed on panel
oldBeatmap.Status == newBeatmap.Status &&
// displayed on panel
oldBeatmap.DifficultyName == newBeatmap.DifficultyName &&
// hidden changed, needs re-filter
oldBeatmap.Hidden == newBeatmap.Hidden &&
// might be used for grouping, returning from gameplay
oldBeatmap.LastPlayed == newBeatmap.LastPlayed;
if (equalForDisplayPurposes)
return false;
}
return true;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override void HandleFilterCompleted()
{
base.HandleFilterCompleted();

View File

@@ -162,6 +162,9 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
public ShearedKeyModeTabControl()
{
RelativeSizeAxes = Axes.X;
@@ -212,13 +215,13 @@ namespace osu.Game.Screens.SelectV2
Left = LabelContainer.DrawWidth + 8,
};
const int mode_id = 3;
var keyModes = EzSelectModes.GetModesForRuleset(mode_id)
int modeID = ruleset.Value.OnlineID;
var keyModes = EzSelectModes.GetModesForRuleset(modeID)
.OrderBy(m => m.Id == "All" ? -1 : m.KeyCount ?? 0)
.Select(m => m.Id)
.ToList();
GetCurrentModeId.Value = mode_id;
GetCurrentModeId.Value = modeID;
Items = keyModes;
}