From bfe675ce503772d32b19cadff947a1cb6c166c7a Mon Sep 17 00:00:00 2001 From: LA <1245661240@qq.com> Date: Sun, 6 Jul 2025 16:56:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0=EF=BC=8CEz?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=A4=A7=E4=BC=98=E5=8C=96=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E5=85=BC=E5=AE=B9=E5=AE=98=E6=96=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osu.Android.props | 2 +- osu.Desktop.slnf | 60 +- osu.Desktop/osu.Desktop.csproj | 13 +- .../Edit/CatchHitObjectComposer.cs | 36 + .../Mods/CatchModDifficultyAdjust.cs | 2 +- .../TestSceneLegacyReplayPlayback.cs | 187 ++- .../TestSceneReplayStability.cs | 51 +- .../ManiaRulesetConfigManager.cs | 31 +- .../LAsEZMania/EzManiaEnums.cs | 21 + .../LAsEZMania/EzManiaHitModeConvertor.cs | 1 + .../LAsEZMania/ManiaKeyCounterDisplay.cs | 3 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 11 +- .../ManiaSettingsSubsection.cs | 68 +- .../Mods/LAsMods/ManiaModJudgmentStyle.cs | 2 +- .../YuLiangSSSMods/ManiaModNewJudgement.cs | 2 +- .../Scoring/ManiaHitWindows.cs | 12 +- .../Skinning/Ez2/Ez2ColumnBackground.cs | 45 +- .../Skinning/Ez2/Ez2KeyArea.cs | 7 - .../Ez2KeyAreaPlus.cs} | 7 +- .../Skinning/Ez2/ManiaEz2SkinTransformer.cs | 9 +- .../Skinning/Ez2HUD/Ez2SongProgress.cs | 2 +- .../Skinning/Ez2HUD/EzComComboCounter.cs | 8 +- .../Skinning/Ez2HUD/EzComComboSprite.cs | 8 +- .../Skinning/Ez2HUD/EzComHitTiming.cs | 2 +- .../Skinning/Ez2HUD/EzComKeyCounterDisplay.cs | 1 - .../Skinning/EzStylePro/EzColumnBackground.cs | 168 ++ .../Skinning/EzStylePro/EzHitExplosion.cs | 57 +- .../Skinning/EzStylePro/EzHitTarget.cs | 42 +- .../Skinning/EzStylePro/EzHoldNoteHead.cs | 21 +- .../EzStylePro/EzHoldNoteHittingLayer.cs | 98 +- .../Skinning/EzStylePro/EzHoldNoteMiddle.cs | 32 +- .../{EzStageKeys.cs => EzKeyArea.cs} | 121 +- .../Skinning/EzStylePro/EzNote.cs | 5 +- .../Skinning/EzStylePro/EzStageBottom.cs | 37 +- .../ManiaEzStyleProSkinTransformer.cs | 13 +- .../Legacy/HitTargetInsetContainer.cs | 15 +- .../Skinning/SbI/ManiaSbISkinTransformer.cs | 3 +- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 41 +- .../Components/HitPositionPaddedContainer.cs | 6 +- .../UI/DrawableManiaRuleset.cs | 26 +- .../Mods/TestSceneOsuModFlashlight.cs | 12 +- .../Mods/TestSceneOsuModRelax.cs | 46 +- .../TestSceneLegacyReplayPlayback.cs | 1 - .../TestSceneOsuHitObjectSamples.cs | 21 +- .../TestSceneReplayStability.cs | 55 +- .../TestSceneSliderLateHitJudgement.cs | 40 +- .../Mods/OsuModDifficultyAdjust.cs | 2 +- .../Scoring/OsuHitWindows.cs | 6 +- .../Judgements/TestSceneHitJudgements.cs | 2 +- .../TestSceneLegacyReplayPlayback.cs | 3 +- .../TestSceneReplayStability.cs | 43 +- .../TestSceneTaikoHitObjectSamples.cs | 21 +- .../Mods/TaikoModDifficultyAdjust.cs | 10 +- .../Scoring/TaikoHitWindows.cs | 6 +- .../Gameplay/TestSceneHitObjectSamples.cs | 3 - osu.Game.Tests/Mods/ModUtilsTest.cs | 13 +- .../Background/TestSceneUserDimBackgrounds.cs | 12 +- .../Visual/Editing/TestSceneEditorSaving.cs | 4 +- .../Editing/TestSceneOpenEditorTimestamp.cs | 10 +- .../TestSceneGameplaySamplePlayback.cs | 5 +- .../Visual/Gameplay/TestSceneReplayPlayer.cs | 50 + .../TestSceneBeatmapEditorNavigation.cs | 20 +- .../TestSceneButtonSystemNavigation.cs | 6 +- .../TestSceneChangeAndUseGameplayBindings.cs | 8 +- .../TestSceneMouseWheelVolumeAdjust.cs | 7 +- .../Navigation/TestScenePerformFromScreen.cs | 16 +- .../Navigation/TestScenePresentBeatmap.cs | 16 +- .../Navigation/TestScenePresentScore.cs | 17 +- .../Navigation/TestSceneScreenNavigation.cs | 256 ++- .../TestSceneSkinEditorNavigation.cs | 15 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 548 ------- .../TestSceneBeatmapRecommendations.cs | 3 +- .../SongSelect/TestScenePlaySongSelect.cs | 1445 ----------------- .../BeatmapCarouselFilterGroupingTest.cs | 4 +- .../TestSceneBeatmapLeaderboardWedge.cs | 224 ++- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 + osu.Game/Configuration/BackgroundSource.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 31 +- osu.Game/Database/ModelManager.cs | 2 + osu.Game/Database/RealmAccess.cs | 44 +- osu.Game/Database/RealmObjectExtensions.cs | 2 + osu.Game/Graphics/Carousel/Carousel.cs | 6 +- .../Graphics/Containers/ExpandingContainer.cs | 25 +- .../Graphics/UserInterface/ShearedButton.cs | 2 +- osu.Game/Localisation/EditorStrings.cs | 5 + osu.Game/Localisation/MenuTipStrings.cs | 5 + osu.Game/Localisation/ResultsScreenStrings.cs | 5 + .../BeatmapAttributeTextStrings.cs | 5 - .../SkinnableComponentStrings.cs | 30 - osu.Game/OsuGame.cs | 10 +- osu.Game/OsuGameBase.cs | 4 +- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 44 +- .../Overlays/FirstRunSetup/ScreenUIScale.cs | 23 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- .../Sections/Gameplay/GeneralSettings.cs | 8 + .../Settings/Sections/Input/TabletSettings.cs | 16 +- .../Overlays/Settings/Sections/SkinSection.cs | 15 +- .../SkinEditor/ExternalEditOverlay.cs | 284 ++++ osu.Game/Overlays/SkinEditor/SkinEditor.cs | 18 + .../Overlays/SkinEditor/SkinEditorOverlay.cs | 36 +- .../SkinEditor/SkinEditorSceneLibrary.cs | 5 +- osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Rulesets/Mods/ModClassic.cs | 2 +- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 41 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- osu.Game/Rulesets/Scoring/IHitWindows.cs | 8 - osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 18 +- .../Backgrounds/BackgroundScreenDefault.cs | 14 +- .../Compose/Components/BeatDivisorControl.cs | 7 +- osu.Game/Screens/Edit/ExternalEditScreen.cs | 3 +- .../{EzColumnColorTab.cs => EzColumnTab.cs} | 91 +- osu.Game/Screens/EzSkinEditorOverlay.cs | 7 +- osu.Game/Screens/EzSkinSettingsManager.cs | 68 +- osu.Game/Screens/EzSkinSettingsTab.cs | 75 +- osu.Game/Screens/Footer/ScreenFooter.cs | 19 +- .../LAsEzExtensions/EzColumnTypeManager.cs | 2 +- .../LAsEzExtensions/EzEditorSidebar.cs | 8 +- .../LAsEzExtensions/EzLocalTextureFactory.cs | 266 +-- .../LAsEzExtensions/VideoBackgroundScreen.cs | 6 +- osu.Game/Screens/Menu/MainMenu.cs | 102 +- osu.Game/Screens/Menu/MenuTipDisplay.cs | 3 +- .../Screens/Play/HUD/ArgonAccuracyCounter.cs | 2 +- .../Screens/Play/HUD/ArgonComboCounter.cs | 2 +- .../Play/HUD/ArgonPerformancePointsCounter.cs | 2 +- .../Screens/Play/HUD/ArgonScoreCounter.cs | 2 +- .../Screens/Play/HUD/ArgonSongProgress.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs | 2 +- .../Screens/Play/HUD/DefaultSongProgress.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 21 +- osu.Game/Screens/Play/ReplayPlayer.cs | 22 +- .../Statistics/PerformanceStatistic.cs | 5 + .../Screens/Select/Details/AdvancedStats.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 166 -- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 37 +- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 3 +- .../SelectV2/BeatmapLeaderboardScore.cs | 57 +- .../BeatmapLeaderboardScore_Tooltip.cs | 58 +- .../SelectV2/BeatmapLeaderboardWedge.cs | 8 + osu.Game/Screens/SelectV2/FilterControl.cs | 6 +- osu.Game/Screens/SelectV2/PanelGroup.cs | 4 +- osu.Game/Screens/SelectV2/SoloSongSelect.cs | 6 +- osu.Game/Screens/SelectV2/SongSelect.cs | 92 +- .../Components/BeatmapAttributeText.cs | 2 +- osu.Game/Skinning/Components/BoxElement.cs | 2 +- .../Components/EzComHitResultScore.cs | 2 +- .../Skinning/Components/EzComScoreCounter.cs | 4 +- .../Skinning/Components/EzSelectorEnumList.cs | 141 +- .../Skinning/Components/EzSelectorTextures.cs | 2 +- osu.Game/Skinning/Components/TextElement.cs | 2 +- osu.Game/Skinning/EzStyleProSkin.cs | 2 + .../Skinning/FontAdjustableSkinComponent.cs | 4 +- .../Skinning/LegacyDefaultComboCounter.cs | 8 +- osu.Game/Skinning/LegacySkin.cs | 13 +- osu.Game/Skinning/Skin.cs | 54 +- osu.Game/Skinning/SkinImporter.cs | 47 + osu.Game/Skinning/SkinInfo.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 4 + osu.Game/Skinning/SkinnableSprite.cs | 5 +- .../Drawables/DrawableStoryboard.cs | 26 +- .../Tests/Visual/EditorSavingTestScene.cs | 11 +- .../Tests/Visual/ReplayStabilityTestScene.cs | 5 + osu.Game/osu.Game.csproj | 4 +- osu.iOS.props | 2 +- osu.sln | 24 + 164 files changed, 2610 insertions(+), 3858 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/LAsEZMania/EzManiaEnums.cs rename osu.Game.Rulesets.Mania/Skinning/{EzStylePro/Ez2KeyAreaPro.cs => Ez2/Ez2KeyAreaPlus.cs} (98%) create mode 100644 osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzColumnBackground.cs rename osu.Game.Rulesets.Mania/Skinning/EzStylePro/{EzStageKeys.cs => EzKeyArea.cs} (60%) delete mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs delete mode 100644 osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs create mode 100644 osu.Game/Overlays/SkinEditor/ExternalEditOverlay.cs rename osu.Game/Screens/{EzColumnColorTab.cs => EzColumnTab.cs} (79%) delete mode 100644 osu.Game/Screens/Select/PlaySongSelect.cs diff --git a/osu.Android.props b/osu.Android.props index aa7b343f38..de3fe31ee6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + + + F:\MUG OSU\Ez2Lazer_Publish\$(Configuration) + bin\$(Configuration)\ + diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index dfe9dc9dd8..370eb37d16 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -219,5 +220,40 @@ namespace osu.Game.Rulesets.Catch.Edit distanceSnapGrid.StartTime = sourceHitObject.GetEndTime(); distanceSnapGrid.StartX = sourceHitObject.EffectiveX; } + + #region Clipboard handling + + public override string ConvertSelectionToString() + => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + + // 1,2,3,4 ... + private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled); + + public override void SelectFromTimestamp(double timestamp, string objectDescription) + { + if (!selection_regex.IsMatch(objectDescription)) + return; + + List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); + string[] splitDescription = objectDescription.Split(','); + + for (int i = 0; i < splitDescription.Length; i++) + { + if (!int.TryParse(splitDescription[i], out int combo) || combo < 1) + continue; + + CatchHitObject? current = remainingHitObjects.FirstOrDefault(h => h.IndexInCurrentCombo + 1 == combo); + + if (current == null) + continue; + + EditorBeatmap.SelectedHitObjects.Add(current); + + if (i < splitDescription.Length - 1) + remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); + } + } + + #endregion } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index c300afa79f..e4a910700c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Mods { get { - if (UserAdjustedSettingsCount != 1) + if (!IsExactlyOneSettingChanged(CircleSize, ApproachRate, OverallDifficulty, DrainRate)) return string.Empty; if (!CircleSize.IsDefault) return format("CS", CircleSize); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs index 2a7f2dc7ea..2c17cd8015 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs @@ -19,7 +19,6 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - [Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")] public partial class TestSceneLegacyReplayPlayback : LegacyReplayPlaybackTestScene { protected override Ruleset CreateRuleset() => new ManiaRuleset(); @@ -72,13 +71,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -137d, HitResult.Miss }, new object[] { 5f, -138d, HitResult.Miss }, new object[] { 5f, 111d, HitResult.Ok }, - new object[] { 5f, 112d, HitResult.Miss }, - new object[] { 5f, 113d, HitResult.Miss }, - new object[] { 5f, 114d, HitResult.Miss }, - new object[] { 5f, 135d, HitResult.Miss }, - new object[] { 5f, 136d, HitResult.Miss }, - new object[] { 5f, 137d, HitResult.Miss }, - new object[] { 5f, 138d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 112d, HitResult.Miss }, + // new object[] { 5f, 113d, HitResult.Miss }, + // new object[] { 5f, 114d, HitResult.Miss }, + // new object[] { 5f, 135d, HitResult.Miss }, + // new object[] { 5f, 136d, HitResult.Miss }, + // new object[] { 5f, 137d, HitResult.Miss }, + // new object[] { 5f, 138d, HitResult.Miss }, // OD = 9.3 test cases. // PERFECT hit window is [ -14ms, 14ms] @@ -99,13 +99,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 9.3f, 70d, HitResult.Ok }, new object[] { 9.3f, 71d, HitResult.Ok }, new object[] { 9.3f, 98d, HitResult.Ok }, - new object[] { 9.3f, 99d, HitResult.Miss }, - new object[] { 9.3f, 100d, HitResult.Miss }, - new object[] { 9.3f, 101d, HitResult.Miss }, - new object[] { 9.3f, 122d, HitResult.Miss }, - new object[] { 9.3f, 123d, HitResult.Miss }, - new object[] { 9.3f, 124d, HitResult.Miss }, - new object[] { 9.3f, 125d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 9.3f, 99d, HitResult.Miss }, + // new object[] { 9.3f, 100d, HitResult.Miss }, + // new object[] { 9.3f, 101d, HitResult.Miss }, + // new object[] { 9.3f, 122d, HitResult.Miss }, + // new object[] { 9.3f, 123d, HitResult.Miss }, + // new object[] { 9.3f, 124d, HitResult.Miss }, + // new object[] { 9.3f, 125d, HitResult.Miss }, new object[] { 9.3f, -98d, HitResult.Ok }, new object[] { 9.3f, -99d, HitResult.Ok }, new object[] { 9.3f, -100d, HitResult.Meh }, @@ -145,13 +146,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -137d, HitResult.Miss }, new object[] { 5f, -138d, HitResult.Miss }, new object[] { 5f, 111d, HitResult.Ok }, - new object[] { 5f, 112d, HitResult.Miss }, - new object[] { 5f, 113d, HitResult.Miss }, - new object[] { 5f, 114d, HitResult.Miss }, - new object[] { 5f, 135d, HitResult.Miss }, - new object[] { 5f, 136d, HitResult.Miss }, - new object[] { 5f, 137d, HitResult.Miss }, - new object[] { 5f, 138d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 112d, HitResult.Miss }, + // new object[] { 5f, 113d, HitResult.Miss }, + // new object[] { 5f, 114d, HitResult.Miss }, + // new object[] { 5f, 135d, HitResult.Miss }, + // new object[] { 5f, 136d, HitResult.Miss }, + // new object[] { 5f, 137d, HitResult.Miss }, + // new object[] { 5f, 138d, HitResult.Miss }, // OD = 9.3 test cases. // PERFECT hit window is [ -16ms, 16ms] @@ -172,13 +174,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 9.3f, 70d, HitResult.Ok }, new object[] { 9.3f, 71d, HitResult.Ok }, new object[] { 9.3f, 98d, HitResult.Ok }, - new object[] { 9.3f, 99d, HitResult.Miss }, - new object[] { 9.3f, 100d, HitResult.Miss }, - new object[] { 9.3f, 101d, HitResult.Miss }, - new object[] { 9.3f, 122d, HitResult.Miss }, - new object[] { 9.3f, 123d, HitResult.Miss }, - new object[] { 9.3f, 124d, HitResult.Miss }, - new object[] { 9.3f, 125d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 9.3f, 99d, HitResult.Miss }, + // new object[] { 9.3f, 100d, HitResult.Miss }, + // new object[] { 9.3f, 101d, HitResult.Miss }, + // new object[] { 9.3f, 122d, HitResult.Miss }, + // new object[] { 9.3f, 123d, HitResult.Miss }, + // new object[] { 9.3f, 124d, HitResult.Miss }, + // new object[] { 9.3f, 125d, HitResult.Miss }, new object[] { 9.3f, -98d, HitResult.Ok }, new object[] { 9.3f, -99d, HitResult.Ok }, new object[] { 9.3f, -100d, HitResult.Meh }, @@ -207,13 +210,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 3.1f, 88d, HitResult.Ok }, new object[] { 3.1f, 89d, HitResult.Ok }, new object[] { 3.1f, 116d, HitResult.Ok }, - new object[] { 3.1f, 117d, HitResult.Miss }, - new object[] { 3.1f, 118d, HitResult.Miss }, - new object[] { 3.1f, 119d, HitResult.Miss }, - new object[] { 3.1f, 140d, HitResult.Miss }, - new object[] { 3.1f, 141d, HitResult.Miss }, - new object[] { 3.1f, 142d, HitResult.Miss }, - new object[] { 3.1f, 143d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 3.1f, 117d, HitResult.Miss }, + // new object[] { 3.1f, 118d, HitResult.Miss }, + // new object[] { 3.1f, 119d, HitResult.Miss }, + // new object[] { 3.1f, 140d, HitResult.Miss }, + // new object[] { 3.1f, 141d, HitResult.Miss }, + // new object[] { 3.1f, 142d, HitResult.Miss }, + // new object[] { 3.1f, 143d, HitResult.Miss }, new object[] { 3.1f, -116d, HitResult.Ok }, new object[] { 3.1f, -117d, HitResult.Ok }, new object[] { 3.1f, -118d, HitResult.Meh }, @@ -253,13 +257,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -122d, HitResult.Miss }, new object[] { 5f, -123d, HitResult.Miss }, new object[] { 5f, 96d, HitResult.Ok }, - new object[] { 5f, 97d, HitResult.Miss }, - new object[] { 5f, 98d, HitResult.Miss }, - new object[] { 5f, 99d, HitResult.Miss }, - new object[] { 5f, 120d, HitResult.Miss }, - new object[] { 5f, 121d, HitResult.Miss }, - new object[] { 5f, 122d, HitResult.Miss }, - new object[] { 5f, 123d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 97d, HitResult.Miss }, + // new object[] { 5f, 98d, HitResult.Miss }, + // new object[] { 5f, 99d, HitResult.Miss }, + // new object[] { 5f, 120d, HitResult.Miss }, + // new object[] { 5f, 121d, HitResult.Miss }, + // new object[] { 5f, 122d, HitResult.Miss }, + // new object[] { 5f, 123d, HitResult.Miss }, // OD = 3.1 test cases. // PERFECT hit window is [ -16ms, 16ms] @@ -280,13 +285,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 3.1f, 78d, HitResult.Ok }, new object[] { 3.1f, 79d, HitResult.Ok }, new object[] { 3.1f, 96d, HitResult.Ok }, - new object[] { 3.1f, 97d, HitResult.Miss }, - new object[] { 3.1f, 98d, HitResult.Miss }, - new object[] { 3.1f, 99d, HitResult.Miss }, - new object[] { 3.1f, 120d, HitResult.Miss }, - new object[] { 3.1f, 121d, HitResult.Miss }, - new object[] { 3.1f, 122d, HitResult.Miss }, - new object[] { 3.1f, 123d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 3.1f, 97d, HitResult.Miss }, + // new object[] { 3.1f, 98d, HitResult.Miss }, + // new object[] { 3.1f, 99d, HitResult.Miss }, + // new object[] { 3.1f, 120d, HitResult.Miss }, + // new object[] { 3.1f, 121d, HitResult.Miss }, + // new object[] { 3.1f, 122d, HitResult.Miss }, + // new object[] { 3.1f, 123d, HitResult.Miss }, new object[] { 3.1f, -96d, HitResult.Ok }, new object[] { 3.1f, -97d, HitResult.Ok }, new object[] { 3.1f, -98d, HitResult.Meh }, @@ -327,13 +333,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -98d, HitResult.Miss }, new object[] { 5f, -99d, HitResult.Miss }, new object[] { 5f, 79d, HitResult.Ok }, - new object[] { 5f, 80d, HitResult.Miss }, - new object[] { 5f, 81d, HitResult.Miss }, - new object[] { 5f, 82d, HitResult.Miss }, - new object[] { 5f, 96d, HitResult.Miss }, - new object[] { 5f, 97d, HitResult.Miss }, - new object[] { 5f, 98d, HitResult.Miss }, - new object[] { 5f, 99d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 80d, HitResult.Miss }, + // new object[] { 5f, 81d, HitResult.Miss }, + // new object[] { 5f, 82d, HitResult.Miss }, + // new object[] { 5f, 96d, HitResult.Miss }, + // new object[] { 5f, 97d, HitResult.Miss }, + // new object[] { 5f, 98d, HitResult.Miss }, + // new object[] { 5f, 99d, HitResult.Miss }, // OD = 9.3 test cases. // This leads to "effective" OD of 13.02. @@ -356,13 +363,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 9.3f, 50d, HitResult.Ok }, new object[] { 9.3f, 51d, HitResult.Ok }, new object[] { 9.3f, 69d, HitResult.Ok }, - new object[] { 9.3f, 70d, HitResult.Miss }, - new object[] { 9.3f, 71d, HitResult.Miss }, - new object[] { 9.3f, 72d, HitResult.Miss }, - new object[] { 9.3f, 86d, HitResult.Miss }, - new object[] { 9.3f, 87d, HitResult.Miss }, - new object[] { 9.3f, 88d, HitResult.Miss }, - new object[] { 9.3f, 89d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 9.3f, 70d, HitResult.Miss }, + // new object[] { 9.3f, 71d, HitResult.Miss }, + // new object[] { 9.3f, 72d, HitResult.Miss }, + // new object[] { 9.3f, 86d, HitResult.Miss }, + // new object[] { 9.3f, 87d, HitResult.Miss }, + // new object[] { 9.3f, 88d, HitResult.Miss }, + // new object[] { 9.3f, 89d, HitResult.Miss }, new object[] { 9.3f, -69d, HitResult.Ok }, new object[] { 9.3f, -70d, HitResult.Ok }, new object[] { 9.3f, -71d, HitResult.Meh }, @@ -402,13 +410,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -191d, HitResult.Miss }, new object[] { 5f, -192d, HitResult.Miss }, new object[] { 5f, 155d, HitResult.Ok }, - new object[] { 5f, 156d, HitResult.Miss }, - new object[] { 5f, 157d, HitResult.Miss }, - new object[] { 5f, 158d, HitResult.Miss }, - new object[] { 5f, 189d, HitResult.Miss }, - new object[] { 5f, 190d, HitResult.Miss }, - new object[] { 5f, 191d, HitResult.Miss }, - new object[] { 5f, 192d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 156d, HitResult.Miss }, + // new object[] { 5f, 157d, HitResult.Miss }, + // new object[] { 5f, 158d, HitResult.Miss }, + // new object[] { 5f, 189d, HitResult.Miss }, + // new object[] { 5f, 190d, HitResult.Miss }, + // new object[] { 5f, 191d, HitResult.Miss }, + // new object[] { 5f, 192d, HitResult.Miss }, }; private static readonly object[][] score_v1_non_convert_double_time_test_cases = @@ -440,13 +449,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -205d, HitResult.Miss }, new object[] { 5f, -206d, HitResult.Miss }, new object[] { 5f, 167d, HitResult.Ok }, - new object[] { 5f, 168d, HitResult.Miss }, - new object[] { 5f, 169d, HitResult.Miss }, - new object[] { 5f, 170d, HitResult.Miss }, - new object[] { 5f, 203d, HitResult.Miss }, - new object[] { 5f, 204d, HitResult.Miss }, - new object[] { 5f, 205d, HitResult.Miss }, - new object[] { 5f, 206d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 168d, HitResult.Miss }, + // new object[] { 5f, 169d, HitResult.Miss }, + // new object[] { 5f, 170d, HitResult.Miss }, + // new object[] { 5f, 203d, HitResult.Miss }, + // new object[] { 5f, 204d, HitResult.Miss }, + // new object[] { 5f, 205d, HitResult.Miss }, + // new object[] { 5f, 206d, HitResult.Miss }, }; private static readonly object[][] score_v1_non_convert_half_time_test_cases = @@ -478,13 +488,14 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { 5f, -103d, HitResult.Miss }, new object[] { 5f, -104d, HitResult.Miss }, new object[] { 5f, 83d, HitResult.Ok }, - new object[] { 5f, 84d, HitResult.Miss }, - new object[] { 5f, 85d, HitResult.Miss }, - new object[] { 5f, 86d, HitResult.Miss }, - new object[] { 5f, 101d, HitResult.Miss }, - new object[] { 5f, 102d, HitResult.Miss }, - new object[] { 5f, 103d, HitResult.Miss }, - new object[] { 5f, 104d, HitResult.Miss }, + // coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced + // new object[] { 5f, 84d, HitResult.Miss }, + // new object[] { 5f, 85d, HitResult.Miss }, + // new object[] { 5f, 86d, HitResult.Miss }, + // new object[] { 5f, 101d, HitResult.Miss }, + // new object[] { 5f, 102d, HitResult.Miss }, + // new object[] { 5f, 103d, HitResult.Miss }, + // new object[] { 5f, 104d, HitResult.Miss }, }; private const double note_time = 300; @@ -517,6 +528,7 @@ namespace osu.Game.Rulesets.Mania.Tests RunTest($@"SV2 single note @ OD{overallDifficulty}", beatmap, $@"SV2 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]); } + [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")] [TestCaseSource(nameof(score_v1_non_convert_test_cases))] public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult) { @@ -544,6 +556,7 @@ namespace osu.Game.Rulesets.Mania.Tests RunTest($@"SV1 single note @ OD{overallDifficulty}", beatmap, $@"SV1 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]); } + [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")] [TestCaseSource(nameof(score_v1_convert_test_cases))] public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult) { @@ -572,6 +585,7 @@ namespace osu.Game.Rulesets.Mania.Tests RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]); } + [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")] [TestCaseSource(nameof(score_v1_non_convert_hard_rock_test_cases))] public void TestHitWindowTreatmentWithScoreV1AndHardRockNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult) { @@ -600,6 +614,7 @@ namespace osu.Game.Rulesets.Mania.Tests RunTest($@"SV1+HR single note @ OD{overallDifficulty}", beatmap, $@"SV1+HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]); } + [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")] [TestCaseSource(nameof(score_v1_non_convert_easy_test_cases))] public void TestHitWindowTreatmentWithScoreV1AndEasyNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult) { @@ -628,6 +643,7 @@ namespace osu.Game.Rulesets.Mania.Tests RunTest($@"SV1+EZ single note @ OD{overallDifficulty}", beatmap, $@"SV1+EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]); } + [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")] [TestCaseSource(nameof(score_v1_non_convert_double_time_test_cases))] public void TestHitWindowTreatmentWithScoreV1AndDoubleTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult) { @@ -656,6 +672,7 @@ namespace osu.Game.Rulesets.Mania.Tests RunTest($@"SV1+DT single note @ OD{overallDifficulty}", beatmap, $@"SV1+DT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]); } + [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")] [TestCaseSource(nameof(score_v1_non_convert_half_time_test_cases))] public void TestHitWindowTreatmentWithScoreV1AndHalfTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult) { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneReplayStability.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneReplayStability.cs index 64496d7628..a8160d3373 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneReplayStability.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneReplayStability.cs @@ -12,7 +12,6 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - [Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")] public partial class TestSceneReplayStability : ReplayStabilityTestScene { private static readonly object[][] test_cases = @@ -22,87 +21,79 @@ namespace osu.Game.Rulesets.Mania.Tests // while round brackets `()` represent *open* or *exclusive* bounds. // OD = 5 test cases. - // PERFECT hit window is [ -19.4ms, 19.4ms] - // GREAT hit window is [ -49.0ms, 49.0ms] - // GOOD hit window is [ -82.0ms, 82.0ms] - // OK hit window is [-112.0ms, 112.0ms] - // MEH hit window is [-136.0ms, 136.0ms] - // MISS hit window is [-173.0ms, 173.0ms] + // PERFECT hit window is [ -19.5ms, 19.5ms] + // GREAT hit window is [ -49.5ms, 49.5ms] + // GOOD hit window is [ -82.5ms, 82.5ms] + // OK hit window is [-112.5ms, 112.5ms] + // MEH hit window is [-136.5ms, 136.5ms] + // MISS hit window is [-173.5ms, 173.5ms] new object[] { 5f, -19d, HitResult.Perfect }, new object[] { 5f, -19.2d, HitResult.Perfect }, - new object[] { 5f, -19.38d, HitResult.Perfect }, - // new object[] { 5f, -19.4d, HitResult.Perfect }, <- in theory this should work, in practice it does not (fails even before encode & rounding due to floating point precision issues) - new object[] { 5f, -19.44d, HitResult.Great }, new object[] { 5f, -19.7d, HitResult.Great }, new object[] { 5f, -20d, HitResult.Great }, new object[] { 5f, -48d, HitResult.Great }, new object[] { 5f, -48.4d, HitResult.Great }, new object[] { 5f, -48.7d, HitResult.Great }, new object[] { 5f, -49d, HitResult.Great }, - new object[] { 5f, -49.2d, HitResult.Good }, + new object[] { 5f, -49.2d, HitResult.Great }, new object[] { 5f, -49.7d, HitResult.Good }, new object[] { 5f, -50d, HitResult.Good }, new object[] { 5f, -81d, HitResult.Good }, new object[] { 5f, -81.2d, HitResult.Good }, new object[] { 5f, -81.7d, HitResult.Good }, new object[] { 5f, -82d, HitResult.Good }, - new object[] { 5f, -82.2d, HitResult.Ok }, + new object[] { 5f, -82.2d, HitResult.Good }, new object[] { 5f, -82.7d, HitResult.Ok }, new object[] { 5f, -83d, HitResult.Ok }, new object[] { 5f, -111d, HitResult.Ok }, new object[] { 5f, -111.2d, HitResult.Ok }, new object[] { 5f, -111.7d, HitResult.Ok }, new object[] { 5f, -112d, HitResult.Ok }, - new object[] { 5f, -112.2d, HitResult.Meh }, + new object[] { 5f, -112.2d, HitResult.Ok }, new object[] { 5f, -112.7d, HitResult.Meh }, new object[] { 5f, -113d, HitResult.Meh }, new object[] { 5f, -135d, HitResult.Meh }, new object[] { 5f, -135.2d, HitResult.Meh }, new object[] { 5f, -135.8d, HitResult.Meh }, new object[] { 5f, -136d, HitResult.Meh }, - new object[] { 5f, -136.2d, HitResult.Miss }, + new object[] { 5f, -136.2d, HitResult.Meh }, new object[] { 5f, -136.7d, HitResult.Miss }, new object[] { 5f, -137d, HitResult.Miss }, // OD = 9.3 test cases. - // PERFECT hit window is [ -14.67ms, 14.67ms] - // GREAT hit window is [ -36.10ms, 36.10ms] - // GOOD hit window is [ -69.10ms, 69.10ms] - // OK hit window is [ -99.10ms, 99.10ms] - // MEH hit window is [-123.10ms, 123.10ms] - // MISS hit window is [-160.10ms, 160.10ms] + // PERFECT hit window is [ -14.5ms, 14.5ms] + // GREAT hit window is [ -36.5ms, 36.5ms] + // GOOD hit window is [ -69.5ms, 69.5ms] + // OK hit window is [ -99.5ms, 99.5ms] + // MEH hit window is [-123.5ms, 123.5ms] + // MISS hit window is [-160.5ms, 160.5ms] new object[] { 9.3f, 14d, HitResult.Perfect }, new object[] { 9.3f, 14.2d, HitResult.Perfect }, - new object[] { 9.3f, 14.6d, HitResult.Perfect }, - // new object[] { 9.3f, 14.67d, HitResult.Perfect }, <- in theory this should work, in practice it does not (fails even before encode & rounding due to floating point precision issues) new object[] { 9.3f, 14.7d, HitResult.Great }, new object[] { 9.3f, 15d, HitResult.Great }, new object[] { 9.3f, 35d, HitResult.Great }, new object[] { 9.3f, 35.3d, HitResult.Great }, new object[] { 9.3f, 35.8d, HitResult.Great }, - new object[] { 9.3f, 36.05d, HitResult.Great }, - new object[] { 9.3f, 36.3d, HitResult.Good }, + new object[] { 9.3f, 36.3d, HitResult.Great }, new object[] { 9.3f, 36.7d, HitResult.Good }, new object[] { 9.3f, 37d, HitResult.Good }, new object[] { 9.3f, 68d, HitResult.Good }, new object[] { 9.3f, 68.4d, HitResult.Good }, new object[] { 9.3f, 68.9d, HitResult.Good }, - new object[] { 9.3f, 69.07d, HitResult.Good }, - new object[] { 9.3f, 69.25d, HitResult.Ok }, + new object[] { 9.3f, 69.25d, HitResult.Good }, new object[] { 9.3f, 69.85d, HitResult.Ok }, new object[] { 9.3f, 70d, HitResult.Ok }, new object[] { 9.3f, 98d, HitResult.Ok }, new object[] { 9.3f, 98.3d, HitResult.Ok }, new object[] { 9.3f, 98.6d, HitResult.Ok }, new object[] { 9.3f, 99d, HitResult.Ok }, - new object[] { 9.3f, 99.3d, HitResult.Meh }, + new object[] { 9.3f, 99.3d, HitResult.Ok }, new object[] { 9.3f, 99.7d, HitResult.Meh }, new object[] { 9.3f, 100d, HitResult.Meh }, new object[] { 9.3f, 122d, HitResult.Meh }, new object[] { 9.3f, 122.34d, HitResult.Meh }, new object[] { 9.3f, 122.57d, HitResult.Meh }, - new object[] { 9.3f, 123.04d, HitResult.Meh }, - new object[] { 9.3f, 123.45d, HitResult.Miss }, + new object[] { 9.3f, 123.45d, HitResult.Meh }, new object[] { 9.3f, 123.95d, HitResult.Miss }, new object[] { 9.3f, 124d, HitResult.Miss }, }; @@ -110,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCaseSource(nameof(test_cases))] public void TestHitWindowStability(float overallDifficulty, double hitOffset, HitResult expectedResult) { - const double note_time = 100; + const double note_time = 300; var beatmap = new ManiaBeatmap(new StageDefinition(1)) { diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 108e24db6c..30a70e910f 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.ComponentModel; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; using osu.Game.Localisation; using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Configuration @@ -24,16 +23,14 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - SetDefault(ManiaRulesetSetting.ColumnWidth, 46, 9, 90, 1.0); - SetDefault(ManiaRulesetSetting.SpecialFactor, 1, 0.1, 4, 0.1); // SetDefault(ManiaRulesetSetting.HitMode, MUGHitMode.EZ2AC); - SetDefault(ManiaRulesetSetting.ScrollSpeed, 200, 1.0, 401.0, current_scroll_speed_precision); SetDefault(ManiaRulesetSetting.ScrollBaseSpeed, 500, 100, 1000, 1.0); SetDefault(ManiaRulesetSetting.ScrollTimePerSpeed, 5, 1.0, 40, 1.0); SetDefault(ManiaRulesetSetting.ScrollStyle, EzManiaScrollingStyle.ScrollTimeStyleFixed); SetDefault(ManiaRulesetSetting.ScrollPerKeyMode, false); SetDefault(ManiaRulesetSetting.PerspectiveAngle, 90.0f, 30.0f, 90.0f); + SetDefault(ManiaRulesetSetting.ScrollSpeed, 200, 1.0, 401.0, current_scroll_speed_precision); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false); SetDefault(ManiaRulesetSetting.MobileLayout, ManiaMobileLayout.Portrait); @@ -56,34 +53,20 @@ namespace osu.Game.Rulesets.Mania.Configuration public enum ManiaRulesetSetting { - [Obsolete("Use ScrollSpeed instead.")] // Can be removed 2023-11-30 + ScrollStyle, ScrollTime, ScrollBaseSpeed, ScrollTimePerSpeed, - ScrollStyle, - // HitMode, + // HitMode, + //暂时无用 PerspectiveAngle, - ColumnWidth, - SpecialFactor, ScrollPerKeyMode, + + //官方设置 ScrollSpeed, ScrollDirection, TimingBasedNoteColouring, MobileLayout, } - - public enum EzManiaScrollingStyle - { - // [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionUp))] - [Description("40速 通配速度风格(不可用)")] - ScrollSpeedStyle, - - // [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))] - [Description("ms值 恒定速度")] - ScrollTimeStyle, - - [Description("ms值 恒定时间")] - ScrollTimeStyleFixed, - } } diff --git a/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaEnums.cs b/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaEnums.cs new file mode 100644 index 0000000000..47459df670 --- /dev/null +++ b/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaEnums.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Rulesets.Mania.LAsEZMania +{ + public enum EzManiaScrollingStyle + { + // [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionUp))] + [Description("40速 通配速度风格(不可用)")] + ScrollSpeedStyle, + + // [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))] + [Description("ms值 相对默认判定线")] + ScrollTimeStyle, + + [Description("ms值 相对屏幕底部")] + ScrollTimeStyleFixed, + } +} diff --git a/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaHitModeConvertor.cs b/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaHitModeConvertor.cs index 8c2d3f0b9e..21b173a334 100644 --- a/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaHitModeConvertor.cs +++ b/osu.Game.Rulesets.Mania/LAsEZMania/EzManiaHitModeConvertor.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; diff --git a/osu.Game.Rulesets.Mania/LAsEZMania/ManiaKeyCounterDisplay.cs b/osu.Game.Rulesets.Mania/LAsEZMania/ManiaKeyCounterDisplay.cs index 816fa4dc35..d640457e9e 100644 --- a/osu.Game.Rulesets.Mania/LAsEZMania/ManiaKeyCounterDisplay.cs +++ b/osu.Game.Rulesets.Mania/LAsEZMania/ManiaKeyCounterDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Screens; +using osu.Game.Screens.LAsEzExtensions; using osu.Game.Screens.Play.HUD; using osuTK; @@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.LAsEZMania float width = (float)columnWidth.Value; int index = KeyFlow.IndexOf(counter); - if (StageDefinition.EzIsSpecialColumn(index)) + if (EzColumnTypeManager.GetColumnType(StageDefinition.Columns, index) == "S1") width *= (float)specialFactor.Value; counter.Width = width; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 924b0565e5..e54c3b8e08 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; @@ -86,13 +87,19 @@ namespace osu.Game.Rulesets.Mania case Ez2Skin: if (GlobalConfigStore.EzConfig == null) - throw new ArgumentNullException(nameof(GlobalConfigStore.Config)); + { + Logger.Log("!EGlobalConfigStore.EzConfig", LoggingTarget.Runtime, LogLevel.Important); + break; + } return new ManiaEz2SkinTransformer(skin, beatmap, GlobalConfigStore.EzConfig); case EzStyleProSkin: if (GlobalConfigStore.EzConfig == null) - throw new ArgumentNullException(nameof(GlobalConfigStore.Config)); + { + Logger.Log("!GlobalConfigStore.EzConfig", LoggingTarget.Runtime, LogLevel.Important); + break; + } return new ManiaEzStyleProSkinTransformer(skin, beatmap, GlobalConfigStore.EzConfig); diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 9dcc49747e..9c831a44ab 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania @@ -45,17 +46,19 @@ namespace osu.Game.Rulesets.Mania Current = config.GetBindable(ManiaRulesetSetting.ScrollSpeed), KeyboardStep = 1, }, - new SettingsSlider + new SettingsSlider { LabelText = "Scroll Base MS (when 200 Speed)", Current = config.GetBindable(ManiaRulesetSetting.ScrollBaseSpeed), KeyboardStep = 1, + Keywords = new[] { "base" } }, - new SettingsSlider + new SettingsSlider { - LabelText = "MS / Scroll Speed", + LabelText = "MS / Speed", Current = config.GetBindable(ManiaRulesetSetting.ScrollTimePerSpeed), KeyboardStep = 1, + Keywords = new[] { "mps" } }, new SettingsCheckbox { @@ -85,20 +88,63 @@ namespace osu.Game.Rulesets.Mania this.config = config; } - public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip( - (int)DrawableManiaRuleset.ComputeScrollTime(Current.Value, config.Get(ManiaRulesetSetting.ScrollBaseSpeed), config.Get(ManiaRulesetSetting.ScrollTimePerSpeed)), - Current.Value - ); + public override LocalisableString TooltipText + { + get + { + double baseSpeed = config.Get(ManiaRulesetSetting.ScrollBaseSpeed); + double timePerSpeed = config.Get(ManiaRulesetSetting.ScrollTimePerSpeed); + int computedTime = (int)DrawableManiaRuleset.ComputeScrollTime(Current.Value, baseSpeed, timePerSpeed); + LocalisableString speedInfo = RulesetSettingsStrings.ScrollSpeedTooltip(computedTime, Current.Value); + return $"{baseSpeed}base - ( {Current.Value} - 200) * {timePerSpeed}mps\n = {speedInfo}"; + } + } } - private partial class ManiaScrollBaseSpeedSlider : RoundedSliderBar + private partial class ManiaScrollBaseSlider : RoundedSliderBar { - public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip( - (int)Current.Value, 200); + private ManiaRulesetConfigManager config = null!; + + [BackgroundDependencyLoader] + private void load(ManiaRulesetConfigManager config) + { + this.config = config; + } + + public override LocalisableString TooltipText + { + get + { + double speed = config.Get(ManiaRulesetSetting.ScrollSpeed); + double timePerSpeed = config.Get(ManiaRulesetSetting.ScrollTimePerSpeed); + int computedTime = (int)DrawableManiaRuleset.ComputeScrollTime(speed, Current.Value, timePerSpeed); + LocalisableString speedInfo = RulesetSettingsStrings.ScrollSpeedTooltip(computedTime, speed); + return $"{Current.Value}base - ( {speed} - 200) * {timePerSpeed}mps\n = {speedInfo}"; + } + } } - private partial class ManiaScrollTimePerSpeedSlider : RoundedSliderBar + private partial class ManiaScrollMsPerSpeedSlider : RoundedSliderBar { + private ManiaRulesetConfigManager config = null!; + + [BackgroundDependencyLoader] + private void load(ManiaRulesetConfigManager config) + { + this.config = config; + } + + public override LocalisableString TooltipText + { + get + { + double speed = config.Get(ManiaRulesetSetting.ScrollSpeed); + double baseSpeed = config.Get(ManiaRulesetSetting.ScrollBaseSpeed); + int computedTime = (int)DrawableManiaRuleset.ComputeScrollTime(speed, baseSpeed, Current.Value); + LocalisableString speedInfo = RulesetSettingsStrings.ScrollSpeedTooltip(computedTime, speed); + return $"{baseSpeed}base - ( {speed} - 200) * {Current.Value}mps\n = {speedInfo}"; + } + } } } } diff --git a/osu.Game.Rulesets.Mania/Mods/LAsMods/ManiaModJudgmentStyle.cs b/osu.Game.Rulesets.Mania/Mods/LAsMods/ManiaModJudgmentStyle.cs index 2464c7ed43..3c9f02b19f 100644 --- a/osu.Game.Rulesets.Mania/Mods/LAsMods/ManiaModJudgmentStyle.cs +++ b/osu.Game.Rulesets.Mania/Mods/LAsMods/ManiaModJudgmentStyle.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Mods.LAsMods hitWindows.SetDifficulty(difficulty.OverallDifficulty); } - public override void ResetHitWindows() + public override void ResetSettingsToDefaults() { hitWindows.ResetHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/Mods/YuLiangSSSMods/ManiaModNewJudgement.cs b/osu.Game.Rulesets.Mania/Mods/YuLiangSSSMods/ManiaModNewJudgement.cs index 843a619f1b..6398063e3f 100644 --- a/osu.Game.Rulesets.Mania/Mods/YuLiangSSSMods/ManiaModNewJudgement.cs +++ b/osu.Game.Rulesets.Mania/Mods/YuLiangSSSMods/ManiaModNewJudgement.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Mods.YuLiangSSSMods HitWindows.SetDifficulty(difficulty.OverallDifficulty); } - public override void ResetHitWindows() + public override void ResetSettingsToDefaults() { HitWindows.ResetHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs index e3f6301bec..28e6447656 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs @@ -55,12 +55,12 @@ namespace osu.Game.Rulesets.Mania.Scoring public override void SetDifficulty(double difficulty) { - perfect = IBeatmapDifficultyInfo.DifficultyRange(difficulty, perfect_window_range) * multiplier; - great = IBeatmapDifficultyInfo.DifficultyRange(difficulty, great_window_range) * multiplier; - good = IBeatmapDifficultyInfo.DifficultyRange(difficulty, good_window_range) * multiplier; - ok = IBeatmapDifficultyInfo.DifficultyRange(difficulty, ok_window_range) * multiplier; - meh = IBeatmapDifficultyInfo.DifficultyRange(difficulty, meh_window_range) * multiplier; - miss = IBeatmapDifficultyInfo.DifficultyRange(difficulty, miss_window_range) * multiplier; + perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, perfect_window_range) * multiplier) + 0.5; + great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, great_window_range) * multiplier) + 0.5; + good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, good_window_range) * multiplier) + 0.5; + ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, ok_window_range) * multiplier) + 0.5; + meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, meh_window_range) * multiplier) + 0.5; + miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, miss_window_range) * multiplier) + 0.5; } public override double WindowFor(HitResult result) diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2ColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2ColumnBackground.cs index 0a6f9af638..1b644a47a1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2ColumnBackground.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens; using osuTK.Graphics; @@ -22,14 +21,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 public partial class Ez2ColumnBackground : CompositeDrawable, IKeyBindingHandler { private readonly Bindable overlayHeight = new Bindable(); - private readonly Bindable hitPosition = new Bindable(); - private readonly IBindable direction = new Bindable(); + private Bindable hitPosition = new Bindable(); private Color4 brightColour; private Color4 dimColour; private Box background = null!; private Box backgroundOverlay = null!; - private Box? separator; + private Box separator = new Box(); private Bindable accentColour = null!; [Resolved] @@ -43,12 +41,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 public Ez2ColumnBackground() { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.Both; - Masking = true; + // Masking = true; } [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load() { InternalChild = new Container { @@ -99,46 +99,27 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 brightColour = colour.NewValue.Opacity(0.6f); dimColour = colour.NewValue.Opacity(0); }, true); - - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); } protected override void LoadComplete() { base.LoadComplete(); - ezSkinConfig.BindWith(EzSkinSetting.HitPosition, hitPosition); + hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); hitPosition.BindValueChanged(_ => OnConfigChanged(), true); - OnConfigChanged(); } private void OnConfigChanged() { - if (separator != null) - { - separator.Height = DrawHeight - (float)hitPosition.Value; + separator.Height = DrawHeight - (float)hitPosition.Value; - if (drawSeparator(column.Index, stageDefinition)) - { - separator.Alpha = 0.2f; - } - else - { - separator.Alpha = 0; - } - } - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - if (direction.NewValue == ScrollingDirection.Up) + if (drawSeparator(column.Index, stageDefinition)) { - backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft; + separator.Alpha = 0.2f; } else { - backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft; + separator.Alpha = 0; } } @@ -150,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 brightColour = noteColour.Opacity(0.9f); dimColour = noteColour.Opacity(0); - backgroundOverlay.Colour = direction.Value == ScrollingDirection.Up - ? ColourInfo.GradientVertical(brightColour, dimColour) - : ColourInfo.GradientVertical(dimColour, brightColour); + backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour); overlayHeight.Value = 0.5f; diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyArea.cs index a8efe03e5b..543448c8de 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyArea.cs @@ -15,7 +15,6 @@ using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens; @@ -46,9 +45,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 [Resolved] private IGameplayClock gameplayClock { get; set; } = null!; - [Resolved] - private StageDefinition stageDefinition { get; set; } = null!; - [Resolved] private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; @@ -60,9 +56,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - if (stageDefinition.Columns == 14 && column.Index == 13) - return; - hitPosition.Value = (float)ezSkinConfig.GetBindable(EzSkinSetting.HitPosition).Value; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/Ez2KeyAreaPro.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyAreaPlus.cs similarity index 98% rename from osu.Game.Rulesets.Mania/Skinning/EzStylePro/Ez2KeyAreaPro.cs rename to osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyAreaPlus.cs index 34e8ff3216..a92c71f9e1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/Ez2KeyAreaPro.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2/Ez2KeyAreaPlus.cs @@ -15,16 +15,15 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.LAsEZMania; -using osu.Game.Rulesets.Mania.Skinning.Ez2; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro +namespace osu.Game.Rulesets.Mania.Skinning.Ez2 { - public partial class Ez2KeyAreaPro : CompositeDrawable, IKeyBindingHandler + public partial class Ez2KeyAreaPlus : CompositeDrawable, IKeyBindingHandler { private readonly IBindable direction = new Bindable(); @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro [Resolved] private IGameplayClock gameplayClock { get; set; } = null!; - public Ez2KeyAreaPro() + public Ez2KeyAreaPlus() { RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2/ManiaEz2SkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2/ManiaEz2SkinTransformer.cs index 17b4436662..8e130f6984 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2/ManiaEz2SkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2/ManiaEz2SkinTransformer.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.Skinning.Ez2HUD; +using osu.Game.Rulesets.Mania.Skinning.EzStylePro; using osu.Game.Rulesets.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -183,7 +184,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 // if (Skin is Ez2Skin && resultComponent.Component >= HitResult.Perfect) // return Drawable.Empty(); - return new Ez2ColumnBackground(); + return new EzColumnBackground(); case ManiaSkinComponents.KeyArea: return new Ez2KeyArea(); @@ -224,7 +225,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 bool isSpecialColumn = stage.EzIsSpecialColumn(columnIndex); float width = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1f); - float hitPositionValue = (float)hitPosition.Value; // + (float)virtualHitPosition.Value - 110f; + // float hitPositionValue = (float)hitPosition.Value; // + (float)virtualHitPosition.Value - 110f; if (stage.Columns == 14 && columnIndex == 13) width = 0f; @@ -234,8 +235,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2 case LegacyManiaSkinConfigurationLookups.ColumnWidth: return SkinUtils.As(new Bindable(width)); - case LegacyManiaSkinConfigurationLookups.HitPosition: - return SkinUtils.As(new Bindable(hitPositionValue)); + // case LegacyManiaSkinConfigurationLookups.HitPosition: + // return SkinUtils.As(new Bindable(hitPositionValue)); case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/Ez2SongProgress.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/Ez2SongProgress.cs index fcffaf0d59..b6f0f79c5b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/Ez2SongProgress.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/Ez2SongProgress.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); [Resolved] diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboCounter.cs index ff19249506..4c4df61841 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboCounter.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD public Bindable NameDropdown { get; } = new Bindable((EzSelectorNameSet)4); [SettingSource("Effect Type", "Effect Type")] - public Bindable Effect { get; } = new Bindable(EffectType.Scale); + public Bindable Effect { get; } = new Bindable(EzComEffectType.Scale); // [SettingSource("Effect Origin", "Effect Origin", SettingControlType = typeof(AnchorDropdown))] // public Bindable EffectOrigin { get; } = new Bindable(Anchor.TopCentre) @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD Precision = 0.01f, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); public EzComboText Text = null!; @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD { switch (Effect.Value) { - case EffectType.Scale: + case EzComEffectType.Scale: EzEffectHelper.ApplyScaleAnimation( Text.TextContainer, wasIncrease, @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD EffectEndDuration.Value); break; - case EffectType.Bounce: + case EzComEffectType.Bounce: EzEffectHelper.ApplyBounceAnimation( Text.TextContainer, wasIncrease, diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboSprite.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboSprite.cs index 3b45b78614..e865c4e6d9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboSprite.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComComboSprite.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD public Bindable NameDropdown { get; } = new Bindable((EzSelectorNameSet)4); [SettingSource("Effect Type", "Effect Type")] - public Bindable Effect { get; } = new Bindable(EffectType.Scale); + public Bindable Effect { get; } = new Bindable(EzComEffectType.Scale); [SettingSource("Effect Origin", "Effect Origin", SettingControlType = typeof(AnchorDropdown))] public Bindable EffectOrigin { get; } = new Bindable(Anchor.TopCentre) @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD Precision = 0.01f, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); public EzComboText Text = null!; @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD { switch (Effect.Value) { - case EffectType.Scale: + case EzComEffectType.Scale: EzEffectHelper.ApplyScaleAnimation( Text.TextContainer, wasIncrease, @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD EffectEndDuration.Value); break; - case EffectType.Bounce: + case EzComEffectType.Bounce: EzEffectHelper.ApplyBounceAnimation( Text.TextContainer, wasIncrease, diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComHitTiming.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComHitTiming.cs index bad86f1d01..13519bc5b7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComHitTiming.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComHitTiming.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD Precision = 0.01f, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); private Container timingContainer = null!; diff --git a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComKeyCounterDisplay.cs b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComKeyCounterDisplay.cs index 2d68ee272d..47ffdc3fd3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComKeyCounterDisplay.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Ez2HUD/EzComKeyCounterDisplay.cs @@ -51,7 +51,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2HUD protected override void LoadComplete() { base.LoadComplete(); - updateWidths(); triggers.BindTo(controller.Triggers); triggers.BindCollectionChanged(triggersChanged, true); columnWidth.BindValueChanged(_ => updateWidths(), true); diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzColumnBackground.cs new file mode 100644 index 0000000000..675e877947 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzColumnBackground.cs @@ -0,0 +1,168 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Screens; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro +{ + public partial class EzColumnBackground : CompositeDrawable, IKeyBindingHandler + { + private Bindable hitPosition = new Bindable(); + private Color4 brightColour; + private Color4 dimColour; + + private Box backgroundOverlay = null!; + + private readonly Box separator = new Box + { + Name = "Separator", + Anchor = Anchor.TopRight, + Origin = Anchor.TopCentre, + Width = 2, + Colour = Color4.White.Opacity(0.5f), + Alpha = 0, + }; + + private Bindable accentColour = null!; + + [Resolved] + protected Column Column { get; private set; } = null!; + + [Resolved] + private StageDefinition stageDefinition { get; set; } = null!; + + [Resolved] + private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + + public EzColumnBackground() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.Both; + // Masking = true; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.8f).Darken(3), + }, + backgroundOverlay = new Box + { + Name = "Background Gradient Overlay", + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 0.5f, + Blending = BlendingParameters.Additive, + Alpha = 0 + }, + } + }; + + accentColour = new Bindable(ezSkinConfig.GetColumnColor(stageDefinition.Columns, Column.Index)); + accentColour.BindValueChanged(colour => + { + var newColour = colour.NewValue.Darken(3); + + if (newColour.A != 0) + { + newColour = newColour.Opacity(0.8f); + } + + backgroundOverlay.Colour = newColour; + brightColour = colour.NewValue.Opacity(0.6f); + dimColour = colour.NewValue.Opacity(0); + }, true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + bool hasSeparator = Column.TopLevelContainer.Children + .OfType() + .Any(b => b.Name == "Separator"); + + if (!hasSeparator) + Column.TopLevelContainer.Add(separator); + + hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + hitPosition.BindValueChanged(_ => OnConfigChanged(), true); + } + + protected virtual Color4 NoteColor + { + get + { + int keyMode = stageDefinition.Columns; + int columnIndex = Column.Index; + return ezSkinConfig.GetColumnColor(keyMode, columnIndex); + } + } + + private void OnConfigChanged() + { + separator.Height = DrawHeight - (float)hitPosition.Value; + + if (drawSeparator(Column.Index, stageDefinition)) + separator.Alpha = 0.25f; + else + separator.Alpha = 0; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == Column.Action.Value) + { + var noteColour = NoteColor; + brightColour = noteColour.Opacity(0.9f); + dimColour = noteColour.Opacity(0); + backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour); + backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == Column.Action.Value) + backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); + } + + //TODO: 这里的逻辑可以优化,避免重复计算 + private bool drawSeparator(int columnIndex, StageDefinition stage) + { + return stage.Columns switch + { + 12 => columnIndex is 0 or 10, + 14 => columnIndex is 0 or 5 or 6 or 11, + 16 => columnIndex is 0 or 5 or 9 or 14, + _ => false + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitExplosion.cs index 3ece24ffd0..b3b39c99fe 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitExplosion.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -9,10 +8,8 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens; using osu.Game.Screens.LAsEzExtensions; using osuTK; @@ -21,22 +18,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { public partial class EzHitExplosion : CompositeDrawable, IHitExplosion { - // public override bool RemoveWhenNotAlive => true; - - private readonly IBindable direction = new Bindable(); - private readonly Bindable columnWidth = new Bindable(); - - private IBindable noteHeightBindable = new Bindable(); - private IBindable columnWidthBindable = new Bindable(); - private IBindable specialFactorBindable = new Bindable(); - private IBindable hitPosition = new Bindable(); - private TextureAnimation? animation; private TextureAnimation? animationP; private Container container = null!; - [UsedImplicitly] - private float baseYPosition; + private IBindable noteHeightBindable = new Bindable(); + private IBindable columnWidthBindable = new Bindable(); + private IBindable specialFactorBindable = new Bindable(); + + // public override bool RemoveWhenNotAlive => true; [Resolved] private Column column { get; set; } = null!; @@ -45,10 +35,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private StageDefinition stageDefinition { get; set; } = null!; [Resolved] - private EzLocalTextureFactory factory { get; set; } = null!; + private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; [Resolved] - private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + private EzLocalTextureFactory factory { get; set; } = null!; public EzHitExplosion() { @@ -57,27 +47,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro } [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load() { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; noteHeightBindable = ezSkinConfig.GetBindable(EzSkinSetting.NonSquareNoteHeight); columnWidthBindable = ezSkinConfig.GetBindable(EzSkinSetting.ColumnWidth); specialFactorBindable = ezSkinConfig.GetBindable(EzSkinSetting.SpecialFactor); - hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); noteHeightBindable.BindValueChanged(_ => updateY(), true); columnWidthBindable.BindValueChanged(_ => updateY(), true); specialFactorBindable.BindValueChanged(_ => updateY(), true); - hitPosition.BindValueChanged(_ => updateY(), true); } protected override void LoadComplete() { base.LoadComplete(); onSkinChanged(); - factory.OnNoteChanged += onSkinChanged; } @@ -85,10 +72,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { base.Dispose(isDisposing); - if (isDisposing) - { - factory.OnNoteChanged -= onSkinChanged; - } + if (isDisposing) { factory.OnNoteChanged -= onSkinChanged; } } private void loadAnimation() @@ -98,9 +82,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro animation = factory.CreateAnimation("noteflare"); animationP = factory.CreateAnimation("noteflaregood"); - animation.Loop = false; - animationP.Loop = false; - container = new Container { Anchor = Anchor.BottomCentre, @@ -116,18 +97,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private void updateY() { - bool isSpecialColumn = stageDefinition.EzIsSpecialColumn(column.Index); - columnWidth.Value = columnWidthBindable.Value * (isSpecialColumn ? specialFactorBindable.Value : 1); + bool isSpecialColumn = EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) == "S1"; + double columnWidth = columnWidthBindable.Value * (isSpecialColumn ? specialFactorBindable.Value : 1); bool isSquare = factory.IsSquareNote("whitenote"); float aspectRatio = factory.GetRatio("whitenote"); float moveY = isSquare - ? (float)columnWidth.Value / 2 * aspectRatio + ? (float)columnWidth / 2 * aspectRatio : (float)noteHeightBindable.Value / 2 * aspectRatio; - baseYPosition = 110f - (float)hitPosition.Value - moveY; - Position = new Vector2(0, baseYPosition); + // baseYPosition = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPosition.Value - moveY; + Position = new Vector2(0, -moveY); } private void onSkinChanged() @@ -135,12 +116,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro loadAnimation(); } - private void onDirectionChanged(ValueChangedEvent direction) - { - Rotation = direction.NewValue == ScrollingDirection.Up ? 90f : 0; - Anchor = Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; - } - public void Animate(JudgementResult result) { loadAnimation(); diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitTarget.cs index da1074c198..2b04878056 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHitTarget.cs @@ -3,20 +3,15 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { internal partial class EzHitTarget : EzNote { - private readonly IBindable direction = new Bindable(); - private IBindable hitPosition = new Bindable(); + // private IBindable hitPosition = new Bindable(); protected override bool ShowSeparators => false; protected override bool UseColorization => false; @@ -28,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro [Resolved] private IGameplayClock gameplayClock { get; set; } = null!; - [Resolved] - private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + // [Resolved] + // private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; public EzHitTarget() { @@ -39,16 +34,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro } [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load() { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - - hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); - hitPosition.BindValueChanged(_ => updateY(), true); + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + // hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + // hitPosition.BindValueChanged(_ => updateY(), true); } - private float baseYPosition; + // private float baseYPosition = 0f; private double beatInterval; protected override void LoadComplete() @@ -65,18 +59,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro double progress = (gameplayClock.CurrentTime % beatInterval) / beatInterval; // 平滑正弦波效果 double smoothValue = 0.3 * Math.Sin(progress * 2 * Math.PI); - Y = baseYPosition + (float)(smoothValue * 6); + Y = (float)(smoothValue * 6); } - private void updateY() - { - baseYPosition = 110f - (float)hitPosition.Value; - Position = new Vector2(0, baseYPosition); - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - Anchor = Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; - } + //DrawableManiaRuleset中关联设置后,此处不必设置 + // private void updateY() + // { + // baseYPosition = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPosition.Value; + // Position = new Vector2(0, baseYPosition); + // } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHead.cs index 43daa02b81..64b247186c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHead.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens; using osu.Game.Screens.LAsEzExtensions; @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private Bindable nonSquareNoteHeight = null!; private TextureAnimation animation = null!; - private Container? container; + private Container container = null!; private EzNoteSideLine? noteSeparatorsL; private EzNoteSideLine? noteSeparatorsR; @@ -45,13 +44,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro nonSquareNoteHeight = ezSkinConfig.GetBindable(EzSkinSetting.NonSquareNoteHeight); enabledColor = ezSkinConfig.GetBindable(EzSkinSetting.ColorSettingsEnabled); + OnSkinChanged(); } protected override void LoadComplete() { base.LoadComplete(); - OnSkinChanged(); - nonSquareNoteHeight.ValueChanged += _ => updateSizes(); + + nonSquareNoteHeight.BindValueChanged(_ => updateSizes(), true); ezSkinConfig.OnSettingsChanged += OnConfigChanged; factory.OnNoteChanged += OnSkinChanged; } @@ -84,14 +84,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro if (enabledColor.Value) return "white"; - if (stageDefinition.EzIsSpecialColumn(column.Index)) + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) == "S1") return "green"; int logicalIndex = 0; for (int i = 0; i < column.Index; i++) { - if (!stageDefinition.EzIsSpecialColumn(i)) + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, i) != "S1") logicalIndex++; } @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro AddInternal(animation); } - Schedule(updateSizes); + OnConfigChanged(); } private void updateSizes() @@ -176,11 +176,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro Height = noteHeight; - if (container != null) + if (container.Children.Count > 0 && container.Child is Container c) { container.Height = noteHeight / 2; - if (container.Child is Container containerA) - containerA.Height = noteHeight; + c.Height = noteHeight; } } @@ -191,7 +190,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro noteColor = NoteColor; animation.Colour = noteColor; - + container.Colour = noteColor; noteSeparatorsL?.UpdateGlowEffect(noteColor); noteSeparatorsR?.UpdateGlowEffect(noteColor); diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHittingLayer.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHittingLayer.cs index 35d3affc49..dcd6d0607b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHittingLayer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteHittingLayer.cs @@ -8,10 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens; using osu.Game.Screens.LAsEzExtensions; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro @@ -19,17 +19,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro public partial class EzHoldNoteHittingLayer : CompositeDrawable { public readonly Bindable IsHitting = new Bindable(); - private readonly IBindable noteHeightBindable = new Bindable(); - private readonly IBindable columnWidthBindable = new Bindable(); - private readonly IBindable specialFactorBindable = new Bindable(); - private readonly IBindable hitPosition = new Bindable(); - private TextureAnimation? animation; - // [UsedImplicitly] - private readonly Bindable noteSize = new Bindable(); + private IBindable noteHeightBindable = new Bindable(); + private IBindable columnWidthBindable = new Bindable(); + private IBindable specialFactorBindable = new Bindable(); - private float baseYPosition; + public IBindable HitPosition { get; set; } = new Bindable(); + + // private IBindable hitPosition = new Bindable(); + // private float baseYPosition; [Resolved] private Column column { get; set; } = null!; @@ -38,50 +37,30 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private StageDefinition stageDefinition { get; set; } = null!; [Resolved] - private EzLocalTextureFactory factory { get; set; } = null!; + private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; [Resolved] - private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + private EzLocalTextureFactory factory { get; set; } = null!; public EzHoldNoteHittingLayer() { Anchor = Anchor.BottomCentre; Origin = Anchor.Centre; - AutoSizeAxes = Axes.Both; + // AutoSizeAxes = Axes.Both; Blending = BlendingParameters.Additive; } [BackgroundDependencyLoader] private void load() { - noteHeightBindable.BindTo(ezSkinConfig.GetBindable(EzSkinSetting.NonSquareNoteHeight)); - columnWidthBindable.BindTo(ezSkinConfig.GetBindable(EzSkinSetting.ColumnWidth)); - specialFactorBindable.BindTo(ezSkinConfig.GetBindable(EzSkinSetting.SpecialFactor)); - hitPosition.BindTo(ezSkinConfig.GetBindable(EzSkinSetting.HitPosition)); - - hitPosition.BindValueChanged(_ => updateY(), true); - noteHeightBindable.BindValueChanged(_ => updateY(), true); - columnWidthBindable.BindValueChanged(_ => updateY()); - specialFactorBindable.BindValueChanged(_ => updateY()); - - noteSize.BindValueChanged(_ => updateY(), true); - IsHitting.BindValueChanged(hitting => - { - ClearTransforms(); - // Logger.Log($"IsHitting changed to: {hitting.NewValue}", LoggingTarget.Runtime, LogLevel.Debug); - // animation.IsPlaying = hitting.NewValue; - - if (hitting.NewValue && animation.IsNotNull()) - { - Alpha = 1; - - animation.Restart(); - } - else - { - Alpha = 0; - } - }, true); + noteHeightBindable = ezSkinConfig.GetBindable(EzSkinSetting.NonSquareNoteHeight); + columnWidthBindable = ezSkinConfig.GetBindable(EzSkinSetting.ColumnWidth); + specialFactorBindable = ezSkinConfig.GetBindable(EzSkinSetting.SpecialFactor); + HitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + noteHeightBindable.BindValueChanged(_ => UpdateLNsLight(), true); + columnWidthBindable.BindValueChanged(_ => UpdateLNsLight(), true); + specialFactorBindable.BindValueChanged(_ => UpdateLNsLight(), true); + // hitPosition.BindValueChanged(_ => updateY(), true); } protected override void LoadComplete() @@ -89,6 +68,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro base.LoadComplete(); onSkinChanged(); factory.OnNoteChanged += onSkinChanged; + HitPosition.BindValueChanged(pos => Y = + LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)pos.NewValue, true); + + IsHitting.BindValueChanged(hitting => + { + ClearTransforms(); + // Logger.Log($"IsHitting changed to: {hitting.NewValue}", LoggingTarget.Runtime, LogLevel.Debug); + // animation.IsPlaying = hitting.NewValue; + + if (hitting.NewValue && animation.IsNotNull() && animation.FrameCount > 0) + { + Alpha = 1; + animation.Restart(); + } + else + { + Alpha = 0; + } + }, true); } protected override void Dispose(bool isDisposing) @@ -126,32 +124,22 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro animation.Loop = true; AddInternal(animation); - updateY(); + UpdateLNsLight(); } - private void updateY() + public void UpdateLNsLight() { - bool isSpecialColumn = stageDefinition.EzIsSpecialColumn(column.Index); + bool isSpecialColumn = EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) == "S1"; double columnWidth = columnWidthBindable.Value * (isSpecialColumn ? specialFactorBindable.Value : 1); bool isSquare = factory.IsSquareNote("whitenote"); - var tempContainer = factory.CreateAnimation("whitenote"); - float aspectRatio = 1f; + float aspectRatio = factory.GetRatio("whitenote"); - if (tempContainer.FrameCount > 0) - { - aspectRatio = tempContainer.CurrentFrame.Height / (float)tempContainer.CurrentFrame.Width; - } - - tempContainer.Dispose(); float moveY = isSquare ? (float)columnWidth / 2 * aspectRatio : (float)noteHeightBindable.Value * aspectRatio; - baseYPosition = 110f - (float)hitPosition.Value - moveY; - Position = new Vector2(0, baseYPosition); - - Invalidate(); + Position = new Vector2(0, moveY); } private void onSkinChanged() diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteMiddle.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteMiddle.cs index c41862fc14..78f9dc2a0e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteMiddle.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzHoldNoteMiddle.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.Skinning.Legacy; @@ -34,7 +33,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private Container middleScaleContainer = null!; private Container middleInnerContainer = null!; - private EzSkinSettingsManager ezSkinConfig = null!; + private IBindable noteHeight = new Bindable(); + private IBindable hitPosition = new Bindable(); private Bindable enabledColor = null!; private Drawable? container; private Drawable? lightContainer; @@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro [Resolved] private StageDefinition stageDefinition { get; set; } = null!; + [Resolved] + private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + [Resolved] private EzLocalTextureFactory factory { get; set; } = null!; @@ -60,12 +63,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro } [BackgroundDependencyLoader(true)] - private void load(EzSkinSettingsManager ezSkinConfig, DrawableHitObject drawableObject) + private void load(DrawableHitObject drawableObject) { - this.ezSkinConfig = ezSkinConfig; holdNote = (DrawableHoldNote)drawableObject; isHitting.BindTo(holdNote.IsHolding); + noteHeight = ezSkinConfig.GetBindable(EzSkinSetting.NonSquareNoteHeight); + hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); enabledColor = ezSkinConfig.GetBindable(EzSkinSetting.ColorSettingsEnabled); } @@ -85,6 +89,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { base.LoadComplete(); OnSkinChanged(); + noteHeight.BindValueChanged(_ => OnSettingsChanged(), true); factory.OnNoteChanged += OnSkinChanged; ezSkinConfig.OnSettingsChanged += OnSettingsChanged; isHitting.BindValueChanged(onIsHittingChanged, true); @@ -104,6 +109,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private void OnSkinChanged() { + if (lightContainer != null) + { + column.TopLevelContainer.Remove(lightContainer, false); + lightContainer.Expire(); + lightContainer = null; + } + loadAnimation(); hittingLayer = new EzHoldNoteHittingLayer { @@ -115,6 +127,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro Alpha = 0, Child = hittingLayer }; + hittingLayer.HitPosition.BindTo(hitPosition); } private void onIsHittingChanged(ValueChangedEvent isHitting) @@ -157,14 +170,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro if (enabledColor.Value) return "white"; - if (stageDefinition.EzIsSpecialColumn(column.Index)) + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) == "S1") return "green"; int logicalIndex = 0; for (int i = 0; i < column.Index; i++) { - if (!stageDefinition.EzIsSpecialColumn(i)) + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) != "S1") logicalIndex++; } @@ -172,15 +185,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro } } - protected virtual string ComponentName => $"{ColorPrefix}longnote/middle"; - protected virtual string ComponentName2 => $"{ColorPrefix}longnote/tail"; - private void loadAnimation() { ClearInternal(); string backupComponentName = $"{ColorPrefix}note"; - middleAnimation = factory.CreateAnimation(ComponentName); - tailAnimation = factory.CreateAnimation(ComponentName2); + middleAnimation = factory.CreateAnimation($"{ColorPrefix}longnote/middle"); + tailAnimation = factory.CreateAnimation($"{ColorPrefix}longnote/tail"); if (middleAnimation.FrameCount == 0) { diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageKeys.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs similarity index 60% rename from osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageKeys.cs rename to osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs index 51adbbc1fd..ce33f47dd5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageKeys.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs @@ -3,21 +3,22 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; +using osu.Game.Rulesets.Mania.Skinning.Legacy; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens; using osu.Game.Screens.LAsEzExtensions; namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { - public partial class EzStageKeys : CompositeDrawable + public partial class EzKeyArea : LegacyManiaColumnElement, IKeyBindingHandler { - private TextureAnimation animation = null!; private Drawable container = null!; - + private Drawable upSprite = null!; + private Drawable downSprite = null!; protected virtual bool IsKeyPress => true; protected virtual bool UseColorization => true; @@ -33,18 +34,21 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro [Resolved] private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + public EzKeyArea() + { + RelativeSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] private void load() { - RelativeSizeAxes = Axes.X; - FillMode = FillMode.Fill; } protected override void LoadComplete() { base.LoadComplete(); OnSkinChanged(); - factory.OnNoteChanged += OnSkinChanged; + factory.OnStageChanged += OnSkinChanged; ezSkinConfig.OnSettingsChanged += OnConfigChanged; } @@ -54,11 +58,55 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro if (isDisposing) { - factory.OnNoteChanged -= OnSkinChanged; + factory.OnStageChanged -= OnSkinChanged; ezSkinConfig.OnSettingsChanged -= OnConfigChanged; } } + protected virtual string KeyBasicSuffix + { + get + { + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) == "S1") + return "02"; + + int logicalIndex = 0; + + for (int i = 0; i < column.Index; i++) + { + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, i) == "S1") + logicalIndex++; + } + + return logicalIndex % 2 == 0 ? "00" : "01"; + } + } + + private void loadAnimation() + { + ClearInternal(); + + upSprite = factory.CreateStage("keybase"); + downSprite = factory.CreateStage("keypress"); + downSprite.Alpha = 0; + + container = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + upSprite, + downSprite, + } + }; + + OnConfigChanged(); + AddInternal(container); + } + private void updateSizes() { bool isSquare = factory.IsSquareNote("whitenote"); @@ -67,25 +115,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro : (float)(ezSkinConfig.GetBindable(EzSkinSetting.NonSquareNoteHeight).Value); } - private void loadAnimation() - { - ClearInternal(); - - animation = factory.CreateAnimation(ComponentName); - container = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = animation - }; - - OnConfigChanged(); - AddInternal(container); - } - - private void OnSkinChanged() => loadAnimation(); - private void OnConfigChanged() { Schedule(() => @@ -95,32 +124,26 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro }); } - protected virtual string KeyPressPrefix => IsKeyPress switch - { - true => "keypress/KeyBasicPress_", - _ => "keybase/KeyBasicBase_", - }; + private void OnSkinChanged() => loadAnimation(); - protected virtual string KeyBasicSuffix + public bool OnPressed(KeyBindingPressEvent e) { - get + if (e.Action == column.Action.Value) { - if (stageDefinition.EzIsSpecialColumn(column.Index)) - return "02"; - - int logicalIndex = 0; - - for (int i = 0; i < column.Index; i++) - { - if (!stageDefinition.EzIsSpecialColumn(i)) - logicalIndex++; - } - - return logicalIndex % 2 == 0 ? "00" : "01"; + upSprite.FadeTo(0); + downSprite.FadeTo(1); } + + return false; } - protected virtual string StagePrefix => EzStageBottom.StagePrefix; - protected virtual string ComponentName => $"{StagePrefix}/{KeyPressPrefix}{KeyBasicSuffix}"; + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == column.Action.Value) + { + upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1); + downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0); + } + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzNote.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzNote.cs index 2eceef6696..0116e07446 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzNote.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzNote.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens; using osu.Game.Screens.LAsEzExtensions; @@ -88,14 +87,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro if (enabledColor.Value) return "white"; - if (stageDefinition.EzIsSpecialColumn(column.Index)) + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, column.Index) == "S1") return "green"; int logicalIndex = 0; for (int i = 0; i < column.Index; i++) { - if (!stageDefinition.EzIsSpecialColumn(i)) + if (EzColumnTypeManager.GetColumnType(stageDefinition.Columns, i) != "S1") logicalIndex++; } diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageBottom.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageBottom.cs index af38c74208..7bfaf9173a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageBottom.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzStageBottom.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Screens; using osu.Game.Screens.LAsEzExtensions; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro @@ -29,20 +30,21 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro private void load() { RelativeSizeAxes = Axes.Both; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; hitPositon = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); columnWidth = ezSkinConfig.GetBindable(EzSkinSetting.ColumnWidth); hitPositon.BindValueChanged(_ => OnConfigChanged()); columnWidth.BindValueChanged(_ => OnConfigChanged()); + OnSkinChanged(); } protected override void LoadComplete() { base.LoadComplete(); - OnSkinChanged(); - factory.OnNoteChanged += OnSkinChanged; + + factory.OnStageChanged += OnSkinChanged; } protected override void Dispose(bool isDisposing) @@ -51,24 +53,27 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro if (isDisposing) { - factory.OnNoteChanged -= OnSkinChanged; + factory.OnStageChanged -= OnSkinChanged; } } - public static string StagePrefix => "Stage/fivekey"; - protected static string ComponentName => $"{StagePrefix}/Body"; + // protected override void Update() + // { + // base.Update(); + // updateSizes(); + // } private void loadAnimation() { ClearInternal(); - var stageBottom = factory.CreateStage(ComponentName); + var stageBottom = factory.CreateStage("Body"); sprite = new Container { RelativeSizeAxes = Axes.None, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - // Masking = true, + Child = stageBottom }; // sprite.Depth = float.MinValue; @@ -76,23 +81,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro AddInternal(sprite); } - protected override void Update() - { - base.Update(); - updateSizes(); - } - private void updateSizes() { if (sprite == null) return; float actualPanelWidth = DrawWidth; - double scale = actualPanelWidth / 410.0; - sprite.Scale = new Vector2((float)scale); + float scale = actualPanelWidth / 410.0f; + sprite.Scale = new Vector2(scale); - Y = 274; - Position = new Vector2(0, 660 + 110 - (float)hitPositon.Value); + sprite.Y = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPositon.Value - DrawHeight * 0.865f; + // Position = new Vector2(0, 415 + 110 - (float)hitPositon.Value); } private void OnConfigChanged() diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/ManiaEzStyleProSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/ManiaEzStyleProSkinTransformer.cs index eb5946bdd7..193cc871a4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/ManiaEzStyleProSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/ManiaEzStyleProSkinTransformer.cs @@ -7,16 +7,15 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.Skinning.Ez2; using osu.Game.Rulesets.Mania.Skinning.Ez2HUD; using osu.Game.Rulesets.Scoring; using osu.Game.Screens; +using osu.Game.Screens.LAsEzExtensions; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Skinning.Components; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { @@ -180,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro // if (Skin is Ez2Skin && resultComponent.Component >= HitResult.Perfect) // return Drawable.Empty(); - return new Ez2ColumnBackground(); + return new EzColumnBackground(); case ManiaSkinComponents.KeyArea: return new Ez2KeyArea(); @@ -227,7 +226,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro { int columnIndex = maniaLookup.ColumnIndex ?? 0; var stage = beatmap.GetStageForColumnIndex(columnIndex); - bool isSpecialColumn = stage.EzIsSpecialColumn(columnIndex); + bool isSpecialColumn = EzColumnTypeManager.GetColumnType(stage.Columns, columnIndex) == "S1"; float width = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1f); // float hitPositionValue = (float)hitPosition.Value; // + (float)virtualHitPosition.Value - 110f; @@ -242,9 +241,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro // case LegacyManiaSkinConfigurationLookups.HitPosition: // return SkinUtils.As(new Bindable(hitPositionValue)); - case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: - var colour = stage.GetColourForLayout(columnIndex); - return SkinUtils.As(new Bindable(colour)); + // case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + // var colour = stage.GetColourForLayout(columnIndex); + // return SkinUtils.As(new Bindable(colour)); case LegacyManiaSkinConfigurationLookups.BarLineHeight: return SkinUtils.As(new Bindable(1)); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs index 608cde7272..130410ab6a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs @@ -5,8 +5,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning.Legacy @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy public partial class HitTargetInsetContainer : Container { private readonly IBindable direction = new Bindable(); - + private Bindable hitPositonBindable = new Bindable(); protected override Container Content => content; private readonly Container content; @@ -28,14 +28,21 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + private void load(ISkinSource skin, EzSkinSettingsManager ezSkinConfig, IScrollingInfo scrollingInfo) { - hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; + hitPositonBindable = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + hitPositonBindable.BindValueChanged(_ => UpdateHitPosition(), true); + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? (float)hitPositonBindable.Value; direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); } + protected virtual void UpdateHitPosition() + { + hitPosition = (float)hitPositonBindable.Value; + } + private void onDirectionChanged(ValueChangedEvent direction) { content.Padding = direction.NewValue == ScrollingDirection.Up diff --git a/osu.Game.Rulesets.Mania/Skinning/SbI/ManiaSbISkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/SbI/ManiaSbISkinTransformer.cs index 5a74a20434..7a62539bae 100644 --- a/osu.Game.Rulesets.Mania/Skinning/SbI/ManiaSbISkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/SbI/ManiaSbISkinTransformer.cs @@ -14,7 +14,6 @@ using osu.Game.Skinning; using osu.Game.Skinning.Components; using osuTK; using osuTK.Graphics; -using EffectType = osu.Game.Skinning.Components.EffectType; namespace osu.Game.Rulesets.Mania.Skinning.SbI { @@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI combo1.Anchor = Anchor.TopCentre; combo1.Origin = Anchor.Centre; combo1.Y = 200; - combo1.Effect.Value = EffectType.None; + combo1.Effect.Value = EzComEffectType.None; combo1.NameDropdown.Value = (EzSelectorNameSet)43; } diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index a112b3ea7d..a3ec5d23c5 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -11,9 +11,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Layout; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Screens; +using osu.Game.Screens.LAsEzExtensions; using osu.Game.Skinning; using osuTK; @@ -68,28 +68,28 @@ namespace osu.Game.Rulesets.Mania.UI [Resolved] private ISkinSource skin { get; set; } = null!; + [Resolved] + private SkinManager skinManager { get; set; } = null!; + [Resolved] private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; private readonly Bindable mobileLayout = new Bindable(); private readonly Bindable columnWidthBindable = new Bindable(); private readonly Bindable specialFactorBindable = new Bindable(); + private readonly Bindable ezColumnWidthStyle = new Bindable(); [BackgroundDependencyLoader] private void load(ManiaRulesetConfigManager? rulesetConfig) { rulesetConfig?.BindWith(ManiaRulesetSetting.MobileLayout, mobileLayout); + ezSkinConfig.BindWith(EzSkinSetting.ColumnWidthStyle, ezColumnWidthStyle); ezSkinConfig.BindWith(EzSkinSetting.ColumnWidth, columnWidthBindable); ezSkinConfig.BindWith(EzSkinSetting.SpecialFactor, specialFactorBindable); - columnWidthBindable.BindValueChanged(v => - { - updateColumnSize(); - }); - specialFactorBindable.BindValueChanged(v => - { - updateColumnSize(); - }); + ezColumnWidthStyle.BindValueChanged(v => updateColumnSize()); + columnWidthBindable.BindValueChanged(v => updateColumnSize()); + specialFactorBindable.BindValueChanged(v => updateColumnSize()); mobileLayout.BindValueChanged(_ => invalidateLayout()); skin.SourceChanged += invalidateLayout; @@ -156,8 +156,6 @@ namespace osu.Game.Rulesets.Mania.UI new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i)) ?.Value; - bool isSpecialColumn = stageDefinition.EzIsSpecialColumn(i); - if (width == 0) { columns[i].Width = 0; @@ -165,7 +163,26 @@ namespace osu.Game.Rulesets.Mania.UI continue; } - width = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1); + bool isSpecialColumn = + EzColumnTypeManager.GetColumnType(stageDefinition.Columns, i) == "S1"; + float ezWidth = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1); + + switch (ezColumnWidthStyle.Value) + { + case EzColumnWidthStyle.EzStyleProOnly: + var skinInfo = skinManager.CurrentSkinInfo.Value; + if (skinInfo.Value.Name.Contains("Ez Style Pro")) + width = ezWidth; + break; + + case EzColumnWidthStyle.GlobalWidth: + width = ezWidth; + break; + + case EzColumnWidthStyle.GlobalTotalWidth: + width = ezWidth * 10 / stageDefinition.Columns; + break; + } // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) width ??= isSpecialColumn ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitPositionPaddedContainer.cs b/osu.Game.Rulesets.Mania/UI/Components/HitPositionPaddedContainer.cs index 0630c67d53..a697000700 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitPositionPaddedContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitPositionPaddedContainer.cs @@ -23,12 +23,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components [Resolved] private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + private Bindable hitPositonBindable = new Bindable(); + [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { Direction.BindTo(scrollingInfo.Direction); Direction.BindValueChanged(_ => UpdateHitPosition(), true); + hitPositonBindable = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + hitPositonBindable.BindValueChanged(_ => UpdateHitPosition(), true); skin.SourceChanged += onSkinChanged; } @@ -38,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { float hitPosition = skin.GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? Stage.HIT_TARGET_POSITION; + ?? (float)hitPositonBindable.Value; Padding = Direction.Value == ScrollingDirection.Up ? new MarginPadding { Top = hitPosition } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 1d04724409..57f57283fb 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.LAsEZMania; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Skinning; @@ -27,6 +28,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -79,6 +81,12 @@ namespace osu.Game.Rulesets.Mania.UI [Resolved] private GameHost gameHost { get; set; } = null!; + [Resolved] + private EzSkinSettingsManager ezSkinConfig { get; set; } = null!; + + private Bindable hitPositonBindable = new Bindable(); + private readonly Bindable globalHitPosition = new Bindable(); + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { @@ -125,6 +133,11 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.MobileLayout, mobileLayout); mobileLayout.BindValueChanged(_ => updateMobileLayout(), true); + + hitPositonBindable = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + hitPositonBindable.BindValueChanged(_ => skinChanged(), true); + ezSkinConfig.BindWith(EzSkinSetting.GlobalHitPosition, globalHitPosition); + globalHitPosition.BindValueChanged(_ => skinChanged(), true); } private ManiaTouchInputArea? touchInputArea; @@ -167,9 +180,14 @@ namespace osu.Game.Rulesets.Mania.UI private void skinChanged() { - hitPosition = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? Stage.HIT_TARGET_POSITION; + if (globalHitPosition.Value) + hitPosition = (float)hitPositonBindable.Value; + else + { + hitPosition = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? (float)hitPositonBindable.Value; + } pendingSkinChange = null; } @@ -192,7 +210,7 @@ namespace osu.Game.Rulesets.Mania.UI case EzManiaScrollingStyle.ScrollTimeStyleFixed: // Ensure the travel time from the top of the screen to the hit position remains constant. - scale = length_to_default_hit_position / lengthToHitPosition; + scale = lengthToHitPosition / 768; break; } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 33ae2c68e6..496e7610ff 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -36,22 +35,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestPlayfieldBasedSize() { - ModFlashlight mod = new OsuModFlashlight(); + OsuModFlashlight flashlight; CreateModTest(new ModTestData { - Mod = mod, + Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()], PassCondition = () => { var flashlightOverlay = Player.DrawableRuleset.Overlays .ChildrenOfType.Flashlight>() .First(); - return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize()); + // the combo check is here because the flashlight radius decreases for the first time at 100 combo + // and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()` + return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100; } }); - - AddStep("adjust playfield scale", () => - Player.DrawableRuleset.Playfield.Scale = new Vector2(.5f)); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs index 1bb2f24c1c..b4298344b8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Tests.Visual; using osuTK; @@ -22,21 +21,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public partial class TestSceneOsuModRelax : OsuModTestScene { - private readonly HitCircle hitObject; - private readonly HitWindows hitWindows = new OsuHitWindows(); - - public TestSceneOsuModRelax() - { - hitWindows.SetDifficulty(9); - - hitObject = new HitCircle - { - StartTime = 1000, - Position = new Vector2(100, 100), - HitWindows = hitWindows - }; - } - protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModRelaxTestPlayer(CurrentTestData, AllowFail); [Test] @@ -46,12 +30,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, CreateBeatmap = () => new Beatmap { - HitObjects = new List { hitObject } + Difficulty = { OverallDifficulty = 9 }, + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100, 100), + HitWindows = new OsuHitWindows() + } + } }, ReplayFrames = new List { new OsuReplayFrame(0, new Vector2()), - new OsuReplayFrame(hitObject.StartTime, hitObject.Position), + new OsuReplayFrame(100, new Vector2(100)), }, PassCondition = () => Player.ScoreProcessor.Combo.Value == 1 }); @@ -63,13 +56,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, CreateBeatmap = () => new Beatmap { - HitObjects = new List { hitObject } + Difficulty = { OverallDifficulty = 9 }, + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100, 100), + HitWindows = new OsuHitWindows() + } + } }, ReplayFrames = new List { - new OsuReplayFrame(0, new Vector2(hitObject.X - 22, hitObject.Y - 22)), // must be an edge hit for the cursor to not stay on the object for too long - new OsuReplayFrame(hitObject.StartTime - OsuModRelax.RELAX_LENIENCY, new Vector2(hitObject.X - 22, hitObject.Y - 22)), - new OsuReplayFrame(hitObject.StartTime, new Vector2(0)), + new OsuReplayFrame(0, new Vector2(78, 78)), // must be an edge hit for the cursor to not stay on the object for too long + new OsuReplayFrame(1000 - OsuModRelax.RELAX_LENIENCY, new Vector2(78, 78)), + new OsuReplayFrame(1000, new Vector2(0)), }, PassCondition = () => Player.ScoreProcessor.Combo.Value == 1 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyReplayPlayback.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyReplayPlayback.cs index 379699b276..404ca0c79e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyReplayPlayback.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyReplayPlayback.cs @@ -17,7 +17,6 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - [Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")] public partial class TestSceneLegacyReplayPlayback : LegacyReplayPlaybackTestScene { protected override Ruleset CreateRuleset() => new OsuRuleset(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs index 61cc10f284..ddea5eed87 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs @@ -14,26 +14,27 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IResourceStore RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneOsuHitObjectSamples))); - [TestCase("normal-hitnormal")] - [TestCase("hitnormal")] - public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + [TestCase("normal-hitnormal2", "normal-hitnormal")] + [TestCase("hitnormal", "hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string beatmapSkinSampleName, string userSkinSampleName) { - SetupSkins(expectedSample, expectedSample); + SetupSkins(beatmapSkinSampleName, userSkinSampleName); CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu"); - AssertBeatmapLookup(expectedSample); + AssertBeatmapLookup(beatmapSkinSampleName); } - [TestCase("normal-hitnormal")] - [TestCase("hitnormal")] - public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + [TestCase("", "normal-hitnormal")] + [TestCase("normal-hitnormal", "normal-hitnormal")] + [TestCase("", "hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string beatmapSkinSampleName, string userSkinSampleName) { - SetupSkins(string.Empty, expectedSample); + SetupSkins(beatmapSkinSampleName, userSkinSampleName); CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu"); - AssertUserLookup(expectedSample); + AssertUserLookup(userSkinSampleName); } [TestCase("normal-hitnormal2")] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneReplayStability.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneReplayStability.cs index 2303b17d96..320fdcff2c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneReplayStability.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneReplayStability.cs @@ -13,7 +13,6 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - [Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")] public partial class TestSceneReplayStability : ReplayStabilityTestScene { private static readonly object[][] test_cases = @@ -23,53 +22,49 @@ namespace osu.Game.Rulesets.Osu.Tests // while round brackets `()` represent *open* or *exclusive* bounds. // OD = 5 test cases. - // GREAT hit window is [ -50ms, 50ms] - // OK hit window is [-100ms, 100ms] - // MEH hit window is [-150ms, 150ms] - // MISS hit window is [-400ms, 400ms] + // GREAT hit window is [ -49.5ms, 49.5ms] + // OK hit window is [ -99.5ms, 99.5ms] + // MEH hit window is [-149.5ms, 149.5ms] new object[] { 5f, 49d, HitResult.Great }, new object[] { 5f, 49.2d, HitResult.Great }, - new object[] { 5f, 49.7d, HitResult.Great }, - new object[] { 5f, 50d, HitResult.Great }, + new object[] { 5f, 49.7d, HitResult.Ok }, + new object[] { 5f, 50d, HitResult.Ok }, new object[] { 5f, 50.4d, HitResult.Ok }, new object[] { 5f, 50.9d, HitResult.Ok }, new object[] { 5f, 51d, HitResult.Ok }, new object[] { 5f, 99d, HitResult.Ok }, new object[] { 5f, 99.2d, HitResult.Ok }, - new object[] { 5f, 99.7d, HitResult.Ok }, - new object[] { 5f, 100d, HitResult.Ok }, + new object[] { 5f, 99.7d, HitResult.Meh }, + new object[] { 5f, 100d, HitResult.Meh }, new object[] { 5f, 100.4d, HitResult.Meh }, new object[] { 5f, 100.9d, HitResult.Meh }, new object[] { 5f, 101d, HitResult.Meh }, new object[] { 5f, 149d, HitResult.Meh }, new object[] { 5f, 149.2d, HitResult.Meh }, - new object[] { 5f, 149.7d, HitResult.Meh }, - new object[] { 5f, 150d, HitResult.Meh }, + new object[] { 5f, 149.7d, HitResult.Miss }, + new object[] { 5f, 150d, HitResult.Miss }, new object[] { 5f, 150.4d, HitResult.Miss }, new object[] { 5f, 150.9d, HitResult.Miss }, new object[] { 5f, 151d, HitResult.Miss }, // OD = 5.7 test cases. - // GREAT hit window is [ -45.8ms, 45.8ms] - // OK hit window is [ -94.4ms, 94.4ms] - // MEH hit window is [-143.0ms, 143.0ms] - // MISS hit window is [-400.0ms, 400.0ms] - new object[] { 5.7f, 45d, HitResult.Great }, - new object[] { 5.7f, 45.2d, HitResult.Great }, - new object[] { 5.7f, 45.8d, HitResult.Great }, - new object[] { 5.7f, 45.9d, HitResult.Ok }, - new object[] { 5.7f, 46d, HitResult.Ok }, - new object[] { 5.7f, 46.4d, HitResult.Ok }, - new object[] { 5.7f, 94d, HitResult.Ok }, - new object[] { 5.7f, 94.2d, HitResult.Ok }, - new object[] { 5.7f, 94.4d, HitResult.Ok }, - new object[] { 5.7f, 94.48d, HitResult.Ok }, - new object[] { 5.7f, 94.9d, HitResult.Meh }, - new object[] { 5.7f, 95d, HitResult.Meh }, - new object[] { 5.7f, 95.4d, HitResult.Meh }, + // GREAT hit window is [ -44.5ms, 44.5ms] + // OK hit window is [ -93.5ms, 93.5ms] + // MEH hit window is [-142.5ms, 142.5ms] + new object[] { 5.7f, 44d, HitResult.Great }, + new object[] { 5.7f, 44.2d, HitResult.Great }, + new object[] { 5.7f, 44.8d, HitResult.Ok }, + new object[] { 5.7f, 45d, HitResult.Ok }, + new object[] { 5.7f, 45.4d, HitResult.Ok }, + new object[] { 5.7f, 93d, HitResult.Ok }, + new object[] { 5.7f, 93.4d, HitResult.Ok }, + new object[] { 5.7f, 93.9d, HitResult.Meh }, + new object[] { 5.7f, 94d, HitResult.Meh }, + new object[] { 5.7f, 94.4d, HitResult.Meh }, new object[] { 5.7f, 142d, HitResult.Meh }, - new object[] { 5.7f, 142.7d, HitResult.Meh }, - new object[] { 5.7f, 143d, HitResult.Meh }, + new object[] { 5.7f, 142.2d, HitResult.Meh }, + new object[] { 5.7f, 142.7d, HitResult.Miss }, + new object[] { 5.7f, 143d, HitResult.Miss }, new object[] { 5.7f, 143.4d, HitResult.Miss }, new object[] { 5.7f, 143.9d, HitResult.Miss }, new object[] { 5.7f, 144d, HitResult.Miss }, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index d089e924ca..3276516d0a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 99, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 99, slider_end_position, OsuAction.LeftButton), }); assertHeadJudgement(HitResult.Ok); @@ -70,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 99, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 99, slider_end_position, OsuAction.LeftButton), }, s => { s.SliderVelocityMultiplier = 2; @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_end_position, OsuAction.LeftButton), }, s => { s.TickDistanceMultiplier = 0.2f; @@ -116,8 +116,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_end_position, OsuAction.LeftButton), }, s => { s.SliderVelocityMultiplier = 2; @@ -165,8 +165,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton), }, s => { s.Path = new SliderPath(PathType.LINEAR, new[] @@ -195,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton), }, s => { s.Path = new SliderPath(PathType.LINEAR, new[] @@ -224,8 +224,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton), }, s => { s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] @@ -259,8 +259,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton), }, s => { s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] @@ -289,8 +289,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton), }, s => { s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] @@ -320,8 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start + 149, slider_start_position - new Vector2(20), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 149, slider_start_position - new Vector2(20), OsuAction.LeftButton), }, s => { s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 1d94ac6335..1c3b7360bc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods { get { - if (UserAdjustedSettingsCount != 1) + if (!IsExactlyOneSettingChanged(CircleSize, ApproachRate, OverallDifficulty, DrainRate)) return string.Empty; if (!CircleSize.IsDefault) return format("CS", CircleSize); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index 154503c20d..a0f235c8c7 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Osu.Scoring public override void SetDifficulty(double difficulty) { - great = IBeatmapDifficultyInfo.DifficultyRange(difficulty, GREAT_WINDOW_RANGE); - ok = IBeatmapDifficultyInfo.DifficultyRange(difficulty, OK_WINDOW_RANGE); - meh = IBeatmapDifficultyInfo.DifficultyRange(difficulty, MEH_WINDOW_RANGE); + great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, GREAT_WINDOW_RANGE)) - 0.5; + ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, OK_WINDOW_RANGE)) - 0.5; + meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, MEH_WINDOW_RANGE)) - 0.5; } public override double WindowFor(HitResult result) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs index 7008d8d37a..c175e3342b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements PerformTest(new List { new TaikoReplayFrame(0), - new TaikoReplayFrame(hit_time - hitWindows.WindowFor(HitResult.Great), TaikoAction.LeftCentre), + new TaikoReplayFrame(hit_time - (hitWindows.WindowFor(HitResult.Great) + 0.1), TaikoAction.LeftCentre), }, beatmap); AssertJudgementCount(1); diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyReplayPlayback.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyReplayPlayback.cs index 5e71f974d8..40a426b360 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyReplayPlayback.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyReplayPlayback.cs @@ -15,7 +15,6 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { - [Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")] public partial class TestSceneLegacyReplayPlayback : LegacyReplayPlaybackTestScene { protected override string? ExportLocation => null; @@ -177,7 +176,7 @@ namespace osu.Game.Rulesets.Taiko.Tests ScoreInfo = new ScoreInfo { Ruleset = CreateRuleset().RulesetInfo, - Mods = [new TaikoModHardRock()] + Mods = [new TaikoModEasy()] } }; diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneReplayStability.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneReplayStability.cs index 62bbebcf0b..c61ae8ecc7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneReplayStability.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneReplayStability.cs @@ -12,7 +12,6 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { - [Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")] public partial class TestSceneReplayStability : ReplayStabilityTestScene { private static readonly object[][] test_cases = @@ -22,40 +21,38 @@ namespace osu.Game.Rulesets.Taiko.Tests // while round brackets `()` represent *open* or *exclusive* bounds. // OD = 5 test cases. - // GREAT hit window is [-35ms, 35ms] - // OK hit window is [-80ms, 80ms] - // MISS hit window is [-95ms, 95ms] + // GREAT hit window is [-34.5ms, 34.5ms] + // OK hit window is [-79.5ms, 79.5ms] + // MISS hit window is [-94.5ms, 94.5ms] new object[] { 5f, -34d, HitResult.Great }, new object[] { 5f, -34.2d, HitResult.Great }, - new object[] { 5f, -34.7d, HitResult.Great }, - new object[] { 5f, -35d, HitResult.Great }, + new object[] { 5f, -34.7d, HitResult.Ok }, + new object[] { 5f, -35d, HitResult.Ok }, new object[] { 5f, -35.2d, HitResult.Ok }, new object[] { 5f, -35.8d, HitResult.Ok }, new object[] { 5f, -36d, HitResult.Ok }, new object[] { 5f, -79d, HitResult.Ok }, new object[] { 5f, -79.3d, HitResult.Ok }, - new object[] { 5f, -79.7d, HitResult.Ok }, - new object[] { 5f, -80d, HitResult.Ok }, + new object[] { 5f, -79.7d, HitResult.Miss }, + new object[] { 5f, -80d, HitResult.Miss }, new object[] { 5f, -80.2d, HitResult.Miss }, new object[] { 5f, -80.8d, HitResult.Miss }, new object[] { 5f, -81d, HitResult.Miss }, // OD = 7.8 test cases. - // GREAT hit window is [-26.6ms, 26.6ms] - // OK hit window is [-63.2ms, 63.2ms] - // MISS hit window is [-81.0ms, 81.0ms] - new object[] { 7.8f, -26d, HitResult.Great }, - new object[] { 7.8f, -26.4d, HitResult.Great }, - new object[] { 7.8f, -26.59d, HitResult.Great }, - new object[] { 7.8f, -26.8d, HitResult.Ok }, - new object[] { 7.8f, -27d, HitResult.Ok }, - new object[] { 7.8f, -27.1d, HitResult.Ok }, - new object[] { 7.8f, -63d, HitResult.Ok }, - new object[] { 7.8f, -63.18d, HitResult.Ok }, - new object[] { 7.8f, -63.4d, HitResult.Ok }, - new object[] { 7.8f, -63.7d, HitResult.Miss }, - new object[] { 7.8f, -64d, HitResult.Miss }, - new object[] { 7.8f, -64.2d, HitResult.Miss }, + // GREAT hit window is [-25.5ms, 25.5ms] + // OK hit window is [-62.5ms, 62.5ms] + // MISS hit window is [-80.5ms, 80.5ms] + new object[] { 7.8f, -25d, HitResult.Great }, + new object[] { 7.8f, -25.4d, HitResult.Great }, + new object[] { 7.8f, -25.8d, HitResult.Ok }, + new object[] { 7.8f, -26d, HitResult.Ok }, + new object[] { 7.8f, -26.1d, HitResult.Ok }, + new object[] { 7.8f, -62d, HitResult.Ok }, + new object[] { 7.8f, -62.4d, HitResult.Ok }, + new object[] { 7.8f, -62.7d, HitResult.Miss }, + new object[] { 7.8f, -63d, HitResult.Miss }, + new object[] { 7.8f, -63.2d, HitResult.Miss }, }; [TestCaseSource(nameof(test_cases))] diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs index 1d1e82fb07..b1df133c30 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -14,26 +14,27 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override IResourceStore RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples))); - [TestCase("taiko-normal-hitnormal")] - [TestCase("hitnormal")] - public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + [TestCase("taiko-normal-hitnormal2", "taiko-normal-hitnormal")] + [TestCase("hitnormal", "hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string beatmapSkinSampleName, string userSkinSampleName) { - SetupSkins(expectedSample, expectedSample); + SetupSkins(beatmapSkinSampleName, userSkinSampleName); CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); - AssertBeatmapLookup(expectedSample); + AssertBeatmapLookup(beatmapSkinSampleName); } - [TestCase("taiko-normal-hitnormal")] - [TestCase("hitnormal")] - public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + [TestCase("", "taiko-normal-hitnormal")] + [TestCase("taiko-normal-hitnormal", "taiko-normal-hitnormal")] + [TestCase("", "hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string beatmapSkinSampleName, string userSkinSampleName) { - SetupSkins(string.Empty, expectedSample); + SetupSkins(beatmapSkinSampleName, userSkinSampleName); CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); - AssertUserLookup(expectedSample); + AssertUserLookup(userSkinSampleName); } [TestCase("taiko-normal-hitnormal2")] diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 57b57555c2..b06d1fe5ac 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -25,16 +25,16 @@ namespace osu.Game.Rulesets.Taiko.Mods { get { - if (UserAdjustedSettingsCount != 1) + if (!IsExactlyOneSettingChanged(ScrollSpeed, OverallDifficulty, DrainRate)) return string.Empty; - if (!ScrollSpeed.IsDefault) return format("SC", ScrollSpeed); - if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty); - if (!DrainRate.IsDefault) return format("HP", DrainRate); + if (!ScrollSpeed.IsDefault) return format("SC", ScrollSpeed, 2); + if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty, 1); + if (!DrainRate.IsDefault) return format("HP", DrainRate, 1); return string.Empty; - string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}"; + string format(string acronym, DifficultyBindable bindable, int digits) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(digits)}"; } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs index 22d268de3b..f3a478f592 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Taiko.Scoring public override void SetDifficulty(double difficulty) { - great = IBeatmapDifficultyInfo.DifficultyRange(difficulty, GREAT_WINDOW_RANGE); - ok = IBeatmapDifficultyInfo.DifficultyRange(difficulty, OK_WINDOW_RANGE); - miss = IBeatmapDifficultyInfo.DifficultyRange(difficulty, MISS_WINDOW_RANGE); + great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, GREAT_WINDOW_RANGE)) - 0.5; + ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, OK_WINDOW_RANGE)) - 0.5; + miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, MISS_WINDOW_RANGE)) - 0.5; } public override double WindowFor(HitResult result) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index d198ef5074..c9f5f50232 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -64,11 +64,9 @@ namespace osu.Game.Tests.Gameplay /// /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the beatmap skin: /// normal-hitnormal2 - /// normal-hitnormal /// hitnormal /// [TestCase("normal-hitnormal2")] - [TestCase("normal-hitnormal")] [TestCase("hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { @@ -162,7 +160,6 @@ namespace osu.Game.Tests.Gameplay /// Tests that a control point that provides a custom sample of 2 causes . /// [TestCase("normal-hitnormal2")] - [TestCase("normal-hitnormal")] [TestCase("hitnormal")] public void TestControlPointCustomSampleFromBeatmap(string sampleName) { diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index b780d60817..6ec4e799e6 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -342,13 +342,14 @@ namespace osu.Game.Tests.Mods { foreach (var mod in ruleset.CreateAllMods()) { - if (mod.ValidForFreestyleAsRequiredMod && mod.UserPlayable && !commonAcronyms.Contains(mod.Acronym)) - Assert.Fail($"{mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but does not exist in all four basic rulesets!"); + if (mod.ValidForFreestyleAsRequiredMod && !mod.UserPlayable) + Assert.Fail($"Mod {mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but is not playable!"); - // downgraded to warning, because there are valid reasons why they may still not be specified to be valid for freestyle as required - // (see `TestModsValidForRequiredFreestyleAreConsistentlyCompatibleAcrossRulesets()` test case below). - if (!mod.ValidForFreestyleAsRequiredMod && mod.UserPlayable && commonAcronyms.Contains(mod.Acronym)) - Assert.Warn($"{mod.GetType().ReadableName()} does not declare {nameof(Mod.ValidForFreestyleAsRequiredMod)} but exists in all four basic rulesets."); + if (mod.ValidForFreestyleAsRequiredMod && !mod.HasImplementation) + Assert.Fail($"Mod {mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but is not implemented!"); + + if (mod.ValidForFreestyleAsRequiredMod && mod.UserPlayable && mod.HasImplementation && !commonAcronyms.Contains(mod.Acronym)) + Assert.Fail($"{mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but does not exist in all four basic rulesets!"); } } }); diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index eeaa68e2ee..58fb02c90c 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -32,7 +32,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osu.Game.Storyboards.Drawables; using osu.Game.Tests.Resources; using osuTK; @@ -325,7 +325,7 @@ namespace osu.Game.Tests.Visual.Background private void setupUserSettings() { AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen()); - AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null); + AddUntilStep("Song select has selection", () => songSelect.Carousel?.CurrentSelection != null); AddStep("Set default user settings", () => { SelectedMods.Value = new[] { new OsuModNoFail() }; @@ -340,7 +340,7 @@ namespace osu.Game.Tests.Visual.Background rulesets?.Dispose(); } - private partial class DummySongSelect : PlaySongSelect + private partial class DummySongSelect : SoloSongSelect { private FadeAccessibleBackground background; @@ -355,7 +355,7 @@ namespace osu.Game.Tests.Visual.Background public readonly Bindable DimLevel = new BindableDouble(); public readonly Bindable BlurLevel = new BindableDouble(); - public new BeatmapCarousel Carousel => base.Carousel; + public BeatmapCarousel Carousel => this.ChildrenOfType().SingleOrDefault(); [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundDimmed() => background.CurrentColour == OsuColour.Gray(1f - background.CurrentDim); - public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White; + public bool IsBackgroundUndimmed() => background.CurrentColour == new Color4(0.9f, 0.9f, 0.9f, 1f); public bool IsUserBlurApplied() => Precision.AlmostEquals(background.CurrentBlur, new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR), 0.1f); @@ -376,7 +376,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundVisible() => background.CurrentAlpha == 1; - public bool IsBackgroundBlur() => Precision.AlmostEquals(background.CurrentBlur, new Vector2(BACKGROUND_BLUR), 0.1f); + public bool IsBackgroundBlur() => Precision.AlmostBigger(background.CurrentBlur.X, 0, 0.1f); public bool CheckBackgroundBlur(Vector2 expected) => Precision.AlmostEquals(background.CurrentBlur, expected, 0.1f); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 2e7b55ab49..7f40da5bab 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -15,7 +15,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard); AddStep("Exit editor", () => Editor.Exit()); - AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 955ded97af..e3b79d4053 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Editing @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Editing () => Is.EqualTo(1)); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); addStepClickLink("00:00:000 (1)", waitForSeek: false); AddUntilStep("received 'must be in edit'", @@ -151,12 +151,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); AddUntilStep("Wait for song select", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.BeatmapSetsLoaded + && Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect + && songSelect.CarouselItemsPresented ); AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); AddStep("Open editor for ruleset", () => - ((PlaySongSelect)Game.ScreenStack.CurrentScreen) + ((SoloSongSelect)Game.ScreenStack.CurrentScreen) .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) ); AddUntilStep("Wait for editor open", () => editor?.ReadyForUse == true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 2334b1c6d6..84b312d5ee 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -9,7 +9,6 @@ using osu.Game.Audio; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Screens.Play; using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay @@ -74,8 +73,8 @@ namespace osu.Game.Tests.Visual.Gameplay // // We want to keep seeking while asserting various test conditions, so // continue to seek until we unset the flag. - var gameplayClockContainer = Player.ChildrenOfType().First(); - gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime > 30000 ? 0 : 60000); + var gameplayClockContainer = Player?.GameplayClockContainer; + gameplayClockContainer?.Seek(gameplayClockContainer.CurrentTime > 30000 ? 0 : 60000); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs index 81dd23661c..b3ed4135a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -6,11 +6,16 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Replays; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -157,6 +162,51 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500); } + [Test] + public void TestReplayDoesNotFailUntilRunningOutOfFrames() + { + var score = new Score + { + ScoreInfo = TestResources.CreateTestScoreInfo(Beatmap.Value.BeatmapInfo), + Replay = new Replay + { + Frames = + { + new OsuReplayFrame(0, Vector2.Zero), + new OsuReplayFrame(10000, Vector2.Zero), + } + } + }; + score.ScoreInfo.Mods = []; + score.ScoreInfo.Rank = ScoreRank.F; + AddStep("set global state", () => + { + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; + SelectedMods.Value = score.ScoreInfo.Mods; + }); + AddStep("create player", () => Player = new TestReplayPlayer(score, showResults: false)); + AddStep("load player", () => LoadScreen(Player)); + AddUntilStep("wait for loaded", () => Player.IsCurrentScreen()); + AddStep("seek to 8000", () => Player.Seek(8000)); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddAssert("player failed after 10000", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(10000)); + } + + [Test] + public void TestPlayerLoaderSettingsHover() + { + loadPlayerWithBeatmap(); + + AddUntilStep("wait for settings overlay hidden", () => settingsOverlay().Expanded.Value, () => Is.False); + AddStep("move mouse to right of screen", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopRight)); + AddUntilStep("wait for settings overlay visible", () => settingsOverlay().Expanded.Value, () => Is.True); + AddStep("move mouse to centre of screen", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for settings overlay hidden", () => settingsOverlay().Expanded.Value, () => Is.False); + + PlayerSettingsOverlay settingsOverlay() => Player.ChildrenOfType().Single(); + } + private void loadPlayerWithBeatmap(IBeatmap? beatmap = null) { AddStep("create player", () => diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index ee5b1797ed..c7499c98b5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -26,8 +26,8 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Resources; using osuTK.Input; @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("exit", () => getEditor().Exit()); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.Beatmap.Value is DummyWorkingBeatmap); } @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("switch ruleset at song select", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddStep("open editor", () => ((SoloSongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); AddAssert("editor ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo)); @@ -187,8 +187,8 @@ namespace osu.Game.Tests.Visual.Navigation }); AddAssert("gameplay ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo)); - AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(SoloSongSelect).Yield())); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo)); } @@ -289,8 +289,8 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true)); AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying); - AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(SoloSongSelect).Yield())); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying); AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true)); @@ -352,13 +352,13 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); AddUntilStep("wait for song select", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.BeatmapSetsLoaded); + && Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect + && songSelect.CarouselItemsPresented); } private void openEditor() { - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddStep("open editor", () => ((SoloSongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs index 43b160250c..0ccfb5a4e3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs @@ -5,7 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osuTK.Input; namespace osu.Game.Tests.Visual.Navigation @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Key(Key.P); }); - AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); } [Test] @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("state is play", () => buttons.State == ButtonSystemState.Play); AddStep("press P", () => InputManager.Key(Key.P)); - AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 3a3af43cb1..4f27d9b323 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -15,7 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; @@ -54,10 +54,10 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - PushAndConfirm(() => new PlaySongSelect()); + PushAndConfirm(() => new SoloSongSelect()); AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault); - AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); + AddUntilStep("wait for carousel load", () => songSelect.CarouselItemsPresented); AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Navigation .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); - private Screens.Select.SongSelect songSelect => Game.ScreenStack.CurrentScreen as Screens.Select.SongSelect; + private SoloSongSelect songSelect => Game.ScreenStack.CurrentScreen as SoloSongSelect; private Player player => Game.ScreenStack.CurrentScreen as Player; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs index 26a37fa211..0a4349d73f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Game.Configuration; using osu.Game.Screens.Play; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; @@ -83,9 +84,9 @@ namespace osu.Game.Tests.Visual.Navigation private void loadToPlayerNonBreakTime() { Player? player = null; - Screens.Select.SongSelect songSelect = null!; - PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + SoloSongSelect songSelect = null!; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 5fe4bb9340..04d7b15295 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -17,9 +17,9 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; -using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; namespace osu.Game.Tests.Visual.Navigation { @@ -44,17 +44,17 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelect() { - PushAndConfirm(() => new TestPlaySongSelect()); + PushAndConfirm(() => new SoloSongSelect()); - AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) })); + AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(SoloSongSelect) })); AddAssert("did perform", () => actionPerformed); - AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect); + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); } [Test] public void TestPerformAtMenuFromSongSelect() { - PushAndConfirm(() => new TestPlaySongSelect()); + PushAndConfirm(() => new SoloSongSelect()); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu); @@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("Press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); - AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) })); - AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect); + AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(SoloSongSelect) })); + AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect); AddAssert("did perform", () => actionPerformed); } @@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Navigation private void importAndWaitForSongSelect() { AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - PushAndConfirm(() => new TestPlaySongSelect()); + PushAndConfirm(() => new SoloSongSelect()); AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index f036b4b3ef..e7172cacbf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; namespace osu.Game.Tests.Visual.Navigation { @@ -81,11 +81,9 @@ namespace osu.Game.Tests.Visual.Navigation presentAndConfirm(osuImport); var maniaImport = importBeatmap(2, new ManiaRuleset().RulesetInfo); - confirmBeatmapInSongSelect(maniaImport); presentAndConfirm(maniaImport); var catchImport = importBeatmap(3, new CatchRuleset().RulesetInfo); - confirmBeatmapInSongSelect(catchImport); presentAndConfirm(catchImport); // Ruleset is always changed. @@ -103,11 +101,9 @@ namespace osu.Game.Tests.Visual.Navigation presentAndConfirm(osuImport); var maniaImport = importBeatmap(2, new ManiaRuleset().RulesetInfo); - confirmBeatmapInSongSelect(maniaImport); presentAndConfirm(maniaImport); var catchImport = importBeatmap(3, new CatchRuleset().RulesetInfo); - confirmBeatmapInSongSelect(catchImport); presentAndConfirm(catchImport); // force ruleset to osu!mania @@ -178,14 +174,14 @@ namespace osu.Game.Tests.Visual.Navigation { AddUntilStep("wait for carousel loaded", () => { - var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen; + var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen; return songSelect.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; }); AddUntilStep("beatmap in song select", () => { - var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen; - return songSelect.ChildrenOfType().Single().BeatmapSets.Any(b => b.MatchesOnlineID(getImport())); + var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen; + return songSelect.ChildrenOfType().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetInfo bsi && bsi.MatchesOnlineID(getImport())); }); } @@ -193,7 +189,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID)); AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset)); } @@ -203,7 +199,7 @@ namespace osu.Game.Tests.Visual.Navigation Predicate pred = b => b.OnlineID == importedID * 1024 + 2; AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 1024 + 2)); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.OnlineID, () => Is.EqualTo(expectedRulesetOnlineID ?? getImport().Beatmaps.First().Ruleset.OnlineID)); } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 2c2335de13..fa337a3ec2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -18,7 +18,8 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; +using FilterControl = osu.Game.Screens.SelectV2.FilterControl; namespace osu.Game.Tests.Visual.Navigation { @@ -96,9 +97,9 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromSongSelectWithFilter([Values] ScorePresentType type) { AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); - AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq"); + AddStep("filter to nothing", () => ((SoloSongSelect)Game.ScreenStack.CurrentScreen).ChildrenOfType().Single().Search("fdsajkl;fgewq")); AddUntilStep("wait for no results", () => Beatmap.IsDefault); var firstImport = importScore(1, new CatchRuleset().RulesetInfo); @@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type) { AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); @@ -121,7 +122,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromSongSelect([Values] ScorePresentType type) { AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); var firstImport = importScore(1); presentAndConfirm(firstImport, type); @@ -134,7 +135,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type) { AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); var firstImport = importScore(1); presentAndConfirm(firstImport, type); @@ -147,7 +148,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestPresentTwoImportsWithSameOnlineIDButDifferentHashes([Values] ScorePresentType type) { AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); var firstImport = importScore(1); presentAndConfirm(firstImport, type); @@ -160,7 +161,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestScoreRefetchIgnoresEmptyHash() { AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); - AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented); importScore(-1, hash: string.Empty); importScore(3, hash: @"deadbeef"); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 2a755b46b3..d50fc69823 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -26,7 +26,6 @@ using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.Leaderboards; using osu.Game.Online.Notifications.WebSocket; using osu.Game.Online.Notifications.WebSocket.Events; using osu.Game.Overlays; @@ -49,20 +48,13 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; -using osu.Game.Screens.Select; -using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Screens.Select.Options; using osu.Game.Screens.SelectV2; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Resources; using osu.Game.Utils; using osuTK; using osuTK.Input; -using BeatmapCarousel = osu.Game.Screens.Select.BeatmapCarousel; -using CollectionDropdown = osu.Game.Collections.CollectionDropdown; -using FilterControl = osu.Game.Screens.Select.FilterControl; -using FooterButtonRandom = osu.Game.Screens.Select.FooterButtonRandom; namespace osu.Game.Tests.Visual.Navigation { @@ -146,62 +138,70 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitSongSelectWithEscape() { - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; + ModSelectOverlay modSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); - AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddStep("Show mods overlay", () => + { + modSelect = songSelect!.ChildrenOfType().Single(); + modSelect.Show(); + }); + AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible); pushEscape(); - AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + AddAssert("Overlay was hidden", () => modSelect.State.Value == Visibility.Hidden); exitViaEscapeAndConfirm(); } [Test] public void TestEnterGameplayWhileFilteringToNoSelection() { - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("force selection", () => + AddStep("force selection and change filter immediately", () => { - songSelect.FinaliseSelection(); - songSelect.FilterControl.CurrentTextSearch.Value = "test"; + InputManager.Key(Key.Enter); + songSelect.ChildrenOfType().Single().Search("test"); }); AddUntilStep("wait for player", () => !songSelect.IsCurrentScreen()); AddStep("return to song select", () => songSelect.MakeCurrent()); - AddUntilStep("wait for selection lost", () => songSelect.Beatmap.IsDefault); + AddUntilStep("selection not lost", () => !songSelect.Beatmap.IsDefault); + AddUntilStep("placeholder visible", () => songSelect.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Visible)); } [Test] public void TestSongSelectBackActionHandling() { - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + + AddUntilStep("wait for filter control", () => filterControlTextBox().IsLoaded); AddStep("set filter", () => filterControlTextBox().Current.Value = "test"); AddStep("press back", () => InputManager.Click(MouseButton.Button1)); - AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); + AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen, () => Is.EqualTo(songSelect)); AddAssert("filter cleared", () => string.IsNullOrEmpty(filterControlTextBox().Current.Value)); AddStep("set filter again", () => filterControlTextBox().Current.Value = "test"); AddStep("open collections dropdown", () => { - InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); + InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddStep("press back once", () => InputManager.Click(MouseButton.Button1)); AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); AddAssert("collections dropdown closed", () => songSelect - .ChildrenOfType().Single() + .ChildrenOfType().Single() .ChildrenOfType.DropdownMenu>().Single().State == MenuState.Closed); AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1)); @@ -210,17 +210,17 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press back a third time", () => InputManager.Click(MouseButton.Button1)); ConfirmAtMainMenu(); - TextBox filterControlTextBox() => songSelect.ChildrenOfType().Single(); + FilterControl.SongSelectSearchTextBox filterControlTextBox() => songSelect.ChildrenOfType().Single(); } [Test] public void TestSongSelectRandomRewindButton() { Guid? originalSelection = null; - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("Add two beatmaps", () => { @@ -247,41 +247,6 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestSongSelectScrollHandling() - { - TestPlaySongSelect songSelect = null; - double scrollPosition = 0; - - AddStep("set game volume to max", () => Game.Dependencies.Get().SetValue(FrameworkSetting.VolumeUniversal, 1d)); - AddUntilStep("wait for volume overlay to hide", () => Game.ChildrenOfType().SingleOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Hidden)); - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - - AddStep("store scroll position", () => scrollPosition = getCarouselScrollPosition()); - - AddStep("move to left side", () => InputManager.MoveMouseTo( - songSelect.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft + new Vector2(1))); - AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1)); - AddAssert("carousel didn't move", getCarouselScrollPosition, () => Is.EqualTo(scrollPosition)); - - AddRepeatStep("alt-scroll down", () => - { - InputManager.PressKey(Key.AltLeft); - InputManager.ScrollVerticalBy(-1); - InputManager.ReleaseKey(Key.AltLeft); - }, 5); - AddAssert("game volume decreased", () => Game.Dependencies.Get().Get(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1)); - - AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single())); - AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1)); - AddAssert("carousel moved", getCarouselScrollPosition, () => Is.Not.EqualTo(scrollPosition)); - - double getCarouselScrollPosition() => Game.ChildrenOfType>().Single().Current; - } - - [Test] - public void TestNewSongSelectScrollHandling() { SoloSongSelect songSelect = null; double scrollPosition = 0; @@ -293,6 +258,8 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for beatmap", () => Game.ChildrenOfType().Any()); + // TODO: this logic can likely be removed when we fix https://github.com/ppy/osu/issues/33379 + // It should be probably be immediate in this case. AddWaitStep("wait for scroll", 10); AddStep("store scroll position", () => scrollPosition = getCarouselScrollPosition()); @@ -325,7 +292,7 @@ namespace osu.Game.Tests.Visual.Navigation }, 5); AddAssert("game volume decreased", () => Game.Dependencies.Get().Get(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1)); - AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single())); + AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single())); AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1)); AddAssert("carousel moved", getCarouselScrollPosition, () => Is.Not.EqualTo(scrollPosition)); @@ -339,21 +306,21 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestOpenModSelectOverlayUsingAction() { - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + PushAndConfirm(() => songSelect = new SoloSongSelect()); AddStep("Show mods overlay", () => InputManager.Key(Key.F1)); - AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + AddAssert("Overlay was shown", () => songSelect!.ChildrenOfType().Single().State.Value == Visibility.Visible); } [Test] public void TestAttemptPlayBeatmapWrongHashFails() { - Screens.Select.SongSelect songSelect = null; + Screens.SelectV2.SongSelect songSelect = null; AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -384,11 +351,11 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestAttemptPlayBeatmapMissingFails() { - Screens.Select.SongSelect songSelect = null; + Screens.SelectV2.SongSelect songSelect = null; AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -418,9 +385,9 @@ namespace osu.Game.Tests.Visual.Navigation { Player player = null; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); @@ -461,9 +428,9 @@ namespace osu.Game.Tests.Visual.Navigation { Player player = null; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game).WaitSafely()); @@ -515,9 +482,9 @@ namespace osu.Game.Tests.Visual.Navigation { Player player = null; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game).WaitSafely()); @@ -558,9 +525,9 @@ namespace osu.Game.Tests.Visual.Navigation { Player player = null; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); @@ -663,7 +630,7 @@ namespace osu.Game.Tests.Visual.Navigation playToResults(); ScoreInfo score = null; - LeaderboardScore scorePanel = null; + BeatmapLeaderboardScore scorePanel = null; AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); @@ -672,18 +639,11 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press back button", () => Game.ChildrenOfType().First().Action!.Invoke()); AddStep("show local scores", - () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + () => Game.ChildrenOfType>().First().Current.Value = BeatmapLeaderboardScope.Local); - AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); - AddStep("open options", () => InputManager.Key(Key.F3)); - - AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); - - AddUntilStep("wait for dialog display", () => ((Drawable)Game.Dependencies.Get()).IsLoaded); - AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); - AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); - AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); + AddStep("Clear all scores", () => Game.Dependencies.Get().Delete()); AddUntilStep("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); @@ -696,7 +656,7 @@ namespace osu.Game.Tests.Visual.Navigation playToResults(); ScoreInfo score = null; - LeaderboardScore scorePanel = null; + BeatmapLeaderboardScore scorePanel = null; AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); @@ -705,9 +665,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press back button", () => Game.ChildrenOfType().First().Action!.Invoke()); AddStep("show local scores", - () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + () => Game.ChildrenOfType>().First().Current.Value = BeatmapLeaderboardScope.Local); - AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); AddStep("right click panel", () => { @@ -718,7 +678,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("click delete", () => { var dropdownItem = Game - .ChildrenOfType().First() + .ChildrenOfType().First() .ChildrenOfType().First() .ChildrenOfType().First(i => i.Item.Text.ToString() == "Delete"); @@ -744,9 +704,9 @@ namespace osu.Game.Tests.Visual.Navigation IWorkingBeatmap beatmap() => Game.Beatmap.Value; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); @@ -777,9 +737,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestMenuMakesMusic() { - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + PushAndConfirm(() => songSelect = new SoloSongSelect()); AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice); @@ -791,7 +751,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPushSongSelectAndPressBackButtonImmediately() { - AddStep("push song select", () => Game.ScreenStack.Push(new TestPlaySongSelect())); + AddStep("push song select", () => Game.ScreenStack.Push(new SoloSongSelect())); AddStep("press back button", () => Game.ChildrenOfType().First().Action!.Invoke()); ConfirmAtMainMenu(); @@ -800,18 +760,23 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitSongSelectWithClick() { - TestPlaySongSelect songSelect = null; + SoloSongSelect songSelect = null; + ModSelectOverlay modSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); - AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddStep("Show mods overlay", () => + { + modSelect = songSelect!.ChildrenOfType().Single(); + modSelect.Show(); + }); + AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible); AddStep("Move mouse to dimmed area", () => InputManager.MoveMouseTo(new Vector2( songSelect.ScreenSpaceDrawQuad.TopLeft.X + 1, songSelect.ScreenSpaceDrawQuad.TopLeft.Y + songSelect.ScreenSpaceDrawQuad.Height / 2))); AddStep("Click left mouse button", () => InputManager.Click(MouseButton.Left)); - AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + AddUntilStep("Overlay was hidden", () => modSelect.State.Value == Visibility.Hidden); exitViaBackButtonAndConfirm(); } @@ -876,10 +841,18 @@ namespace osu.Game.Tests.Visual.Navigation { AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); - TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + SoloSongSelect songSelect = null; + ModSelectOverlay modSelect = null; - AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddStep("Show mods overlay", () => + { + modSelect = songSelect!.ChildrenOfType().Single(); + modSelect.Show(); + }); + AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible); + + AddStep("Show mods overlay", () => modSelect.Show()); AddStep("Change ruleset to osu!taiko", () => { @@ -890,7 +863,7 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType().Single().Current.Value.OnlineID == 1); - AddAssert("Mods overlay still visible", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + AddAssert("Mods overlay still visible", () => modSelect.State.Value == Visibility.Visible); } [Test] @@ -900,10 +873,12 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - TestPlaySongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + SoloSongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); - AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show()); + AddStep("Show options overlay", () => InputManager.Key(Key.F3)); + AddUntilStep("Options overlay visible", () => this.ChildrenOfType().SingleOrDefault()?.State.Value == Visibility.Visible); AddStep("Change ruleset to osu!taiko", () => { @@ -914,7 +889,7 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType().Single().Current.Value.OnlineID == 1); - AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible); + AddAssert("Options overlay still visible", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); } [Test] @@ -1186,7 +1161,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitGameFromSongSelect() { - PushAndConfirm(() => new TestPlaySongSelect()); + PushAndConfirm(() => new SoloSongSelect()); exitViaEscapeAndConfirm(); pushEscape(); // returns to osu! logo @@ -1258,10 +1233,10 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("close settings sidebar", () => InputManager.Key(Key.Escape)); - Screens.Select.SongSelect songSelect = null; + Screens.SelectV2.SongSelect songSelect = null; AddRepeatStep("go to solo", () => InputManager.Key(Key.P), 3); - AddUntilStep("wait for song select", () => (songSelect = Game.ScreenStack.CurrentScreen as Screens.Select.SongSelect) != null); - AddUntilStep("wait for beatmap sets loaded", () => songSelect.BeatmapSetsLoaded); + AddUntilStep("wait for song select", () => (songSelect = Game.ScreenStack.CurrentScreen as Screens.SelectV2.SongSelect) != null); + AddUntilStep("wait for beatmap sets loaded", () => songSelect.CarouselItemsPresented); AddStep("switch to osu! ruleset", () => { @@ -1271,7 +1246,7 @@ namespace osu.Game.Tests.Visual.Navigation }); AddStep("touch beatmap wedge", () => { - var wedge = Game.ChildrenOfType().Single(); + var wedge = Game.ChildrenOfType().Single(); var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); @@ -1287,7 +1262,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf()); AddStep("touch beatmap wedge", () => { - var wedge = Game.ChildrenOfType().Single(); + var wedge = Game.ChildrenOfType().Single(); var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); @@ -1304,7 +1279,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("click beatmap wedge", () => { - InputManager.MoveMouseTo(Game.ChildrenOfType().Single()); + InputManager.MoveMouseTo(Game.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf()); @@ -1315,7 +1290,7 @@ namespace osu.Game.Tests.Visual.Navigation { BeatmapSetInfo beatmapSet = null; - PushAndConfirm(() => new TestPlaySongSelect()); + PushAndConfirm(() => new SoloSongSelect()); AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); AddUntilStep("wait for selected", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)); AddStep("select", () => InputManager.Key(Key.Enter)); @@ -1345,9 +1320,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitSongSelectAndImmediatelyClickLogo() { - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); @@ -1376,9 +1351,9 @@ namespace osu.Game.Tests.Visual.Navigation { BeatmapSetInfo beatmap = null; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -1407,9 +1382,9 @@ namespace osu.Game.Tests.Visual.Navigation IWorkingBeatmap beatmap() => Game.Beatmap.Value; - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + Screens.SelectV2.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); @@ -1447,12 +1422,5 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); ConfirmAtMainMenu(); } - - public partial class TestPlaySongSelect : PlaySongSelect - { - public ModSelectOverlay ModSelectOverlay => ModSelect; - - public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions; - } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 622c85774a..fe76b74bcb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -17,6 +17,7 @@ using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Beatmaps; +using osu.Game.Overlays.Mods; using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets.Mods; @@ -26,17 +27,19 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Screens.SelectV2; using osu.Game.Skinning; using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Input; -using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; namespace osu.Game.Tests.Visual.Navigation { public partial class TestSceneSkinEditorNavigation : OsuGameTestScene { - private TestPlaySongSelect songSelect; + private SoloSongSelect songSelect; + private ModSelectOverlay modSelect => songSelect.ChildrenOfType().First(); + private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); [Test] @@ -331,10 +334,10 @@ namespace osu.Game.Tests.Visual.Navigation public void TestModOverlayClosesOnOpeningSkinEditor() { advanceToSongSelect(); - AddStep("open mod overlay", () => songSelect.ModSelectOverlay.Show()); + AddStep("open mod overlay", () => modSelect.Show()); openSkinEditor(); - AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + AddUntilStep("mod overlay closed", () => modSelect.State.Value == Visibility.Hidden); } [Test] @@ -448,8 +451,8 @@ namespace osu.Game.Tests.Visual.Navigation private void advanceToSongSelect() { - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented); } private void openSkinEditor() diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs deleted file mode 100644 index 44f64365f0..0000000000 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ /dev/null @@ -1,548 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Platform; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Leaderboards; -using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Scoring; -using osu.Game.Screens.Select; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Tests.Resources; -using osu.Game.Users; -using osuTK; -using osuTK.Input; - -namespace osu.Game.Tests.Visual.SongSelect -{ - public partial class TestSceneBeatmapLeaderboard : OsuManualInputManagerTestScene - { - private readonly FailableLeaderboard leaderboard; - - [Cached(typeof(IDialogOverlay))] - private readonly DialogOverlay dialogOverlay; - - private ScoreManager scoreManager = null!; - private RulesetStore rulesetStore = null!; - private BeatmapManager beatmapManager = null!; - private PlaySongSelect songSelect = null!; - - private LeaderboardManager leaderboardManager = null!; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API)); - dependencies.CacheAs(songSelect = new PlaySongSelect()); - dependencies.Cache(leaderboardManager = new LeaderboardManager()); - - Dependencies.Cache(Realm); - - return dependencies; - } - - [BackgroundDependencyLoader] - private void load() - { - LoadComponent(songSelect); - LoadComponent(leaderboardManager); - } - - public TestSceneBeatmapLeaderboard() - { - Add(new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - dialogOverlay = new DialogOverlay - { - Depth = -1 - }, - leaderboard = new FailableLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = BeatmapLeaderboardScope.Global, - } - } - }); - } - - [Test] - public void TestLocalScoresDisplay() - { - BeatmapInfo beatmapInfo = null!; - - AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local); - - AddStep(@"Set beatmap", () => - { - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - - leaderboard.BeatmapInfo = beatmapInfo; - }); - - clearScores(); - checkDisplayedCount(0); - - importMoreScores(() => beatmapInfo); - checkDisplayedCount(10); - - importMoreScores(() => beatmapInfo); - checkDisplayedCount(20); - - clearScores(); - checkDisplayedCount(0); - } - - [Test] - public void TestLocalScoresDisplayWorksWhenStartingOffline() - { - BeatmapInfo beatmapInfo = null!; - - AddStep("Log out", () => API.Logout()); - AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local); - - AddStep(@"Set beatmap", () => - { - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - - leaderboard.BeatmapInfo = beatmapInfo; - }); - - clearScores(); - importMoreScores(() => beatmapInfo); - checkDisplayedCount(10); - } - - [Test] - public void TestLocalScoresDisplayOnBeatmapEdit() - { - BeatmapInfo beatmapInfo = null!; - string originalHash = string.Empty; - - AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local); - - AddStep(@"Import beatmap", () => - { - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - - leaderboard.BeatmapInfo = beatmapInfo; - }); - - clearScores(); - checkDisplayedCount(0); - - AddStep(@"Perform initial save to guarantee stable hash", () => - { - IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap; - beatmapManager.Save(beatmapInfo, beatmap); - - originalHash = beatmapInfo.Hash; - }); - - importMoreScores(() => beatmapInfo); - - checkDisplayedCount(10); - checkStoredCount(10); - - AddStep(@"Save with changes", () => - { - IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap; - beatmap.Difficulty.ApproachRate = 12; - beatmapManager.Save(beatmapInfo, beatmap); - }); - - AddAssert("Hash changed", () => beatmapInfo.Hash, () => Is.Not.EqualTo(originalHash)); - checkDisplayedCount(0); - checkStoredCount(10); - - importMoreScores(() => beatmapInfo); - importMoreScores(() => beatmapInfo); - checkDisplayedCount(20); - checkStoredCount(30); - - AddStep(@"Revert changes", () => - { - IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap; - beatmap.Difficulty.ApproachRate = 8; - beatmapManager.Save(beatmapInfo, beatmap); - }); - - AddAssert("Hash restored", () => beatmapInfo.Hash, () => Is.EqualTo(originalHash)); - checkDisplayedCount(10); - checkStoredCount(30); - - clearScores(); - checkDisplayedCount(0); - checkStoredCount(0); - } - - [Test] - public void TestGlobalScoresDisplay() - { - AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()))); - AddStep(@"New Scores with teams", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()).Select(s => - { - s.User.Team = new APITeam(); - return s; - }))); - } - - [Test] - public void TestPersonalBest() - { - AddStep(@"Show personal best", showPersonalBest); - AddStep("null personal best position", showPersonalBestWithNullPosition); - } - - [Test] - public void TestPlaceholderStates() - { - AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); - - AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure)); - AddStep(@"No team", () => leaderboard.SetErrorState(LeaderboardState.NoTeam)); - AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn)); - AddStep(@"Ruleset unavailable", () => leaderboard.SetErrorState(LeaderboardState.RulesetUnavailable)); - AddStep(@"Beatmap unavailable", () => leaderboard.SetErrorState(LeaderboardState.BeatmapUnavailable)); - AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected)); - } - - [Test] - public void TestUseTheseModsDoesNotCopySystemMods() - { - AddStep(@"set scores", () => leaderboard.SetScores(leaderboard.Scores, new ScoreInfo - { - Position = 999, - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Ruleset = new OsuRuleset().RulesetInfo, - Mods = new Mod[] { new OsuModHidden(), new ModScoreV2(), }, - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - CountryCode = CountryCode.ES, - } - })); - AddUntilStep("wait for scores", () => this.ChildrenOfType().Count(), () => Is.GreaterThan(0)); - AddStep("right click panel", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Right); - }); - AddStep("click use these mods", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("song select received HD", () => songSelect.Mods.Value.Any(m => m is OsuModHidden)); - AddAssert("song select did not receive SV2", () => !songSelect.Mods.Value.Any(m => m is ModScoreV2)); - } - - private void showPersonalBestWithNullPosition() - { - leaderboard.SetScores(leaderboard.Scores, new ScoreInfo - { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - Ruleset = new OsuRuleset().RulesetInfo, - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - CountryCode = CountryCode.ES, - }, - }); - } - - private void showPersonalBest() - { - leaderboard.SetScores(leaderboard.Scores, new ScoreInfo - { - Position = 999, - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Ruleset = new OsuRuleset().RulesetInfo, - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - CountryCode = CountryCode.ES, - } - }); - } - - private void importMoreScores(Func beatmapInfo) - { - AddStep(@"Import new scores", () => - { - foreach (var score in GenerateSampleScores(beatmapInfo())) - scoreManager.Import(score); - }); - } - - private void clearScores() - { - AddStep("Clear all scores", () => scoreManager.Delete()); - } - - private void checkDisplayedCount(int expected) => - AddUntilStep($"{expected} scores displayed", () => leaderboard.ChildrenOfType().Count(), () => Is.EqualTo(expected)); - - private void checkStoredCount(int expected) => - AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All().Count(s => !s.DeletePending)), () => Is.EqualTo(expected)); - - public static ScoreInfo[] GenerateSampleScores(BeatmapInfo beatmapInfo) - { - return new[] - { - new ScoreInfo - { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now, - Mods = new Mod[] - { - new OsuModHidden(), - new OsuModFlashlight - { - FollowDelay = { Value = 200 }, - SizeMultiplier = { Value = 5 }, - }, - new OsuModDifficultyAdjust - { - CircleSize = { Value = 11 }, - ApproachRate = { Value = 10 }, - OverallDifficulty = { Value = 10 }, - DrainRate = { Value = 10 }, - ExtendedLimits = { Value = true } - } - }, - Ruleset = new OsuRuleset().RulesetInfo, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - CountryCode = CountryCode.ES, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.X, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddSeconds(-30), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - User = new APIUser - { - Id = 4608074, - Username = @"Skycries", - CountryCode = CountryCode.BR, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.SH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddSeconds(-70), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 1014222, - Username = @"eLy", - CountryCode = CountryCode.JP, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.S, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddMinutes(-40), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 1541390, - Username = @"Toukai", - CountryCode = CountryCode.CA, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.A, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddHours(-2), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 2243452, - Username = @"Satoruu", - CountryCode = CountryCode.VE, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.B, - Accuracy = 0.9826, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddHours(-25), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 2705430, - Username = @"Mooha", - CountryCode = CountryCode.FR, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.C, - Accuracy = 0.9654, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddHours(-50), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 7151382, - Username = @"Mayuri Hana", - CountryCode = CountryCode.TH, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.D, - Accuracy = 0.6025, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddHours(-72), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 2051389, - Username = @"FunOrange", - CountryCode = CountryCode.CA, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.D, - Accuracy = 0.5140, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddMonths(-10), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 6169483, - Username = @"-Hebel-", - CountryCode = CountryCode.MX, - }, - }, - new ScoreInfo - { - Rank = ScoreRank.D, - Accuracy = 0.4222, - MaxCombo = 244, - TotalScore = 1707827, - Date = DateTime.Now.AddYears(-2), - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - BeatmapInfo = beatmapInfo, - BeatmapHash = beatmapInfo.Hash, - Ruleset = new OsuRuleset().RulesetInfo, - - User = new APIUser - { - Id = 6702666, - Username = @"prhtnsm", - CountryCode = CountryCode.DE, - }, - }, - }; - } - - private partial class FailableLeaderboard : BeatmapLeaderboard - { - public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state); - public new void SetScores(IEnumerable? scores, ScoreInfo? userScore = null) => base.SetScores(scores, userScore); - } - } -} diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index d459eac3c2..832e8fc90f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Resources; using osu.Game.Users; using osu.Game.Utils; @@ -248,7 +249,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect select && select.CarouselItemsPresented); AddUntilStep("recommended beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(getImport().Beatmaps[expectedDiff - 1].OnlineID)); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs deleted file mode 100644 index 9dc6bc8a33..0000000000 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ /dev/null @@ -1,1445 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; -using osu.Framework.Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Platform; -using osu.Framework.Screens; -using osu.Framework.Testing; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Database; -using osu.Game.Extensions; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Chat; -using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; -using osu.Game.Overlays.Mods; -using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Scoring; -using osu.Game.Screens.Play; -using osu.Game.Screens.Select; -using osu.Game.Screens.Select.Carousel; -using osu.Game.Screens.Select.Filter; -using osu.Game.Tests.Resources; -using osuTK.Input; - -namespace osu.Game.Tests.Visual.SongSelect -{ - [TestFixture] - public partial class TestScenePlaySongSelect : ScreenTestScene - { - private BeatmapManager manager = null!; - private RulesetStore rulesets = null!; - private MusicController music = null!; - private WorkingBeatmap defaultBeatmap = null!; - private OsuConfigManager config = null!; - private TestSongSelect? songSelect; - - [BackgroundDependencyLoader] - private void load(GameHost host, AudioManager audio) - { - BeatmapStore beatmapStore; - - // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. - // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(Realm); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); - Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); - - Dependencies.Cache(music = new MusicController()); - - // required to get bindables attached - Add(music); - Add(beatmapStore); - - Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); - } - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("reset defaults", () => - { - Ruleset.Value = new OsuRuleset().RulesetInfo; - - Beatmap.SetDefault(); - SelectedMods.SetDefault(); - - songSelect = null; - }); - - AddStep("delete all beatmaps", () => manager.Delete()); - } - - [Test] - public void TestSpeedChange() - { - createSongSelect(); - changeMods(); - - decreaseModSpeed(); - AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - - decreaseModSpeed(); - AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - - increaseModSpeed(); - AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - - increaseModSpeed(); - AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - - increaseModSpeed(); - AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - - increaseModSpeed(); - AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - - decreaseModSpeed(); - AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - - OsuModNightcore nc = new OsuModNightcore - { - SpeedChange = { Value = 1.05 } - }; - changeMods(nc); - - increaseModSpeed(); - AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - - decreaseModSpeed(); - AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - - decreaseModSpeed(); - AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - - decreaseModSpeed(); - AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - - decreaseModSpeed(); - AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - - increaseModSpeed(); - AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - - OsuModDoubleTime dt = new OsuModDoubleTime - { - SpeedChange = { Value = 1.02 }, - AdjustPitch = { Value = true }, - }; - changeMods(dt); - - decreaseModSpeed(); - AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005)); - AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); - - OsuModHalfTime ht = new OsuModHalfTime - { - SpeedChange = { Value = 0.97 }, - AdjustPitch = { Value = true }, - }; - Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; - changeMods(modlist); - - increaseModSpeed(); - AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005)); - AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); - AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); - AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); - - changeMods(new ModWindUp()); - increaseModSpeed(); - AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); - - changeMods(new ModAdaptiveSpeed()); - increaseModSpeed(); - AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); - - OsuModDoubleTime dtWithAdjustPitch = new OsuModDoubleTime - { - SpeedChange = { Value = 1.05 }, - AdjustPitch = { Value = true }, - }; - changeMods(dtWithAdjustPitch); - - decreaseModSpeed(); - AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - - decreaseModSpeed(); - AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddAssert("half time has adjust pitch active", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); - - AddStep("turn off adjust pitch", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value = false); - - increaseModSpeed(); - AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - - increaseModSpeed(); - AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddAssert("double time has adjust pitch inactive", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.False); - - void increaseModSpeed() => AddStep("increase mod speed", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Up); - InputManager.ReleaseKey(Key.ControlLeft); - }); - - void decreaseModSpeed() => AddStep("decrease mod speed", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Down); - InputManager.ReleaseKey(Key.ControlLeft); - }); - } - - [Test] - public void TestPlaceholderBeatmapPresence() - { - createSongSelect(); - - AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); - - addRulesetImportStep(0); - AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden); - - AddStep("delete all beatmaps", () => manager.Delete()); - AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); - } - - [Test] - public void TestPlaceholderStarDifficulty() - { - addRulesetImportStep(0); - AddStep("change star filter", () => config.SetValue(OsuSetting.DisplayStarsMinimum, 10.0)); - - createSongSelect(); - - AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); - - AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); - - AddUntilStep("star filter reset", () => config.Get(OsuSetting.DisplayStarsMinimum) == 0.0); - AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden); - } - - [Test] - public void TestPlaceholderConvertSetting() - { - addRulesetImportStep(0); - AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); - - createSongSelect(); - - changeRuleset(2); - - AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); - - AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); - - AddUntilStep("convert setting changed", () => config.Get(OsuSetting.ShowConvertedBeatmaps)); - AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden); - } - - [Test] - public void TestSingleFilterOnEnter() - { - addRulesetImportStep(0); - addRulesetImportStep(0); - - createSongSelect(); - - AddAssert("filter count is 0", () => songSelect?.FilterCount, () => Is.EqualTo(0)); - } - - [Test] - public void TestChangeBeatmapBeforeEnter() - { - addRulesetImportStep(0); - - createSongSelect(); - - waitForInitialSelection(); - - WorkingBeatmap? selected = null; - - AddStep("store selected beatmap", () => selected = Beatmap.Value); - - AddStep("select next and enter", () => - { - InputManager.Key(Key.Down); - InputManager.Key(Key.Enter); - }); - - waitForDismissed(); - AddAssert("ensure selection changed", () => selected != Beatmap.Value); - } - - [Test] - public void TestChangeBeatmapAfterEnter() - { - addRulesetImportStep(0); - - createSongSelect(); - - waitForInitialSelection(); - - WorkingBeatmap? selected = null; - - AddStep("store selected beatmap", () => selected = Beatmap.Value); - - AddStep("select next and enter", () => - { - InputManager.Key(Key.Enter); - InputManager.Key(Key.Down); - }); - - waitForDismissed(); - AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); - } - - [Test] - public void TestChangeBeatmapViaMouseBeforeEnter() - { - addRulesetImportStep(0); - - createSongSelect(); - - AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); - - WorkingBeatmap? selected = null; - - AddStep("store selected beatmap", () => selected = Beatmap.Value); - - AddUntilStep("wait for beatmaps to load", () => songSelect!.Carousel.ChildrenOfType().Any()); - - AddStep("select next and enter", () => - { - InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType() - .First(b => !((CarouselBeatmap)b.Item!).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo))); - - InputManager.Click(MouseButton.Left); - - InputManager.Key(Key.Enter); - }); - - waitForDismissed(); - AddAssert("ensure selection changed", () => selected != Beatmap.Value); - } - - [Test] - public void TestChangeBeatmapViaMouseAfterEnter() - { - addRulesetImportStep(0); - - createSongSelect(); - - waitForInitialSelection(); - - WorkingBeatmap? selected = null; - - AddStep("store selected beatmap", () => selected = Beatmap.Value); - - AddStep("select next and enter", () => - { - InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType() - .First(b => !((CarouselBeatmap)b.Item!).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo))); - - InputManager.PressButton(MouseButton.Left); - - InputManager.Key(Key.Enter); - - InputManager.ReleaseButton(MouseButton.Left); - }); - - waitForDismissed(); - AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); - } - - [Test] - public void TestNoFilterOnSimpleResume() - { - addRulesetImportStep(0); - addRulesetImportStep(0); - - createSongSelect(); - - AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - waitForDismissed(); - - AddStep("return", () => songSelect!.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); - AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0)); - } - - [Test] - public void TestFilterOnResumeAfterChange() - { - addRulesetImportStep(0); - addRulesetImportStep(0); - - AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); - - createSongSelect(); - - AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - waitForDismissed(); - - AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - - AddStep("return", () => songSelect!.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); - AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); - } - - [Test] - public void TestCarouselSelectionUpdatesOnResume() - { - addRulesetImportStep(0); - - createSongSelect(); - - AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - waitForDismissed(); - - AddStep("update beatmap", () => - { - var selectedBeatmap = Beatmap.Value.BeatmapInfo; - var anotherBeatmap = Beatmap.Value.BeatmapSetInfo.Beatmaps.Except(selectedBeatmap.Yield()).First(); - Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap); - }); - - AddStep("return", () => songSelect!.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); - AddAssert("carousel updated", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(Beatmap.Value.BeatmapInfo) == true); - } - - [Test] - public void TestAudioResuming() - { - createSongSelect(); - - // We need to use one real beatmap to trigger the "same-track-transfer" logic that we're looking to test here. - // See `SongSelect.ensurePlayingSelected` and `WorkingBeatmap.TryTransferTrack`. - AddStep("import test beatmap", () => manager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).WaitSafely()); - addRulesetImportStep(0); - - checkMusicPlaying(true); - AddStep("select first", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.First())); - checkMusicPlaying(true); - - AddStep("manual pause", () => music.TogglePause()); - checkMusicPlaying(false); - - // Track should not have changed, so music should still not be playing. - AddStep("select next difficulty", () => songSelect!.Carousel.SelectNext(skipDifficulties: false)); - checkMusicPlaying(false); - - AddStep("select next set", () => songSelect!.Carousel.SelectNext()); - checkMusicPlaying(true); - } - - [TestCase(false)] - [TestCase(true)] - public void TestAudioRemainsCorrectOnRulesetChange(bool rulesetsInSameBeatmap) - { - createSongSelect(); - - // start with non-osu! to avoid convert confusion - changeRuleset(1); - - if (rulesetsInSameBeatmap) - { - AddStep("import multi-ruleset map", () => - { - var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)); - }); - } - else - { - addRulesetImportStep(1); - addRulesetImportStep(0); - } - - checkMusicPlaying(true); - - AddStep("manual pause", () => music.TogglePause()); - checkMusicPlaying(false); - - changeRuleset(0); - checkMusicPlaying(!rulesetsInSameBeatmap); - } - - [Test] - public void TestDummy() - { - createSongSelect(); - AddUntilStep("dummy selected", () => songSelect!.CurrentBeatmap == defaultBeatmap); - - AddUntilStep("dummy shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap == defaultBeatmap); - - addManyTestMaps(); - - AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap); - } - - [Test] - public void TestSorting() - { - createSongSelect(); - addManyTestMaps(); - - AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap); - - AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); - AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title)); - AddStep(@"Sort by Author", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Author)); - AddStep(@"Sort by DateAdded", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.DateAdded)); - AddStep(@"Sort by BPM", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.BPM)); - AddStep(@"Sort by Length", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Length)); - AddStep(@"Sort by Difficulty", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Difficulty)); - AddStep(@"Sort by Source", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Source)); - } - - [Test] - public void TestImportUnderDifferentRuleset() - { - createSongSelect(); - addRulesetImportStep(2); - AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - } - - [Test] - public void TestImportUnderCurrentRuleset() - { - createSongSelect(); - changeRuleset(2); - addRulesetImportStep(2); - addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); - - changeRuleset(1); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 1); - - changeRuleset(0); - AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - } - - [Test] - [Ignore("temporary while peppy investigates. probably realm batching related.")] - public void TestSelectionRetainedOnBeatmapUpdate() - { - createSongSelect(); - changeRuleset(0); - - Live? original = null; - int originalOnlineSetID = 0; - - AddStep(@"Sort by artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); - - AddStep("import original", () => - { - original = manager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); - - Debug.Assert(original != null); - - originalOnlineSetID = original.Value.OnlineID; - }); - - // This will move the beatmap set to a different location in the carousel. - AddStep("Update original with bogus info", () => - { - Debug.Assert(original != null); - - original.PerformWrite(set => - { - foreach (var beatmap in set.Beatmaps) - { - beatmap.Metadata.Artist = "ZZZZZ"; - beatmap.OnlineID = 12804; - } - }); - }); - - AddRepeatStep("import other beatmaps", () => - { - var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); - - foreach (var beatmap in testBeatmapSetInfo.Beatmaps) - beatmap.Metadata.Artist = ((char)RNG.Next('A', 'Z')).ToString(); - - manager.Import(testBeatmapSetInfo); - }, 10); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); - - Task?> updateTask = null!; - - AddStep("update beatmap", () => - { - Debug.Assert(original != null); - - updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value); - }); - AddUntilStep("wait for update completion", () => updateTask.IsCompleted); - - AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); - } - - [Test] - public void TestPresentNewRulesetNewBeatmap() - { - createSongSelect(); - changeRuleset(2); - - addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); - - addRulesetImportStep(0); - addRulesetImportStep(0); - addRulesetImportStep(0); - - BeatmapInfo? target = null; - - AddStep("select beatmap/ruleset externally", () => - { - target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); - - Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); - Beatmap.Value = manager.GetWorkingBeatmap(target); - }); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true); - - // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); - } - - [Test] - public void TestPresentNewBeatmapNewRuleset() - { - createSongSelect(); - changeRuleset(2); - - addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); - - addRulesetImportStep(0); - addRulesetImportStep(0); - addRulesetImportStep(0); - - BeatmapInfo? target = null; - - AddStep("select beatmap/ruleset externally", () => - { - target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); - - Beatmap.Value = manager.GetWorkingBeatmap(target); - Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); - }); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true); - - AddUntilStep("has correct ruleset", () => Ruleset.Value.OnlineID == 0); - - // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); - } - - [Test] - public void TestModsRetainedBetweenSongSelect() - { - AddAssert("empty mods", () => !SelectedMods.Value.Any()); - - createSongSelect(); - - addRulesetImportStep(0); - - changeMods(new OsuModHardRock()); - - createSongSelect(); - - AddAssert("mods retained", () => SelectedMods.Value.Any()); - } - - [Test] - public void TestStartAfterUnMatchingFilterDoesNotStart() - { - createSongSelect(); - addManyTestMaps(); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - - bool startRequested = false; - - AddStep("set filter and finalize", () => - { - songSelect!.StartRequested = () => startRequested = true; - - songSelect!.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); - songSelect!.FinaliseSelection(); - - songSelect!.StartRequested = null; - }); - - AddAssert("start not requested", () => !startRequested); - } - - [Test] - public void TestSearchTextWithRulesetCriteria() - { - createSongSelect(); - - addRulesetImportStep(0); - - AddStep("disallow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - - AddStep("set filter to match all", () => songSelect!.FilterControl.CurrentTextSearch.Value = "Some"); - - changeRuleset(1); - - AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - } - - [TestCase(false)] - [TestCase(true)] - public void TestExternalBeatmapChangeWhileFiltered(bool differentRuleset) - { - createSongSelect(); - // ensure there is at least 1 difficulty for each of the rulesets - // (catch is excluded inside of addManyTestMaps). - addManyTestMaps(3); - - changeRuleset(0); - - // used for filter check below - AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); - - AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - - AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - - BeatmapInfo? target = null; - - int targetRuleset = differentRuleset ? 1 : 0; - - AddStep("select beatmap externally", () => - { - target = manager.GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == targetRuleset)) - .Beatmaps - .First(bi => bi.Ruleset.OnlineID == targetRuleset); - - Beatmap.Value = manager.GetWorkingBeatmap(target); - }); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - - AddAssert("selected only shows expected ruleset (plus converts)", () => - { - var selectedPanel = songSelect!.Carousel.ChildrenOfType().First(s => s.Item!.State.Value == CarouselItemState.Selected); - - // special case for converts checked here. - return selectedPanel.ChildrenOfType().All(i => - i.IsFiltered || i.Item.BeatmapInfo.Ruleset.OnlineID == targetRuleset || i.Item.BeatmapInfo.Ruleset.OnlineID == 0); - }); - - AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); - AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - - AddStep("reset filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = string.Empty); - - AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true); - AddAssert("carousel still correct", () => songSelect!.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target)); - } - - [Test] - public void TestExternalBeatmapChangeWhileFilteredThenRefilter() - { - createSongSelect(); - // ensure there is at least 1 difficulty for each of the rulesets - // (catch is excluded inside of addManyTestMaps). - addManyTestMaps(3); - - changeRuleset(0); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); - - AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - - AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - - BeatmapInfo? target = null; - - AddStep("select beatmap externally", () => - { - target = manager - .GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 1)) - .Beatmaps.First(); - - Beatmap.Value = manager.GetWorkingBeatmap(target); - }); - - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - - AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); - AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nononoo"); - - AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap); - AddAssert("carousel lost selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - } - - [Test] - public void TestAutoplayShortcut() - { - addRulesetImportStep(0); - - createSongSelect(); - - AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - - AddStep("press ctrl+enter", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Enter); - InputManager.ReleaseKey(Key.ControlLeft); - }); - - AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - - AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - - AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - - AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - } - - [Test] - public void TestAutoplayShortcutKeepsAutoplayIfSelectedAlready() - { - addRulesetImportStep(0); - - createSongSelect(); - - AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - - changeMods(new OsuModAutoplay()); - - AddStep("press ctrl+enter", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Enter); - InputManager.ReleaseKey(Key.ControlLeft); - }); - - AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - - AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - - AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - - AddAssert("autoplay still selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - } - - [Test] - public void TestAutoplayShortcutReturnsInitialModsOnExit() - { - addRulesetImportStep(0); - - createSongSelect(); - - AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - - changeMods(new OsuModRelax()); - - AddStep("press ctrl+enter", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Enter); - InputManager.ReleaseKey(Key.ControlLeft); - }); - - AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - - AddAssert("only autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - - AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - - AddAssert("relax returned", () => songSelect!.Mods.Value.Single() is ModRelax); - } - - [Test] - public void TestHideSetSelectsCorrectBeatmap() - { - Guid? previousID = null; - createSongSelect(); - addRulesetImportStep(0); - AddStep("Move to last difficulty", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.Last())); - AddStep("Store current ID", () => previousID = songSelect!.Carousel.SelectedBeatmapInfo!.ID); - AddStep("Hide first beatmap", () => manager.Hide(songSelect!.Carousel.SelectedBeatmapSet!.Beatmaps.First())); - AddAssert("Selected beatmap has not changed", () => songSelect!.Carousel.SelectedBeatmapInfo?.ID == previousID); - } - - [Test] - public void TestDifficultyIconSelecting() - { - addRulesetImportStep(0); - createSongSelect(); - - AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - - DrawableCarouselBeatmapSet set = null!; - AddStep("Find the DrawableCarouselBeatmapSet", () => - { - set = songSelect!.Carousel.ChildrenOfType().First(); - }); - - FilterableDifficultyIcon difficultyIcon = null!; - - AddUntilStep("Find an icon", () => - { - var foundIcon = set.ChildrenOfType() - .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); - - if (foundIcon == null) - return false; - - difficultyIcon = foundIcon; - return true; - }); - - AddStep("Click on a difficulty", () => - { - InputManager.MoveMouseTo(difficultyIcon); - - InputManager.Click(MouseButton.Left); - }); - - AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon)); - - double? maxBPM = null; - AddStep("Filter some difficulties", () => songSelect!.Carousel.Filter(new FilterCriteria - { - BPM = new FilterCriteria.OptionalRange - { - Min = maxBPM = songSelect!.Carousel.SelectedBeatmapSet!.MaxBPM, - IsLowerInclusive = true - } - })); - - BeatmapInfo? filteredBeatmap = null; - FilterableDifficultyIcon? filteredIcon = null; - - AddStep("Get filtered icon", () => - { - var selectedSet = songSelect!.Carousel.SelectedBeatmapSet; - - Debug.Assert(selectedSet != null); - - filteredBeatmap = selectedSet.Beatmaps.First(b => b.BPM < maxBPM); - int filteredBeatmapIndex = getBeatmapIndex(selectedSet, filteredBeatmap); - filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); - }); - - AddStep("Click on a filtered difficulty", () => - { - Debug.Assert(filteredIcon != null); - - InputManager.MoveMouseTo(filteredIcon); - - InputManager.Click(MouseButton.Left); - }); - - AddAssert("Selected beatmap correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(filteredBeatmap) == true); - } - - [Test] - public void TestChangingRulesetOnMultiRulesetBeatmap() - { - int changeCount = 0; - - AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); - AddStep("bind beatmap changed", () => - { - Beatmap.ValueChanged += onChange; - changeCount = 0; - }); - - changeRuleset(0); - - createSongSelect(); - - AddStep("import multi-ruleset map", () => - { - var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); - }); - - int previousSetID = 0; - - AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - - AddStep("record set ID", () => previousSetID = ((IBeatmapSetInfo)Beatmap.Value.BeatmapSetInfo).OnlineID); - AddAssert("selection changed once", () => changeCount == 1); - - AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0); - - changeRuleset(3); - - AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3); - - AddUntilStep("selection changed", () => changeCount > 1); - - AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.OnlineID == previousSetID); - AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3); - - AddAssert("selection changed only fired twice", () => changeCount == 2); - - AddStep("unbind beatmap changed", () => Beatmap.ValueChanged -= onChange); - AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - - // ReSharper disable once AccessToModifiedClosure - void onChange(ValueChangedEvent valueChangedEvent) => changeCount++; - } - - [Test] - public void TestDifficultyIconSelectingForDifferentRuleset() - { - changeRuleset(0); - - createSongSelect(); - - AddStep("import multi-ruleset map", () => - { - var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); - }); - - DrawableCarouselBeatmapSet? set = null; - AddUntilStep("Find the DrawableCarouselBeatmapSet", () => - { - set = songSelect!.Carousel.ChildrenOfType().FirstOrDefault(); - return set != null; - }); - - FilterableDifficultyIcon? difficultyIcon = null; - AddUntilStep("Find an icon for different ruleset", () => - { - difficultyIcon = set.ChildrenOfType() - .FirstOrDefault(icon => icon.Item.BeatmapInfo.Ruleset.OnlineID == 3); - return difficultyIcon != null; - }); - - AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0); - - int previousSetID = 0; - - AddStep("record set ID", () => previousSetID = ((IBeatmapSetInfo)Beatmap.Value.BeatmapSetInfo).OnlineID); - - AddStep("Click on a difficulty", () => - { - Debug.Assert(difficultyIcon != null); - - InputManager.MoveMouseTo(difficultyIcon); - - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3); - - AddAssert("Selected beatmap still same set", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == previousSetID); - AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3); - } - - [Test] - public void TestGroupedDifficultyIconSelecting() - { - changeRuleset(0); - - createSongSelect(); - - BeatmapSetInfo? imported = null; - - AddStep("import huge difficulty count map", () => - { - var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value; - }); - - AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported?.Beatmaps.First())); - - DrawableCarouselBeatmapSet? set = null; - AddUntilStep("Find the DrawableCarouselBeatmapSet", () => - { - set = songSelect!.Carousel.ChildrenOfType().FirstOrDefault(); - return set != null; - }); - - GroupedDifficultyIcon groupIcon = null!; - - AddUntilStep("Find group icon for different ruleset", () => - { - var foundIcon = set.ChildrenOfType() - .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3); - - if (foundIcon == null) - return false; - - groupIcon = foundIcon; - return true; - }); - - AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0); - - AddStep("Click on group", () => - { - InputManager.MoveMouseTo(groupIcon); - - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3); - - AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(groupIcon.Items.First().BeatmapInfo)); - } - - [Test] - public void TestChangeRulesetWhilePresentingScore() - { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); - - changeRuleset(0); - - createSongSelect(); - - addRulesetImportStep(0); - addRulesetImportStep(1); - - AddStep("present score", () => - { - // this ruleset change should be overridden by the present. - Ruleset.Value = getSwitchBeatmap().Ruleset; - - songSelect!.PresentScore(new ScoreInfo - { - User = new APIUser { Username = "woo" }, - BeatmapInfo = getPresentBeatmap(), - Ruleset = getPresentBeatmap().Ruleset - }); - }); - - waitForDismissed(); - - AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); - AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); - } - - [Test] - public void TestChangeBeatmapWhilePresentingScore() - { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); - - changeRuleset(0); - - addRulesetImportStep(0); - addRulesetImportStep(1); - - createSongSelect(); - - AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - - AddStep("present score", () => - { - // this beatmap change should be overridden by the present. - Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); - - songSelect!.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); - }); - - waitForDismissed(); - - AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); - AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); - } - - [Test] - public void TestModOverlayToggling() - { - changeRuleset(0); - createSongSelect(); - - AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1)); - AddUntilStep("mod overlay shown", () => songSelect!.ModSelect.State.Value == Visibility.Visible); - - AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1)); - AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden); - } - - [Test] - public void TestBeatmapOptionsDisabled() - { - createSongSelect(); - - addRulesetImportStep(0); - - AddAssert("options enabled", () => songSelect.ChildrenOfType().Single().Enabled.Value); - AddStep("delete all beatmaps", () => manager.Delete()); - AddUntilStep("wait for no beatmap", () => Beatmap.IsDefault); - AddAssert("options disabled", () => !songSelect.ChildrenOfType().Single().Enabled.Value); - } - - [Test] - public void TestTextBoxBeatmapDifficultyCount() - { - createSongSelect(); - - AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); - - addRulesetImportStep(0); - - AddAssert("3 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "3 matches"); - AddStep("delete all beatmaps", () => manager.Delete()); - AddUntilStep("wait for no beatmap", () => Beatmap.IsDefault); - AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); - } - - [Test] - public void TestHardDeleteHandledCorrectly() - { - createSongSelect(); - - addRulesetImportStep(0); - AddAssert("3 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "3 matches"); - - AddStep("hard delete beatmap", () => Realm.Write(r => r.RemoveRange(r.All().Where(s => !s.Protected)))); - - AddUntilStep("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); - } - - [Test] - public void TestDeleteHotkey() - { - createSongSelect(); - - addRulesetImportStep(0); - AddAssert("3 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "3 matches"); - - AddStep("press shift-delete", () => - { - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.Delete); - InputManager.ReleaseKey(Key.ShiftLeft); - }); - AddUntilStep("delete dialog shown", () => DialogOverlay.CurrentDialog, Is.InstanceOf); - AddStep("confirm deletion", () => DialogOverlay.CurrentDialog!.PerformAction()); - AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); - } - - [Test] - public void TestCutInFilterTextBox() - { - createSongSelect(); - - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); - AddStep("select all", () => InputManager.Keys(PlatformAction.SelectAll)); - AddStep("press ctrl/cmd-x", () => InputManager.Keys(PlatformAction.Cut)); - - AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType().First().Text, () => Is.Empty); - } - - [Test] - public void TestNonFilterableModChange() - { - addRulesetImportStep(0); - - createSongSelect(); - - // Mod that is guaranteed to never re-filter. - AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() }); - AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0)); - - // Removing the mod should still not re-filter. - AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty()); - AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0)); - } - - [Test] - public void TestFilterableModChange() - { - addRulesetImportStep(3); - - createSongSelect(); - - // Change to mania ruleset. - AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3)); - AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(1)); - - // Apply a mod, but this should NOT re-filter because there's no search text. - AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() }); - AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); - - // Set search text. Should re-filter. - AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3"); - AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2)); - - // Change filterable mod. Should re-filter. - AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() }); - AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3)); - - // Add non-filterable mod. Should NOT re-filter. - AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() }); - AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3)); - - // Remove filterable mod. Should re-filter. - AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() }); - AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); - - // Remove non-filterable mod. Should NOT re-filter. - AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty()); - AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); - - // Add filterable mod. Should re-filter. - AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() }); - AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5)); - } - - private void waitForInitialSelection() - { - AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); - AddUntilStep("wait for difficulty panels visible", () => songSelect!.Carousel.ChildrenOfType().Any()); - } - - private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info); - - private NoResultsPlaceholder? getPlaceholder() => songSelect!.ChildrenOfType().FirstOrDefault(); - - private int getCurrentBeatmapIndex() - { - Debug.Assert(songSelect!.Carousel.SelectedBeatmapSet != null); - Debug.Assert(songSelect!.Carousel.SelectedBeatmapInfo != null); - - return getBeatmapIndex(songSelect!.Carousel.SelectedBeatmapSet, songSelect!.Carousel.SelectedBeatmapInfo); - } - - private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) - { - return set.ChildrenOfType().ToList().FindIndex(i => i == icon); - } - - private void addRulesetImportStep(int id) - { - Live? imported = null; - AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id)); - // This is specifically for cases where the add is happening post song select load. - // For cases where song select is null, the assertions are provided by the load checks. - AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect!.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); - } - - private Live? importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); - - private void checkMusicPlaying(bool playing) => - AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); - - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods); - - private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == id)); - - private void createSongSelect() - { - AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); - AddUntilStep("wait for present", () => songSelect!.IsCurrentScreen()); - AddUntilStep("wait for carousel loaded", () => songSelect!.Carousel.IsAlive); - } - - /// - /// Imports test beatmap sets to show in the carousel. - /// - /// - /// The exact count of difficulties to create for each beatmap set. - /// A value causes the count of difficulties to be selected randomly. - /// - private void addManyTestMaps(int? difficultyCountPerSet = null) - { - AddStep("import test maps", () => - { - var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - - for (int i = 0; i < 10; i++) - manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)); - }); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (rulesets.IsNotNull()) - rulesets.Dispose(); - } - - private void waitForDismissed() => AddUntilStep("wait for not current", () => !songSelect.AsNonNull().IsCurrentScreen()); - - private partial class TestSongSelect : PlaySongSelect - { - public Action? StartRequested; - - public new FilterControl FilterControl => base.FilterControl; - - public WorkingBeatmap CurrentBeatmap => Beatmap.Value; - public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; - public new BeatmapCarousel Carousel => base.Carousel; - public new ModSelectOverlay ModSelect => base.ModSelect; - - public new void PresentScore(ScoreInfo score) => base.PresentScore(score); - - public int FilterCount; - - protected override bool OnStart() - { - StartRequested?.Invoke(); - return base.OnStart(); - } - - [BackgroundDependencyLoader] - private void load() - { - FilterControl.FilterChanged += _ => FilterCount++; - } - } - } -} diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs index 7f34d7a901..29b4955d02 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs @@ -263,8 +263,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 var results = await runGrouping(GroupMode.Difficulty, beatmapSets); assertGroup(results, 0, "Below 1 Star", new[] { beatmapBelow1 }, ref total); - assertGroup(results, 1, "1 Star", new[] { beatmapAbove1 }, ref total); - assertGroup(results, 2, "2 Stars", new[] { beatmapAlmost2, beatmap2, beatmapAbove2 }, ref total); + assertGroup(results, 1, "1 Star", new[] { beatmapAbove1, beatmapAlmost2 }, ref total); + assertGroup(results, 2, "2 Stars", new[] { beatmap2, beatmapAbove2 }, ref total); assertGroup(results, 3, "7 Stars", new[] { beatmap7 }, ref total); assertTotal(results, total); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs index 992651d73c..8fcb3d7acc 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs @@ -28,7 +28,6 @@ using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.SelectV2; using osu.Game.Tests.Resources; -using osu.Game.Tests.Visual.SongSelect; using osu.Game.Users; using osuTK.Input; @@ -118,8 +117,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { setScope(BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()))); - AddStep(@"New Scores with teams", () => leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()).Select(s => + AddStep(@"New Scores", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()))); + AddStep(@"New Scores with teams", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()).Select(s => { s.User.Team = new APITeam(); return s; @@ -150,7 +149,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Test] public void TestUseTheseModsDoesNotCopySystemMods() { - AddStep(@"set scores", () => leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()), new ScoreInfo + AddStep(@"set scores", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()), new ScoreInfo { OnlineID = 1337, Position = 999, @@ -297,7 +296,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 private void showPersonalBestWithNullPosition() { - leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()), new ScoreInfo + leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()), new ScoreInfo { OnlineID = 1337, Rank = ScoreRank.XH, @@ -318,7 +317,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 private void showPersonalBest() { - leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()), new ScoreInfo + leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()), new ScoreInfo { OnlineID = 1337, Position = 999, @@ -347,7 +346,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep(@"Import new scores", () => { - foreach (var score in TestSceneBeatmapLeaderboard.GenerateSampleScores(beatmapInfo())) + foreach (var score in GenerateSampleScores(beatmapInfo())) scoreManager.Import(score); }); } @@ -368,5 +367,216 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public new void SetState(LeaderboardState state) => base.SetState(state); public new void SetScores(IEnumerable scores, ScoreInfo? userScore = null, int? totalCount = null) => base.SetScores(scores, userScore, totalCount); } + + public static ScoreInfo[] GenerateSampleScores(BeatmapInfo beatmapInfo) + { + return new[] + { + new ScoreInfo + { + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now, + Mods = new Mod[] + { + new OsuModHidden(), + new OsuModFlashlight + { + FollowDelay = { Value = 200 }, + SizeMultiplier = { Value = 5 }, + }, + new OsuModDifficultyAdjust + { + CircleSize = { Value = 11 }, + ApproachRate = { Value = 10 }, + OverallDifficulty = { Value = 10 }, + DrainRate = { Value = 10 }, + ExtendedLimits = { Value = true } + } + }, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + CountryCode = CountryCode.ES, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.X, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-30), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser + { + Id = 4608074, + Username = @"Skycries", + CountryCode = CountryCode.BR, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.SH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-70), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 1014222, + Username = @"eLy", + CountryCode = CountryCode.JP, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.S, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddMinutes(-40), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 1541390, + Username = @"Toukai", + CountryCode = CountryCode.CA, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.A, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddHours(-2), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 2243452, + Username = @"Satoruu", + CountryCode = CountryCode.VE, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.B, + Accuracy = 0.9826, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddHours(-25), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 2705430, + Username = @"Mooha", + CountryCode = CountryCode.FR, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.C, + Accuracy = 0.9654, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddHours(-50), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 7151382, + Username = @"Mayuri Hana", + CountryCode = CountryCode.TH, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.D, + Accuracy = 0.6025, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddHours(-72), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 2051389, + Username = @"FunOrange", + CountryCode = CountryCode.CA, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.D, + Accuracy = 0.5140, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddMonths(-10), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 6169483, + Username = @"-Hebel-", + CountryCode = CountryCode.MX, + }, + }, + new ScoreInfo + { + Rank = ScoreRank.D, + Accuracy = 0.4222, + MaxCombo = 244, + TotalScore = 1707827, + Date = DateTime.Now.AddYears(-2), + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + BeatmapInfo = beatmapInfo, + BeatmapHash = beatmapInfo.Hash, + Ruleset = new OsuRuleset().RulesetInfo, + + User = new APIUser + { + Id = 6702666, + Username = @"prhtnsm", + CountryCode = CountryCode.DE, + }, + }, + }; + } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 6c290c4f1c..6fb762b9ee 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps.Formats public abstract class LegacyDecoder : Decoder where T : new() { + // If this is updated, a new release of `osu-server-beatmap-submission` is required with updated packages. + // See usage at https://github.com/ppy/osu-server-beatmap-submission/blob/master/osu.Server.BeatmapSubmission/Services/BeatmapPackageParser.cs#L96-L97. public const int LATEST_VERSION = 14; public const int MAX_COMBO_COLOUR_COUNT = 8; diff --git a/osu.Game/Configuration/BackgroundSource.cs b/osu.Game/Configuration/BackgroundSource.cs index 5b6f292186..083e249c20 100644 --- a/osu.Game/Configuration/BackgroundSource.cs +++ b/osu.Game/Configuration/BackgroundSource.cs @@ -18,7 +18,7 @@ namespace osu.Game.Configuration [LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.BeatmapWithStoryboard))] BeatmapWithStoryboard, - [Description("Use 'Resource\\Webm\\*.webm' file from local folders")] + [Description("'EzResource/Webm/*.webm' from local folders")] WebmSource, } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 991da5f52e..1f74b2ce68 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Mods.Input; +using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.LAsEzExtensions; @@ -51,7 +52,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.SongSelectGroupMode, GroupMode.None); SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title); - SetDefault(OsuSetting.SelectEzMode, EzSelectMode.All); SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); @@ -157,10 +157,19 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.FloatingComments, false); SetDefault(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); + + //新增自定义 + SetDefault(OsuSetting.SelectEzMode, EzSelectMode.All); SetDefault(OsuSetting.HitMode, MUGHitMode.EZ2AC); SetDefault(OsuSetting.AccuracyCutoffS, 0.95, 0.95, 1, 0.005); SetDefault(OsuSetting.AccuracyCutoffA, 0.9, 0.9, 1, 0.005); + // SetDefault(OsuSetting.ScrollBaseSpeed, 500, 100, 1000, 1.0); + // SetDefault(OsuSetting.ScrollTimePerSpeed, 5, 1.0, 40, 1.0); + // SetDefault(OsuSetting.ScrollStyle, EzManiaScrollingStyle.ScrollTimeStyleFixed); + // SetDefault(OsuSetting.ScrollPerKeyMode, false); + // SetDefault(OsuSetting.PerspectiveAngle, 90.0f, 30.0f, 90.0f); + SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true); SetDefault(OsuSetting.GameplayDisableWinKey, true); @@ -175,8 +184,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false); - SetDefault(OsuSetting.SongSelectRightMouseScroll, true); - SetDefault(OsuSetting.Scaling, ScalingMode.Off); SetDefault(OsuSetting.ScalingGameMode, ScalingGameMode.Mania); SetDefault(OsuSetting.SafeAreaConsiderations, true); @@ -413,19 +420,27 @@ namespace osu.Game.Configuration Skin, ScreenshotFormat, ScreenshotCaptureMenuCursor, - SongSelectRightMouseScroll, BeatmapSkins, BeatmapColours, BeatmapHitsounds, IncreaseFirstObjectVisibility, ScoreDisplayMode, - SelectEzMode, - SelectManiaRulesetSubset, - ScalingGameMode, - HitMode, + //自定义 + SelectEzMode, + ScalingGameMode, AccuracyCutoffS, AccuracyCutoffA, + HitMode, + // //mania用自定义 + // SelectManiaRulesetSubset, + // ScrollBaseSpeed, + // ScrollTimePerSpeed, + // ScrollStyle, + // + // PerspectiveAngle, + // ScrollPerKeyMode, + ExternalLinkWarning, PreferNoVideo, Scaling, diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 7a5fb5efbf..e96a8cc1b1 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -85,6 +86,7 @@ namespace osu.Game.Database /// public void AddFile(TModel item, Stream contents, string filename, Realm realm) { + filename = filename.ToStandardisedPath(); var existing = item.GetFile(filename); if (existing != null) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 49bde7c505..6e8107a9fd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -102,6 +102,8 @@ namespace osu.Game.Database /// private const int schema_version = 49; + public static int SchemaVersion => schema_version; + /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. /// @@ -202,10 +204,10 @@ namespace osu.Game.Database if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal)) Filename += realm_extension; -#if DEBUG +// #if DEBUG if (!DebugUtils.IsNUnitRunning) applyFilenameSchemaSuffix(ref Filename); -#endif +// #endif // `prepareFirstRealmAccess()` triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date. using (var realm = prepareFirstRealmAccess()) @@ -543,6 +545,44 @@ namespace osu.Game.Database return writeTask; } + /// + /// Write changes to realm asynchronously, guaranteeing order of execution. + /// + /// The work to run. + public Task WriteAsync(Func action) + { + ObjectDisposedException.ThrowIf(isDisposed, this); + + // Required to ensure the write is tracked and accounted for before disposal. + // Can potentially be avoided if we have a need to do so in the future. + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread."); + + // CountdownEvent will fail if already at zero. + if (!pendingAsyncWrites.TryAddCount()) + pendingAsyncWrites.Reset(1); + + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + var writeTask = Task.Run(async () => + { + T result; + total_writes_async.Value++; + + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + result = await realm.WriteAsync(() => action(realm)).ConfigureAwait(false); + + pendingAsyncWrites.Signal(); + return result; + }); + + return writeTask; + } + /// /// Subscribe to a realm collection and begin watching for asynchronous changes. /// diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 538ac1dff7..d43f90c292 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -14,6 +14,7 @@ using osu.Game.Input.Bindings; using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Skinning; using Realms; namespace osu.Game.Database @@ -177,6 +178,7 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); + c.CreateMap(); } /// diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 66e8aaf008..b0e2ad428e 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -307,7 +307,7 @@ namespace osu.Game.Graphics.Carousel /// /// Retrieve a list of all s currently displayed. /// - protected IReadOnlyCollection? GetCarouselItems() => carouselItems; + public IReadOnlyCollection? GetCarouselItems() => carouselItems; private List? carouselItems; @@ -670,7 +670,7 @@ namespace osu.Game.Graphics.Carousel private void loadSamples(AudioManager audio) { - sampleKeyboardTraversal = audio.Samples.Get(@"UI/button-hover"); + sampleKeyboardTraversal = audio.Samples.Get(@"SongSelect/select-difficulty"); } private void playTraversalSound() @@ -1028,7 +1028,7 @@ namespace osu.Game.Graphics.Carousel /// Implementation of scroll container which handles very large vertical lists by internally using double precision /// for pre-display Y values. /// - protected partial class CarouselScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler + public partial class CarouselScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler { public readonly Container Panels; diff --git a/osu.Game/Graphics/Containers/ExpandingContainer.cs b/osu.Game/Graphics/Containers/ExpandingContainer.cs index ad5c65c10e..4b70fd6987 100644 --- a/osu.Game/Graphics/Containers/ExpandingContainer.cs +++ b/osu.Game/Graphics/Containers/ExpandingContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -41,24 +40,20 @@ namespace osu.Game.Graphics.Containers RelativeSizeAxes = Axes.Y; Width = contractedWidth; - FillFlow = new FillFlowContainer - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }; - } - - [BackgroundDependencyLoader] - private void load() - { InternalChild = CreateScrollContainer().With(s => { s.RelativeSizeAxes = Axes.Both; s.ScrollbarVisible = false; - }).WithChild(FillFlow); + }).WithChild( + FillFlow = new FillFlowContainer + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } + ); } protected virtual OsuScrollContainer CreateScrollContainer() => new OsuScrollContainer(); diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 16891babf3..2047fc74f4 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -179,7 +179,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnMouseDown(MouseDownEvent e) { Content.ScaleTo(0.9f, 2000, Easing.OutQuint); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 4ce3e7f12b..eeccdc8e8a 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -154,6 +154,11 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTimingChanges => new TranslatableString(getKey(@"timeline_show_timing_changes"), @"Show timing changes"); + /// + /// "Finish editing and import changes" + /// + public static LocalisableString FinishEditingExternally => new TranslatableString(getKey(@"Finish editing and import changes"), @"Finish editing and import changes"); + /// /// "Show breaks" /// diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs index 9d398e8e64..977c0928b2 100644 --- a/osu.Game/Localisation/MenuTipStrings.cs +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -149,6 +149,11 @@ namespace osu.Game.Localisation /// public static LocalisableString DragAndDropImageInSkinEditor => new TranslatableString(getKey(@"drag_and_drop_image_in_skin_editor"), @"Drag and drop any image into the skin editor to load it in quickly!"); + /// + /// "Try holding your right mouse button near the beatmap carousel to quickly scroll to an absolute position!" + /// + public static LocalisableString RightMouseAbsoluteScroll => new TranslatableString(getKey(@"right_mouse_absolute_scroll"), @"Try holding your right mouse button near the beatmap carousel to quickly scroll to an absolute position!"); + /// /// "a tip for you:" /// diff --git a/osu.Game/Localisation/ResultsScreenStrings.cs b/osu.Game/Localisation/ResultsScreenStrings.cs index 54e7717af9..143d5b70bc 100644 --- a/osu.Game/Localisation/ResultsScreenStrings.cs +++ b/osu.Game/Localisation/ResultsScreenStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString NoPPForUnrankedMods => new TranslatableString(getKey(@"no_pp_for_unranked_mods"), @"Performance points are not granted for this score because of unranked mods."); + /// + /// "Performance points are not granted for failed scores." + /// + public static LocalisableString NoPPForFailedScores => new TranslatableString(getKey(@"no_pp_for_failed_scores"), @"Performance points are not granted for failed scores."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs b/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs index 390a6f9ca4..4ddffe615f 100644 --- a/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs +++ b/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs @@ -14,11 +14,6 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), @"Attribute"); - /// - /// "The attribute to be displayed." - /// - public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), @"The attribute to be displayed."); - /// /// "Template" /// diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index 35ed1ea55c..2f34987e8e 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -14,31 +14,16 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), @"Sprite name"); - /// - /// "The filename of the sprite" - /// - public static LocalisableString SpriteNameDescription => new TranslatableString(getKey(@"sprite_name_description"), @"The filename of the sprite"); - /// /// "Font" /// public static LocalisableString Font => new TranslatableString(getKey(@"font"), @"Font"); - /// - /// "The font to use." - /// - public static LocalisableString FontDescription => new TranslatableString(getKey(@"font_description"), @"The font to use."); - /// /// "Text" /// public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), @"Text"); - /// - /// "The text to be displayed." - /// - public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), @"The text to be displayed."); - /// /// "Corner radius" /// @@ -54,31 +39,16 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString ShowLabel => new TranslatableString(getKey(@"show_label"), @"Show label"); - /// - /// "Whether the component's label should be shown." - /// - public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the component's label should be shown."); - /// /// "Colour" /// public static LocalisableString Colour => new TranslatableString(getKey(@"colour"), @"Colour"); - /// - /// "The colour of the component." - /// - public static LocalisableString ColourDescription => new TranslatableString(getKey(@"colour_description"), @"The colour of the component."); - /// /// "Text colour" /// public static LocalisableString TextColour => new TranslatableString(getKey(@"text_colour"), @"Text colour"); - /// - /// "The colour of the text." - /// - public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text."); - /// /// "Text weight" /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 394917dc62..57ed6a5dbf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -227,6 +227,8 @@ namespace osu.Game private Bindable configSkin; + private RealmDetachedBeatmapStore detachedBeatmapStore; + private readonly string[] args; private readonly List focusedOverlays = new List(); @@ -312,6 +314,8 @@ namespace osu.Game foreach (var overlay in focusedOverlays) overlay.Hide(); + ScreenFooter.ActiveOverlay?.Hide(); + if (hideToolbar) Toolbar.Hide(); } @@ -1000,6 +1004,10 @@ namespace osu.Game protected override void Dispose(bool isDisposing) { + // Without this, tests may deadlock due to cancellation token not becoming cancelled before disposal. + // To reproduce, run `TestSceneButtonSystemNavigation` ensuring `TestConstructor` runs before `TestFastShortcutKeys`. + detachedBeatmapStore?.Dispose(); + base.Dispose(isDisposing); SentryLogger.Dispose(); } @@ -1243,7 +1251,7 @@ namespace osu.Game loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); - loadComponentSingleFile(new RealmDetachedBeatmapStore(), Add, true); + loadComponentSingleFile(detachedBeatmapStore = new RealmDetachedBeatmapStore(), Add, true); Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 60735a4b1c..c2da2ebb50 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -78,9 +78,9 @@ namespace osu.Game public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider { #if DEBUG - public const string GAME_NAME = "osu! (development)"; + public const string GAME_NAME = "ez2osu! (development)"; #else - public const string GAME_NAME = "osu!"; + public const string GAME_NAME = "ez2osu!"; #endif public const string OSU_PROTOCOL = "osu://"; diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 74b523fdec..f2630caa83 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -38,6 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet public readonly Bindable Beatmap = new Bindable(); private APIBeatmapSet? beatmapSet; + private readonly Box background; public APIBeatmapSet? BeatmapSet { @@ -68,12 +69,29 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Vertical, Children = new Drawable[] { - Difficulties = new DifficultiesContainer + new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2), Bottom = 10 }, - OnLostHover = () => showBeatmap(Beatmap.Value, withStarRating: false), + Children = new Drawable[] + { + new Container + { + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f + } + }, + Difficulties = new DifficultiesContainer + { + AutoSizeAxes = Axes.Both, + OnLostHover = () => showBeatmap(Beatmap.Value, withStarRating: false), + }, + } }, infoContainer = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11)) { @@ -108,9 +126,10 @@ namespace osu.Game.Overlays.BeatmapSet private IBindable ruleset { get; set; } = null!; [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { updateDisplay(); + background.Colour = colourProvider.Background3; } protected override void LoadComplete() @@ -123,6 +142,12 @@ namespace osu.Game.Overlays.BeatmapSet Beatmap.TriggerChange(); } + protected override void Update() + { + base.Update(); + Difficulties.MaximumSize = new Vector2(DrawWidth, float.MaxValue); + } + private void updateDisplay() { Difficulties.Clear(); @@ -240,8 +265,8 @@ namespace osu.Game.Overlays.BeatmapSet public partial class DifficultySelectorButton : OsuClickableContainer, IStateful { private const float transition_duration = 100; - private const float size = 54; - private const float background_size = size - 2; + private const float size = 40; + private const float background_size = size - 1; private readonly Container background; private readonly Box backgroundBox; @@ -276,7 +301,6 @@ namespace osu.Game.Overlays.BeatmapSet { Beatmap = beatmapInfo; Size = new Vector2(size); - Margin = new MarginPadding { Horizontal = tile_spacing / 2 }; Children = new Drawable[] { @@ -284,7 +308,8 @@ namespace osu.Game.Overlays.BeatmapSet { Size = new Vector2(background_size), Masking = true, - CornerRadius = 4, + CornerRadius = 10, + BorderThickness = 3, Child = backgroundBox = new Box { RelativeSizeAxes = Axes.Both, @@ -338,6 +363,7 @@ namespace osu.Game.Overlays.BeatmapSet private void load(OverlayColourProvider colourProvider) { backgroundBox.Colour = colourProvider.Background6; + background.BorderColour = colourProvider.Light2; } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index fc64408775..edadc333c8 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -25,7 +25,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Visual; using osuTK; @@ -101,11 +101,14 @@ namespace osu.Game.Overlays.FirstRunSetup } } - private partial class NestedSongSelect : PlaySongSelect + private partial class NestedSongSelect : SoloSongSelect { - protected override bool ControlGlobalMusic => false; - public override bool? ApplyModTrackAdjustments => false; + + public NestedSongSelect() + { + ControlGlobalMusic = false; + } } private partial class UIScaleSlider : RoundedSliderBar @@ -145,16 +148,17 @@ namespace osu.Game.Overlays.FirstRunSetup protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new DependencyContainer(new DependencyIsolationContainer(base.CreateChildDependencies(parent))); + private ScreenFooter footer; + [BackgroundDependencyLoader] private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets) { - Beatmap.Value = new DummyWorkingBeatmap(audio, textures); + Beatmap.Default = Beatmap.Value = new DummyWorkingBeatmap(audio, textures); Ruleset.Value = rulesets.AvailableRulesets.First(); OsuScreenStack stack; OsuLogo logo; - ScreenFooter footer; Padding = new MarginPadding(5); @@ -192,6 +196,13 @@ namespace osu.Game.Overlays.FirstRunSetup // intentionally load synchronously so it is included in the initial load of the first run screen. stack.PushSynchronously(screen); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + footer.Show(); + } } private class DependencyIsolationContainer : IReadOnlyDependencyContainer diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 64fa6e37a4..45c4fded6f 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -448,7 +448,7 @@ namespace osu.Game.Overlays.Mods } else { - modState.Mod.ResetHitWindows(); + modState.Mod.ResetSettingsToDefaults(); modState.Active.Value = false; } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index f643d16848..cba57e9d7f 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -60,4 +60,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }; } } + + public enum MUGHitMode + { + Lazer, + EZ2AC, + IIDX, + Melody, + } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 3ce546785a..6aebec88a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -113,15 +112,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input AutoSizeAxes = Axes.Y, }.With(t => { - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - { - t.NewLine(); - var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription( - RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" - : @"https://opentabletdriver.net/Wiki/FAQ/Linux"))); - t.AddLinks(formattedSource.Text, formattedSource.Links); - } + t.NewLine(); + + const string url = @"https://opentabletdriver.net/Wiki/FAQ/General"; + var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription(url))); + + t.AddLinks(formattedSource.Text, formattedSource.Links); }), } }, diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 132656147e..9800fa10f6 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -178,9 +178,12 @@ namespace osu.Game.Overlays.Settings.Sections base.LoadComplete(); currentSkin = skins.CurrentSkin.GetBoundCopy(); - currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.PerformRead(s => !s.Protected), true); + currentSkin.BindValueChanged(_ => updateState()); + currentSkin.BindDisabledChanged(_ => updateState(), true); } + private void updateState() => Enabled.Value = !currentSkin.Disabled && currentSkin.Value.SkinInfo.PerformRead(s => !s.Protected); + public Popover GetPopover() { return new RenameSkinPopover(); @@ -206,9 +209,12 @@ namespace osu.Game.Overlays.Settings.Sections base.LoadComplete(); currentSkin = skins.CurrentSkin.GetBoundCopy(); - currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.PerformRead(s => !s.Protected), true); + currentSkin.BindValueChanged(_ => updateState()); + currentSkin.BindDisabledChanged(_ => updateState(), true); } + private void updateState() => Enabled.Value = !currentSkin.Disabled && currentSkin.Value.SkinInfo.PerformRead(s => !s.Protected); + private void export() { try @@ -244,9 +250,12 @@ namespace osu.Game.Overlays.Settings.Sections base.LoadComplete(); currentSkin = skins.CurrentSkin.GetBoundCopy(); - currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.PerformRead(s => !s.Protected), true); + currentSkin.BindValueChanged(_ => updateState()); + currentSkin.BindDisabledChanged(_ => updateState(), true); } + private void updateState() => Enabled.Value = !currentSkin.Disabled && currentSkin.Value.SkinInfo.PerformRead(s => !s.Protected); + private void delete() { dialogOverlay?.Push(new SkinDeleteDialog(currentSkin.Value)); diff --git a/osu.Game/Overlays/SkinEditor/ExternalEditOverlay.cs b/osu.Game/Overlays/SkinEditor/ExternalEditOverlay.cs new file mode 100644 index 0000000000..e4ac157936 --- /dev/null +++ b/osu.Game/Overlays/SkinEditor/ExternalEditOverlay.cs @@ -0,0 +1,284 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Input.Bindings; +using osu.Game.Localisation; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.SkinEditor +{ + public partial class ExternalEditOverlay : OsuFocusedOverlayContainer + { + private const double transition_duration = 300; + private FillFlowContainer flow = null!; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + [Resolved] + private SkinManager skinManager { get; set; } = null!; + + private ExternalEditOperation? editOperation; + private TaskCompletionSource? taskCompletionSource; + + protected override bool DimMainContent => false; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + // Since we're drawing this overlay on top of another overlay (SkinEditor), the dimming effect isn't applied. So we need to add a dimming effect manually. + new Box + { + Colour = Color4.Black.Opacity(0.5f), + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Masking = true, + CornerRadius = 20, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + flow = new FillFlowContainer + { + Margin = new MarginPadding(20), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(15), + } + } + } + } + }; + } + + public async Task Begin(SkinInfo skinInfo) + { + if (taskCompletionSource != null) + throw new InvalidOperationException("Cannot start multiple concurrent external edits!"); + + Show(); + showSpinner("Mounting external skin..."); + setGlobalSkinDisabled(true); + + await Task.Delay(500).ConfigureAwait(true); + + try + { + editOperation = await skinManager.BeginExternalEditing(skinInfo).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Log($"Failed to initialize external edit operation: {ex}", LoggingTarget.Database, LogLevel.Error); + setGlobalSkinDisabled(false); + Schedule(() => showSpinner("Export failed!")); + Scheduler.AddDelayed(Hide, 1000); + return Task.FromException(ex); + } + + Schedule(() => + { + flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Skin is mounted externally", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new OsuTextFlowContainer + { + Padding = new MarginPadding(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 350, + AutoSizeAxes = Axes.Y, + Text = "Any changes made to the exported folder will be imported to the game, including file additions, modifications and deletions.", + }, + new PurpleRoundedButton + { + Text = "Open folder", + Width = 350, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = openDirectory, + Enabled = { Value = false } + }, + new DangerousRoundedButton + { + Text = EditorStrings.FinishEditingExternally, + Width = 350, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = () => finish().FireAndForget(), + Enabled = { Value = false } + } + }; + }); + + Scheduler.AddDelayed(() => + { + foreach (var b in flow.ChildrenOfType()) + b.Enabled.Value = true; + openDirectory(); + }, 1000); + return (taskCompletionSource = new TaskCompletionSource()).Task; + } + + private void openDirectory() + { + if (editOperation == null) + return; + + gameHost.OpenFileExternally(editOperation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); + } + + private async Task finish() + { + Debug.Assert(taskCompletionSource != null); + + showSpinner("Cleaning up..."); + await Task.Delay(500).ConfigureAwait(true); + + try + { + await editOperation!.Finish().ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Log($"Failed to finish external edit operation: {ex}", LoggingTarget.Database, LogLevel.Error); + showSpinner("Import failed!"); + Scheduler.AddDelayed(Hide, 1000); + setGlobalSkinDisabled(false); + taskCompletionSource.SetException(ex); + taskCompletionSource = null; + return; + } + + Schedule(() => + { + var oldSkin = skinManager.CurrentSkin!.Value; + var newSkinInfo = oldSkin.SkinInfo.PerformRead(s => s); + + // Create a new skin instance to ensure the skin is reloaded + // If there's a better way to reload the skin, this should be replaced with it. + setGlobalSkinDisabled(false); + skinManager.CurrentSkin.Value = newSkinInfo.CreateInstance(skinManager); + + oldSkin.Dispose(); + + Hide(); + }); + taskCompletionSource.SetResult(); + taskCompletionSource = null; + } + + private void setGlobalSkinDisabled(bool disabled) + { + skinManager.CurrentSkin.Disabled = disabled; + skinManager.CurrentSkinInfo.Disabled = disabled; + } + + protected override void PopIn() + { + this.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(transition_duration, Easing.OutQuint).Finally(_ => + { + // Set everything to a clean state + editOperation = null; + flow.Children = Array.Empty(); + }); + } + + public override bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.Back: + case GlobalAction.Select: + if (editOperation == null) return base.OnPressed(e); + + finish().FireAndForget(); + return true; + } + + return base.OnPressed(e); + } + + private void showSpinner(string text) + { + foreach (var b in flow.ChildrenOfType()) + b.Enabled.Value = false; + + flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = text, + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new LoadingSpinner + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + State = { Value = Visibility.Visible } + }, + }; + } + } +} diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 6449c2ce62..487f77ca70 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -49,6 +49,8 @@ namespace osu.Game.Overlays.SkinEditor public readonly BindableList SelectedComponents = new BindableList(); + public bool ExternalEditInProgress => externalEditOperation != null && !externalEditOperation.IsCompleted; + protected override bool StartHidden => true; private Drawable? targetScreen; @@ -105,6 +107,11 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private IDialogOverlay? dialogOverlay { get; set; } + [Resolved] + private ExternalEditOverlay? externalEditOverlay { get; set; } + + private Task? externalEditOperation; + public SkinEditor() { } @@ -160,6 +167,7 @@ namespace osu.Game.Overlays.SkinEditor { new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()) { Hotkey = new Hotkey(PlatformAction.Save) }, new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new EditorMenuItem(EditorStrings.EditExternally, MenuItemType.Standard, () => _ = editExternally()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), new OsuMenuItemSpacer(), @@ -277,6 +285,15 @@ namespace osu.Game.Overlays.SkinEditor selectedTarget.BindValueChanged(targetChanged, true); } + private async Task editExternally() + { + Save(); + + var skin = currentSkin.Value.SkinInfo.PerformRead(s => s.Detach()); + + externalEditOperation = await externalEditOverlay!.Begin(skin).ConfigureAwait(false); + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) @@ -512,6 +529,7 @@ namespace osu.Game.Overlays.SkinEditor private void populateSettings() { + //过滤选择组件 settingsSidebar.PopulateSettings(content => { foreach (var component in SelectedComponents.OfType()) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 571f99bd08..5e71b6922c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -28,7 +28,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osu.Game.Users; using osu.Game.Utils; @@ -49,9 +49,15 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private IPerformFromScreenRunner? performer { get; set; } + [Resolved] + private IOverlayManager? overlayManager { get; set; } + [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); + [Cached] + private readonly ExternalEditOverlay externalEditOverlay = new ExternalEditOverlay(); + [Resolved] private OsuGame game { get; set; } = null!; @@ -69,6 +75,7 @@ namespace osu.Game.Overlays.SkinEditor private OsuScreen? lastTargetScreen; private InvokeOnDisposal? nestedInputManagerDisable; + private IDisposable? externalEditOverlayRegistration; private readonly LayoutValue drawSizeLayout; @@ -86,6 +93,13 @@ namespace osu.Game.Overlays.SkinEditor config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); } + protected override void LoadComplete() + { + base.LoadComplete(); + + externalEditOverlayRegistration = overlayManager?.RegisterBlockingOverlay(externalEditOverlay); + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) @@ -180,7 +194,7 @@ namespace osu.Game.Overlays.SkinEditor // the validity of the current game-wide beatmap + ruleset combination is enforced by song select. // if we're anywhere else, the state is unknown and may not make sense, so forcibly set something that does. - if (screen is not PlaySongSelect) + if (screen is not SoloSongSelect) ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset; var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); @@ -194,7 +208,7 @@ namespace osu.Game.Overlays.SkinEditor if (replayGeneratingMod != null) screen.Push(new EndlessPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods))); - }, new[] { typeof(Player), typeof(PlaySongSelect) }); + }, new[] { typeof(Player), typeof(SoloSongSelect) }); } protected override void Update() @@ -334,6 +348,22 @@ namespace osu.Game.Overlays.SkinEditor leasedBeatmapSkins = null; } + public new void ToggleVisibility() + { + if (skinEditor?.ExternalEditInProgress == true) + return; + + base.ToggleVisibility(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + externalEditOverlayRegistration?.Dispose(); + externalEditOverlayRegistration = null; + } + private partial class EndlessPlayer : ReplayPlayer { protected override UserActivity? InitialActivity => null; diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs index 9ef335243e..7ceb1740ba 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs @@ -12,8 +12,9 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Screens; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osuTK; +using SongSelect = osu.Game.Screens.Select.SongSelect; namespace osu.Game.Overlays.SkinEditor { @@ -81,7 +82,7 @@ namespace osu.Game.Overlays.SkinEditor if (screen is SongSelect) return; - screen.Push(new PlaySongSelect()); + screen.Push(new SoloSongSelect()); }, new[] { typeof(SongSelect) }) }, new SceneButton diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b236831b79..727db913e2 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Mods /// /// Reset all custom settings for this mod back to their defaults. /// - public virtual void ResetHitWindows() => CopyFrom((Mod)Activator.CreateInstance(GetType())!); + public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType())!); private class ModSettingsEqualityComparer : IEqualityComparer { diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index e20ac5dfc7..66d6ea2e66 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods /// public sealed override bool Ranked => false; - public sealed override bool ValidForFreestyleAsRequiredMod => true; + public sealed override bool ValidForFreestyleAsRequiredMod => false; } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 4fd9916b89..dbc690bd15 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mods { get { - if (UserAdjustedSettingsCount != 1) + if (!IsExactlyOneSettingChanged(OverallDifficulty, DrainRate)) return string.Empty; if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty); @@ -84,6 +84,24 @@ namespace osu.Game.Rulesets.Mods } } + protected bool IsExactlyOneSettingChanged(params DifficultyBindable[] difficultySettings) + { + DifficultyBindable? changedSetting = null; + + foreach (var setting in difficultySettings) + { + if (setting.IsDefault) + continue; + + if (changedSetting != null) + return false; + + changedSetting = setting; + } + + return changedSetting != null; + } + public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription { get @@ -107,26 +125,5 @@ namespace osu.Game.Rulesets.Mods if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value; if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value; } - - /// - /// The number of settings on this mod instance which have been adjusted by the user from their default values. - /// - protected int UserAdjustedSettingsCount - { - get - { - int count = 0; - - foreach (var (_, property) in this.GetSettingsSourceProperties()) - { - var bindable = (IBindable)property.GetValue(this)!; - - if (!bindable.IsDefault) - count++; - } - - return count; - } - } } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 64c193d25f..a88d714dce 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mods flashlight.Colour = Color4.Black; flashlight.Combo.BindTo(Combo); - flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale; + flashlight.GetPlayfieldScale = () => drawableRuleset.PlayfieldAdjustmentContainer.Scale; drawableRuleset.Overlays.Add(new Container { diff --git a/osu.Game/Rulesets/Scoring/IHitWindows.cs b/osu.Game/Rulesets/Scoring/IHitWindows.cs index 50fd9c8a87..ba1e0d8323 100644 --- a/osu.Game/Rulesets/Scoring/IHitWindows.cs +++ b/osu.Game/Rulesets/Scoring/IHitWindows.cs @@ -24,14 +24,6 @@ namespace osu.Game.Rulesets.Scoring // }; } - public enum MUGHitMode - { - Lazer, - EZ2AC, - IIDX, - Melody, - } - public static class HitWindowsExtensions { public static void SetDifficultyRange(this HitWindows windows, double perfect, double great, double good, double ok, double meh, double miss) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index cf6819b086..ec2b567a7b 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -286,7 +286,23 @@ namespace osu.Game.Scoring.Legacy // In mania, mouseX encodes the pressed keys in the lower 20 bits int mouseXParseLimit = currentRuleset.RulesetInfo.OnlineID == 3 ? (1 << 20) - 1 : Parsing.MAX_COORDINATE_VALUE; - int diff = Parsing.ParseInt(split[0]); + // the legacy replay format as defined by stable expects frame delta times + // ('delta time' here meaning the amount of time between consecutive frames) + // to be integral and does not allow fractional values. + // one particular reason why this matters is that integral deltas + // avoid nasty floating point traps like accumulation error from summation or round-off error. + // however, there was a period in lazer's lifetime wherein lazer emitted replays + // with fractional (float) frame deltas, up until https://github.com/ppy/osu/pull/12583. + // despite the fact that gameplay mechanics changed multiple times since + // and the replay isn't going to play back anywhere near accurately anyway, + // no mistakes are ever forgiven, thus this attempts to parse the delta as an integer once, + // and if that fails, tries again as float. + // notably this cannot just be `(int)Parsing.ParseFloat(split[0])`, because that can lose information + // (`float` numbers have 24 bits of significand precision, which is not enough to accurately represent every possible value of `int`). + int diff; + if (!int.TryParse(split[0], out diff)) + diff = (int)Math.Round(Parsing.ParseFloat(split[0])); + float mouseX = Parsing.ParseFloat(split[1], mouseXParseLimit); float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 890aef9fbe..4a587bce25 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -20,6 +20,8 @@ using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Screens.LAsEzExtensions; using osu.Game.Skinning; @@ -46,7 +48,7 @@ namespace osu.Game.Screens.Backgrounds protected virtual bool AllowStoryboardBackground => true; [BackgroundDependencyLoader] - private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config) + private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config, EzSkinSettingsManager ezSkinConfig) { user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); @@ -54,6 +56,8 @@ namespace osu.Game.Screens.Backgrounds introSequence = config.GetBindable(OsuSetting.IntroSequence); AddInternal(seasonalBackgroundLoader); + GlobalConfigStore.Config = config; + GlobalConfigStore.EzConfig = ezSkinConfig; } protected override void LoadComplete() @@ -147,6 +151,9 @@ namespace osu.Game.Screens.Backgrounds [Resolved] private TextureStore textures { get; set; } = null!; + [Resolved(CanBeNull = true)] + private NotificationOverlay notifications { get; set; } + private Background createBackground() { switch (source.Value) @@ -160,6 +167,11 @@ namespace osu.Game.Screens.Backgrounds if (!Directory.Exists(dataFolderPath)) { Directory.CreateDirectory(dataFolderPath); + //TODO: 改为游戏内提示空目录 + notifications?.Post(new SimpleErrorNotification + { + Text = $"已创建视频背景目录:{dataFolderPath}\n请将 .webm 文件放入该目录。" + }); } string[] videoFiles = Directory.GetFiles(dataFolderPath, "*.webm"); diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 5386b39190..da145f0994 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -373,10 +373,11 @@ namespace osu.Game.Screens.Edit.Compose.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { - IconColour = Color4.Black; - HoverColour = colours.Gray7; + IconColour = colourProvider.Light3; + IconHoverColour = Color4.White; + HoverColour = colours.Gray6; FlashColour = colours.Gray9; } } diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index 8a97e3dcb2..e906d74855 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -156,7 +157,7 @@ namespace osu.Game.Screens.Edit }, new DangerousRoundedButton { - Text = "Finish editing and import changes", + Text = EditorStrings.FinishEditingExternally, Width = 350, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/EzColumnColorTab.cs b/osu.Game/Screens/EzColumnTab.cs similarity index 79% rename from osu.Game/Screens/EzColumnColorTab.cs rename to osu.Game/Screens/EzColumnTab.cs index 317199831e..d0bc1351bc 100644 --- a/osu.Game/Screens/EzColumnColorTab.cs +++ b/osu.Game/Screens/EzColumnTab.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Screens { - public partial class EzColumnColorTab : EditorSidebarSection + public partial class EzColumnTab : EditorSidebarSection { private readonly List availableKeyModes = new List { 0, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18 }; private FillFlowContainer columnsContainer = null!; @@ -38,8 +38,8 @@ namespace osu.Game.Screens [Resolved] private Bindable beatmap { get; set; } = null!; - public EzColumnColorTab() - : base("EZ Colour Settings") + public EzColumnTab() + : base("EZ Column Settings") { } @@ -49,10 +49,30 @@ namespace osu.Game.Screens keyModeSelection = ezSkinConfig.GetBindable(EzSkinSetting.SelectedKeyMode); colorSettingsEnabled = ezSkinConfig.GetBindable(EzSkinSetting.ColorSettingsEnabled); // 设置双向绑定 - aColorValue = createColorBindable(EzSkinSetting.ColorA); - bColorValue = createColorBindable(EzSkinSetting.ColorB); - s1ColorValue = createColorBindable(EzSkinSetting.ColorS1); - s2ColorValue = createColorBindable(EzSkinSetting.ColorS2); + aColorValue = createColorBindable(EzSkinSetting.ColumnTypeA); + bColorValue = createColorBindable(EzSkinSetting.ColumnTypeB); + s1ColorValue = createColorBindable(EzSkinSetting.ColumnTypeS1); + s2ColorValue = createColorBindable(EzSkinSetting.ColumnTypeS2); + + var baseColorsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding(5f), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Base Colors (基础颜色)", + Margin = new MarginPadding { Bottom = 5 }, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14) + }.WithUnderline(), + SettingsColourExtensions.CreateStyledSettingsColour("A", aColorValue), + SettingsColourExtensions.CreateStyledSettingsColour("B", bColorValue), + SettingsColourExtensions.CreateStyledSettingsColour("S1", s1ColorValue), + SettingsColourExtensions.CreateStyledSettingsColour("S2", s2ColorValue), + } + }; Children = new Drawable[] { @@ -66,32 +86,15 @@ namespace osu.Game.Screens { new SettingsCheckbox { - LabelText = "Enable (启动设置)", - TooltipText = "当切换tab栏或点保存按钮后,当前颜色设置会变成设置默认值,影响重置目标\n" + - "When switching or save tabs , the current setting becomes the default value, affecting the reset target", + LabelText = "Color Enable\n(着色设置)", + TooltipText = "仅支持EZ Style Pro皮肤. Only supports EZ Style Pro skin\n" + + "切换tab栏或保存后, 将重置默认颜色为当前设置\n" + + "Switching tabs or saving will reset the colors to the default values", // Scale = new Vector2(0.9f), Current = colorSettingsEnabled, }, // 基础颜色选择器 - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding(5f), - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Base Colors (基础颜色)", - Margin = new MarginPadding { Bottom = 5 }, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14) - }.WithUnderline(), - SettingsColourExtensions.CreateStyledSettingsColour("A", aColorValue), - SettingsColourExtensions.CreateStyledSettingsColour("B", bColorValue), - SettingsColourExtensions.CreateStyledSettingsColour("S1", s1ColorValue), - SettingsColourExtensions.CreateStyledSettingsColour("S2", s2ColorValue), - } - }, + baseColorsContainer, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -115,7 +118,7 @@ namespace osu.Game.Screens { RelativeSizeAxes = Axes.X, Height = 2, - Colour = Color4.Gray.Opacity(0.5f), + Colour = Color4.DarkGray.Opacity(0.5f), }, // 列颜色设置容器 columnsContainer = new FillFlowContainer @@ -139,6 +142,14 @@ namespace osu.Game.Screens } }; updateKeyModeFromCurrentBeatmap(); + + colorSettingsEnabled.BindValueChanged(e => + { + ezSkinConfig.SetValue(EzSkinSetting.ColorSettingsEnabled, e.NewValue); + ezSkinConfig.Save(); + skinManager.Save(skinManager.CurrentSkin.Value); + baseColorsContainer.Alpha = e.NewValue ? 1f : 0f; + }, true); } private void updateKeyModeFromCurrentBeatmap() @@ -162,16 +173,10 @@ namespace osu.Game.Screens { base.LoadComplete(); - colorSettingsEnabled.ValueChanged += e => - { - ezSkinConfig.SetValue(EzSkinSetting.ColorSettingsEnabled, e.NewValue); - ezSkinConfig.Save(); - skinManager.CurrentSkinInfo.TriggerChange(); - }; - aColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColorA, "A"); - bColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColorB, "B"); - s1ColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColorS1, "S1"); - s2ColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColorS2, "S2"); + aColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColumnTypeA, "A"); + bColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColumnTypeB, "B"); + s1ColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColumnTypeS1, "S1"); + s2ColorValue.ValueChanged += e => updateBaseColour(e.NewValue, EzSkinSetting.ColumnTypeS2, "S2"); keyModeSelection.ValueChanged += e => updateColumnsType(e.NewValue); updateColumnsType(keyModeSelection.Value); @@ -233,8 +238,8 @@ namespace osu.Game.Screens columnsContainer.Add(new OsuSpriteText { - Text = $"{keyMode}K 模式列颜色设置", - Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{keyMode}K ColumnType 列类型", + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), Margin = new MarginPadding { Bottom = 5 }, }.WithUnderline()); @@ -260,7 +265,7 @@ namespace osu.Game.Screens if (string.IsNullOrEmpty(savedType)) { - savedType = EzColumnTypeManager.GetColumnColorType(keyMode, columnIndex); + savedType = EzColumnTypeManager.GetColumnType(keyMode, columnIndex); currentColumnTypes[columnIndex] = savedType; ezSkinConfig.SetColumnType(keyMode, columnIndex, savedType); } diff --git a/osu.Game/Screens/EzSkinEditorOverlay.cs b/osu.Game/Screens/EzSkinEditorOverlay.cs index 97b2f07472..4c1fd6c23b 100644 --- a/osu.Game/Screens/EzSkinEditorOverlay.cs +++ b/osu.Game/Screens/EzSkinEditorOverlay.cs @@ -29,7 +29,6 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osu.Game.Screens.Select; using osu.Game.Users; using osu.Game.Utils; @@ -195,8 +194,8 @@ namespace osu.Game.Screens // the validity of the current game-wide beatmap + ruleset combination is enforced by song select. // if we're anywhere else, the state is unknown and may not make sense, so forcibly set something that does. - if (screen is not PlaySongSelect) - ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset; + // if (screen is not PlaySongSelect) + // ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset; var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); IReadOnlyList usableMods = mods.Value; @@ -209,7 +208,7 @@ namespace osu.Game.Screens if (replayGeneratingMod != null) screen.Push(new EndlessPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods))); - }, new[] { typeof(Player), typeof(PlaySongSelect) }); + }, new[] { typeof(Player)}); } protected override void Update() diff --git a/osu.Game/Screens/EzSkinSettingsManager.cs b/osu.Game/Screens/EzSkinSettingsManager.cs index 3da1cd5371..dc8df3ce3d 100644 --- a/osu.Game/Screens/EzSkinSettingsManager.cs +++ b/osu.Game/Screens/EzSkinSettingsManager.cs @@ -2,17 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; +using System.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Screens.LAsEzExtensions; +using osu.Game.Skinning; namespace osu.Game.Screens { - [Cached] public class EzSkinSettingsManager : IniConfigManager, IGameplaySettings { protected override string Filename => "EzSkinSettings.ini"; @@ -24,10 +24,11 @@ namespace osu.Game.Screens SetDefault(EzSkinSetting.ColumnWidth, 60, 5, 400.0, 1.0); SetDefault(EzSkinSetting.SpecialFactor, 1.2, 0.5, 2.0, 0.1); - SetDefault(EzSkinSetting.HitPosition, 110.0, 0, 500, 1.0); + SetDefault(EzSkinSetting.HitPosition, LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION, 0, 500, 1.0); SetDefault(EzSkinSetting.VisualHitPosition, 0.0, -100, 100, 1.0); - SetDefault(EzSkinSetting.DynamicTracking, false); + SetDefault(EzSkinSetting.ColumnWidthStyle, EzColumnWidthStyle.EzStyleProOnly); + SetDefault(EzSkinSetting.GlobalHitPosition, false); SetDefault(EzSkinSetting.GlobalTextureName, 4); SetDefault(EzSkinSetting.NoteSetName, "evolve"); @@ -37,10 +38,10 @@ namespace osu.Game.Screens SetDefault(EzSkinSetting.NoteTrackLineHeight, 300, 0, 1000, 5.0); SetDefault(EzSkinSetting.ColorSettingsEnabled, true); - SetDefault(EzSkinSetting.ColorA, Colour4.FromHex("#F5F5F5")); - SetDefault(EzSkinSetting.ColorB, Colour4.FromHex("#648FFF")); - SetDefault(EzSkinSetting.ColorS1, Colour4.FromHex("#FF4A4A")); - SetDefault(EzSkinSetting.ColorS2, Colour4.FromHex("#72FF72")); + SetDefault(EzSkinSetting.ColumnTypeA, Colour4.FromHex("#F5F5F5")); + SetDefault(EzSkinSetting.ColumnTypeB, Colour4.FromHex("#648FFF")); + SetDefault(EzSkinSetting.ColumnTypeS1, Colour4.FromHex("#FF4A4A")); + SetDefault(EzSkinSetting.ColumnTypeS2, Colour4.FromHex("#72FF72")); foreach (int keyMode in commonKeyModes) { @@ -74,7 +75,7 @@ namespace osu.Game.Screens for (int i = 0; i < keyMode; i++) { - types[i] = EzColumnTypeManager.GetColumnColorType(keyMode, i); + types[i] = EzColumnTypeManager.GetColumnType(keyMode, i); } return types; @@ -98,11 +99,11 @@ namespace osu.Game.Screens if (columnIndex < types.Length && !string.IsNullOrEmpty(types[columnIndex])) return types[columnIndex]; - return EzColumnTypeManager.GetColumnColorType(keyMode, columnIndex); + return EzColumnTypeManager.GetColumnType(keyMode, columnIndex); } catch (NotSupportedException) { - return EzColumnTypeManager.GetColumnColorType(keyMode, columnIndex); + return EzColumnTypeManager.GetColumnType(keyMode, columnIndex); } } @@ -135,10 +136,10 @@ namespace osu.Game.Screens EzSkinSetting setting = colorType switch { - "S1" => EzSkinSetting.ColorS1, - "S2" => EzSkinSetting.ColorS2, - "B" => EzSkinSetting.ColorB, - _ => EzSkinSetting.ColorA + "S1" => EzSkinSetting.ColumnTypeS1, + "S2" => EzSkinSetting.ColumnTypeS2, + "B" => EzSkinSetting.ColumnTypeB, + _ => EzSkinSetting.ColumnTypeA }; return Get(setting); @@ -186,32 +187,49 @@ namespace osu.Game.Screens IBindable IGameplaySettings.PositionalHitsoundsLevel => null!; } + public enum EzColumnWidthStyle + { + [Description("EzStylePro Only")] + EzStyleProOnly, + + [Description("Global (全局)")] + GlobalWidth, + + [Description("Global Total (全局总宽度)")] + GlobalTotalWidth, + } + public enum EzSkinSetting { SelectedKeyMode, - // 轨道相关 + // 全局开关 + ColumnWidthStyle, + GlobalHitPosition, //TODO:未来改成下拉栏,补充虚拟判定线 + + // 全局设置 ColumnWidth, SpecialFactor, + + // Ez专属皮肤设置 HitPosition, VisualHitPosition, NonSquareNoteHeight, NoteTrackLine, NoteTrackLineHeight, - - // 皮肤设置 - DynamicTracking, GlobalTextureName, NoteSetName, StageName, + // DynamicTracking, + + // 列类型 + ColumnTypeA, + ColumnTypeB, + ColumnTypeS1, + ColumnTypeS2, // 着色系统 ColorSettingsEnabled, - ColorA, - ColorB, - ColorS1, - ColorS2, - ColumnColor4K, ColumnColor5K, ColumnColor6K, @@ -240,7 +258,7 @@ namespace osu.Game.Screens // // for (int i = 0; i < keyMode; i++) // { -// types[i] = EzColumnTypeManager.GetColumnColorType(keyMode, i); +// types[i] = EzColumnTypeManager.GetColumnType(keyMode, i); // } // // SetValue(setting, string.Join(",", types)); diff --git a/osu.Game/Screens/EzSkinSettingsTab.cs b/osu.Game/Screens/EzSkinSettingsTab.cs index 35415f44a4..a4fa4fe434 100644 --- a/osu.Game/Screens/EzSkinSettingsTab.cs +++ b/osu.Game/Screens/EzSkinSettingsTab.cs @@ -28,10 +28,9 @@ namespace osu.Game.Screens { public partial class EzSkinSettings : EditorSidebarSection { - private Bindable hitPosition = new Bindable(); - - private readonly Bindable dynamicTracking = new Bindable(); + private readonly Bindable globalHitPosition = new BindableBool(); private readonly Bindable globalTextureName = new Bindable((EzSelectorNameSet)4); + private readonly Bindable selectedNoteSet = new Bindable(); private readonly List availableNoteSets = new List(); @@ -58,7 +57,7 @@ namespace osu.Game.Screens [BackgroundDependencyLoader] private void load() { - hitPosition = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition); + globalHitPosition.Value = ezSkinConfig.Get(EzSkinSetting.GlobalHitPosition); globalTextureName.Value = (EzSelectorNameSet)ezSkinConfig.GetBindable(EzSkinSetting.GlobalTextureName).Value; @@ -86,12 +85,12 @@ namespace osu.Game.Screens Spacing = new Vector2(10), Children = new Drawable[] { - new SettingsCheckbox + new SettingsEnumDropdown { - LabelText = "Dynamic Tracking\n(动态刷新)", - TooltipText = "开启后, 调整滑块时会实时 刷新并保存 皮肤, 可能导致卡顿\n" - + "Enable this to refresh and save the skin in real-time when adjusting sliders, may cause lag", - Current = ezSkinConfig.GetBindable(EzSkinSetting.DynamicTracking), + LabelText = "Column Width Style(列宽风格)", + TooltipText = "不完善!全局总列宽=设置值×10\n" + + "Global Total Column Width = Configured Value × 10", + Current = ezSkinConfig.GetBindable(EzSkinSetting.ColumnWidthStyle), }, new SettingsSlider { @@ -103,16 +102,22 @@ namespace osu.Game.Screens new SettingsSlider { LabelText = "Special Factor (特殊轨道倍率)", - TooltipText = "设置特殊列的宽度倍率 //未来会联动特殊列颜色定义" - + "\nSet the width factor for special columns", + TooltipText = "特殊列关联S1类型, 可自定义" + + "\nSpecial columns are associated with S1 type, customizable", Current = ezSkinConfig.GetBindable(EzSkinSetting.SpecialFactor), KeyboardStep = 0.1f, }, + new SettingsCheckbox + { + LabelText = "Global HitPosition", + TooltipText = "全局判定线位置开关", + Current = globalHitPosition, + }, new SettingsSlider { LabelText = "Hit Position (可视判定线位置)", TooltipText = "设置判定线位置", - Current = hitPosition, + Current = ezSkinConfig.GetBindable(EzSkinSetting.HitPosition), KeyboardStep = 1f, }, new SettingsSlider @@ -167,29 +172,13 @@ namespace osu.Game.Screens } }; - selectedNoteSet.BindValueChanged(onNoteSetChanged); - selectedStageSet.BindValueChanged(onStageSetChanged); + selectedNoteSet.BindValueChanged(e => ezSkinConfig.SetValue(EzSkinSetting.NoteSetName, e.NewValue)); + selectedStageSet.BindValueChanged(e => ezSkinConfig.SetValue(EzSkinSetting.StageName, e.NewValue)); globalTextureName.BindValueChanged(OnTextureNameChanged); - // dynamicTracking.ValueChanged += tracking => - // { - // if (tracking.NewValue) - // { - // } - // else - // { - // } - // - // ezSkinConfig.SetValue(EzSkinSetting.DynamicTracking, tracking.NewValue); - // }; } #region 追踪处理 - // private void onStageChanged(ValueChangedEvent e) - // { - // Invalidate(); - // } - private void OnTextureNameChanged(ValueChangedEvent textureName) { updateAllSkinComponentsTextureNames(textureName.NewValue); @@ -197,26 +186,6 @@ namespace osu.Game.Screens ezSkinConfig.SetValue(EzSkinSetting.GlobalTextureName, (int)textureName.NewValue); } - private void onNoteSetChanged(ValueChangedEvent e) - { - ezSkinConfig.SetValue(EzSkinSetting.NoteSetName, e.NewValue); - - if (dynamicTracking.Value) - { - ScheduleRefresh(); - } - } - - private void onStageSetChanged(ValueChangedEvent e) - { - ezSkinConfig.SetValue(EzSkinSetting.StageName, e.NewValue); - - if (dynamicTracking.Value) - { - ScheduleRefresh(); - } - } - private ScheduledDelegate? scheduledRefresh; public void ScheduleRefresh() @@ -229,6 +198,8 @@ namespace osu.Game.Screens { isAbsolutePosition = !isAbsolutePosition; skinManager.CurrentSkinInfo.TriggerChange(); + // skinManager.Save(skinManager.CurrentSkin.Value); + // 更新按钮颜色 updateButtonColor(refreshSkinButton, isAbsolutePosition); // 更新按钮文字 @@ -357,8 +328,6 @@ namespace osu.Game.Screens } } - public partial class EzGlobalTextureNameSelector : EzSelectorEnumList; - #region 拓展按钮的多行文本显示 public static class SettingsButtonExtensions @@ -410,5 +379,7 @@ namespace osu.Game.Screens } } + public partial class EzGlobalTextureNameSelector : EzSelectorEnumList; + #endregion } diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index ad3aaaa2c9..6d7a32d57a 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -226,20 +226,21 @@ namespace osu.Game.Screens.Footer } } - private ShearedOverlayContainer? activeOverlay; + public ShearedOverlayContainer? ActiveOverlay { get; private set; } + private VisibilityContainer? activeFooterContent; private readonly List temporarilyHiddenButtons = new List(); public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay, out VisibilityContainer? footerContent) { - if (activeOverlay != null) + if (ActiveOverlay != null) { throw new InvalidOperationException(@"Cannot set overlay content while one is already present. " + - $@"The previous overlay ({activeOverlay.GetType().Name}) should be hidden first."); + $@"The previous overlay ({ActiveOverlay.GetType().Name}) should be hidden first."); } - activeOverlay = overlay; + ActiveOverlay = overlay; Debug.Assert(temporarilyHiddenButtons.Count == 0); @@ -277,7 +278,7 @@ namespace osu.Game.Screens.Footer private void clearActiveOverlayContainer() { - if (activeOverlay == null) + if (ActiveOverlay == null) return; Debug.Assert(activeFooterContent != null); @@ -300,7 +301,7 @@ namespace osu.Game.Screens.Footer activeFooterContent.Delay(timeUntilRun).Expire(); activeFooterContent = null; - activeOverlay = null; + ActiveOverlay = null; } private void updateColourScheme(int hue) @@ -337,12 +338,12 @@ namespace osu.Game.Screens.Footer private void onBackPressed() { - if (activeOverlay != null) + if (ActiveOverlay != null) { - if (activeOverlay.OnBackButton()) + if (ActiveOverlay.OnBackButton()) return; - activeOverlay.Hide(); + ActiveOverlay.Hide(); return; } diff --git a/osu.Game/Screens/LAsEzExtensions/EzColumnTypeManager.cs b/osu.Game/Screens/LAsEzExtensions/EzColumnTypeManager.cs index 833c502582..bf3baf89d2 100644 --- a/osu.Game/Screens/LAsEzExtensions/EzColumnTypeManager.cs +++ b/osu.Game/Screens/LAsEzExtensions/EzColumnTypeManager.cs @@ -5,7 +5,7 @@ namespace osu.Game.Screens.LAsEzExtensions { public static class EzColumnTypeManager { - public static string GetColumnColorType(int keyMode, int columnIndex) + public static string GetColumnType(int keyMode, int columnIndex) { // 边界检查 if (columnIndex < 0 || columnIndex >= keyMode) diff --git a/osu.Game/Screens/LAsEzExtensions/EzEditorSidebar.cs b/osu.Game/Screens/LAsEzExtensions/EzEditorSidebar.cs index 766bf363b8..afdce43e29 100644 --- a/osu.Game/Screens/LAsEzExtensions/EzEditorSidebar.cs +++ b/osu.Game/Screens/LAsEzExtensions/EzEditorSidebar.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.LAsEzExtensions public EzEditorSidebar() { - OsuTabControl tabControl1; + OsuTabControl tabControl; // 添加tabControl背景,防止内容遮挡tab标签 var tabBackground = new Box { @@ -36,7 +36,7 @@ namespace osu.Game.Screens.LAsEzExtensions AddInternal(tabBackground); // 只添加tabControl,滚动和内容由基类EditorSidebar负责 - AddInternal(tabControl1 = new OsuTabControl + AddInternal(tabControl = new OsuTabControl { RelativeSizeAxes = Axes.X, Height = 30, @@ -47,7 +47,7 @@ namespace osu.Game.Screens.LAsEzExtensions // 设置内容区整体下移,避免与tab栏重叠 Content.Margin = new MarginPadding { Top = 30 }; - tabControl1.Current.ValueChanged += e => + tabControl.Current.ValueChanged += e => { currentTab = e.NewValue; Content.Clear(); @@ -80,7 +80,7 @@ namespace osu.Game.Screens.LAsEzExtensions private void showColorSettings() { - var colorSettings = new EzColumnColorTab + var colorSettings = new EzColumnTab { RelativeSizeAxes = Axes.X }; diff --git a/osu.Game/Screens/LAsEzExtensions/EzLocalTextureFactory.cs b/osu.Game/Screens/LAsEzExtensions/EzLocalTextureFactory.cs index 7833044089..b668f61673 100644 --- a/osu.Game/Screens/LAsEzExtensions/EzLocalTextureFactory.cs +++ b/osu.Game/Screens/LAsEzExtensions/EzLocalTextureFactory.cs @@ -52,6 +52,20 @@ namespace osu.Game.Screens.LAsEzExtensions } } + private void clearCacheForPrefix(string prefix) + { + var keysToRemove = new List(); + + foreach (string key in ratioCache.Keys) + { + if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + keysToRemove.Add(key); + } + + foreach (string key in keysToRemove) + ratioCache.Remove(key); + } + private void initialize() { if (initialized) return; @@ -61,20 +75,34 @@ namespace osu.Game.Screens.LAsEzExtensions NoteSetName = ezSkinConfig.GetBindable(EzSkinSetting.NoteSetName); NoteSetName.BindValueChanged(e => { - foreach (var loader in loaderStoreCache.Values) - loader.Dispose(); - loaderStoreCache.Clear(); - textureRatioCache.Clear(); + // foreach (var loader in loaderStoreCache.Values) + // loader.Dispose(); + // loaderStoreCache.Clear(); + // ratioCache.Clear(); + clearCacheForPrefix("note/"); OnNoteChanged?.Invoke(); }, true); StageName = ezSkinConfig.GetBindable(EzSkinSetting.StageName); StageName.BindValueChanged(e => + { + // foreach (var loader in loaderStoreCache.Values) + // loader.Dispose(); + // loaderStoreCache.Clear(); + clearCacheForPrefix("Stage/"); + OnStageChanged?.Invoke(); + }, true); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (isDisposing) { foreach (var loader in loaderStoreCache.Values) loader.Dispose(); loaderStoreCache.Clear(); - OnStageChanged?.Invoke(); - }, true); + } } private bool isHitCom(string componentName) @@ -82,11 +110,6 @@ namespace osu.Game.Screens.LAsEzExtensions return componentName.Contains("flare", StringComparison.InvariantCultureIgnoreCase); } - // private bool isStageCom(string componentName) - // { - // return componentName.Contains("Stage", StringComparison.InvariantCultureIgnoreCase); - // } - public virtual TextureAnimation CreateAnimation(string component) { bool isHit = isHitCom(component); @@ -103,26 +126,17 @@ namespace osu.Game.Screens.LAsEzExtensions if (!isHit) animation.DefaultFrameLength = 1000.0 / 60.0 * 4; - string path = $"note/{NoteSetName.Value}/{component}"; - - for (int i = 0; i < 300; i++) + string[] pathsToTry = { - string frameFile = $"{path}/{i:D3}.png"; - var texture = textureStore.Get(frameFile); + $"note/{NoteSetName.Value}/{component}", + $"note/circle/{component}" + }; - if (texture == null) - break; - - animation.AddFrame(texture); - } - - if (animation.FrameCount == 0) + foreach (string basePath in pathsToTry) { - path = $"note/circle/{component}"; - - for (int i = 0; i < 60; i++) + for (int i = 0; i < 300; i++) { - string frameFile = $"{path}/{i:D3}.png"; + string frameFile = $"{basePath}/{i:D3}.png"; var texture = textureStore.Get(frameFile); if (texture == null) @@ -130,137 +144,151 @@ namespace osu.Game.Screens.LAsEzExtensions animation.AddFrame(texture); } + + if (animation.FrameCount > 0) + break; } return animation; } + private bool isStageBody(string componentName) + { + return componentName.Contains("Body", StringComparison.InvariantCultureIgnoreCase); + } + public virtual Drawable CreateStage(string component) { - string path = $"Stage/{StageName.Value}/{component}"; + bool isBody = isStageBody(component); + string basePath = $"Stage/{StageName.Value}/Stage"; var container = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Y = 0, - AutoSizeAxes = Axes.X, - Height = 247f, - Masking = true, - FillMode = FillMode.Fill + AutoSizeAxes = Axes.Both, + FillMode = FillMode.Fill, }; - var bodyTexture = textureStore.Get($"{path}.png"); - - if (bodyTexture != null) + if (isBody) { - container.Add(new Sprite + container = new Container { - Texture = bodyTexture, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.None, - Width = bodyTexture.Width, - Height = bodyTexture.Height, - FillMode = FillMode.Fill - }); - // Logger.Log($"Creating stage component: {path}"); - } - - string grooveLightPath = $"Stage/{StageName.Value}/Stage/GrooveLight"; - var grooveLight = textureStore.Get($"{grooveLightPath}.png"); - - if (grooveLight != null) - { - var grooveLightSprite = new Sprite - { - Texture = grooveLight, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.None, - Width = grooveLight.Width, - Height = grooveLight.Height, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, FillMode = FillMode.Fill, - Alpha = 0 + Y = 0, + AutoSizeAxes = Axes.X, + Height = 247f, + Masking = true, }; - container.Add(grooveLightSprite); + addStageComponent(container, $"{basePath}/fivekey/{component}"); + addStageComponent(container, $"{basePath}/GrooveLight"); + string overObjectPath = $"{basePath}/Stage/{StageName.Value}_OverObject/{StageName.Value}_OverObject"; - grooveLightSprite.OnLoadComplete += _ => - grooveLightSprite.Loop(b => b.FadeTo(1f, 300).FadeOut(300)); - - Logger.Log($"Creating stage grooveLight {grooveLight}"); - } - - string overObjectPath = $"Stage/{StageName.Value}/Stage/{StageName.Value}_Overobject/{StageName.Value}_Overobject"; - - for (int i = 0; i < 120; i++) - { - var overObject = textureStore.Get($"{overObjectPath}_{i}.png"); - - if (overObject != null) + var animation = new TextureAnimation { - // overObject.ScaleAdjust = 0.00125f; + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.None, + FillMode = FillMode.Fill, + Loop = true, + DefaultFrameLength = 1000.0 / 60.0 * 4 + }; - container.Add(new Sprite - { - Texture = overObject, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.None, - Width = overObject.Width, - Height = overObject.Height, - FillMode = FillMode.Fill - }); - Logger.Log($"Creating stage overObject {overObjectPath}"); + for (int i = 0; i < 120; i++) + { + var texture = textureStore.Get($"{overObjectPath}_{i}.png"); + if (texture == null) + break; + + animation.AddFrame(texture); + } + + container.Add(animation); + } + else + { + string baseKeyPath = $"{basePath}/eightkey/{component}"; + string[] pathsToTry = + { + $"{baseKeyPath}/KeyBase", + $"{baseKeyPath}/KeyPress", + $"{baseKeyPath}/KeyBase_0", + $"{baseKeyPath}/KeyPress_0", + $"{baseKeyPath}/2KeyBase_0", + $"{baseKeyPath}/2KeyPress_0", + }; + + foreach (string paths in pathsToTry) + { + addStageComponent(container, $"{paths}"); } } return container; } - private readonly Dictionary textureRatioCache = new Dictionary(); - - public float GetRatio(string path) + private void addStageComponent(Container container, string basePath, int frameIndex = -1) { + Texture? texture = loadStageTexture(basePath, frameIndex); + if (texture == null) return; + + container.Add(new Sprite + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.None, + FillMode = FillMode.Fill, + Width = texture.Width, + Height = texture.Height, + Texture = texture, + // Blending = BlendingParameters.Additive, + }); + + Logger.Log($"Factory load {basePath}"); + } + + private Texture? loadStageTexture(string basePath, int i = -1) + { + string texturePath; + + if (i < 0) { texturePath = $"{basePath}.png"; } + else + { + texturePath = i == 0 + ? $"{basePath}.png" + : $"{basePath}_{i}.png"; + } + + return textureStore.Get(texturePath); + } + + private readonly Dictionary ratioCache = new Dictionary(); + + public float GetRatio(string component) + { + string path = $"note/{NoteSetName.Value}/{component}"; + + if (ratioCache.TryGetValue(path, out float cachedRatio)) + return cachedRatio; + Texture texture = textureStore.Get($"{path}/000.png") ?? textureStore.Get($"{path}/001.png"); - float ratio = 1.0f; - if (texture != null) - ratio = texture.Height / (float)texture.Width; - - texture?.Dispose(); + float ratio = texture == null + ? 1.0f + : texture.Height / (float)texture.Width; + ratioCache[path] = ratio; return ratio; } public bool IsSquareNote(string component) { - if (textureRatioCache.TryGetValue(component, out float ratio)) - return ratio >= 0.75f; - - if (isHitCom(component)) - return true; - - string path = $"note/{NoteSetName.Value}/{component}"; - - ratio = GetRatio(path); - textureRatioCache[component] = ratio; - + float ratio = GetRatio(component); return ratio >= 0.75f; } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (isDisposing) - { - foreach (var loader in loaderStoreCache.Values) - loader.Dispose(); - loaderStoreCache.Clear(); - } - } } public enum EzAnimationType diff --git a/osu.Game/Screens/LAsEzExtensions/VideoBackgroundScreen.cs b/osu.Game/Screens/LAsEzExtensions/VideoBackgroundScreen.cs index 4f8ccf7986..b023e82529 100644 --- a/osu.Game/Screens/LAsEzExtensions/VideoBackgroundScreen.cs +++ b/osu.Game/Screens/LAsEzExtensions/VideoBackgroundScreen.cs @@ -30,9 +30,9 @@ namespace osu.Game.Screens.LAsEzExtensions AddInternal(video); - //下面只用于注册全局设置 - GlobalConfigStore.Config = config; - GlobalConfigStore.EzConfig = ezSkinConfig; + // //下面只用于注册全局设置 + // GlobalConfigStore.Config = config; + // GlobalConfigStore.EzConfig = ezSkinConfig; } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d87727b797..bc3bcbd800 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -14,13 +14,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -41,12 +39,10 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Screens.Select; using osu.Game.Screens.SelectV2; using osu.Game.Seasonal; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Screens.Menu { @@ -93,8 +89,6 @@ namespace osu.Game.Screens.Menu IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; private readonly Bindable samplePlaybackDisabled = new Bindable(); - private InputManager inputManager; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); protected override bool PlayExitSound => false; @@ -121,6 +115,9 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private SkinEditorOverlay skinEditor { get; set; } + [CanBeNull] + private IDisposable logoProxy; + [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio) { @@ -160,7 +157,7 @@ namespace osu.Game.Screens.Menu { skinEditor?.Show(); }, - OnSolo = loadPreferredSongSelect, + OnSolo = loadSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), OnPlaylists = () => this.Push(new Playlists()), OnDailyChallenge = room => @@ -241,19 +238,12 @@ namespace osu.Game.Screens.Menu Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh"); - loadSongSelectV2Samples(audio); - } - - protected override void Update() - { - base.Update(); - updateSongSelectV2HoldState(); } protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); + GetContainingInputManager(); } public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial; @@ -465,7 +455,7 @@ namespace osu.Game.Screens.Menu Beatmap.Value = beatmap; Ruleset.Value = ruleset; - Schedule(loadPreferredSongSelect); + Schedule(loadSongSelect); } public bool OnPressed(KeyBindingPressEvent e) @@ -489,85 +479,7 @@ namespace osu.Game.Screens.Menu { } - #region TEMPORARY: Song Select v2 easter egg - - private const double required_hold_time = 500; - - private double holdTime; - private bool ssv2Expanded; - private IDisposable ssv2Duck; - private Sample ssv2Sample; - - [CanBeNull] - private IDisposable logoProxy; - - private void loadPreferredSongSelect() - { - if (holdTime >= required_hold_time) - { - ssv2Sample?.Play(); - this.Push(new SoloSongSelect()); - } - else - this.Push(new PlaySongSelect()); - } - - private void loadSongSelectV2Samples(AudioManager audio) - { - ssv2Sample = audio.Samples.Get(@"UI/bss-complete"); - } - - private void updateSongSelectV2HoldState() - { - bool isValidHoverState = Buttons.State == ButtonSystemState.Play && - inputManager.CurrentState.Mouse.IsPressed(MouseButton.Left) && - inputManager.HoveredDrawables.Any(h => h is OsuLogo || (h is MainMenuButton b && b.TriggerKeys.Contains(Key.P))); - - if (isValidHoverState) - { - holdTime += Time.Elapsed; - - if (holdTime >= required_hold_time && !ssv2Expanded) - { - var transformTarget = Game.ChildrenOfType().First(); - - transformTarget.Anchor = Anchor.Centre; - transformTarget.Origin = Anchor.Centre; - - transformTarget.ScaleTo(1.2f, 5000, Easing.OutPow10) - .RotateTo(2, 5000, Easing.OutPow10) - .FadeColour(Color4.BlueViolet, 10000, Easing.OutPow10); - - ssv2Duck = musicController.Duck(new DuckParameters - { - DuckDuration = 2000, - DuckVolumeTo = 0.8f, - DuckCutoffTo = 500, - DuckEasing = Easing.OutQuint, - RestoreDuration = 200, - RestoreEasing = Easing.OutQuint - }); - - ssv2Expanded = true; - } - } - else if (holdTime > 0) - { - var transformTarget = Game.ChildrenOfType().FirstOrDefault(); - - transformTarget.ScaleTo(1, 500, Easing.OutQuint) - .RotateTo(0, 500, Easing.OutQuint) - .FadeColour(OsuColour.Gray(1f), 500, Easing.OutQuint); - - ssv2Duck?.Dispose(); - ssv2Duck = null; - - ssv2Expanded = false; - holdTime = 0; - } - } - - #endregion + private void loadSongSelect() => this.Push(new SoloSongSelect()); private partial class MobileDisclaimerDialog : PopupDialog { diff --git a/osu.Game/Screens/Menu/MenuTipDisplay.cs b/osu.Game/Screens/Menu/MenuTipDisplay.cs index 283528d22a..9430d65433 100644 --- a/osu.Game/Screens/Menu/MenuTipDisplay.cs +++ b/osu.Game/Screens/Menu/MenuTipDisplay.cs @@ -128,7 +128,8 @@ namespace osu.Game.Screens.Menu MenuTipStrings.ToggleReplaySettingsShortcut, MenuTipStrings.CopyModsFromScore, MenuTipStrings.AutoplayBeatmapShortcut, - MenuTipStrings.LazerIsNotAWord + MenuTipStrings.LazerIsNotAWord, + MenuTipStrings.RightMouseAbsoluteScroll, }; return tips[RNG.Next(0, tips.Length)]; diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index d7fe1f52ff..c4cf52c254 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))] public Bindable ShowLabel { get; } = new BindableBool(true); public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index e82e8f4b6f..22d65601cd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))] public Bindable ShowLabel { get; } = new BindableBool(true); [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs index 1620da2f2e..8e9360920c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))] public Bindable ShowLabel { get; } = new BindableBool(true); public override bool IsValid diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 8658651407..f000a5977c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))] public Bindable ShowLabel { get; } = new BindableBool(true); public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 8dc5d60352..5b2efb447b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); [Resolved] diff --git a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs index 46a658cd1c..810100532b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs +++ b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource("Inverted shear")] public BindableBool InvertShear { get; } = new BindableBool(); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Color4Extensions.FromHex("#66CCFF")); public ArgonWedgePiece() diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 672017750d..06d541d838 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); [Resolved] diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f1a31b809f..848b8292d4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play // this makes the game stay in portrait mode when restarting gameplay rather than switching back to landscape. public override bool RequiresPortraitOrientation => CurrentPlayer?.RequiresPortraitOrientation == true; - public override float BackgroundParallaxAmount => quickRestart ? 0 : 1; + public override float BackgroundParallaxAmount => QuickRestart ? 0 : 1; // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; @@ -98,6 +98,8 @@ namespace osu.Game.Screens.Play private Box? quickRestartBlackLayer; + private ScheduledDelegate? quickRestartBackButtonRestore; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -158,7 +160,7 @@ namespace osu.Game.Screens.Play private PlayerLoaderDisclaimer? epilepsyWarning; - private bool quickRestart; + protected bool QuickRestart { get; private set; } private IDisposable? highPerformanceSession; @@ -380,7 +382,7 @@ namespace osu.Game.Screens.Play logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint); - if (quickRestart) + if (QuickRestart) { logo.Delay(quick_restart_initial_delay) .FadeIn(350); @@ -430,7 +432,7 @@ namespace osu.Game.Screens.Play // We need to perform this check here rather than in OnHover as any number of children of VisualSettings // may also be handling the hover events. - if (inputManager.HoveredDrawables.Contains(VisualSettings) || quickRestart) + if (inputManager.HoveredDrawables.Contains(VisualSettings) || QuickRestart) { // Preview user-defined background dim and blur when hovered on the visual settings panel. ApplyToBackground(b => @@ -469,7 +471,7 @@ namespace osu.Game.Screens.Play return; CurrentPlayer = createPlayer(); - CurrentPlayer.Configuration.AutomaticallySkipIntro |= quickRestart; + CurrentPlayer.Configuration.AutomaticallySkipIntro |= QuickRestart; CurrentPlayer.RestartCount = restartCount++; CurrentPlayer.PrepareLoaderForRestart = prepareForRestart; @@ -486,7 +488,7 @@ namespace osu.Game.Screens.Play private void prepareForRestart(bool quickRestartRequested) { - quickRestart = quickRestartRequested; + QuickRestart = quickRestartRequested; hideOverlays = true; ValidForResume = true; } @@ -495,7 +497,7 @@ namespace osu.Game.Screens.Play { MetadataInfo.Loading = true; - if (quickRestart) + if (QuickRestart) { BackButtonVisibility.Value = false; @@ -518,7 +520,8 @@ namespace osu.Game.Screens.Play .ScaleTo(1) .FadeInFromZero(500, Easing.OutQuint); - this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonVisibility.Value = true); + quickRestartBackButtonRestore?.Cancel(); + quickRestartBackButtonRestore = Scheduler.AddDelayed(() => BackButtonVisibility.Value = true, quick_restart_initial_delay); } else { @@ -635,7 +638,7 @@ namespace osu.Game.Screens.Play }, // When a quick restart is activated, the metadata content will display some time later if it's taking too long. // To avoid it appearing too briefly, if it begins to fade in let's induce a standard delay. - quickRestart && content.Alpha == 0 ? 0 : 500); + QuickRestart && content.Alpha == 0 ? 0 : 500); } private void cancelLoad() diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 882e556965..c058238a0a 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -29,8 +29,6 @@ namespace osu.Game.Screens.Play private readonly Func, Score> createScore; - private readonly bool replayIsFailedScore; - private PlaybackSettings playbackSettings; [Cached(typeof(IGameplayLeaderboardProvider))] @@ -40,19 +38,28 @@ namespace osu.Game.Screens.Play private bool isAutoplayPlayback => GameplayState.Mods.OfType().Any(); - // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) + private double? lastFrameTime; + protected override bool CheckModsAllowFailure() { - if (!replayIsFailedScore && !isAutoplayPlayback) - return false; + // autoplay should be able to fail if the beatmap is not humanly beatable + if (isAutoplayPlayback) + return base.CheckModsAllowFailure(); - return base.CheckModsAllowFailure(); + // non-autoplay replays should be able to fail, but only after they've exhausted their frames. + // note that the rank isn't checked here - that's because it is generally unreliable. + // stable replays, as well as lazer replays recorded prior to https://github.com/ppy/osu/pull/28058, + // do not even *contain* the user's rank. + // not to mention possible gameplay mechanics changes that could make a replay fail sooner than it really should. + if (GameplayClockContainer.CurrentTime >= lastFrameTime) + return base.CheckModsAllowFailure(); + + return false; } public ReplayPlayer(Score score, PlayerConfiguration configuration = null) : this((_, _) => score, configuration) { - replayIsFailedScore = score.ScoreInfo.Rank == ScoreRank.F; } public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) @@ -95,6 +102,7 @@ namespace osu.Game.Screens.Play protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(Score); + lastFrameTime = Score.Replay.Frames.LastOrDefault()?.Time; } protected override Score CreateScore(IBeatmap beatmap) => createScore(beatmap, Mods.Value); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 7d155e32b0..8a84501a17 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -79,6 +79,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Alpha = 0.5f; TooltipText = ResultsScreenStrings.NoPPForUnrankedMods; } + else if (scoreInfo.Rank == ScoreRank.F) + { + Alpha = 0.5f; + TooltipText = ResultsScreenStrings.NoPPForFailedScores; + } else { Alpha = 1f; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index b7086d2416..152398dee3 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Select.Details if (normalDifficulty == null || moddedDifficulty == null) return; - starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); + starDifficulty.Value = ((float)normalDifficulty.Value.Stars.FloorToDecimalDigits(2), (float)moddedDifficulty.Value.Stars.FloorToDecimalDigits(2)); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs deleted file mode 100644 index 2f47243b50..0000000000 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Extensions.LocalisationExtensions; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Framework.Screens; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Localisation; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; -using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking; -using osu.Game.Users; -using osu.Game.Utils; -using osuTK.Input; - -namespace osu.Game.Screens.Select -{ - public partial class PlaySongSelect : SongSelect - { - private OsuScreen? playerLoader; - - [Resolved] - private INotificationOverlay? notifications { get; set; } - - public override bool AllowExternalScreenChange => true; - - public override MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(Func getBeatmap) => new MenuItem[] - { - new OsuMenuItem(ButtonSystemStrings.Play.ToSentence(), MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())), - new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => Edit(getBeatmap())) - }; - - protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); - - private PlayBeatmapDetailArea playBeatmapDetailArea = null!; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BeatmapOptions.AddButton(ButtonSystemStrings.Edit.ToSentence(), @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit()); - - AddInternal(new SongSelectTouchInputDetector()); - } - - protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score))); - - protected override BeatmapDetailArea CreateBeatmapDetailArea() - { - playBeatmapDetailArea = new PlayBeatmapDetailArea - { - Leaderboard = - { - ScoreSelected = PresentScore - } - }; - - return playBeatmapDetailArea; - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - switch (e.Key) - { - case Key.Enter: - case Key.KeypadEnter: - // this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is - // matching with exact modifier consideration (so Ctrl+Enter would be ignored). - FinaliseSelection(); - return true; - } - - return base.OnKeyDown(e); - } - - private IReadOnlyList? modsAtGameplayStart; - - private ModAutoplay? getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod(); - - protected override bool OnStart() - { - if (playerLoader != null) return false; - - modsAtGameplayStart = Mods.Value.Select(m => m.DeepClone()).ToArray(); - - // Ctrl+Enter should start map with autoplay enabled. - if (GetContainingInputManager()?.CurrentState?.Keyboard.ControlPressed == true) - { - var autoInstance = getAutoplayMod(); - - if (autoInstance == null) - { - notifications?.Post(new SimpleNotification - { - Text = NotificationsStrings.NoAutoplayMod - }); - return false; - } - - var mods = Mods.Value.Append(autoInstance).ToArray(); - - if (!ModUtils.CheckCompatibleSet(mods, out var invalid)) - mods = mods.Except(invalid).Append(autoInstance).ToArray(); - - Mods.Value = mods; - } - - SampleConfirm?.Play(); - - this.Push(playerLoader = new PlayerLoader(createPlayer)); - return true; - - Player createPlayer() - { - Player player; - - var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); - - if (replayGeneratingMod != null) - { - player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)); - } - else - { - player = new SoloPlayer(); - } - - return player; - } - } - - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - revertMods(); - } - - public override bool OnExiting(ScreenExitEvent e) - { - if (base.OnExiting(e)) - return true; - - revertMods(); - return false; - } - - private void revertMods() - { - if (playerLoader == null) return; - - Mods.Value = modsAtGameplayStart; - playerLoader = null; - } - } -} diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index b09490ce32..4119807692 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -451,8 +451,7 @@ namespace osu.Game.Screens.SelectV2 private Sample? sampleChangeDifficulty; private Sample? sampleChangeSet; - private Sample? sampleOpen; - private Sample? sampleClose; + private Sample? sampleToggleGroup; private double audioFeedbackLastPlaybackTime; @@ -460,8 +459,7 @@ namespace osu.Game.Screens.SelectV2 { sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeSet = audio.Samples.Get(@"SongSelect/select-expand"); - sampleOpen = audio.Samples.Get(@"UI/menu-open"); - sampleClose = audio.Samples.Get(@"UI/menu-close"); + sampleToggleGroup = audio.Samples.Get(@"SongSelect/select-group"); spinSample = audio.Samples.Get("SongSelect/random-spin"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); @@ -474,10 +472,7 @@ namespace osu.Game.Screens.SelectV2 switch (item.Model) { case GroupDefinition: - if (item.IsExpanded) - sampleOpen?.Play(); - else - sampleClose?.Play(); + sampleToggleGroup?.Play(); return; case BeatmapSetInfo: @@ -667,10 +662,12 @@ namespace osu.Game.Screens.SelectV2 return false; } - Scheduler.Add(() => + // CurrentSelectionItem won't be valid until UpdateAfterChildren. + // We probably want to fix this at some point since a few places are working-around this quirk. + ScheduleAfterChildren(() => { if (selectionBefore != null && CurrentSelectionItem != null) - playSpinSample(distanceBetween(selectionBefore, CurrentSelectionItem), carouselItems.Count); + playSpinSample(visiblePanelCountBetweenItems(selectionBefore, CurrentSelectionItem)); }); return true; @@ -769,12 +766,12 @@ namespace osu.Game.Screens.SelectV2 return true; } - public void PreviousRandom() + public bool PreviousRandom() { var carouselItems = GetCarouselItems(); if (carouselItems?.Any() != true) - return; + return false; while (randomHistory.Any()) { @@ -784,7 +781,7 @@ namespace osu.Game.Screens.SelectV2 var previousBeatmapItem = carouselItems.FirstOrDefault(i => i.Model is BeatmapInfo b && b.Equals(previousBeatmap)); if (previousBeatmapItem == null) - return; + return false; if (CurrentSelection is BeatmapInfo beatmapInfo) { @@ -792,25 +789,27 @@ namespace osu.Game.Screens.SelectV2 previouslyVisitedRandomBeatmaps.Remove(beatmapInfo); if (CurrentSelectionItem == null) - playSpinSample(0, carouselItems.Count); + playSpinSample(0); else - playSpinSample(distanceBetween(previousBeatmapItem, CurrentSelectionItem), carouselItems.Count); + playSpinSample(visiblePanelCountBetweenItems(previousBeatmapItem, CurrentSelectionItem)); } RequestSelection(previousBeatmap); - break; + return true; } + + return false; } - private double distanceBetween(CarouselItem item1, CarouselItem item2) => Math.Ceiling(Math.Abs(item1.CarouselYPosition - item2.CarouselYPosition) / PanelBeatmapSet.HEIGHT); + private double visiblePanelCountBetweenItems(CarouselItem item1, CarouselItem item2) => Math.Ceiling(Math.Abs(item1.CarouselYPosition - item2.CarouselYPosition) / PanelBeatmapSet.HEIGHT); - private void playSpinSample(double distance, int count) + private void playSpinSample(double distance) { var chan = spinSample?.GetChannel(); if (chan != null) { - chan.Frequency.Value = 1f + Math.Min(1f, distance / count); + chan.Frequency.Value = 1f + Math.Clamp(distance / 200, 0, 1); chan.Play(); } diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index c68f377fbb..772d4123c2 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -323,7 +323,8 @@ namespace osu.Game.Screens.SelectV2 private GroupDefinition defineGroupByStars(double stars) { - int starInt = (int)Math.Round(stars, 2); + // truncation is intentional - compare `FormatUtils.FormatStarRating()` + int starInt = (int)stars; var starDifficulty = new StarDifficulty(starInt, 0); if (starInt == 0) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index e8dc58ff1b..be507e7b36 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -46,6 +46,8 @@ namespace osu.Game.Screens.SelectV2 { public const int HEIGHT = 50; + public readonly ScoreInfo Score; + public Bindable> SelectedMods = new Bindable>(); /// @@ -115,8 +117,6 @@ namespace osu.Game.Screens.SelectV2 private Container rankLabelStandalone = null!; private Container rankLabelOverlay = null!; - private readonly ScoreInfo score; - private readonly bool sheared; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) @@ -130,7 +130,8 @@ namespace osu.Game.Screens.SelectV2 public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true) { - this.score = score; + Score = score; + this.sheared = sheared; Shear = sheared ? OsuGame.SHEAR : Vector2.Zero; @@ -198,7 +199,7 @@ namespace osu.Game.Screens.SelectV2 new UserCoverBackground { RelativeSizeAxes = Axes.Both, - User = score.User, + User = Score.User, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -224,7 +225,7 @@ namespace osu.Game.Screens.SelectV2 Masking = true, Children = new Drawable[] { - new DelayedLoadWrapper(innerAvatar = new ClickableAvatar(score.User) + new DelayedLoadWrapper(innerAvatar = new ClickableAvatar(Score.User) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -276,19 +277,19 @@ namespace osu.Game.Screens.SelectV2 Masking = true, Children = new Drawable[] { - new UpdateableFlag(score.User.CountryCode) + new UpdateableFlag(Score.User.CountryCode) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(20, 14), }, - new UpdateableTeamFlag(score.User.Team) + new UpdateableTeamFlag(Score.User.Team) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(30, 15), }, - new DateLabel(score.Date) + new DateLabel(Score.Date) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -301,7 +302,7 @@ namespace osu.Game.Screens.SelectV2 { RelativeSizeAxes = Axes.X, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Text = score.User.Username, + Text = Score.User.Username, Font = OsuFont.Style.Heading2, } } @@ -323,9 +324,9 @@ namespace osu.Game.Screens.SelectV2 Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ScoreComponentLabel(BeatmapsetsStrings.ShowScoreboardHeadersCombo.ToUpper(), $"{score.MaxCombo.ToString()}x", - score.MaxCombo == score.GetMaximumAchievableCombo(), 60), - new ScoreComponentLabel(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper(), score.DisplayAccuracy, score.Accuracy == 1, + new ScoreComponentLabel(BeatmapsetsStrings.ShowScoreboardHeadersCombo.ToUpper(), $"{Score.MaxCombo.ToString()}x", + Score.MaxCombo == Score.GetMaximumAchievableCombo(), 60), + new ScoreComponentLabel(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper(), Score.DisplayAccuracy, Score.Accuracy == 1, 55), }, Alpha = 0, @@ -357,7 +358,7 @@ namespace osu.Game.Screens.SelectV2 Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(Score.Rank)), }, }, new Box @@ -366,7 +367,7 @@ namespace osu.Game.Screens.SelectV2 Width = grade_width, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Colour = OsuColour.ForRank(score.Rank), + Colour = OsuColour.ForRank(Score.Rank), }, new TrianglesV2 { @@ -376,7 +377,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.TopRight, SpawnRatio = 2, Velocity = 0.7f, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Darken(0.2f)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(Score.Rank).Darken(0.2f)), }, new Container { @@ -390,9 +391,9 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.Centre, Origin = Anchor.Centre, Spacing = new Vector2(-2), - Colour = DrawableRank.GetRankNameColour(score.Rank), + Colour = DrawableRank.GetRankNameColour(Score.Rank), Font = OsuFont.Numeric.With(size: 14), - Text = DrawableRank.GetRankName(score.Rank), + Text = DrawableRank.GetRankName(Score.Rank), ShadowColour = Color4.Black.Opacity(0.3f), ShadowOffset = new Vector2(0, 0.08f), Shadow = true, @@ -420,7 +421,7 @@ namespace osu.Game.Screens.SelectV2 new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Opacity(0.5f)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(Score.Rank).Opacity(0.5f)), }, new FillFlowContainer { @@ -437,7 +438,7 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.TopRight, Origin = Anchor.TopRight, UseFullGlyphHeight = false, - Current = scoreManager.GetBindableTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(Score), Spacing = new Vector2(-1.5f), Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, @@ -503,10 +504,10 @@ namespace osu.Game.Screens.SelectV2 private void updateModDisplay() { - if (score.Mods.Length > 0) + if (Score.Mods.Length > 0) { modsContainer.Padding = new MarginPadding { Top = 4f }; - modsContainer.ChildrenEnumerable = score.Mods.AsOrdered().Select(mod => new ModIcon(mod) + modsContainer.ChildrenEnumerable = Score.Mods.AsOrdered().Select(mod => new ModIcon(mod) { Scale = new Vector2(0.3f), // trim mod icon height down to its true height for alignment purposes. @@ -608,7 +609,7 @@ namespace osu.Game.Screens.SelectV2 ITooltip IHasCustomTooltip.GetCustomTooltip() => new LeaderboardScoreTooltip(colourProvider); - ScoreInfo IHasCustomTooltip.TooltipContent => score; + ScoreInfo IHasCustomTooltip.TooltipContent => Score; MenuItem[] IHasContextMenu.ContextMenuItems { @@ -617,18 +618,18 @@ namespace osu.Game.Screens.SelectV2 List items = new List(); // system mods should never be copied across regardless of anything. - var copyableMods = score.Mods.Where(m => IsValidMod.Invoke(m) && m.Type != ModType.System).ToArray(); + var copyableMods = Score.Mods.Where(m => IsValidMod.Invoke(m) && m.Type != ModType.System).ToArray(); if (copyableMods.Length > 0) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = copyableMods)); - if (score.OnlineID > 0) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{score.OnlineID}"))); + if (Score.OnlineID > 0) + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{Score.OnlineID}"))); - if (score.Files.Count <= 0) return items.ToArray(); + if (Score.Files.Count <= 0) return items.ToArray(); - items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); - items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score))); + items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); return items.ToArray(); } diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs index bc684dfc13..1f92699887 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -117,7 +118,7 @@ namespace osu.Game.Screens.SelectV2 relativeDate.Date = value.Date; var judgementsStatistics = value.GetStatisticsForDisplay().Select(s => - new StatisticRow(s.DisplayName.ToUpper(), colours.ForHitResult(s.Result), s.Count.ToLocalisableString("N0"))); + new StatisticRow(s.DisplayName.ToUpper(), s.Count.ToLocalisableString("N0"), colours.ForHitResult(s.Result))); double multiplier = 1.0; @@ -126,10 +127,11 @@ namespace osu.Game.Screens.SelectV2 var generalStatistics = new[] { - new PerformanceStatisticRow(BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper(), colourProvider.Content2, score), - new StatisticRow(ModSelectOverlayStrings.ScoreMultiplier, colourProvider.Content2, ModUtils.FormatScoreMultiplier(multiplier)), - new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeadersCombo, colourProvider.Content2, value.MaxCombo.ToLocalisableString(@"0\x")), - new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, colourProvider.Content2, value.Accuracy.FormatAccuracy()), + new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeadersCombo, value.MaxCombo.ToLocalisableString(@"0\x")), + new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, value.Accuracy.FormatAccuracy()), + new PerformanceStatisticRow(BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper(), score), + Empty().With(d => d.Height = 20), + new StatisticRow(ModSelectOverlayStrings.ScoreMultiplier, ModUtils.FormatScoreMultiplier(multiplier)), }; statistics.ChildrenEnumerable = judgementsStatistics @@ -205,7 +207,7 @@ namespace osu.Game.Screens.SelectV2 { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 4f), + Spacing = new Vector2(0f, 2f), Padding = new MarginPadding(8f), }, }, @@ -230,22 +232,26 @@ namespace osu.Game.Screens.SelectV2 public partial class StatisticRow : CompositeDrawable { - protected OsuSpriteText ValueLabel; + private readonly OsuSpriteText labelText; + protected readonly OsuSpriteText ValueText; - public StatisticRow(LocalisableString label, Color4 labelColour, LocalisableString value) + private readonly Color4? colour; + + public StatisticRow(LocalisableString label, LocalisableString value, Color4? colour = null) { + this.colour = colour; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; InternalChildren = new[] { - new OsuSpriteText + labelText = new OsuSpriteText { Text = label, - Colour = labelColour, Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold), }, - ValueLabel = new OsuSpriteText + ValueText = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -255,14 +261,21 @@ namespace osu.Game.Screens.SelectV2 }, }; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + labelText.Colour = colour ?? colourProvider.Content2; + ValueText.Colour = Interpolation.ValueAt(0.85f, colourProvider.Content1, colour ?? colourProvider.Content1, 0, 1); + } } public partial class PerformanceStatisticRow : StatisticRow { private readonly ScoreInfo score; - public PerformanceStatisticRow(LocalisableString label, Color4 labelColour, ScoreInfo score) - : base(label, labelColour, 0.ToLocalisableString("N0")) + public PerformanceStatisticRow(LocalisableString label, ScoreInfo score) + : base(label, @"0pp") { this.score = score; } @@ -285,24 +298,21 @@ namespace osu.Game.Screens.SelectV2 if (attributes?.DifficultyAttributes == null || performanceCalculator == null) return; - var result = await performanceCalculator.CalculateAsync(score, attributes.Value.DifficultyAttributes, cancellationToken ?? default).ConfigureAwait(false); + var result = await performanceCalculator.CalculateAsync(score, attributes.Value.DifficultyAttributes, cancellationToken ?? CancellationToken.None).ConfigureAwait(false); Schedule(() => setPerformanceValue(score, result.Total)); }, cancellationToken ?? default); } - private void setPerformanceValue(ScoreInfo scoreInfo, double? pp) + private void setPerformanceValue(ScoreInfo scoreInfo, double pp) { - if (pp.HasValue) - { - int ppValue = (int)Math.Round(pp.Value, MidpointRounding.AwayFromZero); - ValueLabel.Text = ppValue.ToLocalisableString("N0"); + int ppValue = (int)Math.Round(pp, MidpointRounding.AwayFromZero); + ValueText.Text = LocalisableString.Interpolate(@$"{ppValue:N0}pp"); - if (!scoreInfo.BeatmapInfo!.Status.GrantsPerformancePoints() || hasUnrankedMods(scoreInfo)) - Alpha = 0.5f; - else - Alpha = 1f; - } + if (!scoreInfo.BeatmapInfo!.Status.GrantsPerformancePoints() || hasUnrankedMods(scoreInfo)) + Alpha = 0.5f; + else + Alpha = 1f; } private static bool hasUnrankedMods(ScoreInfo scoreInfo) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index 10917f08ac..0554b1b815 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -82,6 +83,13 @@ namespace osu.Game.Screens.SelectV2 private const float personal_best_height = 112; + // Blocking mouse down is required to avoid song select's background reveal logic happening while hovering scores. + // Our horizontal alignment doesn't really align with the rest of the sheared components (protrudes a touch to the right) which makes + // it complicated to handle this at a higher level. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => scoresScroll.ReceivePositionalInputAt(screenSpacePos); + + protected override bool OnMouseDown(MouseDownEvent e) => true; + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/SelectV2/FilterControl.cs b/osu.Game/Screens/SelectV2/FilterControl.cs index 4ff8e020a4..d2e5bd88c7 100644 --- a/osu.Game/Screens/SelectV2/FilterControl.cs +++ b/osu.Game/Screens/SelectV2/FilterControl.cs @@ -197,7 +197,7 @@ namespace osu.Game.Screens.SelectV2 new OsuSpriteText { Text = "Keys", - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 16), Margin = new MarginPadding(5), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -206,7 +206,7 @@ namespace osu.Game.Screens.SelectV2 new OsuTabControl { RelativeSizeAxes = Axes.X, - Height = 24, + Height = 20, AutoSort = false, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -330,7 +330,7 @@ namespace osu.Game.Screens.SelectV2 .FadeOut(SongSelect.ENTER_DURATION / 3, Easing.In); } - private partial class SongSelectSearchTextBox : ShearedFilterTextBox + internal partial class SongSelectSearchTextBox : ShearedFilterTextBox { protected override InnerSearchTextBox CreateInnerTextBox() => new InnerTextBox(); diff --git a/osu.Game/Screens/SelectV2/PanelGroup.cs b/osu.Game/Screens/SelectV2/PanelGroup.cs index c0c4676a30..b7288f1da4 100644 --- a/osu.Game/Screens/SelectV2/PanelGroup.cs +++ b/osu.Game/Screens/SelectV2/PanelGroup.cs @@ -150,9 +150,9 @@ namespace osu.Game.Screens.SelectV2 countText.Text = Item.NestedItemCount.ToString("N0"); } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); // Move the count pill in the opposite direction to keep it pinned to the screen regardless of the X position of TopLevelContent. countPill.X = -TopLevelContent.X; diff --git a/osu.Game/Screens/SelectV2/SoloSongSelect.cs b/osu.Game/Screens/SelectV2/SoloSongSelect.cs index 4ef73d4c49..4c4df7f389 100644 --- a/osu.Game/Screens/SelectV2/SoloSongSelect.cs +++ b/osu.Game/Screens/SelectV2/SoloSongSelect.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.SelectV2 public override IEnumerable GetForwardActions(BeatmapInfo beatmap) { yield return new OsuMenuItem(ButtonSystemStrings.Play.ToSentence(), MenuItemType.Highlighted, () => SelectAndRun(beatmap, OnStart)) { Icon = FontAwesome.Solid.Check }; - yield return new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => edit(beatmap)) { Icon = FontAwesome.Solid.PencilAlt }; + yield return new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => Edit(beatmap)) { Icon = FontAwesome.Solid.PencilAlt }; yield return new OsuMenuItemSpacer(); @@ -138,7 +138,7 @@ namespace osu.Game.Screens.SelectV2 } } - private void edit(BeatmapInfo beatmap) + public void Edit(BeatmapInfo beatmap) { if (!this.IsCurrentScreen()) return; @@ -173,7 +173,7 @@ namespace osu.Game.Screens.SelectV2 private partial class PlayerLoader : Play.PlayerLoader { - public override bool ShowFooter => true; + public override bool ShowFooter => !QuickRestart; public PlayerLoader(Func createPlayer) : base(createPlayer) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 4c30662bd4..8030290aab 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -6,7 +6,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -22,6 +25,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Containers; @@ -103,6 +107,8 @@ namespace osu.Game.Screens.SelectV2 public override bool ShowFooter => true; + private Sample? errorSample; + [Resolved] private OsuGameBase? game { get; set; } @@ -127,9 +133,13 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private IDialogOverlay? dialogOverlay { get; set; } + private Bindable configBackgroundBlur = null!; + [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio, OsuConfigManager config) { + errorSample = audio.Samples.Get(@"UI/generic-error"); + AddRangeInternal(new Drawable[] { new GlobalScrollAdjustsVolume(), @@ -160,7 +170,7 @@ namespace osu.Game.Screens.SelectV2 { new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 700), new Dimension(), - new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 660), + new Dimension(GridSizeMode.Relative, 0.5f, minSize: 500, maxSize: 900), }, Content = new[] { @@ -267,6 +277,15 @@ namespace osu.Game.Screens.SelectV2 modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), modSelectOverlay, }); + + configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgroundBlur); + configBackgroundBlur.BindValueChanged(e => + { + if (!this.IsCurrentScreen()) + return; + + updateBackgroundDim(); + }); } /// @@ -286,8 +305,16 @@ namespace osu.Game.Screens.SelectV2 }, new FooterButtonRandom { - NextRandom = () => carousel.NextRandom(), - PreviousRandom = () => carousel.PreviousRandom() + NextRandom = () => + { + if (!carousel.NextRandom()) + errorSample?.Play(); + }, + PreviousRandom = () => + { + if (!carousel.PreviousRandom()) + errorSample?.Play(); + } }, new FooterButtonOptions { @@ -388,22 +415,6 @@ namespace osu.Game.Screens.SelectV2 private void ensureTrackLooping(IWorkingBeatmap beatmap, TrackChangeDirection changeDirection) => beatmap.PrepareTrackForPreview(true); - private IDisposable? trackDuck; - - private void attachTrackDuckingIfShould() - { - bool shouldDuck = noResultsPlaceholder.State.Value == Visibility.Visible; - - if (shouldDuck && trackDuck == null) - trackDuck = music.Duck(new DuckParameters { DuckVolumeTo = 1, DuckCutoffTo = 500 }); - } - - private void detachTrackDucking() - { - trackDuck?.Dispose(); - trackDuck = null; - } - #endregion #region Selection handling @@ -420,8 +431,6 @@ namespace osu.Game.Screens.SelectV2 if (!this.IsCurrentScreen()) return; - // `ensureGlobalBeatmapValid` also performs this checks, but it will change the active selection on fail. - // By checking locally first, we can correctly perform a no-op rather than changing selection. if (!checkBeatmapValidForSelection(beatmap, carousel.Criteria)) return; @@ -435,9 +444,6 @@ namespace osu.Game.Screens.SelectV2 if (Beatmap.IsDefault) return; - if (!ensureGlobalBeatmapValid()) - return; - startAction(); } @@ -590,7 +596,6 @@ namespace osu.Game.Screens.SelectV2 updateWedgeVisibility(); beginLooping(); - attachTrackDuckingIfShould(); ensureGlobalBeatmapValid(); @@ -608,7 +613,6 @@ namespace osu.Game.Screens.SelectV2 updateWedgeVisibility(); endLooping(); - detachTrackDucking(); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -656,7 +660,7 @@ namespace osu.Game.Screens.SelectV2 // This avoids a flicker of a placeholder or invalid beatmap before a proper selection. // // After the carousel finishes filtering, it will attempt a selection then call this method again. - if (!carouselItemsPresented && !checkBeatmapValidForSelection(Beatmap.Value.BeatmapInfo, filterControl.CreateCriteria())) + if (!CarouselItemsPresented && !checkBeatmapValidForSelection(Beatmap.Value.BeatmapInfo, filterControl.CreateCriteria())) return; if (carousel.VisuallyFocusSelected) @@ -675,21 +679,26 @@ namespace osu.Game.Screens.SelectV2 private void updateBackgroundDim() => ApplyToBackground(backgroundModeBeatmap => { - backgroundModeBeatmap.BlurAmount.Value = 0; backgroundModeBeatmap.Beatmap = Beatmap.Value; backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.1f; // Required to undo results screen dimming the background. // Probably needs more thought because this needs to be in every `ApplyToBackground` currently to restore sane defaults. backgroundModeBeatmap.FadeColour(Color4.White, 250); + + backgroundModeBeatmap.BlurAmount.Value = revealingBackground == null && configBackgroundBlur.Value ? 20 : 0f; }); #endregion #region Filtering - private bool carouselItemsPresented; + /// + /// Whether the carousel has finished initial presentation of beatmap panels. + /// + public bool CarouselItemsPresented { get; private set; } private const double filter_delay = 250; @@ -713,7 +722,7 @@ namespace osu.Game.Screens.SelectV2 if (carousel.Criteria == null) return; - carouselItemsPresented = true; + CarouselItemsPresented = true; int count = carousel.MatchedBeatmapsCount; @@ -734,17 +743,30 @@ namespace osu.Game.Screens.SelectV2 if (count == 0) { + if (noResultsPlaceholder.State.Value == Visibility.Hidden) + { + // Duck audio temporarily when the no results placeholder becomes visible. + // + // Temporary ducking makes it easier to avoid scenarios where the ducking interacts badly + // with other global UI components (like overlays). + music.DuckMomentarily(400, new DuckParameters + { + DuckVolumeTo = 1, + DuckCutoffTo = 500, + DuckDuration = 250, + RestoreDuration = 2000, + }); + } + noResultsPlaceholder.Show(); noResultsPlaceholder.Filter = carousel.Criteria!; - attachTrackDuckingIfShould(); rightGradientBackground.ResizeWidthTo(3, 1000, Easing.OutPow10); } else { noResultsPlaceholder.Hide(); - detachTrackDucking(); rightGradientBackground.ResizeWidthTo(1, 400, Easing.OutPow10); } } @@ -786,6 +808,8 @@ namespace osu.Game.Screens.SelectV2 skinnableContent.ScaleTo(1.2f, 600, Easing.OutQuint); skinnableContent.FadeOut(200, Easing.OutQuint); + updateBackgroundDim(); + Footer?.Hide(); }, 200); } @@ -819,6 +843,8 @@ namespace osu.Game.Screens.SelectV2 revealingBackground.Cancel(); revealingBackground = null; + + updateBackgroundDim(); } public virtual bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 76c8d54f50..58821f869a 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning.Components [UsedImplicitly] public partial class BeatmapAttributeText : FontAdjustableSkinComponent { - [SettingSource(typeof(BeatmapAttributeTextStrings), nameof(BeatmapAttributeTextStrings.Attribute), nameof(BeatmapAttributeTextStrings.AttributeDescription))] + [SettingSource(typeof(BeatmapAttributeTextStrings), nameof(BeatmapAttributeTextStrings.Attribute))] public Bindable Attribute { get; } = new Bindable(BeatmapAttribute.StarRating); [SettingSource(typeof(BeatmapAttributeTextStrings), nameof(BeatmapAttributeTextStrings.Template), nameof(BeatmapAttributeTextStrings.TemplateDescription))] diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index 7f052a8523..ddfa1aa446 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -27,7 +27,7 @@ namespace osu.Game.Skinning.Components Precision = 0.01f }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); public BoxElement() diff --git a/osu.Game/Skinning/Components/EzComHitResultScore.cs b/osu.Game/Skinning/Components/EzComHitResultScore.cs index fb9320fe0a..5ab5f65565 100644 --- a/osu.Game/Skinning/Components/EzComHitResultScore.cs +++ b/osu.Game/Skinning/Components/EzComHitResultScore.cs @@ -40,7 +40,7 @@ namespace osu.Game.Skinning.Components public string TextureBasePath => @"EzResources/enumBase/enumJudgement"; // [SettingSource("Effect Type", "Effect Type")] - // public Bindable Effect { get; } = new Bindable(EffectType.Scale); + // public Bindable Effect { get; } = new Bindable(EzComEffectType.Scale); // // [SettingSource("Effect Origin", "Effect Origin", SettingControlType = typeof(AnchorDropdown))] // public Bindable EffectOrigin { get; } = new Bindable(Anchor.TopCentre) diff --git a/osu.Game/Skinning/Components/EzComScoreCounter.cs b/osu.Game/Skinning/Components/EzComScoreCounter.cs index 8c969b0efb..be95ba4b6a 100644 --- a/osu.Game/Skinning/Components/EzComScoreCounter.cs +++ b/osu.Game/Skinning/Components/EzComScoreCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning.Components [SettingSource("Font", "Font", SettingControlType = typeof(EzSelectorEnumList))] public Bindable FontNameDropdown { get; } = new Bindable((EzSelectorNameSet)Enum.ToObject(typeof(EzSelectorNameSet), 43)); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))] public Bindable ShowLabel { get; } = new BindableBool(); [SettingSource("Alpha", "The alpha value of this box")] @@ -30,7 +30,7 @@ namespace osu.Game.Skinning.Components Precision = 0.01f, }; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/Components/EzSelectorEnumList.cs b/osu.Game/Skinning/Components/EzSelectorEnumList.cs index 41927c75b6..dd23f95117 100644 --- a/osu.Game/Skinning/Components/EzSelectorEnumList.cs +++ b/osu.Game/Skinning/Components/EzSelectorEnumList.cs @@ -3,15 +3,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Skinning.Components @@ -21,7 +14,6 @@ namespace osu.Game.Skinning.Components protected override void LoadComplete() { base.LoadComplete(); - Items = Enum.GetValues(typeof(EzSelectorNameSet)).Cast().ToList(); } } @@ -31,7 +23,6 @@ namespace osu.Game.Skinning.Components protected override void LoadComplete() { base.LoadComplete(); - // 限制选项范围 Items = new List { @@ -42,7 +33,7 @@ namespace osu.Game.Skinning.Components } } - public enum EffectType + public enum EzComEffectType { Scale, Bounce, @@ -104,134 +95,4 @@ namespace osu.Game.Skinning.Components ArcadeScore, // ReSharper restore InconsistentNaming } - - public partial class EzSelector - { - /// - /// 子文件夹路径,默认为Gameplay/Fonts/ - /// - protected virtual string SetPath => @"Gameplay/Fonts/"; - - /// - /// 当前选择的子文件夹 - /// - public Bindable Selected { get; } = new Bindable(); - - private Dropdown dropdown = null!; - - /// - /// 存储服务,用于访问文件系统 - /// - [Resolved] - protected Storage Storage { get; private set; } = null!; - - [BackgroundDependencyLoader] - private void load() - { - // 加载子文件夹并创建下拉列表 - LoadSubfoldersToDropdown(); - } - - /// - /// 加载指定路径下的所有子文件夹到下拉列表 - /// - protected void LoadSubfoldersToDropdown() - { - // 确保路径存在 - EnsureDirectoryExists(SetPath); - - // 获取所有子文件夹 - List subfolders = GetSubfolders(SetPath); - - // 创建下拉列表 - InternalChild = dropdown = new OsuDropdown - { - RelativeSizeAxes = Axes.X, - Items = subfolders - }; - - dropdown.Current.BindTo(Selected); - - // 默认选择第一个子文件夹,如果有的话 - if (subfolders.Count > 0) - Selected.Value = subfolders[0]; - } - - /// - /// 确保目录存在,不存在则创建 - /// - /// 相对路径 - /// 完整路径 - protected string EnsureDirectoryExists(string relativePath) - { - try - { - string fullPath = Storage.GetFullPath(relativePath); - - if (!Directory.Exists(fullPath)) - { - Directory.CreateDirectory(fullPath); - Logger.Log($"已创建目录: {fullPath}"); - } - - return fullPath; - } - catch (Exception ex) - { - Logger.Error(ex, $"创建目录 {relativePath} 失败"); - return string.Empty; - } - } - - /// - /// 获取指定路径下的所有子文件夹 - /// - /// 相对路径 - /// 子文件夹名称列表 - protected List GetSubfolders(string relativePath) - { - List result = new List(); - - try - { - string fullPath = Storage.GetFullPath(relativePath); - - // 获取所有子文件夹 - string[] directories = Directory.GetDirectories(fullPath); - - // 提取文件夹名称 - foreach (string dir in directories) - { - string dirName = Path.GetFileName(dir); - if (!string.IsNullOrEmpty(dirName)) - result.Add(dirName); - } - - Logger.Log($"在 {fullPath} 中找到 {result.Count} 个子文件夹"); - } - catch (Exception ex) - { - Logger.Error(ex, $"获取 {relativePath} 的子文件夹时出错"); - } - - // 如果没有找到子文件夹,添加一个默认选项 - if (result.Count == 0) - result.Add("Default"); - - return result; - } - - public Dropdown InternalChild { get; set; } = null!; - } - - /// - /// 专门用于加载Note套图的选择器 - /// - public partial class NoteSetSelector : EzSelector - { - /// - /// 重写路径指向note套图目录 - /// - protected override string SetPath => @"Resource\Textures\note"; - } } diff --git a/osu.Game/Skinning/Components/EzSelectorTextures.cs b/osu.Game/Skinning/Components/EzSelectorTextures.cs index 8bf32c7314..e5a66d31f8 100644 --- a/osu.Game/Skinning/Components/EzSelectorTextures.cs +++ b/osu.Game/Skinning/Components/EzSelectorTextures.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Skinning.Components { - //TODO 代码不对,无法加载 + //TODO 代码不对, 无法加载, 用于缩略图选择纹理 public partial class EzSelectorTextures : SettingsItem { // private FillFlowContainer previewList = null!; diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index 6e875c5590..a271857c03 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -15,7 +15,7 @@ namespace osu.Game.Skinning.Components [UsedImplicitly] public partial class TextElement : FontAdjustableSkinComponent { - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.TextElementText), nameof(SkinnableComponentStrings.TextElementTextDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.TextElementText))] public Bindable Text { get; } = new Bindable("Circles!"); private readonly OsuSpriteText text; diff --git a/osu.Game/Skinning/EzStyleProSkin.cs b/osu.Game/Skinning/EzStyleProSkin.cs index c54b42bc29..8b8a919b39 100644 --- a/osu.Game/Skinning/EzStyleProSkin.cs +++ b/osu.Game/Skinning/EzStyleProSkin.cs @@ -22,6 +22,8 @@ namespace osu.Game.Skinning { public class EzStyleProSkin : Skin { + public const int EZ_STYLE_PRO_SKIN_ID = 999; + public static SkinInfo CreateInfo() => new SkinInfo { ID = Skinning.SkinInfo.EZ_STYLE_PRO_SKIN, diff --git a/osu.Game/Skinning/FontAdjustableSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs index f2d8c9e440..eba29e9b79 100644 --- a/osu.Game/Skinning/FontAdjustableSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -21,13 +21,13 @@ namespace osu.Game.Skinning { public bool UsesFixedAnchor { get; set; } - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Font), nameof(SkinnableComponentStrings.FontDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Font))] public Bindable Font { get; } = new Bindable(Typeface.Torus); [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.TextWeight), SettingControlType = typeof(WeightDropdown))] public Bindable TextWeight { get; } = new Bindable(FontWeight.Regular); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.TextColour), nameof(SkinnableComponentStrings.TextColourDescription))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.TextColour))] public BindableColour4 TextColour { get; } = new BindableColour4(Colour4.White); /// diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs index 1524f0f3da..7a11680a98 100644 --- a/osu.Game/Skinning/LegacyDefaultComboCounter.cs +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -49,8 +49,8 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; Margin = new MarginPadding(10); @@ -67,14 +67,14 @@ namespace osu.Game.Skinning { Alpha = 0, Blending = BlendingParameters.Additive, - Anchor = Anchor.BottomCentre, + Anchor = Anchor.BottomLeft, BypassAutoSizeAxes = Axes.Both, }, displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, AlwaysPresent = true, - Anchor = Anchor.BottomCentre, + Anchor = Anchor.BottomLeft, BypassAutoSizeAxes = Axes.Both, }, } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index bbed434b3a..b648299787 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -594,12 +594,17 @@ namespace osu.Game.Skinning { var lookupNames = hitSample.LookupNames.SelectMany(getFallbackSampleNames); - if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + if (!string.IsNullOrEmpty(hitSample.Suffix)) { - // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // for compatibility with stable: + // - if the skin can use custom sample banks, it MUST use the custom sample bank suffix. it is not allowed to fall back to a non-custom sound. + // - if the skin cannot use custom sample banks, it MUST NOT use the custom sample bank suffix. // using .EndsWith() is intentional as it ensures parity in all edge cases - // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply). + if (UseCustomSampleBanks) + lookupNames = lookupNames.Where(name => name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); + else + lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); } foreach (string l in lookupNames) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index e93a10d50b..bf5d00425b 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -224,26 +224,60 @@ namespace osu.Game.Skinning try { - // First attempt to deserialise using the new SkinLayoutInfo format - layout = JsonConvert.DeserializeObject(jsonContent); + // 检查是否包含版本信息 + dynamic? tempObject = JsonConvert.DeserializeObject(jsonContent); + string? versionString = tempObject?.Version?.ToString(); + + // 如果版本是 0.0.0.0 或者没有版本信息,按旧格式处理 + if (string.IsNullOrEmpty(versionString) || versionString == "0.0.0.0") + { + // 尝试按数组格式反序列化(旧格式) + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); + + if (deserializedContent != null) + { + layout = new SkinLayoutInfo { Version = 0 }; + layout.Update(null, deserializedContent.ToArray()); + Logger.Log($"Loaded legacy format skin layout for {target} (version 0.0.0.0)"); + } + } + else + { + // 新格式,直接按 SkinLayoutInfo 反序列化 + layout = JsonConvert.DeserializeObject(jsonContent); + } } - catch + catch (Exception ex) { + Logger.Log($"Failed to deserialize skin layout using new format for {target}: {ex.Message}", LoggingTarget.Runtime, LogLevel.Debug); + layout = null; // 确保 layout 为 null,以便进入后续的fallback逻辑 } // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. if (layout == null) { - var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); - if (deserializedContent == null) + try + { + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); + + if (deserializedContent != null) + { + layout = new SkinLayoutInfo { Version = 0 }; + layout.Update(null, deserializedContent.ToArray()); + + Logger.Log($"Ferrying {deserializedContent.Count()} components in {target} to global section of new {nameof(SkinLayoutInfo)} format"); + } + } + catch (Exception ex) + { + Logger.Log($"Failed to deserialize skin layout using legacy format for {target}: {ex.Message}", LoggingTarget.Runtime, LogLevel.Error); return null; - - layout = new SkinLayoutInfo { Version = 0 }; - layout.Update(null, deserializedContent.ToArray()); - - Logger.Log($"Ferrying {deserializedContent.Count()} components in {target} to global section of new {nameof(SkinLayoutInfo)} format"); + } } + if (layout == null) + return null; + for (int i = layout.Version + 1; i <= SkinLayoutInfo.LATEST_VERSION; i++) applyMigration(layout, target, i); diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 70d3195ecd..382a7b56c2 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -13,6 +15,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Overlays.Notifications; using Realms; namespace osu.Game.Skinning @@ -43,6 +46,50 @@ namespace osu.Game.Skinning private const string unknown_creator_string = @"Unknown"; + /// + /// Update an existing skin with the contents of a path + /// + /// The progress notification + /// The to update the with + /// The to update + /// + public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, SkinInfo original) + { + return await Realm.WriteAsync?>(r => + { + var skinInfo = r.Find(original.ID)!; + skinInfo.Files.Clear(); + + string[] filesInMountedDirectory = Directory.EnumerateFiles(task.Path, "*.*", SearchOption.AllDirectories).Select(f => Path.GetRelativePath(task.Path, f)).ToArray(); + + foreach (string file in filesInMountedDirectory) + { + using var stream = File.OpenRead(Path.Combine(task.Path, file)); + + modelManager.AddFile(skinInfo, stream, file, r); + } + + string skinIniPath = Path.Combine(task.Path, "skin.ini"); + + if (File.Exists(skinIniPath)) + { + using (var stream = File.OpenRead(skinIniPath)) + using (var lineReader = new LineBufferedReader(stream)) + { + var decodedSkinIni = new LegacySkinDecoder().Decode(lineReader); + + if (!string.IsNullOrEmpty(decodedSkinIni.SkinInfo.Name)) + skinInfo.Name = decodedSkinIni.SkinInfo.Name; + + if (!string.IsNullOrEmpty(decodedSkinIni.SkinInfo.Creator)) + skinInfo.Creator = decodedSkinIni.SkinInfo.Creator; + } + } + + return skinInfo.ToLive(Realm); + }).ConfigureAwait(false); + } + protected override void Populate(SkinInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { var skinInfoFile = model.GetFile(skin_info_file); diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 26b9e5b9bc..dfb26d2dbc 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -22,7 +22,7 @@ namespace osu.Game.Skinning internal static readonly Guid CLASSIC_SKIN = new Guid("81F02CD3-EEC6-4865-AC23-FAE26A386187"); internal static readonly Guid RANDOM_SKIN = new Guid("D39DFEFB-477C-4372-B1EA-2BCEA5FB8908"); internal static readonly Guid EZ2_SKIN = new Guid("fc372386-381d-4f8e-897a-c1d89ef39f9c"); - internal static readonly Guid EZ_STYLE_PRO_SKIN = new Guid("fc372386-381d-4f8e-897a-c1d89ef39f9d"); + internal static readonly Guid EZ_STYLE_PRO_SKIN = new Guid("1E70839C-C0D8-4DBF-B747-0C08C89D412B"); internal static readonly Guid SBI_SKIN = new Guid("fc372386-381d-4f8e-897a-c1d89ef39f2c"); [PrimaryKey] diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e8c5a88c63..77d6155c9c 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -134,6 +134,10 @@ namespace osu.Game.Skinning { Realm.Run(r => { + // can be the case when the current skin is externally mounted for editing + if (CurrentSkinInfo.Disabled) + return; + // Required local for iOS. Will cause runtime crash if inlined. Guid currentSkinId = CurrentSkinInfo.Value.ID; diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 47618f6296..883f9e6a6d 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } = null!; - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.SpriteName), nameof(SkinnableComponentStrings.SpriteNameDescription), SettingControlType = typeof(SpriteSelectorControl))] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.SpriteName), SettingControlType = typeof(SpriteSelectorControl))] public Bindable SpriteName { get; } = new Bindable(string.Empty); [Resolved] @@ -119,6 +119,9 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) => skin.GetType() == typeof(TrianglesSkin) + || skin.GetType() == typeof(Ez2Skin) + || skin.GetType() == typeof(EzStyleProSkin) + || skin.GetType() == typeof(SbISkin) || skin.GetType() == typeof(ArgonProSkin) || skin.GetType() == typeof(ArgonSkin) || skin.GetType() == typeof(DefaultLegacySkin) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index a7c2dc2bf0..1568583a83 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -63,20 +63,11 @@ namespace osu.Game.Storyboards.Drawables Storyboard = storyboard; Mods = mods ?? Array.Empty(); - Size = new Vector2(640, 480); - bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).All(e => e is StoryboardVideo); - if (onlyHasVideoElements) - { - RelativeSizeAxes = Axes.Both; - Size = Vector2.One; - FillMode = FillMode.Fill; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - else + if (!onlyHasVideoElements) { + Size = new Vector2(640, 480); Width = Height * (storyboard.Beatmap.WidescreenStoryboard ? 16 / 9f : 4 / 3f); } @@ -126,20 +117,17 @@ namespace osu.Game.Storyboards.Drawables { if (Parent != null) { - float windowAspectRatio = Parent.DrawWidth / Parent.DrawHeight; float videoAspectRatio = drawableVideo.DrawSize.X / drawableVideo.DrawSize.Y; - if (windowAspectRatio > videoAspectRatio) + if (videoAspectRatio < 1.5f) { - // 窗口更高,以宽度为基准调整高度 - Width = Parent.DrawWidth; - Height = Width / videoAspectRatio; + RelativeSizeAxes = Axes.X; + Width = 1f / DrawScale.X; } else { - // 窗口更宽,以高度为基准调整宽度 - Height = Parent.DrawHeight; - Width = Height * videoAspectRatio; + RelativeSizeAxes = Axes.Y; + Height = 1f / DrawScale.Y; } } }); diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs index 78188d7cf7..d2b216caa8 100644 --- a/osu.Game/Tests/Visual/EditorSavingTestScene.cs +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; using osuTK.Input; namespace osu.Game.Tests.Visual @@ -74,15 +74,14 @@ namespace osu.Game.Tests.Visual AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - SongSelect songSelect = null; + SoloSongSelect songSelect = null; - PushAndConfirm(() => songSelect = new PlaySongSelect()); - AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); + PushAndConfirm(() => songSelect = new SoloSongSelect()); + AddUntilStep("wait for carousel load", () => songSelect.CarouselItemsPresented); AddStep("Present same beatmap", () => Game.PresentBeatmap(Game.BeatmapManager.QueryBeatmapSet(set => set.ID == beatmapSetGuid)!.Value, beatmap => beatmap.ID == beatmapGuid)); AddUntilStep("Wait for beatmap selected", () => Game.Beatmap.Value.BeatmapInfo.ID == beatmapGuid); - AddStep("Open options", () => InputManager.Key(Key.F3)); - AddStep("Enter editor", () => InputManager.Key(Key.Number5)); + AddStep("Open editor", () => songSelect.Edit(Game.Beatmap.Value.BeatmapInfo)); AddUntilStep("Wait for editor load", () => Editor != null); } diff --git a/osu.Game/Tests/Visual/ReplayStabilityTestScene.cs b/osu.Game/Tests/Visual/ReplayStabilityTestScene.cs index af41617a7b..a84fb86200 100644 --- a/osu.Game/Tests/Visual/ReplayStabilityTestScene.cs +++ b/osu.Game/Tests/Visual/ReplayStabilityTestScene.cs @@ -57,6 +57,11 @@ namespace osu.Game.Tests.Visual AddStep(@"exit player", () => currentPlayer.Exit()); + // The incoming beatmap is ruleset-typed in every usage, so the incoming hitobjects will be used as-is rather than being converted. + // Because we'll be re-using the beatmap (thus also the hitobjects), we need to make sure the previous player has been fully disposed. + AddUntilStep("player exited", () => !currentPlayer.IsCurrentScreen()); + AddStep("dispose player", () => currentPlayer.Dispose()); + AddStep(@"encode and decode score", () => { var encoder = new LegacyScoreEncoder(originalScore, beatmap); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9ae4aa9d6a..d1f3d3f107 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -40,7 +40,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -55,4 +54,7 @@ + + + diff --git a/osu.iOS.props b/osu.iOS.props index ab3fc11cca..bb5e3da49e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + diff --git a/osu.sln b/osu.sln index 2bcca95720..3878444d1c 100644 --- a/osu.sln +++ b/osu.sln @@ -101,6 +101,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeAnalysis", "CodeAnalysi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Resources", "..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj", "{18ED4ABC-6064-46F1-9A05-4E0BE88A635A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework", "..\osu-framework\osu.Framework\osu.Framework.csproj", "{E46405B5-643D-400C-A1F2-F770FBCC54B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.NativeLibs", "..\osu-framework\osu.Framework.NativeLibs\osu.Framework.NativeLibs.csproj", "{F5037F74-E137-4940-8CE7-4D3E5DF0FB39}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.Android", "..\osu-framework\osu.Framework.Android\osu.Framework.Android.csproj", "{5ABDA3A9-BDAD-48A2-945E-EF78228FD5FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.iOS", "..\osu-framework\osu.Framework.iOS\osu.Framework.iOS.csproj", "{335CB301-EB9C-4108-A353-CD68AFA200AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -259,6 +267,22 @@ Global {18ED4ABC-6064-46F1-9A05-4E0BE88A635A}.Debug|Any CPU.Build.0 = Debug|Any CPU {18ED4ABC-6064-46F1-9A05-4E0BE88A635A}.Release|Any CPU.ActiveCfg = Release|Any CPU {18ED4ABC-6064-46F1-9A05-4E0BE88A635A}.Release|Any CPU.Build.0 = Release|Any CPU + {E46405B5-643D-400C-A1F2-F770FBCC54B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E46405B5-643D-400C-A1F2-F770FBCC54B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E46405B5-643D-400C-A1F2-F770FBCC54B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E46405B5-643D-400C-A1F2-F770FBCC54B8}.Release|Any CPU.Build.0 = Release|Any CPU + {F5037F74-E137-4940-8CE7-4D3E5DF0FB39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5037F74-E137-4940-8CE7-4D3E5DF0FB39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5037F74-E137-4940-8CE7-4D3E5DF0FB39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5037F74-E137-4940-8CE7-4D3E5DF0FB39}.Release|Any CPU.Build.0 = Release|Any CPU + {5ABDA3A9-BDAD-48A2-945E-EF78228FD5FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ABDA3A9-BDAD-48A2-945E-EF78228FD5FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ABDA3A9-BDAD-48A2-945E-EF78228FD5FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ABDA3A9-BDAD-48A2-945E-EF78228FD5FC}.Release|Any CPU.Build.0 = Release|Any CPU + {335CB301-EB9C-4108-A353-CD68AFA200AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {335CB301-EB9C-4108-A353-CD68AFA200AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {335CB301-EB9C-4108-A353-CD68AFA200AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {335CB301-EB9C-4108-A353-CD68AFA200AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE