From b77e6f92b741a20adfd539d8fdbaf183fcbe8d13 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 19 Jan 2022 22:31:11 +0300 Subject: [PATCH 001/583] Fix touch device difficulty reduction not affecting star rating --- .../Difficulty/OsuDifficultyCalculator.cs | 4 ++++ .../Difficulty/OsuPerformanceCalculator.cs | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c5b1baaad1..537d63356b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; + if (mods.Any(m => m is OsuModTouchDevice)) + aimRating = Math.Pow(aimRating, 0.8); + if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -120,6 +123,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override Mod[] DifficultyAdjustmentMods => new Mod[] { + new OsuModTouchDevice(), new OsuModDoubleTime(), new OsuModHalfTime(), new OsuModEasy(), diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 604ab73454..07be3571f6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -84,12 +84,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue() { - double rawAim = Attributes.AimDifficulty; - - if (mods.Any(m => m is OsuModTouchDevice)) - rawAim = Math.Pow(rawAim, 0.8); - - double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; + double aimValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); From 855debd5f66c2c1a4cd25d63bfd61d0dc477fbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:29:17 +0800 Subject: [PATCH 002/583] Remove nullable disable annotation and mark some return value as nullable. --- osu.Game/Rulesets/Ruleset.cs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c1ec6c30ef..7130f4bc35 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -81,7 +79,7 @@ namespace osu.Game.Rulesets /// Returns a fresh instance of the mod matching the specified acronym. /// /// The acronym to query for . - public Mod CreateModFromAcronym(string acronym) + public Mod? CreateModFromAcronym(string acronym) { return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance(); } @@ -89,7 +87,7 @@ namespace osu.Game.Rulesets /// /// Returns a fresh instance of the mod matching the specified type. /// - public T CreateMod() + public T? CreateMod() where T : Mod { return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T; @@ -183,10 +181,9 @@ namespace osu.Game.Rulesets return value; } - [CanBeNull] - public ModAutoplay GetAutoplayMod() => CreateMod(); + public ModAutoplay? GetAutoplayMod() => CreateMod(); - public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin? CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { @@ -206,7 +203,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. - public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null); + public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null); /// /// Creates a for this . @@ -240,8 +237,7 @@ namespace osu.Game.Rulesets /// Optionally creates a to generate performance data from the provided score. /// /// A performance calculator instance for the provided score. - [CanBeNull] - public virtual PerformanceCalculator CreatePerformanceCalculator() => null; + public virtual PerformanceCalculator? CreatePerformanceCalculator() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; @@ -253,7 +249,7 @@ namespace osu.Game.Rulesets public abstract string Description { get; } - public virtual RulesetSettingsSubsection CreateSettings() => null; + public virtual RulesetSettingsSubsection? CreateSettings() => null; /// /// Creates the for this . @@ -303,7 +299,6 @@ namespace osu.Game.Rulesets /// The to create the statistics for. The score is guaranteed to have populated. /// The , converted for this with all relevant s applied. /// The s to display. Each may contain 0 or more . - [NotNull] public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); /// @@ -356,13 +351,11 @@ namespace osu.Game.Rulesets /// /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen. /// - [CanBeNull] - public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria() => null; + public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null; /// /// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen. /// - [CanBeNull] - public virtual RulesetSetupSection CreateEditorSetupSection() => null; + public virtual RulesetSetupSection? CreateEditorSetupSection() => null; } } From 4a503bab0a3d520965efeb54594053baaacc348c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:29:59 +0800 Subject: [PATCH 003/583] Remove unnecessary attribute. --- osu.Game/Rulesets/Ruleset.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7130f4bc35..af8ff10fa1 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; -using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; @@ -101,7 +100,6 @@ namespace osu.Game.Rulesets /// then the proper behaviour is to return an empty enumerable. /// mods should not be present in the returned enumerable. /// - [ItemNotNull] public abstract IEnumerable GetModsFor(ModType type); /// @@ -183,7 +181,7 @@ namespace osu.Game.Rulesets public ModAutoplay? GetAutoplayMod() => CreateMod(); - public virtual ISkin? CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin? CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { From e67cb4c90520e4c328c653372bd11b20a67a9935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:35:46 +0800 Subject: [PATCH 004/583] Mark create beatmap verifier as nullable because seems it's not requirement to be implemented. --- osu.Game/Rulesets/Ruleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index af8ff10fa1..14513b06b8 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual IBeatmapVerifier CreateBeatmapVerifier() => null; + public virtual IBeatmapVerifier? CreateBeatmapVerifier() => null; public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; From 857377e1451402cb8848da21c9cd0d00c01dbb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:53:05 +0800 Subject: [PATCH 005/583] Move CreateConvertibleReplayFrame() into legacy ruleset interface because technically only legacy ruleset use it to convert the legacy frame. But seems some of the customized ruleset use it for save the replay frame. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/ILegacyRuleset.cs | 9 ++++++++- osu.Game/Rulesets/Ruleset.cs | 8 -------- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 6 +++++- 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f832d99807..766b5f6f58 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Catch public int LegacyID => 2; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 4723416c30..f45a2517df 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -280,7 +280,7 @@ namespace osu.Game.Rulesets.Mania public int LegacyID => 3; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 120ce32612..392574ef03 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Osu public int LegacyID => 0; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 223e268d7f..66fc64c018 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko public int LegacyID => 1; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs index a8cfed4866..c2617a065c 100644 --- a/osu.Game/Rulesets/ILegacyRuleset.cs +++ b/osu.Game/Rulesets/ILegacyRuleset.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable +using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets { @@ -13,5 +13,12 @@ namespace osu.Game.Rulesets /// Identifies the server-side ID of a legacy ruleset. /// int LegacyID { get; } + + /// + /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame + /// for conversion use. + /// + /// An empty frame for the current ruleset, or null if unsupported. + IConvertibleReplayFrame CreateConvertibleReplayFrame(); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 14513b06b8..89ef6d2d54 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -13,7 +13,6 @@ using osu.Game.Beatmaps; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; @@ -284,13 +283,6 @@ namespace osu.Game.Rulesets /// A descriptive name of the variant. public virtual string GetVariantName(int variant) => string.Empty; - /// - /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame - /// for conversion use. - /// - /// An empty frame for the current ruleset, or null if unsupported. - public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; - /// /// Creates the statistics for a to be displayed in the results screen. /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f64e730c06..2b22b4abc7 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -282,7 +282,7 @@ namespace osu.Game.Scoring.Legacy private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, ReplayFrame lastFrame) { - var convertible = currentRuleset.CreateConvertibleReplayFrame(); + var convertible = (currentRuleset as ILegacyRuleset)?.CreateConvertibleReplayFrame(); if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 797787b194..f59fd9559c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; +using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; @@ -79,11 +80,14 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + if (GameplayState.Ruleset is not ILegacyRuleset legacyRuleset) + return; + bool isFirstBundle = score.Replay.Frames.Count == 0; foreach (var frame in bundle.Frames) { - IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame(); + IConvertibleReplayFrame convertibleFrame = legacyRuleset.CreateConvertibleReplayFrame(); convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); var convertedFrame = (ReplayFrame)convertibleFrame; From d39f53f1f0e2ab307c593eb928078f0c55a13684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:01:56 +0800 Subject: [PATCH 006/583] Mark `CreateConfig()` return type as nullable because it's not required all ruleset to implement. Also, remove nullable disable annotation for all using classes. Setting store can be nullable because `RulesetConfigManager()` can accept null setting store. --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/RulesetConfigCache.cs | 6 ++---- osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs | 6 ++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 89ef6d2d54..cd3ab4f726 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets /// Creates the for this . /// /// The to store the settings. - public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null; + public virtual IRulesetConfigManager? CreateConfig(SettingsStore? settings) => null; /// /// A unique short name to reference this ruleset in online requests. diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 017214df61..ab44e86048 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Graphics; @@ -18,7 +16,7 @@ namespace osu.Game.Rulesets private readonly RealmAccess realm; private readonly RulesetStore rulesets; - private readonly Dictionary configCache = new Dictionary(); + private readonly Dictionary configCache = new Dictionary(); public RulesetConfigCache(RealmAccess realm, RulesetStore rulesets) { @@ -42,7 +40,7 @@ namespace osu.Game.Rulesets } } - public IRulesetConfigManager GetConfigFor(Ruleset ruleset) + public IRulesetConfigManager? GetConfigFor(Ruleset ruleset) { if (!IsLoaded) throw new InvalidOperationException($@"Cannot retrieve {nameof(IRulesetConfigManager)} before {nameof(RulesetConfigCache)} has loaded"); diff --git a/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs b/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs index a80154c38e..cf637983d9 100644 --- a/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs +++ b/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Concurrent; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -14,8 +12,8 @@ namespace osu.Game.Tests.Rulesets /// public class TestRulesetConfigCache : IRulesetConfigCache { - private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); - public IRulesetConfigManager GetConfigFor(Ruleset ruleset) => configCache.GetOrAdd(ruleset.ShortName, _ => ruleset.CreateConfig(null)); + public IRulesetConfigManager? GetConfigFor(Ruleset ruleset) => configCache.GetOrAdd(ruleset.ShortName, _ => ruleset.CreateConfig(null)); } } From 57c6763556920d09399fe679c1dfc8d386686759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:07:09 +0800 Subject: [PATCH 007/583] Mark the `CreateBeatmapProcessor()` as nullable. Also, should add the null check in the working beatmap. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 11 +++++++---- osu.Game/Rulesets/Ruleset.cs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 16464932e0..22c3ad43e6 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -279,12 +279,15 @@ namespace osu.Game.Beatmaps } } - IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + var processor = rulesetInstance.CreateBeatmapProcessor(converted); - foreach (var mod in mods.OfType()) - mod.ApplyToBeatmapProcessor(processor); + if (processor != null) + { + foreach (var mod in mods.OfType()) + mod.ApplyToBeatmapProcessor(processor); - processor?.PreProcess(); + processor.PreProcess(); + } // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed foreach (var obj in converted.HitObjects) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cd3ab4f726..bd2d147a08 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -226,7 +226,7 @@ namespace osu.Game.Rulesets /// /// The to be processed. /// The . - public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null; + public virtual IBeatmapProcessor? CreateBeatmapProcessor(IBeatmap beatmap) => null; public abstract DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap); From 8e1ed1c621d12c4b1a1f97812968043b8eb7017c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:09:32 +0800 Subject: [PATCH 008/583] Mark CreateHitObjectComposer() accept null. And add the null check in the test case. --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 8 +++++++- osu.Game/Rulesets/Ruleset.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 437f06c47f..7c11b4aa56 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -39,7 +39,13 @@ namespace osu.Game.Tests.Visual.Editing Dependencies.Cache(EditorBeatmap); Dependencies.CacheAs(EditorBeatmap); - Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0); + Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => + { + if (d == null) + return; + + d.Alpha = 0; + }); Add(new OsuContextMenuContainer { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index bd2d147a08..2cab529a4b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets /// A performance calculator instance for the provided score. public virtual PerformanceCalculator? CreatePerformanceCalculator() => null; - public virtual HitObjectComposer CreateHitObjectComposer() => null; + public virtual HitObjectComposer? CreateHitObjectComposer() => null; public virtual IBeatmapVerifier? CreateBeatmapVerifier() => null; From 1725a76fa0183cd9e9c9b7a2139ee498f525a11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:15:02 +0800 Subject: [PATCH 009/583] Remove the nullable disable annotation for all rulesets. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 +--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++---- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 4 +--- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 766b5f6f58..964598dcaf 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Mods; @@ -32,7 +30,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset, ILegacyRuleset { - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableCatchRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f45a2517df..b391c64572 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; @@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Mania /// public const int MAX_STAGE_KEYS = 10; - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableManiaRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); @@ -282,7 +280,7 @@ namespace osu.Game.Rulesets.Mania public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 392574ef03..458a0a12f4 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -43,7 +41,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset, ILegacyRuleset { - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableOsuRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); @@ -236,7 +234,7 @@ namespace osu.Game.Rulesets.Osu public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 66fc64c018..19aa3e1016 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -36,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset, ILegacyRuleset { - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); From 3dbd0c9055dac6c32e9b8bd31baad2d6d42ac7cb Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 12 Jul 2022 09:37:16 +0800 Subject: [PATCH 010/583] Update osu.Game.Tests/Visual/Editing/TimelineTestScene.cs Co-authored-by: Salman Ahmed --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 7c11b4aa56..40d91dcbaa 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -39,13 +39,10 @@ namespace osu.Game.Tests.Visual.Editing Dependencies.Cache(EditorBeatmap); Dependencies.CacheAs(EditorBeatmap); - Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => - { - if (d == null) - return; + Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer(); + Debug.Assert(Composer != null); - d.Alpha = 0; - }); + Composer.Alpha = 0; Add(new OsuContextMenuContainer { From 079fcf13a6245a987e12fa7db7a06401452be51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Tue, 12 Jul 2022 09:41:03 +0800 Subject: [PATCH 011/583] Add missing import. --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 40d91dcbaa..d4c96a837b 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; From 6a643cb6eae329fe7e434c07392f195f283890ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Tue, 12 Jul 2022 09:41:19 +0800 Subject: [PATCH 012/583] Use Debug.Assert instead. --- osu.Game/Screens/Play/SpectatorPlayer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index f59fd9559c..659d7d5d6c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -80,8 +81,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - if (GameplayState.Ruleset is not ILegacyRuleset legacyRuleset) - return; + var legacyRuleset = GameplayState.Ruleset as ILegacyRuleset; + Debug.Assert(legacyRuleset != null); bool isFirstBundle = score.Replay.Frames.Count == 0; From a5a8fac66f6e58afd9b4984c94be0dedef7a2e4a Mon Sep 17 00:00:00 2001 From: solstice23 Date: Thu, 21 Jul 2022 18:24:31 +0800 Subject: [PATCH 013/583] Add multiple units support in search length criteria --- osu.Game/Screens/Select/FilterQueryParser.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 03b72bf5e9..436beae30d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -312,11 +312,18 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { - if (!tryParseDoubleWithPoint(val.TrimEnd('m', 's', 'h'), out double length)) - return false; - - int scale = getLengthScale(val); - return tryUpdateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); + string[] parts = Regex.Split(val, @"(?<=[msh])").Where(x => x.Length > 0).ToArray(); + double totalLength = 0; + int minScale = 1000; + foreach (string part in parts) + { + if (!tryParseDoubleWithPoint(part.TrimEnd('m', 's', 'h'), out double length)) + return false; + int scale = getLengthScale(part); + totalLength += length * scale; + minScale = Math.Min(minScale, scale); + } + return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } } From 6baaef432f086209c163b52d5393d3d32ce62e7c Mon Sep 17 00:00:00 2001 From: solstice23 Date: Thu, 21 Jul 2022 18:49:13 +0800 Subject: [PATCH 014/583] Add colon parsing support in search length criteria --- osu.Game/Screens/Select/FilterQueryParser.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 436beae30d..ee7a1617cd 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -312,9 +312,25 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { + if (val.Contains(':')) + { + if (val.Contains('h') || val.Contains('m') || val.Contains('s')) + return false; + + int count = val.Count(c => c == ':'); + if (count > 3) + return false; + + if (count > 2) + val = val.Replace(':', 'h'); + + val = val.Replace(':', 'm'); + } + string[] parts = Regex.Split(val, @"(?<=[msh])").Where(x => x.Length > 0).ToArray(); double totalLength = 0; int minScale = 1000; + foreach (string part in parts) { if (!tryParseDoubleWithPoint(part.TrimEnd('m', 's', 'h'), out double length)) @@ -323,6 +339,7 @@ namespace osu.Game.Screens.Select totalLength += length * scale; minScale = Math.Min(minScale, scale); } + return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } From 52fad1e14d4292f0c867cb099eb1fcee1b6b960f Mon Sep 17 00:00:00 2001 From: solstice23 Date: Thu, 21 Jul 2022 18:59:48 +0800 Subject: [PATCH 015/583] Add test cases for search length criteria --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index bd0617515b..9f97c39462 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -125,6 +125,10 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, + new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, + new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "0:2:35", TimeSpan.FromSeconds(155), TimeSpan.FromSeconds(1) }, }; [Test] From ae0902ca86e30d690459c1dedb28a9d01f0d6ff7 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 02:55:11 +0800 Subject: [PATCH 016/583] Fix lax in search criteria parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 49 +++++++++++++------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ee7a1617cd..25dd342353 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -312,34 +313,50 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { - if (val.Contains(':')) + List parts = new List(); + + if (Regex.IsMatch(val, @"^\d+(:\d+(:\d+)?)?$")) // formats like 12:34 { - if (val.Contains('h') || val.Contains('m') || val.Contains('s')) + string[] splited = val.Split(':'); + for (int i = splited.Length - 1; i >= 0; i--) + parts.Add(splited[i] + "smh"[splited.Length - i - 1]); + } + else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$")) // formats like 1h2m3s + { + if (!"hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) return false; - int count = val.Count(c => c == ':'); - if (count > 3) - return false; - - if (count > 2) - val = val.Replace(':', 'h'); - - val = val.Replace(':', 'm'); + string[] splited = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).ToArray(); + for (int i = splited.Length - 1; i >= 0; i--) + parts.Add(splited[i]); + } + else if (Regex.IsMatch(val, @"^\d+(\.\d+)?$")) // only one number + { + parts.Add(val + 's'); } - string[] parts = Regex.Split(val, @"(?<=[msh])").Where(x => x.Length > 0).ToArray(); - double totalLength = 0; - int minScale = 1000; + if (parts.Count == 0) + return false; - foreach (string part in parts) + double totalLength = 0; + int minScale = 3600000; + + for (int i = 0; i < parts.Count; i++) { - if (!tryParseDoubleWithPoint(part.TrimEnd('m', 's', 'h'), out double length)) + string part = parts[i]; + string partNoUnit = part.TrimEnd('m', 's', 'h'); + if (!tryParseDoubleWithPoint(partNoUnit, out double length)) return false; + + if (i != parts.Count - 1 && length >= 60) + return false; + if (i != 0 && partNoUnit.Contains('.')) + return false; + int scale = getLengthScale(part); totalLength += length * scale; minScale = Math.Min(minScale, scale); } - return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } From de25830b2ba1b0a696331d79da54f2ea7605a8e2 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 03:27:08 +0800 Subject: [PATCH 017/583] Add more test cases --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9f97c39462..8f17410cad 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -126,9 +126,16 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "0:2:35", TimeSpan.FromSeconds(155), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) }, + new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) }, + new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, }; [Test] From b36e23c0da7c02e61d87862bbe30eb5f8b3ccc1e Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 03:30:31 +0800 Subject: [PATCH 018/583] Simplify the regex expression --- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 25dd342353..9245c13243 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Select { List parts = new List(); - if (Regex.IsMatch(val, @"^\d+(:\d+(:\d+)?)?$")) // formats like 12:34 + if (Regex.IsMatch(val, @"^\d+(:\d+){1,2}$")) // formats like 12:34 { string[] splited = val.Split(':'); for (int i = splited.Length - 1; i >= 0; i--) From 80e82763e35304022e9c42a35a4f4c18fba7ef2e Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 12:09:47 +0800 Subject: [PATCH 019/583] Add negative test cases --- .../Filtering/FilterQueryParserTest.cs | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 8f17410cad..4b20fc42c9 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -120,35 +120,54 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] length_query_examples = { - new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) }, - new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, - new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, - new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, - new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, - new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, - new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) }, - new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, - new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, - new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) }, - new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, - new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, - new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) }, - new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, + new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1), true }, + new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1), true }, + new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1), true }, + new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1), true }, + new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, + new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, + new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, + new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1), true }, + new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, + new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, + new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1), true }, + new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, + new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, + new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1), true }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1), true }, + new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1), true }, + new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1), true }, + new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1), true }, + new object[] { "7.5m27s", new TimeSpan(), new TimeSpan(), false }, + new object[] { "7m27", new TimeSpan(), new TimeSpan(), false }, + new object[] { "7m7m7m", new TimeSpan(), new TimeSpan(), false }, + new object[] { "7m70s", new TimeSpan(), new TimeSpan(), false }, + new object[] { "5s6m", new TimeSpan(), new TimeSpan(), false }, + new object[] { "0:", new TimeSpan(), new TimeSpan(), false }, + new object[] { ":0", new TimeSpan(), new TimeSpan(), false }, + new object[] { "0:3:", new TimeSpan(), new TimeSpan(), false }, + new object[] { "3:15.5", new TimeSpan(), new TimeSpan(), false }, }; [Test] [TestCaseSource(nameof(length_query_examples))] - public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) + public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale, bool isValid) { string query = $"length={lengthQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual("time", filterCriteria.SearchText.Trim()); - Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + if (isValid) + { + Assert.AreEqual("time", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); + Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + } + else + { + Assert.AreEqual(false, filterCriteria.Length.HasFilter); + } + } [Test] From 7c222505e941f0afd2f4925cacb3922c6d10938a Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 14:24:17 +0800 Subject: [PATCH 020/583] Simplify length parsing --- .../Filtering/FilterQueryParserTest.cs | 1 - osu.Game/Screens/Select/FilterQueryParser.cs | 22 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 4b20fc42c9..f08bd09cf5 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -167,7 +167,6 @@ namespace osu.Game.Tests.NonVisual.Filtering { Assert.AreEqual(false, filterCriteria.Length.HasFilter); } - } [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 9245c13243..b8c85c5ce0 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -317,18 +317,18 @@ namespace osu.Game.Screens.Select if (Regex.IsMatch(val, @"^\d+(:\d+){1,2}$")) // formats like 12:34 { - string[] splited = val.Split(':'); - for (int i = splited.Length - 1; i >= 0; i--) - parts.Add(splited[i] + "smh"[splited.Length - i - 1]); - } - else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$")) // formats like 1h2m3s - { - if (!"hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) - return false; + List splitted = val.Split(':').ToList(); + while (splitted.Count < 3) + splitted.Insert(0, "0"); - string[] splited = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).ToArray(); - for (int i = splited.Length - 1; i >= 0; i--) - parts.Add(splited[i]); + parts.Add(splitted[2] + 's'); + parts.Add(splitted[1] + 'm'); + parts.Add(splitted[0] + 'h'); + } + else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$") && "hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) // formats like 1h2m3s + { + string[] splitted = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).Reverse().ToArray(); + parts.AddRange(splitted); } else if (Regex.IsMatch(val, @"^\d+(\.\d+)?$")) // only one number { From 1220250bb677567498a10e4f9604ce8291bc8b3d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 07:26:34 +0300 Subject: [PATCH 021/583] Improve test scene and add failing test case --- osu.Game.Tests/Resources/TestResources.cs | 2 +- .../SongSelect/TestSceneTopLocalRank.cs | 79 ++++++++++++++++--- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index ee29cc8644..37bcef0414 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Resources [HitResult.SmallTickMiss] = 25, [HitResult.LargeTickHit] = 100, [HitResult.LargeTickMiss] = 50, - [HitResult.SmallBonus] = 10, + [HitResult.LargeBonus] = 10, [HitResult.SmallBonus] = 50 }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 05b5c5c0cd..d998c4e161 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -3,6 +3,8 @@ #nullable disable +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -13,6 +15,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Select.Carousel; using osu.Game.Tests.Resources; @@ -54,6 +57,8 @@ namespace osu.Game.Tests.Visual.SongSelect Scale = new Vector2(10), }); }); + + AddAssert("No rank displayed initially", () => topLocalRank.Rank == null); } [Test] @@ -61,8 +66,6 @@ namespace osu.Game.Tests.Visual.SongSelect { ScoreInfo testScoreInfo = null; - AddAssert("Initially not present", () => !topLocalRank.IsPresent); - AddStep("Add score for current user", () => { testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); @@ -73,15 +76,14 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Became present", () => topLocalRank.IsPresent); - AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); AddStep("Delete score", () => { scoreManager.Delete(testScoreInfo); }); - AddUntilStep("Became not present", () => !topLocalRank.IsPresent); + AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); } [Test] @@ -99,13 +101,13 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Wait for initial presence", () => topLocalRank.IsPresent); + AddUntilStep("Wait for initial display", () => topLocalRank.Rank == ScoreRank.B); AddStep("Change ruleset", () => Ruleset.Value = rulesets.GetRuleset("fruits")); - AddUntilStep("Became not present", () => !topLocalRank.IsPresent); + AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); AddStep("Change ruleset back", () => Ruleset.Value = rulesets.GetRuleset("osu")); - AddUntilStep("Became present", () => topLocalRank.IsPresent); + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); } [Test] @@ -113,8 +115,6 @@ namespace osu.Game.Tests.Visual.SongSelect { ScoreInfo testScoreInfo = null; - AddAssert("Initially not present", () => !topLocalRank.IsPresent); - AddStep("Add score for current user", () => { testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); @@ -125,8 +125,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Became present", () => topLocalRank.IsPresent); - AddUntilStep("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -135,11 +134,65 @@ namespace osu.Game.Tests.Visual.SongSelect testScoreInfo2.User = API.LocalUser.Value; testScoreInfo2.Rank = ScoreRank.S; testScoreInfo2.TotalScore = testScoreInfo.TotalScore + 1; + testScoreInfo2.Statistics = new Dictionary + { + [HitResult.Miss] = 0, + [HitResult.Perfect] = 970, + [HitResult.SmallTickHit] = 75, + [HitResult.LargeTickHit] = 150, + [HitResult.LargeBonus] = 10, + [HitResult.SmallBonus] = 50 + }; scoreManager.Import(testScoreInfo2); }); - AddUntilStep("Correct rank", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); + } + + [Test] + public void TestLegacyScore() + { + ScoreInfo testScoreInfo = null; + + AddStep("Add legacy score for current user", () => + { + testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = API.LocalUser.Value; + testScoreInfo.Rank = ScoreRank.B; + testScoreInfo.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo, ScoringMode.Classic).GetResultSafely(); + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + + AddStep("Add higher score for current user", () => + { + var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo2.User = API.LocalUser.Value; + testScoreInfo2.Rank = ScoreRank.S; + testScoreInfo2.Statistics = new Dictionary + { + [HitResult.Miss] = 0, + [HitResult.Perfect] = 970, + [HitResult.SmallTickHit] = 75, + [HitResult.LargeTickHit] = 150, + [HitResult.LargeBonus] = 10, + [HitResult.SmallBonus] = 50 + }; + + testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo).GetResultSafely(); + + // ensure standardised total score is less than classic, otherwise this test is pointless. + Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.TotalScore); + + scoreManager.Import(testScoreInfo2); + }); + + AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); } } } From 727fe76b6090e2071507e1e1622e1f79fd2d22ca Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 08:09:00 +0300 Subject: [PATCH 022/583] Fix `TopLocalRank` hacking around presence to hide on null rank Fixed this here because that blocks `Schedule` from running, and I don't want to add another override to the `IsPresent` flag. --- .../SongSelect/TestSceneTopLocalRank.cs | 20 ++++++------- .../Online/Leaderboards/UpdateableRank.cs | 2 +- .../Screens/Select/Carousel/TopLocalRank.cs | 29 ++++++++++++++----- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index d998c4e161..90d7aaacc3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); }); - AddAssert("No rank displayed initially", () => topLocalRank.Rank == null); + AddAssert("No rank displayed initially", () => topLocalRank.DisplayedRank == null); } [Test] @@ -76,14 +76,14 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Delete score", () => { scoreManager.Delete(testScoreInfo); }); - AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); + AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); } [Test] @@ -101,13 +101,13 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Wait for initial display", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("Wait for initial display", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Change ruleset", () => Ruleset.Value = rulesets.GetRuleset("fruits")); - AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); + AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); AddStep("Change ruleset back", () => Ruleset.Value = rulesets.GetRuleset("osu")); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); } [Test] @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); } [Test] @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); } } } diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index e4f5f72886..e640fe8494 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -17,7 +17,7 @@ namespace osu.Game.Online.Leaderboards set => Model = value; } - public UpdateableRank(ScoreRank? rank) + public UpdateableRank(ScoreRank? rank = null) { Rank = rank; } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index cc01f61c57..033da61461 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -5,9 +5,12 @@ using System; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Models; @@ -20,7 +23,7 @@ using Realms; namespace osu.Game.Screens.Select.Carousel { - public class TopLocalRank : UpdateableRank + public class TopLocalRank : CompositeDrawable { private readonly BeatmapInfo beatmapInfo; @@ -34,13 +37,25 @@ namespace osu.Game.Screens.Select.Carousel private IAPIProvider api { get; set; } private IDisposable scoreSubscription; + private CancellationTokenSource scoreOrderCancellationSource; + + private readonly UpdateableRank updateable; + + public ScoreRank? DisplayedRank => updateable.Rank; public TopLocalRank(BeatmapInfo beatmapInfo) - : base(null) { this.beatmapInfo = beatmapInfo; - Size = new Vector2(40, 20); + AutoSizeAxes = Axes.Both; + + InternalChild = updateable = new UpdateableRank + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40, 20), + Alpha = 0, + }; } protected override void LoadComplete() @@ -59,19 +74,17 @@ namespace osu.Game.Screens.Select.Carousel .OrderByDescending(s => s.TotalScore), (items, _, _) => { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); + updateable.Rank = items.FirstOrDefault()?.Rank; + updateable.Alpha = updateable.Rank != null ? 1 : 0; }); }, true); } - public override bool IsPresent => base.IsPresent && Rank != null; - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + scoreOrderCancellationSource?.Cancel(); scoreSubscription?.Dispose(); } } From 1d0306810ab63e471a459c5488bef69cfe96610c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 08:13:05 +0300 Subject: [PATCH 023/583] Fix `TopLocalRank` not handling legacy scores properly --- .../Screens/Select/Carousel/TopLocalRank.cs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 033da61461..d372cb8dbc 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -33,6 +34,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private RealmAccess realm { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + [Resolved] private IAPIProvider api { get; set; } @@ -70,14 +74,29 @@ namespace osu.Game.Screens.Select.Carousel .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore), - (items, _, _) => - { - updateable.Rank = items.FirstOrDefault()?.Rank; - updateable.Alpha = updateable.Rank != null ? 1 : 0; - }); + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName), + localScoresChanged); }, true); + + void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + // This subscription may fire from changes to linked beatmaps, which we don't care about. + // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. + if (changes?.HasCollectionChanges() == false) + return; + + scoreOrderCancellationSource?.Cancel(); + + scoreManager.OrderByTotalScoreAsync(sender.Detach().ToArray(), (scoreOrderCancellationSource = new CancellationTokenSource()).Token) + .ContinueWith(ordered => Schedule(() => + { + if (scoreOrderCancellationSource.IsCancellationRequested) + return; + + updateable.Rank = ordered.GetResultSafely().FirstOrDefault()?.Rank; + updateable.Alpha = updateable.Rank != null ? 1 : 0; + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } } protected override void Dispose(bool isDisposing) From 3aecd288e22d985bb42b934bfc749fbe2f73b97d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 09:25:06 +0300 Subject: [PATCH 024/583] Enable NRT on `TopLocalRank` --- .../SongSelect/TestSceneTopLocalRank.cs | 25 +++++++++---------- .../Screens/Select/Carousel/TopLocalRank.cs | 16 ++++++------ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 90d7aaacc3..b122898346 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -10,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -25,10 +24,10 @@ namespace osu.Game.Tests.Visual.SongSelect { public class TestSceneTopLocalRank : OsuTestScene { - private RulesetStore rulesets; - private BeatmapManager beatmapManager; - private ScoreManager scoreManager; - private TopLocalRank topLocalRank; + private RulesetStore rulesets = null!; + private BeatmapManager beatmapManager = null!; + private ScoreManager scoreManager = null!; + private TopLocalRank topLocalRank = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -64,7 +63,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBasicImportDelete() { - ScoreInfo testScoreInfo = null; + ScoreInfo? testScoreInfo = null; AddStep("Add score for current user", () => { @@ -80,7 +79,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Delete score", () => { - scoreManager.Delete(testScoreInfo); + scoreManager.Delete(testScoreInfo.AsNonNull()); }); AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); @@ -113,7 +112,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHigherScoreSet() { - ScoreInfo testScoreInfo = null; + ScoreInfo? testScoreInfo = null; AddStep("Add score for current user", () => { @@ -133,7 +132,7 @@ namespace osu.Game.Tests.Visual.SongSelect testScoreInfo2.User = API.LocalUser.Value; testScoreInfo2.Rank = ScoreRank.S; - testScoreInfo2.TotalScore = testScoreInfo.TotalScore + 1; + testScoreInfo2.TotalScore = testScoreInfo.AsNonNull().TotalScore + 1; testScoreInfo2.Statistics = new Dictionary { [HitResult.Miss] = 0, @@ -153,7 +152,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestLegacyScore() { - ScoreInfo testScoreInfo = null; + ScoreInfo? testScoreInfo = null; AddStep("Add legacy score for current user", () => { @@ -184,10 +183,10 @@ namespace osu.Game.Tests.Visual.SongSelect [HitResult.SmallBonus] = 50 }; - testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo).GetResultSafely(); + testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo.AsNonNull()).GetResultSafely(); // ensure standardised total score is less than classic, otherwise this test is pointless. - Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.TotalScore); + Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.AsNonNull().TotalScore); scoreManager.Import(testScoreInfo2); }); diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index d372cb8dbc..2a9dae6712 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using System.Threading; @@ -29,19 +27,19 @@ namespace osu.Game.Screens.Select.Carousel private readonly BeatmapInfo beatmapInfo; [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; [Resolved] - private ScoreManager scoreManager { get; set; } + private ScoreManager scoreManager { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private IDisposable scoreSubscription; - private CancellationTokenSource scoreOrderCancellationSource; + private IDisposable? scoreSubscription; + private CancellationTokenSource? scoreOrderCancellationSource; private readonly UpdateableRank updateable; @@ -78,7 +76,7 @@ namespace osu.Game.Screens.Select.Carousel localScoresChanged); }, true); - void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception error) + void localScoresChanged(IRealmCollection sender, ChangeSet? changes, Exception _) { // This subscription may fire from changes to linked beatmaps, which we don't care about. // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. From 3f2c3413691d1745b7688b01b0fbe159cc633ec2 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Sat, 23 Jul 2022 18:13:19 +0800 Subject: [PATCH 025/583] Simplify length parsing --- .../Filtering/FilterQueryParserTest.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index f08bd09cf5..181b740729 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -138,6 +138,7 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1), true }, new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1), true }, new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1), true }, + new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1), true }, new object[] { "7.5m27s", new TimeSpan(), new TimeSpan(), false }, new object[] { "7m27", new TimeSpan(), new TimeSpan(), false }, new object[] { "7m7m7m", new TimeSpan(), new TimeSpan(), false }, diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b8c85c5ce0..19fb8665d2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -129,6 +129,16 @@ namespace osu.Game.Screens.Select value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); return Enum.TryParse(value, true, out result); } + private static bool tryMatchRegex(string value, string regex, ref GroupCollection result) + { + Match matchs = Regex.Match(value, regex); + if (matchs.Success) + { + result = matchs.Groups; + return true; + } + return false; + } /// /// Attempts to parse a keyword filter with the specified and textual . @@ -314,28 +324,22 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { List parts = new List(); + GroupCollection groups = null; - if (Regex.IsMatch(val, @"^\d+(:\d+){1,2}$")) // formats like 12:34 + if ( + tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$", ref groups) || + tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$", ref groups) || + tryMatchRegex(val, @"^(?\d+(\.\d+)?)$", ref groups) + ) { - List splitted = val.Split(':').ToList(); - while (splitted.Count < 3) - splitted.Insert(0, "0"); - - parts.Add(splitted[2] + 's'); - parts.Add(splitted[1] + 'm'); - parts.Add(splitted[0] + 'h'); + if (groups["seconds"].Success) + parts.Add(groups["seconds"].Value + "s"); + if (groups["minutes"].Success) + parts.Add(groups["minutes"].Value + "m"); + if (groups["hours"].Success) + parts.Add(groups["hours"].Value + "h"); } - else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$") && "hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) // formats like 1h2m3s - { - string[] splitted = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).Reverse().ToArray(); - parts.AddRange(splitted); - } - else if (Regex.IsMatch(val, @"^\d+(\.\d+)?$")) // only one number - { - parts.Add(val + 's'); - } - - if (parts.Count == 0) + else return false; double totalLength = 0; From aaad2e474ce271166de0aa9655352cc70cd4db07 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Sat, 23 Jul 2022 21:43:27 +0800 Subject: [PATCH 026/583] Refactor the multiple regex checks in criteria parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 35 +++++++++----------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 19fb8665d2..75574c061d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -129,15 +129,14 @@ namespace osu.Game.Screens.Select value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); return Enum.TryParse(value, true, out result); } - private static bool tryMatchRegex(string value, string regex, ref GroupCollection result) + private static GroupCollection tryMatchRegex(string value, string regex) { Match matchs = Regex.Match(value, regex); if (matchs.Success) { - result = matchs.Groups; - return true; + return matchs.Groups; } - return false; + return null; } /// @@ -324,24 +323,22 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { List parts = new List(); - GroupCollection groups = null; - if ( - tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$", ref groups) || - tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$", ref groups) || - tryMatchRegex(val, @"^(?\d+(\.\d+)?)$", ref groups) - ) - { - if (groups["seconds"].Success) - parts.Add(groups["seconds"].Value + "s"); - if (groups["minutes"].Success) - parts.Add(groups["minutes"].Value + "m"); - if (groups["hours"].Success) - parts.Add(groups["hours"].Value + "h"); - } - else + GroupCollection match = null; + match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); + + if (match == null) return false; + if (match["seconds"].Success) + parts.Add(match["seconds"].Value + "s"); + if (match["minutes"].Success) + parts.Add(match["minutes"].Value + "m"); + if (match["hours"].Success) + parts.Add(match["hours"].Value + "h"); + double totalLength = 0; int minScale = 3600000; From ea97122425706078cd91944eb099dff9f4d8311d Mon Sep 17 00:00:00 2001 From: solstice23 Date: Sat, 23 Jul 2022 21:47:51 +0800 Subject: [PATCH 027/583] Separate positive and negative test cases --- .../Filtering/FilterQueryParserTest.cs | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 181b740729..ac16c59e5b 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -118,56 +118,63 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsNull(filterCriteria.BPM.Max); } - private static readonly object[] length_query_examples = + private static readonly object[] correct_length_query_examples = { - new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1), true }, - new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1), true }, - new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1), true }, - new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1), true }, - new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, - new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, - new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, - new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1), true }, - new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, - new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, - new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1), true }, - new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, - new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, - new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1), true }, - new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1), true }, - new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1), true }, - new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1), true }, - new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1), true }, - new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1), true }, - new object[] { "7.5m27s", new TimeSpan(), new TimeSpan(), false }, - new object[] { "7m27", new TimeSpan(), new TimeSpan(), false }, - new object[] { "7m7m7m", new TimeSpan(), new TimeSpan(), false }, - new object[] { "7m70s", new TimeSpan(), new TimeSpan(), false }, - new object[] { "5s6m", new TimeSpan(), new TimeSpan(), false }, - new object[] { "0:", new TimeSpan(), new TimeSpan(), false }, - new object[] { ":0", new TimeSpan(), new TimeSpan(), false }, - new object[] { "0:3:", new TimeSpan(), new TimeSpan(), false }, - new object[] { "3:15.5", new TimeSpan(), new TimeSpan(), false }, + new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, + new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, + new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, + new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, + new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) }, + new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) }, + new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, + new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1) }, + new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1) }, + new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1) }, + new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1) }, }; [Test] - [TestCaseSource(nameof(length_query_examples))] - public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale, bool isValid) + [TestCaseSource(nameof(correct_length_query_examples))] + public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) { string query = $"length={lengthQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - if (isValid) - { - Assert.AreEqual("time", filterCriteria.SearchText.Trim()); - Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); - } - else - { - Assert.AreEqual(false, filterCriteria.Length.HasFilter); - } + Assert.AreEqual("time", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); + Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + } + + private static readonly object[] incorrect_length_query_examples = + { + new object[] { "7.5m27s" }, + new object[] { "7m27" }, + new object[] { "7m7m7m" }, + new object[] { "7m70s" }, + new object[] { "5s6m" }, + new object[] { "0:" }, + new object[] { ":0" }, + new object[] { "0:3:" }, + new object[] { "3:15.5" }, + }; + + [Test] + [TestCaseSource(nameof(incorrect_length_query_examples))] + public void TestInvalidLengthQueries(string lengthQuery) + { + string query = $"length={lengthQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.Length.HasFilter); } [Test] From 3e828c3416bd6d4f4701a5916be185eff71ab0f5 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 27 Aug 2022 20:11:38 +0300 Subject: [PATCH 028/583] Move base layout from DifficultyMultiplierDisplay to another class --- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 144 ++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 osu.Game/Overlays/Mods/ModsEffectDisplay.cs diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs new file mode 100644 index 0000000000..a7c5ec9ef9 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -0,0 +1,144 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + /// + /// + /// Base class for displays of mods effects. + /// + public abstract class ModsEffectDisplay : Container + { + public const float HEIGHT = 42; + private const float value_area_width = 56; + private const float transition_duration = 200; + + private readonly Box contentBackground; + private readonly Box labelBackground; + private readonly Container content; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + /// + /// Text to display in the left area of the display. + /// + protected abstract LocalisableString Label { get; } + + protected override Container Content => content; + + protected ModsEffectDisplay() + { + Height = HEIGHT; + AutoSizeAxes = Axes.X; + + InternalChild = new InputBlockingContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + Children = new Drawable[] + { + contentBackground = new Box + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = value_area_width + ModSelectPanel.CORNER_RADIUS + }, + new GridContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, value_area_width) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + Children = new Drawable[] + { + labelBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 18 }, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Text = Label, + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } + } + }, + content = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + labelBackground.Colour = colourProvider.Background4; + } + + /// + /// Fades colours of text and its background according to displayed value. + /// + /// Difference between actual value and default value. + /// Negative leads to green color, positive to red. Can be obtained via CompareTo(defaultValue). + protected void SetColours(int difference) + { + if (difference == 0) + { + contentBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); + content.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + } + else if (difference < 0) + { + contentBackground.FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); + content.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + } + else + { + contentBackground.FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); + content.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + } + } + } +} From 90334a2b91888698e449797f8c29d6f71c526885 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 27 Aug 2022 20:12:45 +0300 Subject: [PATCH 029/583] Add test scene --- .../TestSceneModsEffectDisplay.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs new file mode 100644 index 0000000000..36267b0e2a --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModsEffectDisplay : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestEffectDisplay() + { + TestDisplay dsp; + Add(dsp = new TestDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + AddSliderStep("value", 40, 60, 50, i => dsp.Value = i); + } + + private class TestDisplay : ModsEffectDisplay + { + private readonly OsuSpriteText text; + + protected override LocalisableString Label => "Test display"; + + public int Value + { + set + { + text.Text = value.ToString(); + SetColours(value.CompareTo(50)); + } + } + + public TestDisplay() + { + Add(text = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold), + Text = "50" + }); + } + + [BackgroundDependencyLoader] + private void load() => SetColours(0); + } + } +} From 039f00956225ef2459da64469206a6a168c18687 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 27 Aug 2022 20:26:05 +0300 Subject: [PATCH 030/583] Inherit difficulty multiplier display from `ModsEffectDiplay` --- .../Mods/DifficultyMultiplierDisplay.cs | 140 +++--------------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 +- 2 files changed, 21 insertions(+), 123 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 835883fb93..e5daa2de63 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -3,27 +3,24 @@ #nullable disable -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Mods; using osuTK; using osu.Game.Localisation; namespace osu.Game.Overlays.Mods { - public class DifficultyMultiplierDisplay : CompositeDrawable, IHasCurrentValue + public sealed class DifficultyMultiplierDisplay : ModsEffectDisplay, IHasCurrentValue { - public const float HEIGHT = 42; + protected override LocalisableString Label => DifficultyMultiplierDisplayStrings.DifficultyMultiplier; public Bindable Current { @@ -33,143 +30,44 @@ namespace osu.Game.Overlays.Mods private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1); - private readonly Box underlayBackground; - private readonly Box contentBackground; - private readonly FillFlowContainer multiplierFlow; private readonly MultiplierCounter multiplierCounter; - [Resolved] - private OsuColour colours { get; set; } - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private const float multiplier_value_area_width = 56; - private const float transition_duration = 200; - public DifficultyMultiplierDisplay() { - Height = HEIGHT; - AutoSizeAxes = Axes.X; - - InternalChild = new InputBlockingContainer + Add(new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2, 0), Children = new Drawable[] { - underlayBackground = new Box + multiplierCounter = new MultiplierCounter { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModSelectPanel.CORNER_RADIUS + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = Current } }, - new GridContainer + new SpriteIcon { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, multiplier_value_area_width) - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - Children = new Drawable[] - { - contentBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = 18 }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Text = DifficultyMultiplierDisplayStrings.DifficultyMultiplier, - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) - } - } - }, - multiplierFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2, 0), - Children = new Drawable[] - { - multiplierCounter = new MultiplierCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = { BindTarget = Current } - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.Times, - Size = new Vector2(7), - Margin = new MarginPadding { Top = 1 } - } - } - } - } - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Times, + Size = new Vector2(7), + Margin = new MarginPadding { Top = 1 } } } - }; - } - - [BackgroundDependencyLoader] - private void load() - { - contentBackground.Colour = colourProvider.Background4; + }); } protected override void LoadComplete() { base.LoadComplete(); - current.BindValueChanged(_ => updateState(), true); + current.BindValueChanged(e => SetColours(e.NewValue.CompareTo(current.Default)), true); // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. - multiplierCounter.SetCountWithoutRolling(Current.Value); - } - - private void updateState() - { - if (Current.IsDefault) - { - underlayBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); - multiplierFlow.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); - } - else - { - var backgroundColour = Current.Value < 1 - ? colours.ForModType(ModType.DifficultyReduction) - : colours.ForModType(ModType.DifficultyIncrease); - - underlayBackground.FadeColour(backgroundColour, transition_duration, Easing.OutQuint); - multiplierFlow.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); - } + multiplierCounter.SetCountWithoutRolling(current.Value); } private class MultiplierCounter : RollingCounter diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b993aca0ca..ccc075b190 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING, + Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, @@ -191,7 +191,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.X, - Height = DifficultyMultiplierDisplay.HEIGHT, + Height = ModsEffectDisplay.HEIGHT, Margin = new MarginPadding { Horizontal = 100 }, Child = multiplierDisplay = new DifficultyMultiplierDisplay { From 2a12194ff9243e44dc8b29ff6e25dfd456ff4767 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sun, 28 Aug 2022 02:59:38 +0300 Subject: [PATCH 031/583] Test colours --- .../TestSceneModsEffectDisplay.cs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs index 36267b0e2a..dd663ad59d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs @@ -1,38 +1,65 @@ // 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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { + [TestFixture] public class TestSceneModsEffectDisplay : OsuTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] + private OsuColour colours { get; set; } = null!; + [Test] - public void TestEffectDisplay() + public void TestModsEffectDisplay() { - TestDisplay dsp; - Add(dsp = new TestDisplay + TestDisplay testDisplay = null!; + Box background = null!; + + AddStep("add display", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre + Add(testDisplay = new TestDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + var boxes = testDisplay.ChildrenOfType(); + background = boxes.First(); }); - AddSliderStep("value", 40, 60, 50, i => dsp.Value = i); + + AddStep("set value to default", () => testDisplay.Value = 50); + AddUntilStep("colours are correct", () => testDisplay.Container.Colour == Color4.White && background.Colour == colourProvider.Background3); + + AddStep("set value to less", () => testDisplay.Value = 40); + AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyReduction)); + + AddStep("set value to bigger", () => testDisplay.Value = 60); + AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyIncrease)); } private class TestDisplay : ModsEffectDisplay { private readonly OsuSpriteText text; + public Container Container => Content; + protected override LocalisableString Label => "Test display"; public int Value From 7faeed88b053e6b06f3de90fdb74b89ca1793215 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Mon, 29 Aug 2022 22:08:43 +0300 Subject: [PATCH 032/583] Add ability to override width of value area --- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs index a7c5ec9ef9..4b97614ade 100644 --- a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -20,7 +20,6 @@ namespace osu.Game.Overlays.Mods public abstract class ModsEffectDisplay : Container { public const float HEIGHT = 42; - private const float value_area_width = 56; private const float transition_duration = 200; private readonly Box contentBackground; @@ -38,6 +37,8 @@ namespace osu.Game.Overlays.Mods /// protected abstract LocalisableString Label { get; } + protected virtual float ValueAreaWidth => 56; + protected override Container Content => content; protected ModsEffectDisplay() @@ -59,7 +60,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = value_area_width + ModSelectPanel.CORNER_RADIUS + Width = ValueAreaWidth + ModSelectPanel.CORNER_RADIUS }, new GridContainer { @@ -68,7 +69,7 @@ namespace osu.Game.Overlays.Mods ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, value_area_width) + new Dimension(GridSizeMode.Absolute, ValueAreaWidth) }, Content = new[] { From 5343c2645253407a218ee7916dfe11cd347ed4e5 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Mon, 29 Aug 2022 22:48:27 +0300 Subject: [PATCH 033/583] Control colour via `Current` bindable --- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 92 +++++++++++++++++---- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs index 4b97614ade..2ff6bf043b 100644 --- a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -1,10 +1,13 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -13,11 +16,10 @@ using osuTK; namespace osu.Game.Overlays.Mods { - /// /// /// Base class for displays of mods effects. /// - public abstract class ModsEffectDisplay : Container + public abstract class ModsEffectDisplay : Container, IHasCurrentValue { public const float HEIGHT = 42; private const float transition_duration = 200; @@ -26,6 +28,14 @@ namespace osu.Game.Overlays.Mods private readonly Box labelBackground; private readonly Container content; + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + [Resolved] private OsuColour colours { get; set; } = null!; @@ -37,6 +47,13 @@ namespace osu.Game.Overlays.Mods /// protected abstract LocalisableString Label { get; } + /// + /// Calculates, does current value increase difficulty or decrease it. + /// + /// Value to calculate for. Will arrive from bindable once it changes. + /// Effect of the value. + protected abstract ModEffect CalculateEffect(TValue value); + protected virtual float ValueAreaWidth => 56; protected override Container Content => content; @@ -118,28 +135,67 @@ namespace osu.Game.Overlays.Mods labelBackground.Colour = colourProvider.Background4; } + protected override void LoadComplete() + { + Current.BindValueChanged(e => + { + var effect = CalculateEffect(e.NewValue); + setColours(effect); + }, true); + } + /// /// Fades colours of text and its background according to displayed value. /// - /// Difference between actual value and default value. - /// Negative leads to green color, positive to red. Can be obtained via CompareTo(defaultValue). - protected void SetColours(int difference) + /// Effect of the value. + private void setColours(ModEffect effect) { - if (difference == 0) + switch (effect) { - contentBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); - content.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); - } - else if (difference < 0) - { - contentBackground.FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); - content.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); - } - else - { - contentBackground.FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); - content.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + case ModEffect.NotChanged: + contentBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); + content.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + break; + + case ModEffect.DifficultyReduction: + contentBackground.FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); + content.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + break; + + case ModEffect.DifficultyIncrease: + contentBackground.FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); + content.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(effect)); } } + + #region Quick implementations for CalculateEffect + + /// + /// Converts signed integer into . Negative values are counted as difficulty reduction, positive as increase. + /// + /// Value to convert. Can be obtained from CompareTo. + /// Effect. + protected ModEffect CalculateForSign(int comparison) + { + if (comparison == 0) + return ModEffect.NotChanged; + if (comparison < 0) + return ModEffect.DifficultyReduction; + + return ModEffect.DifficultyIncrease; + } + + #endregion + + protected enum ModEffect + { + NotChanged, + DifficultyReduction, + DifficultyIncrease + } } } From 545e0bbcef580ab282235ba69585996a16bd6020 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Mon, 29 Aug 2022 22:48:45 +0300 Subject: [PATCH 034/583] Adjust inheritors and test --- .../TestSceneModsEffectDisplay.cs | 25 ++++++++----------- .../Mods/DifficultyMultiplierDisplay.cs | 19 ++++---------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 +-- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs index dd663ad59d..ec50a07998 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs @@ -44,32 +44,24 @@ namespace osu.Game.Tests.Visual.UserInterface background = boxes.First(); }); - AddStep("set value to default", () => testDisplay.Value = 50); + AddStep("set value to default", () => testDisplay.Current.Value = 50); AddUntilStep("colours are correct", () => testDisplay.Container.Colour == Color4.White && background.Colour == colourProvider.Background3); - AddStep("set value to less", () => testDisplay.Value = 40); + AddStep("set value to less", () => testDisplay.Current.Value = 40); AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyReduction)); - AddStep("set value to bigger", () => testDisplay.Value = 60); + AddStep("set value to bigger", () => testDisplay.Current.Value = 60); AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyIncrease)); } - private class TestDisplay : ModsEffectDisplay + private class TestDisplay : ModsEffectDisplay { private readonly OsuSpriteText text; public Container Container => Content; protected override LocalisableString Label => "Test display"; - - public int Value - { - set - { - text.Text = value.ToString(); - SetColours(value.CompareTo(50)); - } - } + protected override ModEffect CalculateEffect(int value) => CalculateForSign(value.CompareTo(50)); public TestDisplay() { @@ -80,8 +72,11 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - [BackgroundDependencyLoader] - private void load() => SetColours(0); + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(e => text.Text = e.NewValue.ToString(), true); + } } } } diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index e5daa2de63..843310249e 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -3,12 +3,10 @@ #nullable disable -using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,22 +16,17 @@ using osu.Game.Localisation; namespace osu.Game.Overlays.Mods { - public sealed class DifficultyMultiplierDisplay : ModsEffectDisplay, IHasCurrentValue + public sealed class DifficultyMultiplierDisplay : ModsEffectDisplay { protected override LocalisableString Label => DifficultyMultiplierDisplayStrings.DifficultyMultiplier; - - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } - - private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1); + protected override ModEffect CalculateEffect(double value) => CalculateForSign(value.CompareTo(1d)); private readonly MultiplierCounter multiplierCounter; public DifficultyMultiplierDisplay() { + Current.Default = 1d; + Current.Value = 1d; Add(new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -63,11 +56,9 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - current.BindValueChanged(e => SetColours(e.NewValue.CompareTo(current.Default)), true); - // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. - multiplierCounter.SetCountWithoutRolling(current.Value); + multiplierCounter.SetCountWithoutRolling(Current.Value); } private class MultiplierCounter : RollingCounter diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ccc075b190..80f18eafeb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING, + Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, @@ -191,7 +191,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.X, - Height = ModsEffectDisplay.HEIGHT, + Height = ModsEffectDisplay.HEIGHT, Margin = new MarginPadding { Horizontal = 100 }, Child = multiplierDisplay = new DifficultyMultiplierDisplay { From 534c40e18eb2911f449f1aa3016b4f3809057330 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Fri, 2 Sep 2022 23:07:30 +0200 Subject: [PATCH 035/583] Initial version --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 335d956e39..4332532df7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -5,9 +5,11 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -17,18 +19,39 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class ColourHitErrorMeter : HitErrorMeter { - internal const int MAX_DISPLAYED_JUDGEMENTS = 20; - private const int animation_duration = 200; private const int drawable_judgement_size = 8; private const int spacing = 2; - private readonly JudgementFlow judgementsFlow; + [SettingSource("Colour hit number", "number of coloured hits")] + public BindableNumber HitCircleAmount { get; } = new BindableNumber(20) + { + MinValue = 1, + MaxValue = 30, + Precision = 1 + }; + + [SettingSource("Opacity", "Visibility of object")] + public BindableNumber HitOpacity { get; } = new BindableNumber(1) + { + MinValue = 0, + MaxValue = 1, + Precision = .01f + }; + + [SettingSource("Spacing", "space between hit colour circles")] + public BindableNumber HitSpacing { get; } = new BindableNumber(1) + { + MinValue = 0, + MaxValue = 1, + Precision = .01f + }; + public ColourHitErrorMeter() { AutoSizeAxes = Axes.Both; - InternalChild = judgementsFlow = new JudgementFlow(); + InternalChild = judgementsFlow = new JudgementFlow(HitCircleAmount.Value, HitOpacity.Value); } protected override void OnNewJudgement(JudgementResult judgement) @@ -36,7 +59,15 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) return; - judgementsFlow.Push(GetColourForHitResult(judgement.Type)); + judgementsFlow.Push(GetColourForHitResult(judgement.Type), HitCircleAmount.Value); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + judgementsFlow.Height = HitCircleAmount.Value * (drawable_judgement_size + spacing) - spacing; + judgementsFlow.Alpha = HitOpacity.Value; + judgementsFlow.Spacing = new Vector2(0, HitSpacing.Value); } public override void Clear() => judgementsFlow.Clear(); @@ -45,21 +76,20 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); - public JudgementFlow() + public JudgementFlow(int hitCircleAmount, float opacity) { AutoSizeAxes = Axes.X; - Height = MAX_DISPLAYED_JUDGEMENTS * (drawable_judgement_size + spacing) - spacing; Spacing = new Vector2(0, spacing); Direction = FillDirection.Vertical; LayoutDuration = animation_duration; LayoutEasing = Easing.OutQuint; } - public void Push(Color4 colour) + public void Push(Color4 colour, int amount) { Add(new HitErrorCircle(colour, drawable_judgement_size)); - if (Children.Count > MAX_DISPLAYED_JUDGEMENTS) + if (Children.Count > amount) Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); } } From 51061c3a12af4711314c6a945f96a5ec220844be Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Sat, 3 Sep 2022 01:27:22 +0200 Subject: [PATCH 036/583] Bug fixing and parameter adjustments --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 4332532df7..f3d2c84177 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -21,7 +21,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { private const int animation_duration = 200; private const int drawable_judgement_size = 8; - private const int spacing = 2; private readonly JudgementFlow judgementsFlow; [SettingSource("Colour hit number", "number of coloured hits")] @@ -35,23 +34,23 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [SettingSource("Opacity", "Visibility of object")] public BindableNumber HitOpacity { get; } = new BindableNumber(1) { - MinValue = 0, + MinValue = 0.01f, MaxValue = 1, Precision = .01f }; [SettingSource("Spacing", "space between hit colour circles")] - public BindableNumber HitSpacing { get; } = new BindableNumber(1) + public BindableNumber HitSpacing { get; } = new BindableNumber(2) { MinValue = 0, - MaxValue = 1, - Precision = .01f + MaxValue = 10, + Precision = .1f }; public ColourHitErrorMeter() { AutoSizeAxes = Axes.Both; - InternalChild = judgementsFlow = new JudgementFlow(HitCircleAmount.Value, HitOpacity.Value); + InternalChild = judgementsFlow = new JudgementFlow(); } protected override void OnNewJudgement(JudgementResult judgement) @@ -65,9 +64,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void LoadComplete() { base.LoadComplete(); - judgementsFlow.Height = HitCircleAmount.Value * (drawable_judgement_size + spacing) - spacing; - judgementsFlow.Alpha = HitOpacity.Value; - judgementsFlow.Spacing = new Vector2(0, HitSpacing.Value); + HitOpacity.BindValueChanged(_ => judgementsFlow.Alpha = HitOpacity.Value, true); + HitSpacing.BindValueChanged(_ => judgementsFlow.Spacing = new Vector2(0, HitSpacing.Value), true); + HitSpacing.BindValueChanged(_ => judgementsFlow.Height = HitCircleAmount.Value * (drawable_judgement_size + HitSpacing.Value) - HitSpacing.Value, true); + HitCircleAmount.BindValueChanged(_ => judgementsFlow.Height = HitCircleAmount.Value * (drawable_judgement_size + HitSpacing.Value) - HitSpacing.Value, true); + HitCircleAmount.BindValueChanged(_ => judgementsFlow.Clear(), true); } public override void Clear() => judgementsFlow.Clear(); @@ -76,10 +77,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); - public JudgementFlow(int hitCircleAmount, float opacity) + public JudgementFlow() { - AutoSizeAxes = Axes.X; - Spacing = new Vector2(0, spacing); + Width = drawable_judgement_size; Direction = FillDirection.Vertical; LayoutDuration = animation_duration; LayoutEasing = Easing.OutQuint; @@ -97,7 +97,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters internal class HitErrorCircle : Container { public bool IsRemoved { get; private set; } - private readonly Circle circle; public HitErrorCircle(Color4 colour, int size) From 0c6d8efa28ae40f6f5301c9e455baa34dde6b7d3 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Sun, 4 Sep 2022 17:24:12 +0200 Subject: [PATCH 037/583] Large code refactor, Implementation of shapes option drop down --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 111 +++++++++++++----- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index f3d2c84177..9bc6cdbb9b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -19,12 +18,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class ColourHitErrorMeter : HitErrorMeter { + private const int default_shape_alpha = 0; private const int animation_duration = 200; private const int drawable_judgement_size = 8; - private readonly JudgementFlow judgementsFlow; - [SettingSource("Colour hit number", "number of coloured hits")] - public BindableNumber HitCircleAmount { get; } = new BindableNumber(20) + [SettingSource("Hit error amount", "Number of hit error shapes")] + public BindableNumber HitShapeCount { get; } = new BindableNumber(20) { MinValue = 1, MaxValue = 30, @@ -32,21 +31,26 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters }; [SettingSource("Opacity", "Visibility of object")] - public BindableNumber HitOpacity { get; } = new BindableNumber(1) + public BindableNumber HitShapeOpacity { get; } = new BindableNumber(1) { MinValue = 0.01f, MaxValue = 1, - Precision = .01f + Precision = .01f, }; - [SettingSource("Spacing", "space between hit colour circles")] - public BindableNumber HitSpacing { get; } = new BindableNumber(2) + [SettingSource("Spacing", "Space between hit error shapes")] + public BindableNumber HitShapeSpacing { get; } = new BindableNumber(2) { MinValue = 0, MaxValue = 10, Precision = .1f }; + [SettingSource("Shape", "What shape to use for hit errors")] + public Bindable HitShape { get; } = new Bindable(); + + private readonly JudgementFlow judgementsFlow; + public ColourHitErrorMeter() { AutoSizeAxes = Axes.Both; @@ -58,24 +62,36 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) return; - judgementsFlow.Push(GetColourForHitResult(judgement.Type), HitCircleAmount.Value); + judgementsFlow.Push(GetColourForHitResult(judgement.Type), HitShapeCount.Value); } protected override void LoadComplete() { base.LoadComplete(); - HitOpacity.BindValueChanged(_ => judgementsFlow.Alpha = HitOpacity.Value, true); - HitSpacing.BindValueChanged(_ => judgementsFlow.Spacing = new Vector2(0, HitSpacing.Value), true); - HitSpacing.BindValueChanged(_ => judgementsFlow.Height = HitCircleAmount.Value * (drawable_judgement_size + HitSpacing.Value) - HitSpacing.Value, true); - HitCircleAmount.BindValueChanged(_ => judgementsFlow.Height = HitCircleAmount.Value * (drawable_judgement_size + HitSpacing.Value) - HitSpacing.Value, true); - HitCircleAmount.BindValueChanged(_ => judgementsFlow.Clear(), true); + HitShapeOpacity.BindValueChanged(_ => judgementsFlow.Alpha = HitShapeOpacity.Value, true); + HitShapeSpacing.BindValueChanged(_ => + { + judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; + judgementsFlow.Spacing = new Vector2(0, HitShapeSpacing.Value); + }, true); + HitShapeCount.BindValueChanged(_ => + { + judgementsFlow.Clear(); + judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; + }, true); + HitShape.BindValueChanged(_ => + { + judgementsFlow.ValueParser = getShapeStyle(HitShape.Value); + judgementsFlow.Clear(); + }, true); } public override void Clear() => judgementsFlow.Clear(); - private class JudgementFlow : FillFlowContainer + private class JudgementFlow : FillFlowContainer { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + internal string ValueParser = null!; public JudgementFlow() { @@ -85,38 +101,52 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters LayoutEasing = Easing.OutQuint; } - public void Push(Color4 colour, int amount) + public void Push(Color4 colour, int maxErrorShapeCount) { - Add(new HitErrorCircle(colour, drawable_judgement_size)); + Add(new HitErrorShape(colour, drawable_judgement_size, ValueParser)); - if (Children.Count > amount) + if (Children.Count > maxErrorShapeCount) Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); } } - internal class HitErrorCircle : Container + private class HitErrorShape : Container { public bool IsRemoved { get; private set; } - private readonly Circle circle; - public HitErrorCircle(Color4 colour, int size) + public HitErrorShape(Color4 colour, int size, string shape) { Size = new Vector2(size); - Child = circle = new Circle + + switch (shape) { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = colour - }; + case "circle": + Child = new Circle + { + RelativeSizeAxes = Axes.Both, + Alpha = default_shape_alpha, + Colour = colour + }; + break; + + case "square": + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = default_shape_alpha, + Colour = colour + }; + break; + } } protected override void LoadComplete() { base.LoadComplete(); - circle.FadeInFromZero(animation_duration, Easing.OutQuint); - circle.MoveToY(-DrawSize.Y); - circle.MoveToY(0, animation_duration, Easing.OutQuint); + Child.FadeInFromZero(animation_duration, Easing.OutQuint); + Child.MoveToY(-DrawSize.Y); + Child.MoveToY(0, animation_duration, Easing.OutQuint); } public void Remove() @@ -126,5 +156,26 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters this.FadeOut(animation_duration, Easing.OutQuint).Expire(); } } + + private string getShapeStyle(ShapeStyle shape) + { + switch (shape) + { + case ShapeStyle.Circle: + return "circle"; + + case ShapeStyle.Square: + return "square"; + + default: + throw new ArgumentOutOfRangeException(nameof(shape), shape, @"Unsupported animation style"); + } + } + + public enum ShapeStyle + { + Circle, + Square + } } } From aace334fb365485ed41e809d19d34961b57d6920 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Mon, 5 Sep 2022 04:49:48 +0200 Subject: [PATCH 038/583] Fix some test issues --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 22 +++++++++---------- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 707f807e64..74387e1d8e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -107,13 +107,13 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("no bars added", () => !this.ChildrenOfType().Any()); AddAssert("circle added", () => this.ChildrenOfType().All( - meter => meter.ChildrenOfType().Count() == 1)); + meter => meter.ChildrenOfType().Count() == 1)); AddStep("miss", () => newJudgement(50, HitResult.Miss)); AddAssert("no bars added", () => !this.ChildrenOfType().Any()); AddAssert("circle added", () => this.ChildrenOfType().All( - meter => meter.ChildrenOfType().Count() == 2)); + meter => meter.ChildrenOfType().Count() == 2)); } [Test] @@ -123,11 +123,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("small bonus", () => newJudgement(result: HitResult.SmallBonus)); AddAssert("no bars added", () => !this.ChildrenOfType().Any()); - AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); AddStep("large bonus", () => newJudgement(result: HitResult.LargeBonus)); AddAssert("no bars added", () => !this.ChildrenOfType().Any()); - AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); } [Test] @@ -137,11 +137,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("ignore hit", () => newJudgement(result: HitResult.IgnoreHit)); AddAssert("no bars added", () => !this.ChildrenOfType().Any()); - AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); AddStep("ignore miss", () => newJudgement(result: HitResult.IgnoreMiss)); AddAssert("no bars added", () => !this.ChildrenOfType().Any()); - AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); } [Test] @@ -155,16 +155,16 @@ namespace osu.Game.Tests.Visual.Gameplay hitErrorMeter.Hide(); }); - AddRepeatStep("hit", () => newJudgement(), ColourHitErrorMeter.MAX_DISPLAYED_JUDGEMENTS * 2); + AddRepeatStep("hit", () => newJudgement(), ColourHitErrorMeter.HitErrorShape. *2); AddAssert("bars added", () => this.ChildrenOfType().Any()); - AddAssert("circle added", () => this.ChildrenOfType().Any()); + AddAssert("circle added", () => this.ChildrenOfType().Any()); AddUntilStep("wait for bars to disappear", () => !this.ChildrenOfType().Any()); AddUntilStep("ensure max circles not exceeded", () => { return this.ChildrenOfType() - .All(m => m.ChildrenOfType().Count() <= ColourHitErrorMeter.MAX_DISPLAYED_JUDGEMENTS); + .All(m => m.ChildrenOfType().Count() <= ColourHitErrorMeter.HitShapeCount.Default); }); AddStep("show displays", () => @@ -183,12 +183,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("bar added", () => this.ChildrenOfType().All( meter => meter.ChildrenOfType().Count() == 1)); AddAssert("circle added", () => this.ChildrenOfType().All( - meter => meter.ChildrenOfType().Count() == 1)); + meter => meter.ChildrenOfType().Count() == 1)); AddStep("clear", () => this.ChildrenOfType().ForEach(meter => meter.Clear())); AddAssert("bar cleared", () => !this.ChildrenOfType().Any()); - AddAssert("colour cleared", () => !this.ChildrenOfType().Any()); + AddAssert("colour cleared", () => !this.ChildrenOfType().Any()); } private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 9bc6cdbb9b..8901612b9b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } } - private class HitErrorShape : Container + public class HitErrorShape : Container { public bool IsRemoved { get; private set; } From cfa1ebd0cb632cfba79e1c535952f282880ccc84 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Tue, 6 Sep 2022 03:00:13 +0200 Subject: [PATCH 039/583] Test fix --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 74387e1d8e..e3603ee90c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay hitErrorMeter.Hide(); }); - AddRepeatStep("hit", () => newJudgement(), ColourHitErrorMeter.HitErrorShape. *2); + AddRepeatStep("hit", () => newJudgement(), 10); AddAssert("bars added", () => this.ChildrenOfType().Any()); AddAssert("circle added", () => this.ChildrenOfType().Any()); @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("ensure max circles not exceeded", () => { return this.ChildrenOfType() - .All(m => m.ChildrenOfType().Count() <= ColourHitErrorMeter.HitShapeCount.Default); + .All(m => m.ChildrenOfType().Count() <= 10); }); AddStep("show displays", () => From b5779508d06bc3455d61a0ef617a35b8f5d8c826 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:10:32 +0100 Subject: [PATCH 040/583] Retrieve great hit window from the hit object --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++-- .../Difficulty/Evaluators/SpeedEvaluator.cs | 7 +++---- .../Difficulty/OsuDifficultyCalculator.cs | 13 ++++++------- .../Preprocessing/OsuDifficultyHitObject.cs | 15 +++++++++++++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 8 +++----- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 3ffd45b588..ca57c87af9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . /// - public static double EvaluateDifficultyOf(DifficultyHitObject current, double greatWindow) + public static double EvaluateDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) return 0; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double lastDelta = lastObj.StrainTime; double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. - double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6)); + double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.6) / (currObj.HitWindowGreat * 0.6)); windowPenalty = Math.Min(1, windowPenalty); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 5187717639..c98f875eb5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators /// and how easily they can be cheesed. /// /// - public static double EvaluateDifficultyOf(DifficultyHitObject current, double greatWindow) + public static double EvaluateDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) return 0; @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var osuNextObj = (OsuDifficultyHitObject)current.Next(0); double strainTime = osuCurrObj.StrainTime; - double greatWindowFull = greatWindow * 2; double doubletapness = 1; // Nerf doubletappable doubles. @@ -45,13 +44,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime); double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference); - double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2); + double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / osuCurrObj.HitWindowGreat), 2); doubletapness = Math.Pow(speedRatio, 1 - windowRatio); } // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. - strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1); + strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1); // derive speedBonus for calculation double speedBonus = 1.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 6ef17d47c0..33787da8f6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty public class OsuDifficultyCalculator : DifficultyCalculator { private const double difficulty_multiplier = 0.0675; - private double hitWindowGreat; public override int Version => 20220902; @@ -76,6 +75,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty int sliderCount = beatmap.HitObjects.Count(h => h is Slider); int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); + HitWindows hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); + + double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; + return new OsuDifficultyAttributes { StarRating = starRating, @@ -112,16 +116,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { - HitWindows hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); - - hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; - return new Skill[] { new Aim(mods, true), new Aim(mods, false), - new Speed(mods, hitWindowGreat), + new Speed(mods), new Flashlight(mods) }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index c7c5650184..6aea00fd35 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing @@ -78,6 +79,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double? Angle { get; private set; } + /// + /// Retrieves the full hit window for a Great . + /// + public double HitWindowGreat { get; private set; } + private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; @@ -90,6 +96,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); + if (BaseObject is Slider sliderObject) + { + HitWindowGreat = 2 * sliderObject.HeadCircle.HitWindows.WindowFor(HitResult.Great) / clockRate; + } + else + { + HitWindowGreat = 2 * BaseObject.HitWindows.WindowFor(HitResult.Great) / clockRate; + } + setDistances(clockRate); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index c39d61020c..efe0e136bf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -26,14 +26,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override int ReducedSectionCount => 5; protected override double DifficultyMultiplier => 1.04; - private readonly double greatWindow; private readonly List objectStrains = new List(); - public Speed(Mod[] mods, double hitWindowGreat) + public Speed(Mod[] mods) : base(mods) { - greatWindow = hitWindowGreat; } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -43,9 +41,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); - currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier; + currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow); + currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; From 1997519364810b178ddda6b7a21b6110737c4c39 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 7 Sep 2022 13:24:54 +0100 Subject: [PATCH 041/583] Don't use full hit window in rhythm --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index ca57c87af9..3bec2346ce 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double lastDelta = lastObj.StrainTime; double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. - double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.6) / (currObj.HitWindowGreat * 0.6)); + double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); windowPenalty = Math.Min(1, windowPenalty); From 07c16224d27fa38ba060f16bc2a3d227d4c2e96c Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Thu, 8 Sep 2022 11:58:14 +0200 Subject: [PATCH 042/583] Fix Main file issues --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 8901612b9b..0713469e10 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -18,7 +18,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class ColourHitErrorMeter : HitErrorMeter { - private const int default_shape_alpha = 0; private const int animation_duration = 200; private const int drawable_judgement_size = 8; @@ -35,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { MinValue = 0.01f, MaxValue = 1, - Precision = .01f, + Precision = 0.01f, }; [SettingSource("Spacing", "Space between hit error shapes")] @@ -43,10 +42,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { MinValue = 0, MaxValue = 10, - Precision = .1f + Precision = 0.1f }; - [SettingSource("Shape", "What shape to use for hit errors")] + [SettingSource("Shape", "The shape of each displayed error")] public Bindable HitShape { get; } = new Bindable(); private readonly JudgementFlow judgementsFlow; @@ -81,7 +80,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters }, true); HitShape.BindValueChanged(_ => { - judgementsFlow.ValueParser = getShapeStyle(HitShape.Value); + judgementsFlow.Shape = getShapeStyle(HitShape.Value); judgementsFlow.Clear(); }, true); } @@ -91,7 +90,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private class JudgementFlow : FillFlowContainer { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); - internal string ValueParser = null!; + internal string Shape = null!; public JudgementFlow() { @@ -103,7 +102,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public void Push(Color4 colour, int maxErrorShapeCount) { - Add(new HitErrorShape(colour, drawable_judgement_size, ValueParser)); + Add(new HitErrorShape(colour, drawable_judgement_size, Shape)); if (Children.Count > maxErrorShapeCount) Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); @@ -124,7 +123,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Child = new Circle { RelativeSizeAxes = Axes.Both, - Alpha = default_shape_alpha, + Alpha = 0, Colour = colour }; break; @@ -133,7 +132,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Child = new Box { RelativeSizeAxes = Axes.Both, - Alpha = default_shape_alpha, + Alpha = 0, Colour = colour }; break; From 38ccd06d5e650ae24507187e207871d476cdcf68 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Thu, 8 Sep 2022 20:42:55 +0200 Subject: [PATCH 043/583] Test fix --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index e3603ee90c..e2576e2577 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -147,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestProcessingWhileHidden() { + const int max_displayed_judgements = 20; AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); AddStep("hide displays", () => @@ -155,7 +156,7 @@ namespace osu.Game.Tests.Visual.Gameplay hitErrorMeter.Hide(); }); - AddRepeatStep("hit", () => newJudgement(), 10); + AddRepeatStep("hit", () => newJudgement(), max_displayed_judgements * 2); AddAssert("bars added", () => this.ChildrenOfType().Any()); AddAssert("circle added", () => this.ChildrenOfType().Any()); From 5852a09003fdd41c065f93e64b8d412cf6bfcee0 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Fri, 9 Sep 2022 08:21:33 +0200 Subject: [PATCH 044/583] small fix --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index e2576e2577..7c668adba5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("ensure max circles not exceeded", () => { return this.ChildrenOfType() - .All(m => m.ChildrenOfType().Count() <= 10); + .All(m => m.ChildrenOfType().Count() <= max_displayed_judgements); }); AddStep("show displays", () => From 05797cb9e55447823b72c4a56e61a4d152472284 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:30:08 +0900 Subject: [PATCH 045/583] Fix enum to STRING????????? conversion (and use bindable flow) --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 81 +++++++++---------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 0713469e10..c218ff11e9 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -78,11 +77,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters judgementsFlow.Clear(); judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; }, true); - HitShape.BindValueChanged(_ => - { - judgementsFlow.Shape = getShapeStyle(HitShape.Value); - judgementsFlow.Clear(); - }, true); } public override void Clear() => judgementsFlow.Clear(); @@ -90,7 +84,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private class JudgementFlow : FillFlowContainer { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); - internal string Shape = null!; + + public readonly Bindable Shape = new Bindable(); public JudgementFlow() { @@ -102,7 +97,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public void Push(Color4 colour, int maxErrorShapeCount) { - Add(new HitErrorShape(colour, drawable_judgement_size, Shape)); + Add(new HitErrorShape(colour, drawable_judgement_size) + { + Shape = { BindTarget = Shape }, + }); if (Children.Count > maxErrorShapeCount) Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); @@ -113,36 +111,44 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public bool IsRemoved { get; private set; } - public HitErrorShape(Color4 colour, int size, string shape) + public readonly Bindable Shape = new Bindable(); + + private readonly Color4 colour; + + public HitErrorShape(Color4 colour, int size) { + this.colour = colour; Size = new Vector2(size); - - switch (shape) - { - case "circle": - Child = new Circle - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = colour - }; - break; - - case "square": - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = colour - }; - break; - } } protected override void LoadComplete() { base.LoadComplete(); + Shape.BindValueChanged(shape => + { + switch (shape.NewValue) + { + case ShapeStyle.Circle: + Child = new Circle + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = colour + }; + break; + + case ShapeStyle.Square: + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = colour + }; + break; + } + }, true); + Child.FadeInFromZero(animation_duration, Easing.OutQuint); Child.MoveToY(-DrawSize.Y); Child.MoveToY(0, animation_duration, Easing.OutQuint); @@ -156,21 +162,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } } - private string getShapeStyle(ShapeStyle shape) - { - switch (shape) - { - case ShapeStyle.Circle: - return "circle"; - - case ShapeStyle.Square: - return "square"; - - default: - throw new ArgumentOutOfRangeException(nameof(shape), shape, @"Unsupported animation style"); - } - } - public enum ShapeStyle { Circle, From 602ffebd54d79e76bb19a4b95572b4023f07562f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 23:29:04 +0900 Subject: [PATCH 046/583] Apply NRT and fix code style --- osu.Game/Screens/Select/FilterQueryParser.cs | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 75574c061d..a7c1aec361 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; @@ -126,16 +124,21 @@ namespace osu.Game.Screens.Select { if (Enum.TryParse(value, true, out result)) return true; - value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + string? prefixMatch = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + + if (prefixMatch == null) + return false; + return Enum.TryParse(value, true, out result); } - private static GroupCollection tryMatchRegex(string value, string regex) + + private static GroupCollection? tryMatchRegex(string value, string regex) { - Match matchs = Regex.Match(value, regex); - if (matchs.Success) - { - return matchs.Groups; - } + Match matches = Regex.Match(value, regex); + + if (matches.Success) + return matches.Groups; + return null; } @@ -324,7 +327,8 @@ namespace osu.Game.Screens.Select { List parts = new List(); - GroupCollection match = null; + GroupCollection? match = null; + match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); @@ -358,6 +362,7 @@ namespace osu.Game.Screens.Select totalLength += length * scale; minScale = Math.Min(minScale, scale); } + return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } From 6338b87c63721b941a68397a18b9c2f195be3374 Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 9 Sep 2022 17:31:52 +0300 Subject: [PATCH 047/583] attributes --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6a221c1a73..2b0e2db898 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double aimValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); From ae53e27e6cad2157dba515c71946012dcbca1c5d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Sep 2022 19:39:01 +0300 Subject: [PATCH 048/583] Adjust test coverage to read better --- .../SongSelect/TestSceneTopLocalRank.cs | 63 ++++++------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 1a72dc2a72..cced9b8b89 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -49,12 +47,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Create local rank", () => { - Add(topLocalRank = new TopLocalRank(importedBeatmap) + Child = topLocalRank = new TopLocalRank(importedBeatmap) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(10), - }); + }; }); AddAssert("No rank displayed initially", () => topLocalRank.DisplayedRank == null); @@ -63,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBasicImportDelete() { - ScoreInfo? testScoreInfo = null; + ScoreInfo testScoreInfo = null!; AddStep("Add score for current user", () => { @@ -77,10 +75,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); - AddStep("Delete score", () => - { - scoreManager.Delete(testScoreInfo.AsNonNull()); - }); + AddStep("Delete score", () => scoreManager.Delete(testScoreInfo)); AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); } @@ -88,11 +83,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestRulesetChange() { - ScoreInfo testScoreInfo; - AddStep("Add score for current user", () => { - testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo.User = API.LocalUser.Value; testScoreInfo.Rank = ScoreRank.B; @@ -112,11 +105,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHigherScoreSet() { - ScoreInfo? testScoreInfo = null; - AddStep("Add score for current user", () => { - testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo.User = API.LocalUser.Value; testScoreInfo.Rank = ScoreRank.B; @@ -131,28 +122,20 @@ namespace osu.Game.Tests.Visual.SongSelect var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo2.User = API.LocalUser.Value; - testScoreInfo2.Rank = ScoreRank.S; - testScoreInfo2.TotalScore = testScoreInfo.AsNonNull().TotalScore + 1; - testScoreInfo2.Statistics = new Dictionary - { - [HitResult.Miss] = 0, - [HitResult.Perfect] = 970, - [HitResult.SmallTickHit] = 75, - [HitResult.LargeTickHit] = 150, - [HitResult.LargeBonus] = 10, - [HitResult.SmallBonus] = 50 - }; + testScoreInfo2.Rank = ScoreRank.X; + testScoreInfo2.TotalScore = 1000000; + testScoreInfo2.Statistics = testScoreInfo2.MaximumStatistics; scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); + AddUntilStep("SS rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.X); } [Test] public void TestLegacyScore() { - ScoreInfo? testScoreInfo = null; + ScoreInfo testScoreInfo = null!; AddStep("Add legacy score for current user", () => { @@ -160,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect testScoreInfo.User = API.LocalUser.Value; testScoreInfo.Rank = ScoreRank.B; - testScoreInfo.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo, ScoringMode.Classic).GetResultSafely(); + testScoreInfo.TotalScore = scoreManager.GetTotalScore(testScoreInfo, ScoringMode.Classic); scoreManager.Import(testScoreInfo); }); @@ -172,26 +155,18 @@ namespace osu.Game.Tests.Visual.SongSelect var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo2.User = API.LocalUser.Value; - testScoreInfo2.Rank = ScoreRank.S; - testScoreInfo2.Statistics = new Dictionary - { - [HitResult.Miss] = 0, - [HitResult.Perfect] = 970, - [HitResult.SmallTickHit] = 75, - [HitResult.LargeTickHit] = 150, - [HitResult.LargeBonus] = 10, - [HitResult.SmallBonus] = 50 - }; + testScoreInfo2.Rank = ScoreRank.X; + testScoreInfo2.Statistics = testScoreInfo2.MaximumStatistics; + testScoreInfo2.TotalScore = scoreManager.GetTotalScore(testScoreInfo2); - testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo.AsNonNull()).GetResultSafely(); - - // ensure standardised total score is less than classic, otherwise this test is pointless. - Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.AsNonNull().TotalScore); + // ensure second score has a total score (standardised) less than first one (classic) + // despite having better statistics, otherwise this test is pointless. + Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.TotalScore); scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); + AddUntilStep("SS rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.X); } } } From 95062c2cc8fc804eb8e4a7d52d28e1f8b190d6c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Sep 2022 19:42:58 +0300 Subject: [PATCH 049/583] Remove center origin specification to avoid single-frame layout issues --- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index cb2ff86794..0f000555d5 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Select.Carousel InternalChild = updateable = new UpdateableRank { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(40, 20), Alpha = 0, }; From ec86700f2903bca40fd9cbc7f5a0d062e0373466 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 10:45:51 +0800 Subject: [PATCH 050/583] Add the nullable disable annotation back becuse it will cause the api broken if remove the nullable disable annotation in the mania ruleset. --- .../Configuration/ManiaRulesetConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 64e1f75e2c..b30d72f2e2 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; From 2f91b5c846b4055422574620717416c60480f60d Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 10:46:10 +0800 Subject: [PATCH 051/583] Add missing import. --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 24a4f16e9f..90733e0206 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,6 +1,7 @@ // 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.Extensions.EnumExtensions; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index b53dc67584..90d9ffe527 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -1,6 +1,7 @@ // 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.Extensions.EnumExtensions; From 1398a7e11ebffc6815bf26a33a615ae63e30f54c Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 10:46:40 +0800 Subject: [PATCH 052/583] Property should accept the nullable state. --- osu.Game/Online/API/APIMod.cs | 2 +- osu.Game/Overlays/Settings/Sections/RulesetSection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index dcbaaea012..176f10975d 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.API public Mod ToMod(Ruleset ruleset) { - Mod resultMod = ruleset.CreateModFromAcronym(Acronym); + Mod? resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) { diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs index a5f5810214..6f0b3c27a0 100644 --- a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs +++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections { try { - SettingsSubsection section = ruleset.CreateSettings(); + SettingsSubsection? section = ruleset.CreateSettings(); if (section != null) Add(section); From 3d7367a8420ca60b029d4b557b7458dff7e7d487 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 11:07:23 +0800 Subject: [PATCH 053/583] Move the CreateConvertibleReplayFrame() into the base ruleset class for avoid api breaking change in the customized ruleset. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/ILegacyRuleset.cs | 9 --------- osu.Game/Rulesets/Ruleset.cs | 8 ++++++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 7 ++----- 8 files changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index d2e05fbeb9..999b9f6e2c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Catch public int LegacyID => 2; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 72be77ddab..c6a5e8bdaa 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Rulesets.Mania public int LegacyID => 3; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 90733e0206..6af765361d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Osu public int LegacyID => 0; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 90d9ffe527..6beb83575d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko public int LegacyID => 1; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs index c2617a065c..f4b03baccd 100644 --- a/osu.Game/Rulesets/ILegacyRuleset.cs +++ b/osu.Game/Rulesets/ILegacyRuleset.cs @@ -1,8 +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.Game.Rulesets.Replays.Types; - namespace osu.Game.Rulesets { public interface ILegacyRuleset @@ -13,12 +11,5 @@ namespace osu.Game.Rulesets /// Identifies the server-side ID of a legacy ruleset. /// int LegacyID { get; } - - /// - /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame - /// for conversion use. - /// - /// An empty frame for the current ruleset, or null if unsupported. - IConvertibleReplayFrame CreateConvertibleReplayFrame(); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a10575ef73..4bc82a5f7e 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -302,6 +303,13 @@ namespace osu.Game.Rulesets /// A descriptive name of the variant. public virtual LocalisableString GetVariantName(int variant) => string.Empty; + /// + /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame + /// for conversion use. + /// + /// An empty frame for the current ruleset, or null if unsupported. + public virtual IConvertibleReplayFrame? CreateConvertibleReplayFrame() => null; + /// /// Creates the statistics for a to be displayed in the results screen. /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 2b22b4abc7..f64e730c06 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -282,7 +282,7 @@ namespace osu.Game.Scoring.Legacy private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, ReplayFrame lastFrame) { - var convertible = (currentRuleset as ILegacyRuleset)?.CreateConvertibleReplayFrame(); + var convertible = currentRuleset.CreateConvertibleReplayFrame(); if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 659d7d5d6c..68cc21fc1c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; -using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; @@ -81,14 +80,12 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - var legacyRuleset = GameplayState.Ruleset as ILegacyRuleset; - Debug.Assert(legacyRuleset != null); - bool isFirstBundle = score.Replay.Frames.Count == 0; foreach (var frame in bundle.Frames) { - IConvertibleReplayFrame convertibleFrame = legacyRuleset.CreateConvertibleReplayFrame(); + IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame(); + Debug.Assert(convertibleFrame != null); convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); var convertedFrame = (ReplayFrame)convertibleFrame; From b056cac10a1259044e65e67ab059f108b66c23bb Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 10 Sep 2022 08:21:38 +0300 Subject: [PATCH 054/583] Remove generic and add default implementation for `CalculateEffect` --- .../Overlays/Mods/DifficultyMultiplierDisplay.cs | 3 +-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 14 ++++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 843310249e..5530947d3d 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -16,10 +16,9 @@ using osu.Game.Localisation; namespace osu.Game.Overlays.Mods { - public sealed class DifficultyMultiplierDisplay : ModsEffectDisplay + public sealed class DifficultyMultiplierDisplay : ModsEffectDisplay { protected override LocalisableString Label => DifficultyMultiplierDisplayStrings.DifficultyMultiplier; - protected override ModEffect CalculateEffect(double value) => CalculateForSign(value.CompareTo(1d)); private readonly MultiplierCounter multiplierCounter; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 80f18eafeb..ccc075b190 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING, + Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, @@ -191,7 +191,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.X, - Height = ModsEffectDisplay.HEIGHT, + Height = ModsEffectDisplay.HEIGHT, Margin = new MarginPadding { Horizontal = 100 }, Child = multiplierDisplay = new DifficultyMultiplierDisplay { diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs index 2ff6bf043b..4e630439ee 100644 --- a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Mods /// /// Base class for displays of mods effects. /// - public abstract class ModsEffectDisplay : Container, IHasCurrentValue + public abstract class ModsEffectDisplay : Container, IHasCurrentValue { public const float HEIGHT = 42; private const float transition_duration = 200; @@ -28,13 +28,13 @@ namespace osu.Game.Overlays.Mods private readonly Box labelBackground; private readonly Container content; - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; } - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); [Resolved] private OsuColour colours { get; set; } = null!; @@ -52,7 +52,13 @@ namespace osu.Game.Overlays.Mods /// /// Value to calculate for. Will arrive from bindable once it changes. /// Effect of the value. - protected abstract ModEffect CalculateEffect(TValue value); + /// + /// Default implementation compares the value with ., bigger value counts as difficulty increase. + /// + protected virtual ModEffect CalculateEffect(double value) + { + return CalculateForSign(value.CompareTo(Current.Default)); + } protected virtual float ValueAreaWidth => 56; From efe3b1aa7bd047847510fe50f75f1f87d0c2f78b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Sep 2022 14:10:49 +0900 Subject: [PATCH 055/583] Allow dismissing notifications without performing action using middle / right click --- .../TestSceneNotificationOverlay.cs | 60 ++++++++++++++++++- .../Overlays/Notifications/Notification.cs | 19 +++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 699b8f7d89..c0b29dde9a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -12,11 +12,13 @@ using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneNotificationOverlay : OsuTestScene + public class TestSceneNotificationOverlay : OsuManualInputManagerTestScene { private NotificationOverlay notificationOverlay = null!; @@ -32,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface TimeToCompleteProgress = 2000; progressingNotifications.Clear(); - Content.Children = new Drawable[] + Children = new Drawable[] { notificationOverlay = new NotificationOverlay { @@ -45,6 +47,60 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestDismissWithoutActivationRightClick() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("click to activate", () => + { + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Right); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was not activated", () => !activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + } + + [Test] + public void TestActivate() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("click to activate", () => + { + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was activated", () => activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + } + [Test] public void TestPresence() { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index e5f739bb08..09abdb865d 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Notifications { @@ -170,11 +171,25 @@ namespace osu.Game.Overlays.Notifications base.OnHoverLost(e); } + protected override bool OnMouseDown(MouseDownEvent e) + { + // right click doesn't trigger OnClick so we need to handle here until that changes. + if (e.Button != MouseButton.Left) + { + Close(); + return true; + } + + return base.OnMouseDown(e); + } + protected override bool OnClick(ClickEvent e) { - if (Activated?.Invoke() ?? true) - Close(); + // Clicking with anything but left button should dismiss but not perform the activation action. + if (e.Button == MouseButton.Left) + Activated?.Invoke(); + Close(); return true; } From a9094c6b03bf302382cf171c72a5f217e2668189 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Sep 2022 15:18:32 +0900 Subject: [PATCH 056/583] Add test coverage of clicking close button to dismiss --- .../TestSceneNotificationOverlay.cs | 29 +++++++++++++++++++ .../Overlays/Notifications/Notification.cs | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index c0b29dde9a..5f82a0024b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -47,6 +47,35 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestDismissWithoutActivationCloseButton() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("click to activate", () => + { + InputManager.MoveMouseTo(notificationOverlay + .ChildrenOfType().Single() + .ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was not activated", () => !activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + } + [Test] public void TestDismissWithoutActivationRightClick() { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 09abdb865d..67739c2089 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -218,7 +218,7 @@ namespace osu.Game.Overlays.Notifications Expire(); } - private class CloseButton : OsuClickableContainer + internal class CloseButton : OsuClickableContainer { private SpriteIcon icon = null!; private Box background = null!; From f6aef73f9ee1f4cb76a31cbed78741d4b5675fd8 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Sat, 10 Sep 2022 20:30:43 +0200 Subject: [PATCH 057/583] fixed shape bindable not working, test creation, removal of Clear(), adjusted settings names --- .../Gameplay/TestSceneColourHitErrorMeter.cs | 119 ++++++++++++++++++ .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 12 +- 2 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs new file mode 100644 index 0000000000..7cbc70c605 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -0,0 +1,119 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using System.Diagnostics; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneColourHitErrorMeter : OsuTestScene + { + private DependencyProvidingContainer dependencyContainer; + + private readonly Bindable lastJudgementResult = new Bindable(); + private ScoreProcessor scoreProcessor; + private int iteration; + private ColourHitErrorMeter colourHitErrorMeter; + + public TestSceneColourHitErrorMeter() + { + AddSliderStep("Manual Opacity test", 0.01f, 1, 1, alpha => + { + if (colourHitErrorMeter != null) colourHitErrorMeter.HitShapeOpacity.Value = alpha; + }); + AddSliderStep("Manual spacing test", 0, 10, 2, spacing => + { + if (colourHitErrorMeter != null) colourHitErrorMeter.HitShapeSpacing.Value = spacing; + }); + } + + [SetUpSteps] + public void SetupSteps() => AddStep("Create components", () => + { + var ruleset = CreateRuleset(); + + Debug.Assert(ruleset != null); + + scoreProcessor = new ScoreProcessor(ruleset); + Child = dependencyContainer = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(ScoreProcessor), scoreProcessor) + } + }; + dependencyContainer.Child = colourHitErrorMeter = new ColourHitErrorMeter + { + Margin = new MarginPadding + { + Top = 100 + }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(2), + }; + }); + + protected override Ruleset CreateRuleset() => new OsuRuleset(); + + [Test] + public void TestOpacityChange() + { + AddRepeatStep("Add judgement", applyOneJudgement, 5); + AddStep("Change opacity to 30%", () => colourHitErrorMeter.HitShapeOpacity.Value = 0.3f); + AddRepeatStep("Add judgement", applyOneJudgement, 5); + } + + [Test] + public void TestSpacingChange() + { + AddRepeatStep("Add judgement", applyOneJudgement, 5); + AddStep("Change spacing", () => colourHitErrorMeter.HitShapeSpacing.Value = 10); + AddRepeatStep("Add judgement", applyOneJudgement, 5); + } + + [Test] + public void TestShapeChange() + { + AddRepeatStep("Add judgement", applyOneJudgement, 10); + AddStep("Judgement count change to 4", () => colourHitErrorMeter.HitShapeCount.Value = 4); + AddRepeatStep("Add judgement", applyOneJudgement, 8); + } + + [Test] + public void TestHitErrorShapeChange() + { + AddRepeatStep("Add judgement", applyOneJudgement, 8); + AddStep("Change shape square", () => colourHitErrorMeter.HitShape.Value = ColourHitErrorMeter.ShapeStyle.Square); + AddRepeatStep("Add judgement", applyOneJudgement, 10); + } + + private void applyOneJudgement() + { + lastJudgementResult.Value = new OsuJudgementResult(new HitObject + { + StartTime = iteration * 10000, + }, new OsuJudgement()) + { + Type = HitResult.Great, + }; + scoreProcessor.ApplyResult(lastJudgementResult.Value); + + iteration++; + } + } +} diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index c218ff11e9..b5a8cfc44f 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const int animation_duration = 200; private const int drawable_judgement_size = 8; - [SettingSource("Hit error amount", "Number of hit error shapes")] + [SettingSource("Judgement count", "Number of displayed judgements")] public BindableNumber HitShapeCount { get; } = new BindableNumber(20) { MinValue = 1, @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Precision = 1 }; - [SettingSource("Opacity", "Visibility of object")] + [SettingSource("Opacity", "Visibility of the displayed judgements")] public BindableNumber HitShapeOpacity { get; } = new BindableNumber(1) { MinValue = 0.01f, @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Precision = 0.01f, }; - [SettingSource("Spacing", "Space between hit error shapes")] + [SettingSource("Spacing", "Space between each displayed judgement")] public BindableNumber HitShapeSpacing { get; } = new BindableNumber(2) { MinValue = 0, @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Precision = 0.1f }; - [SettingSource("Shape", "The shape of each displayed error")] + [SettingSource("Shape", "The shape of each displayed judgement")] public Bindable HitShape { get; } = new Bindable(); private readonly JudgementFlow judgementsFlow; @@ -74,9 +74,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters }, true); HitShapeCount.BindValueChanged(_ => { - judgementsFlow.Clear(); + //Used to clear out the overflowing judgement children when the value is lowered + judgementsFlow.RemoveAll(_ => true); judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; }, true); + HitShape.BindValueChanged(_ => judgementsFlow.Shape.Value = HitShape.Value, true); } public override void Clear() => judgementsFlow.Clear(); From 46d9262a6066c47f5fdf694e5d1ea78bb5dabec6 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Sat, 10 Sep 2022 20:38:34 +0200 Subject: [PATCH 058/583] test naming oversight --- osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs index 7cbc70c605..5fdd00a7f6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestShapeChange() + public void TestJudgementAmountChange() { AddRepeatStep("Add judgement", applyOneJudgement, 10); AddStep("Judgement count change to 4", () => colourHitErrorMeter.HitShapeCount.Value = 4); From c00bf612738c7a75ab00803349eeeea664b61186 Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Sat, 10 Sep 2022 21:24:29 +0200 Subject: [PATCH 059/583] Framework change adressed --- osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index b5a8cfc44f..8991d97bd4 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters HitShapeCount.BindValueChanged(_ => { //Used to clear out the overflowing judgement children when the value is lowered - judgementsFlow.RemoveAll(_ => true); + judgementsFlow.RemoveAll(_ => true, true); judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; }, true); HitShape.BindValueChanged(_ => judgementsFlow.Shape.Value = HitShape.Value, true); From 667854b03445f7eb73292fa40fcc84ac2970fd4a Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 10 Sep 2022 23:18:48 +0300 Subject: [PATCH 060/583] Make effect display have a built-in counter --- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 37 +++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs index 4e630439ee..98651ef5f0 100644 --- a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,6 +12,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osuTK; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.Mods private readonly Box contentBackground; private readonly Box labelBackground; - private readonly Container content; + private readonly FillFlowContainer content; public Bindable Current { @@ -62,6 +64,8 @@ namespace osu.Game.Overlays.Mods protected virtual float ValueAreaWidth => 56; + protected virtual string CounterFormat => "N0"; + protected override Container Content => content; protected ModsEffectDisplay() @@ -121,12 +125,20 @@ namespace osu.Game.Overlays.Mods } } }, - content = new Container + content = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + Direction = FillDirection.Horizontal, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Spacing = new Vector2(2, 0), + Child = new EffectCounter(CounterFormat) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = Current } + } } } } @@ -203,5 +215,24 @@ namespace osu.Game.Overlays.Mods DifficultyReduction, DifficultyIncrease } + + private class EffectCounter : RollingCounter + { + private readonly string? format; + + public EffectCounter(string? format) + { + this.format = format; + } + + protected override double RollingDuration => 500; + + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(format); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + }; + } } } From 064fe832c78196b05cd0a807951747940e81d39b Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 10 Sep 2022 23:18:59 +0300 Subject: [PATCH 061/583] Fix test --- .../TestSceneModsEffectDisplay.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs index ec50a07998..42eceb3242 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModsEffectDisplay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -54,28 +53,15 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyIncrease)); } - private class TestDisplay : ModsEffectDisplay + private class TestDisplay : ModsEffectDisplay { - private readonly OsuSpriteText text; - public Container Container => Content; protected override LocalisableString Label => "Test display"; - protected override ModEffect CalculateEffect(int value) => CalculateForSign(value.CompareTo(50)); public TestDisplay() { - Add(text = new OsuSpriteText - { - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold), - Text = "50" - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Current.BindValueChanged(e => text.Text = e.NewValue.ToString(), true); + Current.Default = 50; } } } From 3f93ec8538d3082bd0255d46ec0995372c18ed90 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 10 Sep 2022 23:20:28 +0300 Subject: [PATCH 062/583] Expose counter to inheritors --- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs index 98651ef5f0..3b112fdbd3 100644 --- a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -68,6 +68,8 @@ namespace osu.Game.Overlays.Mods protected override Container Content => content; + protected readonly RollingCounter Counter; + protected ModsEffectDisplay() { Height = HEIGHT; @@ -133,7 +135,7 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Horizontal, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Spacing = new Vector2(2, 0), - Child = new EffectCounter(CounterFormat) + Child = Counter = new EffectCounter(CounterFormat) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 324a3723a5dbba4b85f6d39617028acef7af005a Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 10 Sep 2022 23:23:04 +0300 Subject: [PATCH 063/583] Rewrite DMD --- .../Mods/DifficultyMultiplierDisplay.cs | 48 ++++--------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 5530947d3d..f582a010c6 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -3,14 +3,9 @@ #nullable disable -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osuTK; using osu.Game.Localisation; @@ -20,34 +15,19 @@ namespace osu.Game.Overlays.Mods { protected override LocalisableString Label => DifficultyMultiplierDisplayStrings.DifficultyMultiplier; - private readonly MultiplierCounter multiplierCounter; + protected override string CounterFormat => @"N2"; public DifficultyMultiplierDisplay() { Current.Default = 1d; Current.Value = 1d; - Add(new FillFlowContainer + Add(new SpriteIcon { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2, 0), - Children = new Drawable[] - { - multiplierCounter = new MultiplierCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = { BindTarget = Current } - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.Times, - Size = new Vector2(7), - Margin = new MarginPadding { Top = 1 } - } - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Times, + Size = new Vector2(7), + Margin = new MarginPadding { Top = 1 } }); } @@ -57,19 +37,7 @@ namespace osu.Game.Overlays.Mods // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. - multiplierCounter.SetCountWithoutRolling(Current.Value); - } - - private class MultiplierCounter : RollingCounter - { - protected override double RollingDuration => 500; - - protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"N2"); - - protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText - { - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) - }; + Counter.SetCountWithoutRolling(Current.Value); } } } From 1eb2c74e57c3d18dfcac974bf7c52101a18806ec Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 11 Sep 2022 18:50:51 +0900 Subject: [PATCH 064/583] Fix countdown serialisation --- osu.Game/Online/Multiplayer/MultiplayerCountdown.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 61637ae970..fd22420b99 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -33,6 +33,7 @@ namespace osu.Game.Online.Multiplayer /// /// Whether only a single instance of this type may be active at any one time. /// + [IgnoreMember] public virtual bool IsExclusive => true; } } From d6f90e3b9fd23496db6c57b8a3cb5339cf691cca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Sep 2022 21:32:22 +0900 Subject: [PATCH 065/583] Add basic fling-to-dismiss support --- .../Overlays/Notifications/Notification.cs | 132 +++++++++++++++++- 1 file changed, 128 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 67739c2089..3e7bd0ab2f 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -2,16 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -56,6 +59,8 @@ namespace osu.Game.Overlays.Notifications protected Container MainContent; + private readonly DragContainer dragContainer; + public virtual bool Read { get; set; } protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check; @@ -80,7 +85,11 @@ namespace osu.Game.Overlays.Notifications Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, }, - MainContent = new Container + dragContainer = new DragContainer(this) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }.WithChild(MainContent = new Container { CornerRadius = 6, Masking = true, @@ -144,7 +153,7 @@ namespace osu.Game.Overlays.Notifications Blending = BlendingParameters.Additive, }, } - } + }) }; } @@ -213,11 +222,126 @@ namespace osu.Game.Overlays.Notifications WasClosed = true; - Closed?.Invoke(); - this.FadeOut(100); + if (dragContainer.FlingLeft()) + { + Light.FadeOut(100); + this.FadeOut(600, Easing.In); + } + else + { + Closed?.Invoke(); + this.FadeOut(100); + } + Expire(); } + private class DragContainer : Container + { + private Vector2 velocity; + private Vector2 lastPosition; + + private readonly Notification notification; + + public DragContainer(Notification notification) + { + this.notification = notification; + notification.Closed += () => FlingLeft(); + } + + public override RectangleF BoundingBox + { + get + { + var childBounding = Children.First().BoundingBox; + + if (X < 0) childBounding *= new Vector2(1, Math.Max(0, 1 + (X / 800))); + if (Y > 0) childBounding *= new Vector2(1, Math.Max(0, 1 - (Y / 800))); + + return childBounding; + } + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override void OnDrag(DragEvent e) + { + Vector2 change = e.MousePosition - e.MouseDownPosition; + + // Diminish the drag distance as we go further to simulate "rubber band" feeling. + change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.9f) / change.Length; + + // Only apply Y change if dragging to the left. + if (change.X > 0) + change.Y = 0; + + this.MoveTo(change); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (Rotation < -10 || velocity.X < -0.3f) + { + FlingLeft(); + } + else + { + this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); + this.RotateTo(0, 800, Easing.OutElastic); + } + + base.OnDragEnd(e); + } + + private bool flinging; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Rotation = Math.Min(0, X * 0.1f); + + if (flinging) + { + velocity.Y += (float)Clock.ElapsedFrameTime * 0.005f; + Position += (float)Clock.ElapsedFrameTime * velocity; + } + else + { + Vector2 change = (Position - lastPosition) / (float)Clock.ElapsedFrameTime; + + if (velocity.X == 0) + velocity = change; + else + { + velocity = new Vector2( + (float)Interpolation.DampContinuously(velocity.X, change.X, 40, Clock.ElapsedFrameTime), + (float)Interpolation.DampContinuously(velocity.Y, change.Y, 40, Clock.ElapsedFrameTime) + ); + } + + lastPosition = Position; + } + } + + public bool FlingLeft() + { + if (this.FindClosestParent() == null) + return false; + + if (flinging) + return true; + + if (velocity.X > -0.3f) + velocity.X = -0.3f - 0.5f * RNG.NextSingle(); + + flinging = true; + ClearTransforms(); + notification.Close(); + return true; + } + } + internal class CloseButton : OsuClickableContainer { private SpriteIcon icon = null!; From b5a2f7003e0d0770c53c775a36a2a24a893be05a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Sep 2022 21:34:35 +0900 Subject: [PATCH 066/583] Disallow flinging when not in toast state --- osu.Game/Overlays/NotificationOverlay.cs | 5 +++++ osu.Game/Overlays/Notifications/Notification.cs | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index b170ea5dfa..708bd4874d 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -158,7 +158,10 @@ namespace osu.Game.Overlays playDebouncedSample(notification.PopInSampleName); if (State.Value == Visibility.Hidden) + { + notification.IsInTray = true; toastTray.Post(notification); + } else addPermanently(notification); @@ -167,6 +170,8 @@ namespace osu.Game.Overlays private void addPermanently(Notification notification) { + notification.IsInTray = false; + var ourType = notification.GetType(); int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 3e7bd0ab2f..595cff4171 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -68,6 +68,11 @@ namespace osu.Game.Overlays.Notifications [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + /// + /// Whether this notification is in the . + /// + public bool IsInTray { get; set; } + private readonly Box initialFlash; private Box background = null!; @@ -262,7 +267,7 @@ namespace osu.Game.Overlays.Notifications } } - protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnDragStart(DragStartEvent e) => notification.IsInTray; protected override void OnDrag(DragEvent e) { @@ -326,7 +331,7 @@ namespace osu.Game.Overlays.Notifications public bool FlingLeft() { - if (this.FindClosestParent() == null) + if (!notification.IsInTray) return false; if (flinging) From a56cadcf9057278f2aa3735a79385e0c2c097239 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Sep 2022 21:52:19 +0900 Subject: [PATCH 067/583] Ensure drag position is reset when transferred to tray --- osu.Game/Overlays/NotificationOverlay.cs | 4 +-- .../Overlays/Notifications/Notification.cs | 33 ++++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 708bd4874d..15573f99af 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -159,7 +159,7 @@ namespace osu.Game.Overlays if (State.Value == Visibility.Hidden) { - notification.IsInTray = true; + notification.IsInToastTray = true; toastTray.Post(notification); } else @@ -170,7 +170,7 @@ namespace osu.Game.Overlays private void addPermanently(Notification notification) { - notification.IsInTray = false; + notification.IsInToastTray = false; var ourType = notification.GetType(); int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 595cff4171..ded3c1606a 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -68,10 +68,21 @@ namespace osu.Game.Overlays.Notifications [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + private bool isInToastTray; + /// /// Whether this notification is in the . /// - public bool IsInTray { get; set; } + public bool IsInToastTray + { + private get => isInToastTray; + set + { + isInToastTray = value; + if (!isInToastTray) + dragContainer.ResetPosition(); + } + } private readonly Box initialFlash; @@ -267,10 +278,13 @@ namespace osu.Game.Overlays.Notifications } } - protected override bool OnDragStart(DragStartEvent e) => notification.IsInTray; + protected override bool OnDragStart(DragStartEvent e) => notification.IsInToastTray; protected override void OnDrag(DragEvent e) { + if (!notification.IsInToastTray) + return; + Vector2 change = e.MousePosition - e.MouseDownPosition; // Diminish the drag distance as we go further to simulate "rubber band" feeling. @@ -286,14 +300,9 @@ namespace osu.Game.Overlays.Notifications protected override void OnDragEnd(DragEndEvent e) { if (Rotation < -10 || velocity.X < -0.3f) - { FlingLeft(); - } else - { - this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); - this.RotateTo(0, 800, Easing.OutElastic); - } + ResetPosition(); base.OnDragEnd(e); } @@ -331,7 +340,7 @@ namespace osu.Game.Overlays.Notifications public bool FlingLeft() { - if (!notification.IsInTray) + if (!notification.IsInToastTray) return false; if (flinging) @@ -345,6 +354,12 @@ namespace osu.Game.Overlays.Notifications notification.Close(); return true; } + + public void ResetPosition() + { + this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); + this.RotateTo(0, 800, Easing.OutElastic); + } } internal class CloseButton : OsuClickableContainer From 9ef23c79ced4e8ca705bec6ba20505a216791312 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Sep 2022 22:05:44 +0900 Subject: [PATCH 068/583] Disallow forwarding during a drag operation --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 40324963fc..e4d3864beb 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -118,7 +118,7 @@ namespace osu.Game.Overlays return; // Notification hovered; delay dismissal. - if (notification.IsHovered) + if (notification.IsHovered || notification.IsDragged) { scheduleDismissal(); return; From bb3d5bc9f4d8b5c47ec2c833fd59a985c90264e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 04:43:15 +0900 Subject: [PATCH 069/583] test(osu.Game): add slider steps to change the size in timing distribution graph tests --- .../TestSceneHitEventTimingDistributionGraph.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 198be4035b..e014d79402 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -19,14 +20,24 @@ namespace osu.Game.Tests.Visual.Ranking public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { private HitEventTimingDistributionGraph graph = null!; + private readonly BindableFloat width = new BindableFloat(600); + private readonly BindableFloat height = new BindableFloat(130); private static readonly HitObject placeholder_object = new HitCircle(); + public TestSceneHitEventTimingDistributionGraph() + { + width.BindValueChanged(e => graph.Width = e.NewValue); + height.BindValueChanged(e => graph.Height = e.NewValue); + } + [Test] public void TestManyDistributedEvents() { createTest(CreateDistributedHitEvents()); AddStep("add adjustment", () => graph.UpdateOffset(10)); + AddSliderStep("width", 0.0f, 1000.0f, width.Value, width.Set); + AddSliderStep("height", 0.0f, 1000.0f, height.Value, height.Set); } [Test] @@ -137,7 +148,7 @@ namespace osu.Game.Tests.Visual.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(600, 130) + Size = new Vector2(width.Value, height.Value) } }; }); From ac324c63e2d0e3f0e9293519b6728aa7d415f90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 04:46:32 +0900 Subject: [PATCH 070/583] fix(osu.Game): handle size changes in timing distribution graph --- .../HitEventTimingDistributionGraph.cs | 83 ++++++++++++++----- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 5335d77243..72603fd46b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -209,13 +210,12 @@ namespace osu.Game.Screens.Ranking.Statistics private class Bar : CompositeDrawable { - private float totalValue => values.Sum(v => v.Value); - private float basalHeight => BoundingBox.Width / BoundingBox.Height; - private float availableHeight => 1 - basalHeight; - private readonly IReadOnlyList> values; private readonly float maxValue; private readonly bool isCentre; + private readonly float totalValue; + private readonly BindableFloat basalHeight; + private readonly BindableFloat offsetAdjustment; private Circle[] boxOriginals; private Circle boxAdjustment; @@ -228,6 +228,9 @@ namespace osu.Game.Screens.Ranking.Statistics this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); this.maxValue = maxValue; this.isCentre = isCentre; + totalValue = values.Sum(v => v.Value); + basalHeight = new BindableFloat(); + offsetAdjustment = new BindableFloat(totalValue); RelativeSizeAxes = Axes.Both; Masking = true; @@ -254,38 +257,70 @@ namespace osu.Game.Screens.Ranking.Statistics else { // A bin with no value draws a grey dot instead. - InternalChildren = boxOriginals = new[] + Circle dot = new Circle { - new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : Color4.Gray, - Height = 0, - }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4.Gray, + Height = 0, }; + InternalChildren = boxOriginals = new[] { dot }; + basalHeight.BindValueChanged(e => dot.Height = e.NewValue); } + + offsetAdjustment.BindValueChanged(_ => drawAdjustmentBar()); } private const double duration = 300; - private float offsetForValue(float value) + private float calculateBasalHeight() => DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; + + private float offsetForValue(float value) => (1 - basalHeight.Value) * value / maxValue; + + private float heightForValue(float value) => MathF.Max(basalHeight.Value + offsetForValue(value), 0); + + private void draw() { - return availableHeight * value / maxValue; + resizeBars(); + + if (boxAdjustment != null) + drawAdjustmentBar(); } - private float heightForValue(float value) + private void resizeBars() { - return basalHeight + offsetForValue(value); + float offsetValue = 0; + + for (int i = 0; i < values.Count; i++) + { + boxOriginals[i].Y = offsetForValue(offsetValue) * BoundingBox.Height; + boxOriginals[i].Height = heightForValue(values[i].Value); + offsetValue -= values[i].Value; + } + } + + private void drawAdjustmentBar() + { + bool hasAdjustment = offsetAdjustment.Value != totalValue; + + boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment.Value), duration, Easing.OutQuint); + boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } protected override void LoadComplete() { base.LoadComplete(); + float height = calculateBasalHeight(); + + basalHeight.Set(height); + + if (!values.Any()) + return; + foreach (var boxOriginal in boxOriginals) - boxOriginal.Height = basalHeight; + boxOriginal.Height = height; float offsetValue = 0; @@ -295,6 +330,15 @@ namespace osu.Game.Screens.Ranking.Statistics boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); offsetValue -= values[i].Value; } + + basalHeight.BindValueChanged(_ => draw()); + } + + protected override void Update() + { + base.Update(); + + basalHeight.Set(calculateBasalHeight()); } public void UpdateOffset(float adjustment) @@ -318,8 +362,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(heightForValue(adjustment), duration, Easing.OutQuint); - boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); + offsetAdjustment.Set(adjustment); } } } From 24138b65a74c1d57a2267dce3510f01442a3fabb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:05:16 +0900 Subject: [PATCH 071/583] Fix storyboard animations not starting their animation playback from the correct point in time --- .../Drawables/DrawableStoryboardAnimation.cs | 16 ++++++++++++++++ osu.Game/Storyboards/StoryboardSprite.cs | 10 +++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index f3187d77b7..369a3ee7ba 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -115,6 +116,21 @@ namespace osu.Game.Storyboards.Drawables Animation.ApplyTransforms(this); } + [Resolved] + private IGameplayClock gameplayClock { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Framework animation class tries its best to synchronise the animation at LoadComplete, + // but in some cases (such as fast forward) this results in an incorrect start offset. + // + // In the case of storyboard animations, we want to synchronise with game time perfectly + // so let's get a correct time based on gameplay clock and earliest transform. + PlaybackPosition = gameplayClock.CurrentTime - Animation.EarliestTransformTime; + } + private void skinSourceChanged() { ClearFrames(); diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 1eeaa0f084..589bcb60be 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Storyboards public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - public double StartTime + public virtual double StartTime { get { @@ -54,6 +54,14 @@ namespace osu.Game.Storyboards return firstAlpha.startTime; } + return EarliestTransformTime; + } + } + + public double EarliestTransformTime + { + get + { // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. // The sprite's StartTime will be determined by the earliest command, regardless of type. double earliestStartTime = TimelineGroup.StartTime; From cf25ee8e8482b5c60fb6fd8b8f0d7e23fcf903d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:17:15 +0900 Subject: [PATCH 072/583] Add test coverage of storyboard animation start time --- .../Formats/LegacyStoryboardDecoderTest.cs | 19 +++++++++++++++++++ .../animation-starts-before-alpha.osb | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 osu.Game.Tests/Resources/animation-starts-before-alpha.osb diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 6e41043b0b..d9e80fa111 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -97,6 +97,25 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestCorrectAnimationStartTime() + { + var decoder = new LegacyStoryboardDecoder(); + + using (var resStream = TestResources.OpenResource("animation-starts-before-alpha.osb")) + using (var stream = new LineBufferedReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3); + Assert.AreEqual(1, background.Elements.Count); + + Assert.AreEqual(2000, background.Elements[0].StartTime); + // This property should be used in DrawableStoryboardAnimation as a starting point for animation playback. + Assert.AreEqual(1000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime); + } + } + [Test] public void TestOutOfOrderStartTimes() { diff --git a/osu.Game.Tests/Resources/animation-starts-before-alpha.osb b/osu.Game.Tests/Resources/animation-starts-before-alpha.osb new file mode 100644 index 0000000000..ceef204f3f --- /dev/null +++ b/osu.Game.Tests/Resources/animation-starts-before-alpha.osb @@ -0,0 +1,5 @@ +[Events] +//Storyboard Layer 0 (Background) +Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever + S,0,1000,1500,0.08 // animation should start playing from this point in time.. + F,0,2000,,0,1 // .. not this point in time From bbf906ee064507157512ba692962707a4c682b1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:20:48 +0900 Subject: [PATCH 073/583] Remove unnecessary `virtual` spec --- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 589bcb60be..cd7788bb08 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Storyboards public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - public virtual double StartTime + public double StartTime { get { From eca241e9a71943f45b0226b02ea645c1790751e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:52:11 +0900 Subject: [PATCH 074/583] Move `UpdateProgressNotification` to base `UpdateManager` class --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 112 ++++++------------- osu.Game/Updater/UpdateManager.cs | 72 +++++++++++- 2 files changed, 99 insertions(+), 85 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 6f45237522..84bac9da7c 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -5,26 +5,24 @@ using System; using System.Runtime.Versioning; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game; -using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osuTK; using Squirrel; using Squirrel.SimpleSplat; +using LogLevel = Squirrel.SimpleSplat.LogLevel; +using UpdateManager = osu.Game.Updater.UpdateManager; namespace osu.Desktop.Updater { [SupportedOSPlatform("windows")] - public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager + public class SquirrelUpdateManager : UpdateManager { - private UpdateManager? updateManager; + private Squirrel.UpdateManager? updateManager; private INotificationOverlay notificationOverlay = null!; - public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited(); + public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited(); private static readonly Logger logger = Logger.GetLogger("updater"); @@ -35,6 +33,9 @@ namespace osu.Desktop.Updater private readonly SquirrelLogger squirrelLogger = new SquirrelLogger(); + [Resolved] + private OsuGameBase game { get; set; } = null!; + [BackgroundDependencyLoader] private void load(INotificationOverlay notifications) { @@ -63,7 +64,14 @@ namespace osu.Desktop.Updater if (updatePending) { // the user may have dismissed the completion notice, so show it again. - notificationOverlay.Post(new UpdateCompleteNotification(this)); + notificationOverlay.Post(new UpdateApplicationCompleteNotification + { + Activated = () => + { + restartToApplyUpdate(); + return true; + }, + }); return true; } @@ -75,19 +83,21 @@ namespace osu.Desktop.Updater if (notification == null) { - notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; + notification = new UpdateProgressNotification + { + CompletionClickAction = restartToApplyUpdate, + }; + Schedule(() => notificationOverlay.Post(notification)); } - notification.Progress = 0; - notification.Text = @"Downloading update..."; + notification.StartDownload(); try { await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false); - notification.Progress = 0; - notification.Text = @"Installing update..."; + notification.StartInstall(); await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false); @@ -107,9 +117,7 @@ namespace osu.Desktop.Updater else { // In the case of an error, a separate notification will be displayed. - notification.State = ProgressNotificationState.Cancelled; - notification.Close(); - + notification.FailDownload(); Logger.Error(e, @"update failed!"); } } @@ -131,78 +139,24 @@ namespace osu.Desktop.Updater return true; } + private bool restartToApplyUpdate() + { + PrepareUpdateAsync() + .ContinueWith(_ => Schedule(() => game.AttemptExit())); + return true; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); updateManager?.Dispose(); } - private class UpdateCompleteNotification : ProgressCompletionNotification - { - [Resolved] - private OsuGame game { get; set; } = null!; - - public UpdateCompleteNotification(SquirrelUpdateManager updateManager) - { - Text = @"Update ready to install. Click to restart!"; - - Activated = () => - { - updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game.AttemptExit())); - return true; - }; - } - } - - private class UpdateProgressNotification : ProgressNotification - { - private readonly SquirrelUpdateManager updateManager; - - public UpdateProgressNotification(SquirrelUpdateManager updateManager) - { - this.updateManager = updateManager; - } - - protected override Notification CreateCompletionNotification() - { - return new UpdateCompleteNotification(updateManager); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IconContent.AddRange(new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Upload, - Size = new Vector2(20), - } - }); - } - - public override void Close() - { - // cancelling updates is not currently supported by the underlying updater. - // only allow dismissing for now. - - switch (State) - { - case ProgressNotificationState.Cancelled: - base.Close(); - break; - } - } - } - private class SquirrelLogger : ILogger, IDisposable { - public Squirrel.SimpleSplat.LogLevel Level { get; set; } = Squirrel.SimpleSplat.LogLevel.Info; + public LogLevel Level { get; set; } = LogLevel.Info; - public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel) + public void Write(string message, LogLevel logLevel) { if (logLevel < Level) return; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 4790055cd1..801f2daac5 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -1,16 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osuTK; namespace osu.Game.Updater { @@ -27,13 +27,13 @@ namespace osu.Game.Updater GetType() != typeof(UpdateManager); [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; [Resolved] - protected INotificationOverlay Notifications { get; private set; } + protected INotificationOverlay Notifications { get; private set; } = null!; protected override void LoadComplete() { @@ -59,7 +59,7 @@ namespace osu.Game.Updater private readonly object updateTaskLock = new object(); - private Task updateCheckTask; + private Task? updateCheckTask; public async Task CheckForUpdateAsync() { @@ -109,5 +109,65 @@ namespace osu.Game.Updater }; } } + + protected class UpdateApplicationCompleteNotification : ProgressCompletionNotification + { + public UpdateApplicationCompleteNotification() + { + Text = @"Update ready to install. Click to restart!"; + } + } + + public class UpdateProgressNotification : ProgressNotification + { + protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification(); + + [BackgroundDependencyLoader] + private void load() + { + IconContent.AddRange(new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Upload, + Size = new Vector2(14), + } + }); + } + + public override void Close() + { + // cancelling updates is not currently supported by the underlying updater. + // only allow dismissing for now. + + switch (State) + { + case ProgressNotificationState.Cancelled: + base.Close(); + break; + } + } + + public void StartDownload() + { + State = ProgressNotificationState.Active; + Progress = 0; + Text = @"Downloading update..."; + } + + public void StartInstall() + { + Progress = 0; + Text = @"Installing update..."; + } + + public void FailDownload() + { + State = ProgressNotificationState.Cancelled; + Close(); + } + } } } From 2b79e6b2de20e64cd984c4bb8c5878bc28bdb10e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:52:16 +0900 Subject: [PATCH 075/583] Add test coverage of update notification --- .../TestSceneNotificationOverlay.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 699b8f7d89..1afe8327b3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Updater; namespace osu.Game.Tests.Visual.UserInterface { @@ -134,6 +135,31 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("cancel notification", () => notification.State = ProgressNotificationState.Cancelled); } + [Test] + public void TestUpdateNotificationFlow() + { + bool applyUpdate = false; + + AddStep(@"post update", () => + { + applyUpdate = false; + + var updateNotification = new UpdateManager.UpdateProgressNotification + { + CompletionClickAction = () => applyUpdate = true + }; + + notificationOverlay.Post(updateNotification); + progressingNotifications.Add(updateNotification); + }); + + checkProgressingCount(1); + waitForCompletion(); + AddStep("click notification", () => notificationOverlay.ChildrenOfType().Single().TriggerClick()); + + AddUntilStep("wait for update applied", () => applyUpdate); + } + [Test] public void TestBasicFlow() { From 60b0b909a51d1986956cddbf56a6927f1025b649 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:58:46 +0900 Subject: [PATCH 076/583] Move update icon to background to avoid colour collission with progress spinner --- .../Overlays/Notifications/ProgressNotification.cs | 1 + osu.Game/Updater/UpdateManager.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 64ad69adf3..bdf6f704e5 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -226,6 +226,7 @@ namespace osu.Game.Overlays.Notifications { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5, + Depth = float.MaxValue, }, loadingSpinner = new LoadingSpinner { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 801f2daac5..8dcb1c4562 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -132,11 +132,19 @@ namespace osu.Game.Updater Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Upload, - Size = new Vector2(14), + Size = new Vector2(34), + Colour = OsuColour.Gray(0.2f), + Depth = float.MaxValue, } }); } + protected override void LoadComplete() + { + base.LoadComplete(); + StartDownload(); + } + public override void Close() { // cancelling updates is not currently supported by the underlying updater. From 9b31aa6d7ace1a41888315f0bdcfc06ebf86c01a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:04:30 +0900 Subject: [PATCH 077/583] Fix activation not firing with refactors --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 6 +++++- osu.Game/Updater/UpdateManager.cs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 1afe8327b3..f497a5bd6b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -155,7 +155,11 @@ namespace osu.Game.Tests.Visual.UserInterface checkProgressingCount(1); waitForCompletion(); - AddStep("click notification", () => notificationOverlay.ChildrenOfType().Single().TriggerClick()); + + UpdateManager.UpdateApplicationCompleteNotification? completionNotification = null; + AddUntilStep("wait for completion notification", + () => (completionNotification = notificationOverlay.ChildrenOfType().SingleOrDefault()) != null); + AddStep("click notification", () => completionNotification?.TriggerClick()); AddUntilStep("wait for update applied", () => applyUpdate); } diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 8dcb1c4562..49009e9124 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -110,7 +110,7 @@ namespace osu.Game.Updater } } - protected class UpdateApplicationCompleteNotification : ProgressCompletionNotification + public class UpdateApplicationCompleteNotification : ProgressCompletionNotification { public UpdateApplicationCompleteNotification() { @@ -120,7 +120,10 @@ namespace osu.Game.Updater public class UpdateProgressNotification : ProgressNotification { - protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification(); + protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification + { + Activated = CompletionClickAction + }; [BackgroundDependencyLoader] private void load() From 92beb6cbe740336ab84e920818bb4fe5b372563f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:16:45 +0900 Subject: [PATCH 078/583] Hide notification read light when in a toast state Also adds test coverage of read state and light. --- .../TestSceneNotificationOverlay.cs | 20 +++++++++++++++++++ .../Overlays/Notifications/Notification.cs | 11 ++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 5f82a0024b..6b737b7f5f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -219,6 +219,26 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("cancel notification", () => notification.State = ProgressNotificationState.Cancelled); } + [Test] + public void TestReadState() + { + SimpleNotification notification = null!; + AddStep(@"post", () => notificationOverlay.Post(notification = new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" })); + AddUntilStep("check is toast", () => !notification.IsInToastTray); + AddAssert("light is not visible", () => notification.ChildrenOfType().Single().Alpha == 0); + + AddUntilStep("wait for forward to overlay", () => !notification.IsInToastTray); + + setState(Visibility.Visible); + AddAssert("state is not read", () => !notification.Read); + AddUntilStep("light is visible", () => notification.ChildrenOfType().Single().Alpha == 1); + + setState(Visibility.Hidden); + setState(Visibility.Visible); + AddAssert("state is read", () => notification.Read); + AddUntilStep("light is not visible", () => notification.ChildrenOfType().Single().Alpha == 0); + } + [Test] public void TestBasicFlow() { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index ded3c1606a..91f5f3b19b 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -75,12 +75,17 @@ namespace osu.Game.Overlays.Notifications /// public bool IsInToastTray { - private get => isInToastTray; + get => isInToastTray; set { isInToastTray = value; + if (!isInToastTray) + { dragContainer.ResetPosition(); + if (!Read) + Light.FadeIn(100); + } } } @@ -97,6 +102,7 @@ namespace osu.Game.Overlays.Notifications { Light = new NotificationLight { + Alpha = 0, Margin = new MarginPadding { Right = 5 }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, @@ -239,10 +245,7 @@ namespace osu.Game.Overlays.Notifications WasClosed = true; if (dragContainer.FlingLeft()) - { - Light.FadeOut(100); this.FadeOut(600, Easing.In); - } else { Closed?.Invoke(); From e06a0f7300fc9cb71b7a9914a1f8143481a57790 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:21:01 +0900 Subject: [PATCH 079/583] Fix dragged state not being exposed correctly --- osu.Game/Overlays/Notifications/Notification.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 91f5f3b19b..248d66ac16 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -63,6 +63,8 @@ namespace osu.Game.Overlays.Notifications public virtual bool Read { get; set; } + public new bool IsDragged => dragContainer.IsDragged; + protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check; [Resolved] From 2476cf8fb36e4e964b397d86ae082a0ab1de6149 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:37:48 +0900 Subject: [PATCH 080/583] Adjust movement to look less sudden when snapping back to Y=0 --- osu.Game/Overlays/Notifications/Notification.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 248d66ac16..2e4b0c430a 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -293,11 +293,13 @@ namespace osu.Game.Overlays.Notifications Vector2 change = e.MousePosition - e.MouseDownPosition; // Diminish the drag distance as we go further to simulate "rubber band" feeling. - change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.9f) / change.Length; + change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.8f) / change.Length; // Only apply Y change if dragging to the left. - if (change.X > 0) + if (change.X >= 0) change.Y = 0; + else + change.Y *= (float)Interpolation.ApplyEasing(Easing.InOutQuart, Math.Min(1, -change.X / 200)); this.MoveTo(change); } From 0755289ec3cfce14b47250baffabbbcacb76203e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:48:50 +0900 Subject: [PATCH 081/583] Apply NRT to `HitEventTimingDistributionGraph` --- .../HitEventTimingDistributionGraph.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 72603fd46b..930a702958 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -48,6 +46,12 @@ namespace osu.Game.Screens.Ranking.Statistics /// private readonly IReadOnlyList hitEvents; + private readonly IDictionary[] bins; + private double binSize; + private double hitOffset; + + private Bar[]? barDrawables; + /// /// Creates a new . /// @@ -55,22 +59,15 @@ namespace osu.Game.Screens.Ranking.Statistics public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); + bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); } - private IDictionary[] bins; - private double binSize; - private double hitOffset; - - private Bar[] barDrawables; - [BackgroundDependencyLoader] private void load() { - if (hitEvents == null || hitEvents.Count == 0) + if (hitEvents.Count == 0) return; - bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); - binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); // Prevent div-by-0 by enforcing a minimum bin size @@ -217,11 +214,12 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly BindableFloat basalHeight; private readonly BindableFloat offsetAdjustment; - private Circle[] boxOriginals; - private Circle boxAdjustment; + private Circle[] boxOriginals = null!; + + private Circle? boxAdjustment; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public Bar(IDictionary values, float maxValue, bool isCentre) { From bcd2445d9b7a91d9e870cde3d3db5b505ca94a21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:50:10 +0900 Subject: [PATCH 082/583] Move helper functions down in file --- .../HitEventTimingDistributionGraph.cs | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 930a702958..8ef85d24c4 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -221,6 +221,8 @@ namespace osu.Game.Screens.Ranking.Statistics [Resolved] private OsuColour colours { get; set; } = null!; + private const double duration = 300; + public Bar(IDictionary values, float maxValue, bool isCentre) { this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); @@ -270,7 +272,61 @@ namespace osu.Game.Screens.Ranking.Statistics offsetAdjustment.BindValueChanged(_ => drawAdjustmentBar()); } - private const double duration = 300; + protected override void LoadComplete() + { + base.LoadComplete(); + + float height = calculateBasalHeight(); + + basalHeight.Value = height; + + if (!values.Any()) + return; + + foreach (var boxOriginal in boxOriginals) + boxOriginal.Height = height; + + float offsetValue = 0; + + for (int i = 0; i < values.Count; i++) + { + boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + offsetValue -= values[i].Value; + } + + basalHeight.BindValueChanged(_ => draw()); + } + + protected override void Update() + { + base.Update(); + basalHeight.Value = calculateBasalHeight(); + } + + public void UpdateOffset(float adjustment) + { + bool hasAdjustment = adjustment != totalValue; + + if (boxAdjustment == null) + { + if (!hasAdjustment) + return; + + AddInternal(boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }); + } + + offsetAdjustment.Set(adjustment); + } private float calculateBasalHeight() => DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; @@ -305,63 +361,6 @@ namespace osu.Game.Screens.Ranking.Statistics boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment.Value), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } - - protected override void LoadComplete() - { - base.LoadComplete(); - - float height = calculateBasalHeight(); - - basalHeight.Set(height); - - if (!values.Any()) - return; - - foreach (var boxOriginal in boxOriginals) - boxOriginal.Height = height; - - float offsetValue = 0; - - for (int i = 0; i < values.Count; i++) - { - boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); - boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); - offsetValue -= values[i].Value; - } - - basalHeight.BindValueChanged(_ => draw()); - } - - protected override void Update() - { - base.Update(); - - basalHeight.Set(calculateBasalHeight()); - } - - public void UpdateOffset(float adjustment) - { - bool hasAdjustment = adjustment != totalValue; - - if (boxAdjustment == null) - { - if (!hasAdjustment) - return; - - AddInternal(boxAdjustment = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = Color4.Yellow, - Blending = BlendingParameters.Additive, - Alpha = 0.6f, - Height = 0, - }); - } - - offsetAdjustment.Set(adjustment); - } } } } From 96951057df199df099e61af0b4136cd1909ebe1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:56:06 +0900 Subject: [PATCH 083/583] Avoid using bindables --- .../HitEventTimingDistributionGraph.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 8ef85d24c4..ebf521ba21 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -211,8 +210,9 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly float maxValue; private readonly bool isCentre; private readonly float totalValue; - private readonly BindableFloat basalHeight; - private readonly BindableFloat offsetAdjustment; + + private float basalHeight; + private float offsetAdjustment; private Circle[] boxOriginals = null!; @@ -229,8 +229,7 @@ namespace osu.Game.Screens.Ranking.Statistics this.maxValue = maxValue; this.isCentre = isCentre; totalValue = values.Sum(v => v.Value); - basalHeight = new BindableFloat(); - offsetAdjustment = new BindableFloat(totalValue); + offsetAdjustment = totalValue; RelativeSizeAxes = Axes.Both; Masking = true; @@ -266,25 +265,20 @@ namespace osu.Game.Screens.Ranking.Statistics Height = 0, }; InternalChildren = boxOriginals = new[] { dot }; - basalHeight.BindValueChanged(e => dot.Height = e.NewValue); } - - offsetAdjustment.BindValueChanged(_ => drawAdjustmentBar()); } protected override void LoadComplete() { base.LoadComplete(); - float height = calculateBasalHeight(); - - basalHeight.Value = height; - if (!values.Any()) return; + updateBasalHeight(); + foreach (var boxOriginal in boxOriginals) - boxOriginal.Height = height; + boxOriginal.Height = basalHeight; float offsetValue = 0; @@ -294,14 +288,12 @@ namespace osu.Game.Screens.Ranking.Statistics boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); offsetValue -= values[i].Value; } - - basalHeight.BindValueChanged(_ => draw()); } protected override void Update() { base.Update(); - basalHeight.Value = calculateBasalHeight(); + updateBasalHeight(); } public void UpdateOffset(float adjustment) @@ -325,14 +317,27 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - offsetAdjustment.Set(adjustment); + offsetAdjustment = adjustment; + drawAdjustmentBar(); } - private float calculateBasalHeight() => DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; + private void updateBasalHeight() + { + float newBasalHeight = DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; - private float offsetForValue(float value) => (1 - basalHeight.Value) * value / maxValue; + if (newBasalHeight == basalHeight) + return; - private float heightForValue(float value) => MathF.Max(basalHeight.Value + offsetForValue(value), 0); + basalHeight = newBasalHeight; + foreach (var dot in boxOriginals) + dot.Height = basalHeight; + + draw(); + } + + private float offsetForValue(float value) => (1 - basalHeight) * value / maxValue; + + private float heightForValue(float value) => MathF.Max(basalHeight + offsetForValue(value), 0); private void draw() { @@ -356,9 +361,9 @@ namespace osu.Game.Screens.Ranking.Statistics private void drawAdjustmentBar() { - bool hasAdjustment = offsetAdjustment.Value != totalValue; + bool hasAdjustment = offsetAdjustment != totalValue; - boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment.Value), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From 94f8197e22f647d61ee82c42bee7d9dbd386d65c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:15:14 +0900 Subject: [PATCH 084/583] Show login overlay when attempting to update a beatmap while logged out --- .../Select/Carousel/UpdateBeatmapSetButton.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 73e7d23df0..3c4ed4734b 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -11,6 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -22,6 +24,15 @@ namespace osu.Game.Screens.Select.Carousel private SpriteIcon icon = null!; private Box progressFill = null!; + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved(canBeNull: true)] + private LoginOverlay? loginOverlay { get; set; } + public UpdateBeatmapSetButton(BeatmapSetInfo beatmapSetInfo) { this.beatmapSetInfo = beatmapSetInfo; @@ -32,9 +43,6 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft; } - [Resolved] - private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; - [BackgroundDependencyLoader] private void load() { @@ -90,6 +98,12 @@ namespace osu.Game.Screens.Select.Carousel Action = () => { + if (!api.IsLoggedIn) + { + loginOverlay?.Show(); + return; + } + beatmapDownloader.DownloadAsUpdate(beatmapSetInfo); attachExistingDownload(); }; From 0d0e2e7bcce9445e9513cacb8281b03b8adf91be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 16:20:33 +0900 Subject: [PATCH 085/583] fix(osu.Game): bars in hit distribution graph stretching in the opposite direction when `DrawHeight` is less than `DrawWidth` --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index ebf521ba21..5f3b2773ea 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -323,7 +323,7 @@ namespace osu.Game.Screens.Ranking.Statistics private void updateBasalHeight() { - float newBasalHeight = DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; + float newBasalHeight = DrawHeight > DrawWidth ? DrawWidth / DrawHeight : 1; if (newBasalHeight == basalHeight) return; @@ -353,7 +353,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 0; i < values.Count; i++) { - boxOriginals[i].Y = offsetForValue(offsetValue) * BoundingBox.Height; + boxOriginals[i].Y = offsetForValue(offsetValue) * DrawHeight; boxOriginals[i].Height = heightForValue(values[i].Value); offsetValue -= values[i].Value; } From 4f298db066bba408f1aaaa8c5cfa76b29738cec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 16:25:29 +0900 Subject: [PATCH 086/583] fix(osu.Game): reset Y axis of the bars in hit distribution graph at the first drawing --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 5f3b2773ea..764237ef96 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -278,7 +278,10 @@ namespace osu.Game.Screens.Ranking.Statistics updateBasalHeight(); foreach (var boxOriginal in boxOriginals) + { + boxOriginal.Y = 0; boxOriginal.Height = basalHeight; + } float offsetValue = 0; From bd3673baa9fe9fa5c5b77659e270c1e784938191 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:25:32 +0900 Subject: [PATCH 087/583] Fix being able to drag after already closing a notification --- osu.Game/Overlays/Notifications/Notification.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 2e4b0c430a..facf43128d 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -70,6 +70,8 @@ namespace osu.Game.Overlays.Notifications [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && !WasClosed; + private bool isInToastTray; /// @@ -327,7 +329,7 @@ namespace osu.Game.Overlays.Notifications velocity.Y += (float)Clock.ElapsedFrameTime * 0.005f; Position += (float)Clock.ElapsedFrameTime * velocity; } - else + else if (Clock.ElapsedFrameTime > 0) { Vector2 change = (Position - lastPosition) / (float)Clock.ElapsedFrameTime; From a50617857176acbefb571e777429d9411cfdbff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:39:46 +0900 Subject: [PATCH 088/583] Make bounding box shrink faster to allow for rapid flinging --- osu.Game/Overlays/Notifications/Notification.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index facf43128d..f83be0c446 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -278,8 +278,8 @@ namespace osu.Game.Overlays.Notifications { var childBounding = Children.First().BoundingBox; - if (X < 0) childBounding *= new Vector2(1, Math.Max(0, 1 + (X / 800))); - if (Y > 0) childBounding *= new Vector2(1, Math.Max(0, 1 - (Y / 800))); + if (X < 0) childBounding *= new Vector2(1, Math.Max(0, 1 + (X / 300))); + if (Y > 0) childBounding *= new Vector2(1, Math.Max(0, 1 - (Y / 200))); return childBounding; } From 5a02e1e7133e2be8f6eba967049b058a720957f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:46:45 +0900 Subject: [PATCH 089/583] Use padding instead of `FillFlow.Spacing` to avoid artifact during animation --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 1 - osu.Game/Overlays/Notifications/Notification.cs | 3 +++ osu.Game/Overlays/Notifications/NotificationSection.cs | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index e4d3864beb..1da05dab75 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -78,7 +78,6 @@ namespace osu.Game.Overlays { LayoutDuration = 150, LayoutEasing = Easing.OutQuart, - Spacing = new Vector2(3), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index f83be0c446..fe2f67973a 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -113,6 +113,9 @@ namespace osu.Game.Overlays.Notifications }, dragContainer = new DragContainer(this) { + // Use margin instead of FillFlow spacing to fix extra padding appearing when notification shrinks + // in height. + Padding = new MarginPadding { Vertical = 3f }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }.WithChild(MainContent = new Container diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index d2e18a0cee..14bacca534 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -106,7 +106,6 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.X, LayoutDuration = 150, LayoutEasing = Easing.OutQuart, - Spacing = new Vector2(3), } }); } From d561fcb126689dade097acce3f4f425504680fff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:54:25 +0900 Subject: [PATCH 090/583] Don't trigger fling animation when `Close` is triggered by non-user action --- .../Online/TestSceneBeatmapDownloading.cs | 2 +- osu.Game/Database/ModelDownloader.cs | 2 +- osu.Game/Overlays/Notifications/Notification.cs | 13 ++++++------- .../Overlays/Notifications/NotificationSection.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 6 +++--- osu.Game/Updater/UpdateManager.cs | 6 +++--- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs index e7a6e9a543..641c1ad523 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Online { AddStep("download beatmap", () => beatmaps.Download(test_db_model)); - AddStep("cancel download from notification", () => recentNotification.Close()); + AddStep("cancel download from notification", () => recentNotification.Close(true)); AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index a3678602d1..877c90a534 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -111,7 +111,7 @@ namespace osu.Game.Database { if (error is WebException webException && webException.Message == @"TooManyRequests") { - notification.Close(); + notification.Close(false); PostNotification?.Invoke(new TooManyDownloadsNotification()); } else diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index fe2f67973a..acb277d4b4 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -168,7 +168,7 @@ namespace osu.Game.Overlays.Notifications }, new CloseButton(CloseButtonIcon) { - Action = Close, + Action = () => Close(true), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, } @@ -214,7 +214,7 @@ namespace osu.Game.Overlays.Notifications // right click doesn't trigger OnClick so we need to handle here until that changes. if (e.Button != MouseButton.Left) { - Close(); + Close(true); return true; } @@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Notifications if (e.Button == MouseButton.Left) Activated?.Invoke(); - Close(); + Close(true); return true; } @@ -245,13 +245,13 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close() + public virtual void Close(bool userTriggered) { if (WasClosed) return; WasClosed = true; - if (dragContainer.FlingLeft()) + if (userTriggered && dragContainer.FlingLeft()) this.FadeOut(600, Easing.In); else { @@ -272,7 +272,6 @@ namespace osu.Game.Overlays.Notifications public DragContainer(Notification notification) { this.notification = notification; - notification.Closed += () => FlingLeft(); } public override RectangleF BoundingBox @@ -363,7 +362,7 @@ namespace osu.Game.Overlays.Notifications flinging = true; ClearTransforms(); - notification.Close(); + notification.Close(true); return true; } diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 14bacca534..16105f913f 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Notifications private void clearAll() { - notifications.Children.ForEach(c => c.Close()); + notifications.Children.ForEach(c => c.Close(true)); } protected override void Update() diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index bdf6f704e5..55d6d5057a 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); attemptPostCompletion(); - base.Close(); + base.Close(false); break; } } @@ -235,12 +235,12 @@ namespace osu.Game.Overlays.Notifications }); } - public override void Close() + public override void Close(bool userTriggered) { switch (State) { case ProgressNotificationState.Cancelled: - base.Close(); + base.Close(userTriggered); break; case ProgressNotificationState.Active: diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 49009e9124..2ef6741ffd 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -148,7 +148,7 @@ namespace osu.Game.Updater StartDownload(); } - public override void Close() + public override void Close(bool userTriggered) { // cancelling updates is not currently supported by the underlying updater. // only allow dismissing for now. @@ -156,7 +156,7 @@ namespace osu.Game.Updater switch (State) { case ProgressNotificationState.Cancelled: - base.Close(); + base.Close(userTriggered); break; } } @@ -177,7 +177,7 @@ namespace osu.Game.Updater public void FailDownload() { State = ProgressNotificationState.Cancelled; - Close(); + Close(false); } } } From 91c415f29b693460123762b6e7023c15d32b856c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:58:20 +0900 Subject: [PATCH 091/583] Fix nullability oversight in `ManiaRulesetConfigManager` --- .../Configuration/ManiaRulesetConfigManager.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index b30d72f2e2..d367c82ed8 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; @@ -13,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { public class ManiaRulesetConfigManager : RulesetConfigManager { - public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + public ManiaRulesetConfigManager(SettingsStore? settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) { } From 89e128c0f3469e31d80ef2e165b324d4cbe80385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 17:35:56 +0900 Subject: [PATCH 092/583] Fix bindable event binds to `HitObject` directly --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 92d83d900e..ab4b492767 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -53,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable sliderPosition; private IBindable sliderScale; + [UsedImplicitly] + private readonly IBindable sliderVersion; + public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; @@ -61,7 +65,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // we don't want to run the path type update on construction as it may inadvertently change the slider. cachePoints(slider); - slider.Path.Version.BindValueChanged(_ => + sliderVersion = slider.Path.Version.GetBoundCopy(); + sliderVersion.BindValueChanged(_ => { cachePoints(slider); updatePathType(); From 58c1ea03215bc894bb770e2864d3b33c869d19db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 17:51:15 +0900 Subject: [PATCH 093/583] Fix potential crash when opening changelog overlay if entry has no URL --- osu.Game/Overlays/Changelog/ChangelogEntry.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogEntry.cs b/osu.Game/Overlays/Changelog/ChangelogEntry.cs index fdeba3f304..4d034007b1 100644 --- a/osu.Game/Overlays/Changelog/ChangelogEntry.cs +++ b/osu.Game/Overlays/Changelog/ChangelogEntry.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using System.Net; using System.Text.RegularExpressions; using osu.Framework.Allocation; @@ -93,7 +94,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); - if (!string.IsNullOrEmpty(entry.Repository)) + if (!string.IsNullOrEmpty(entry.Repository) && !string.IsNullOrEmpty(entry.GithubUrl)) addRepositoryReference(title, entryColour); if (entry.GithubUser != null) @@ -104,17 +105,22 @@ namespace osu.Game.Overlays.Changelog private void addRepositoryReference(LinkFlowContainer title, Color4 entryColour) { + Debug.Assert(!string.IsNullOrEmpty(entry.Repository)); + Debug.Assert(!string.IsNullOrEmpty(entry.GithubUrl)); + title.AddText(" (", t => { t.Font = fontLarge; t.Colour = entryColour; }); + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, t => { t.Font = fontLarge; t.Colour = entryColour; }); + title.AddText(")", t => { t.Font = fontLarge; From 27d4016ccb360991c37750bf5fe67e193b8fa038 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 12 Sep 2022 18:32:14 +0900 Subject: [PATCH 094/583] Add `ScoreProcessor.ComputeAccuracy()` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 64d5639133..7456ce06bd 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -272,7 +272,24 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Computes the total score of a given finalised . This should be used when a score is known to be complete. + /// Computes the accuracy of a given . + /// + /// The to compute the total score of. + /// The score's accuracy. + [Pure] + public double ComputeAccuracy(ScoreInfo scoreInfo) + { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + + // We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap. + extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); + + return maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; + } + + /// + /// Computes the total score of a given . /// /// /// Does not require to have been called before use. From e29f5cb4568019d031c85394aaca8b648e471a78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 18:53:37 +0900 Subject: [PATCH 095/583] Fix new-style legacy skins with animated judgements not adding correct transforms Closes #16173. --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index b87fc9acb5..2cb055d8ba 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -56,7 +56,7 @@ namespace osu.Game.Skinning if (result != HitResult.Miss) { //new judgement shows old as a temporary effect - AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f) + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) { Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index c47f46ed2b..7e7f0f027d 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -19,10 +19,13 @@ namespace osu.Game.Skinning private readonly float finalScale; - public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f) + private readonly bool allowTransforms; + + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool allowTransforms = false) { this.result = result; this.finalScale = finalScale; + this.allowTransforms = allowTransforms; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; @@ -43,8 +46,8 @@ namespace osu.Game.Skinning this.FadeInFromZero(fade_in_length); this.Delay(fade_out_delay).FadeOut(fade_out_length); - // legacy judgements don't play any transforms if they are an animation. - if (animation?.FrameCount > 1) + // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece. + if (!allowTransforms && animation?.FrameCount > 1) return; switch (result) From 4ee3e8f087e14631622ffca7f446485f50d4982c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 18:57:18 +0900 Subject: [PATCH 096/583] Don't play fling animation when activating a notification --- osu.Game/Overlays/Notifications/Notification.cs | 6 +++--- osu.Game/Overlays/Notifications/ProgressNotification.cs | 4 ++-- osu.Game/Updater/UpdateManager.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index acb277d4b4..887d729de7 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Notifications if (e.Button == MouseButton.Left) Activated?.Invoke(); - Close(true); + Close(false); return true; } @@ -245,13 +245,13 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close(bool userTriggered) + public virtual void Close(bool runFlingAnimation) { if (WasClosed) return; WasClosed = true; - if (userTriggered && dragContainer.FlingLeft()) + if (runFlingAnimation && dragContainer.FlingLeft()) this.FadeOut(600, Easing.In); else { diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 55d6d5057a..c4d402e5b9 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -235,12 +235,12 @@ namespace osu.Game.Overlays.Notifications }); } - public override void Close(bool userTriggered) + public override void Close(bool runFlingAnimation) { switch (State) { case ProgressNotificationState.Cancelled: - base.Close(userTriggered); + base.Close(runFlingAnimation); break; case ProgressNotificationState.Active: diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 2ef6741ffd..100464029b 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -148,7 +148,7 @@ namespace osu.Game.Updater StartDownload(); } - public override void Close(bool userTriggered) + public override void Close(bool runFlingAnimation) { // cancelling updates is not currently supported by the underlying updater. // only allow dismissing for now. @@ -156,7 +156,7 @@ namespace osu.Game.Updater switch (State) { case ProgressNotificationState.Cancelled: - base.Close(userTriggered); + base.Close(runFlingAnimation); break; } } From d92e000fe6db82e258ec9a68e6d0fe9885c99d75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 19:00:03 +0900 Subject: [PATCH 097/583] Fix flinging a notification not correctly running `Close` --- osu.Game/Overlays/Notifications/Notification.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 887d729de7..6c1b43c3b5 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -254,11 +254,9 @@ namespace osu.Game.Overlays.Notifications if (runFlingAnimation && dragContainer.FlingLeft()) this.FadeOut(600, Easing.In); else - { - Closed?.Invoke(); this.FadeOut(100); - } + Closed?.Invoke(); Expire(); } @@ -311,7 +309,7 @@ namespace osu.Game.Overlays.Notifications protected override void OnDragEnd(DragEndEvent e) { if (Rotation < -10 || velocity.X < -0.3f) - FlingLeft(); + notification.Close(true); else ResetPosition(); @@ -362,7 +360,6 @@ namespace osu.Game.Overlays.Notifications flinging = true; ClearTransforms(); - notification.Close(true); return true; } From f56f6545c04cd3f4f56a7398a49998ab8fe13da7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 19:06:09 +0900 Subject: [PATCH 098/583] Add test coverage of flinging --- .../TestSceneNotificationOverlay.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 8504da69a7..3d13e98865 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -48,6 +48,40 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestDismissWithoutActivationFling() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("start drag", () => + { + InputManager.MoveMouseTo(notification.ChildrenOfType().Single()); + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(notification.ChildrenOfType().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0)); + }); + + AddStep("fling away", () => + { + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was not activated", () => !activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0); + } + [Test] public void TestDismissWithoutActivationCloseButton() { @@ -75,6 +109,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for closed", () => notification.WasClosed); AddAssert("was not activated", () => !activated); AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0); } [Test] From 431e84f99207fb0a429b85ad0a61ab7f7f55a206 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 19:09:24 +0900 Subject: [PATCH 099/583] Cancel in progress CI runs when new commits are pushed Same as https://github.com/ppy/osu-web/pull/9268. Should hopefully help with organisation concurrent limits. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef729a779f..082e0d247c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ on: [push, pull_request] name: Continuous Integration +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: inspect-code: From 837cc760195cd5c0c4aa5c445657a42151b8b7ca Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 12 Sep 2022 12:50:16 +0200 Subject: [PATCH 100/583] Create TestSceneSelectionBlueprintDeselection.cs --- .../TestSceneSelectionBlueprintDeselection.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs new file mode 100644 index 0000000000..fdc05e9456 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSelectionBlueprintDeselection : TestSceneOsuEditor + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestSingleDeleteAtSameTime() + { + HitCircle? circle1 = null; + HitCircle? circle2 = null; + + AddStep("add two circles at the same time", () => + { + circle1 = new HitCircle(); + circle2 = new HitCircle(); + EditorClock.Seek(0); + EditorBeatmap.Add(circle1); + EditorBeatmap.Add(circle2); + EditorBeatmap.SelectedHitObjects.Add(circle1); + EditorBeatmap.SelectedHitObjects.Add(circle2); + }); + + AddStep("delete the first circle", () => EditorBeatmap.Remove(circle1)); + } + } +} From f31deaef7ca8dd0f7defd794c77f8a8b6f0f718c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 19:51:49 +0900 Subject: [PATCH 101/583] Simplify skin bindable flow Now, nothing touches the configuration apart from `OsuGame`, making everything else flow better and avoid weird cyclic set bugs. Closes https://github.com/ppy/osu/issues/20234. --- osu.Game/OsuGame.cs | 24 ++----- .../Overlays/Settings/Sections/SkinSection.cs | 62 ++++--------------- osu.Game/Skinning/SkinManager.cs | 20 +++++- 3 files changed, 36 insertions(+), 70 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9e2384322a..0d115f62fe 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -56,14 +56,12 @@ using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; -using osu.Game.Skinning; using osu.Game.Skinning.Editor; using osu.Game.Updater; using osu.Game.Users; using osu.Game.Utils; using osuTK.Graphics; using Sentry; -using Logger = osu.Framework.Logging.Logger; namespace osu.Game { @@ -294,25 +292,13 @@ namespace osu.Game Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ShortName; - // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); + + // Transfer skin from config to realm instance once on startup. + SkinManager.SetSkinFromConfiguration(configSkin.Value); + + // Transfer any runtime changes back to configuration file. SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID.ToString(); - configSkin.ValueChanged += skinId => - { - Live skinInfo = null; - - if (Guid.TryParse(skinId.NewValue, out var guid)) - skinInfo = SkinManager.Query(s => s.ID == guid); - - if (skinInfo == null) - { - if (guid == SkinInfo.CLASSIC_SKIN) - skinInfo = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged(); - } - - SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLiveUnmanaged(); - }; - configSkin.TriggerChange(); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index d23ef7e3e7..13249e37ca 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; @@ -36,9 +35,6 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.PaintBrush }; - private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() }; - private readonly Bindable configBindable = new Bindable(); - private static readonly Live random_skin_info = new SkinInfo { ID = SkinInfo.RANDOM_SKIN, @@ -56,13 +52,14 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) + private void load([CanBeNull] SkinEditorOverlay skinEditor) { Children = new Drawable[] { skinDropdown = new SkinSettingsDropdown { LabelText = SkinSettingsStrings.CurrentSkin, + Current = skins.CurrentSkinInfo, Keywords = new[] { @"skins" } }, new SettingsButton @@ -73,47 +70,30 @@ namespace osu.Game.Overlays.Settings.Sections new ExportSkinButton(), new DeleteSkinButton(), }; - - config.BindWith(OsuSetting.Skin, configBindable); } protected override void LoadComplete() { base.LoadComplete(); - skinDropdown.Current = dropdownBindable; + skinDropdown.Current = skins.CurrentSkinInfo; realmSubscription = realm.RegisterForNotifications(_ => realm.Realm.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); - configBindable.BindValueChanged(_ => Scheduler.AddOnce(updateSelectedSkinFromConfig)); - - dropdownBindable.BindValueChanged(dropdownSelectionChanged); - } - - private void dropdownSelectionChanged(ValueChangedEvent> skin) - { - // Only handle cases where it's clear the user has intent to change skins. - if (skin.OldValue == null) return; - - if (skin.NewValue.Equals(random_skin_info)) + skinDropdown.Current.BindValueChanged(skin => { - var skinBefore = skins.CurrentSkinInfo.Value; - - skins.SelectRandomSkin(); - - if (skinBefore == skins.CurrentSkinInfo.Value) + if (skin.NewValue == random_skin_info) { - // the random selection didn't change the skin, so we should manually update the dropdown to match. - dropdownBindable.Value = skins.CurrentSkinInfo.Value; + // before selecting random, set the skin back to the previous selection. + // this is done because at this point it will be random_skin_info, and would + // cause SelectRandomSkin to be unable to skip the previous selection. + skins.CurrentSkinInfo.Value = skin.OldValue; + skins.SelectRandomSkin(); } - - return; - } - - configBindable.Value = skin.NewValue.ID.ToString(); + }); } private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -132,25 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections dropdownItems.Add(skin.ToLive(realm)); dropdownItems.Insert(protectedCount, random_skin_info); - Schedule(() => - { - skinDropdown.Items = dropdownItems; - - updateSelectedSkinFromConfig(); - }); - } - - private void updateSelectedSkinFromConfig() - { - if (!skinDropdown.Items.Any()) - return; - - Live skin = null; - - if (Guid.TryParse(configBindable.Value, out var configId)) - skin = skinDropdown.Items.FirstOrDefault(s => s.ID == configId); - - dropdownBindable.Value = skin ?? skinDropdown.Items.First(); + Schedule(() => skinDropdown.Items = dropdownItems); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index f677cebe51..7ffea3b54f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -119,7 +119,9 @@ namespace osu.Game.Skinning Realm.Run(r => { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = r.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = r.All() + .Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID) + .ToArray(); if (randomChoices.Length == 0) { @@ -297,5 +299,21 @@ namespace osu.Game.Skinning Delete(items.ToList(), silent); }); } + + public void SetSkinFromConfiguration(string guidString) + { + Live skinInfo = null; + + if (Guid.TryParse(guidString, out var guid)) + skinInfo = Query(s => s.ID == guid); + + if (skinInfo == null) + { + if (guid == SkinInfo.CLASSIC_SKIN) + skinInfo = DefaultLegacySkin.SkinInfo; + } + + CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.SkinInfo; + } } } From 2b4b14ca9990a4626fefcbecf624ca2d97bf4244 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 20:06:52 +0900 Subject: [PATCH 102/583] Fix `SongProgress` invalidating too often This regressed with https://github.com/ppy/osu/pull/19556. Rather than try and figure whether that new container needs to handle size differently, this is a simple solution. Height was taken from a runtime check (maxes out at about 14.5). Closes #20235. --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index d0eb8f8ca1..b3d5066a9e 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -47,7 +47,10 @@ namespace osu.Game.Screens.Play.HUD if (clock != null) gameplayClock = clock; - AutoSizeAxes = Axes.Y; + // Lock height so changes in text autosize (if character height changes) + // don't cause parent invalidation. + Height = 14; + Children = new Drawable[] { new Container From 88107108eea0c440babac6e69c1367274c509e18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 20:18:40 +0900 Subject: [PATCH 103/583] Add ability to flick notifications to the right to store for later --- .../TestSceneNotificationOverlay.cs | 37 +++++++++++++++++++ .../Overlays/NotificationOverlayToastTray.cs | 5 +++ .../Overlays/Notifications/Notification.cs | 4 ++ 3 files changed, 46 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 3d13e98865..e978b57ba4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -48,6 +48,43 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestForwardWithFlingRight() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("start drag", () => + { + InputManager.MoveMouseTo(notification.ChildrenOfType().Single()); + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(notification.ChildrenOfType().Single().ScreenSpaceDrawQuad.Centre + new Vector2(500, 0)); + }); + + AddStep("fling away", () => + { + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("was not closed", () => !notification.WasClosed); + AddAssert("was not activated", () => !activated); + AddAssert("is not read", () => !notification.Read); + AddAssert("is not toast", () => !notification.IsInToastTray); + + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("unread count one", () => notificationOverlay.UnreadCount.Value == 1); + } + [Test] public void TestDismissWithoutActivationFling() { diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 1da05dab75..e3f8734581 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -100,6 +100,8 @@ namespace osu.Game.Overlays { ++runningDepth; + notification.ForwardToOverlay = () => forwardNotification(notification); + int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; toastFlow.Insert(depth, notification); @@ -130,6 +132,9 @@ namespace osu.Game.Overlays private void forwardNotification(Notification notification) { + if (!notification.IsInToastTray) + return; + Debug.Assert(notification.Parent == toastFlow); // Temporarily remove from flow so we can animate the position off to the right. diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 6c1b43c3b5..885bb2fc6c 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -42,6 +42,8 @@ namespace osu.Game.Overlays.Notifications /// public Func? Activated; + public Action? ForwardToOverlay { get; set; } + /// /// Should we show at the top of our section on display? /// @@ -310,6 +312,8 @@ namespace osu.Game.Overlays.Notifications { if (Rotation < -10 || velocity.X < -0.3f) notification.Close(true); + else if (X > 30 || velocity.X > 0.3f) + notification.ForwardToOverlay?.Invoke(); else ResetPosition(); From 8cbc0502aea885ce2468ccfda0b457297382ead9 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Mon, 12 Sep 2022 15:51:18 +0300 Subject: [PATCH 104/583] Inline `CalculateEffect` --- osu.Game/Overlays/Mods/ModsEffectDisplay.cs | 27 ++++----------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs index 3b112fdbd3..eed3181e49 100644 --- a/osu.Game/Overlays/Mods/ModsEffectDisplay.cs +++ b/osu.Game/Overlays/Mods/ModsEffectDisplay.cs @@ -49,22 +49,9 @@ namespace osu.Game.Overlays.Mods /// protected abstract LocalisableString Label { get; } - /// - /// Calculates, does current value increase difficulty or decrease it. - /// - /// Value to calculate for. Will arrive from bindable once it changes. - /// Effect of the value. - /// - /// Default implementation compares the value with ., bigger value counts as difficulty increase. - /// - protected virtual ModEffect CalculateEffect(double value) - { - return CalculateForSign(value.CompareTo(Current.Default)); - } - protected virtual float ValueAreaWidth => 56; - protected virtual string CounterFormat => "N0"; + protected virtual string CounterFormat => @"N0"; protected override Container Content => content; @@ -159,7 +146,7 @@ namespace osu.Game.Overlays.Mods { Current.BindValueChanged(e => { - var effect = CalculateEffect(e.NewValue); + var effect = CalculateEffectForComparison(e.NewValue.CompareTo(Current.Default)); setColours(effect); }, true); } @@ -192,14 +179,12 @@ namespace osu.Game.Overlays.Mods } } - #region Quick implementations for CalculateEffect - /// /// Converts signed integer into . Negative values are counted as difficulty reduction, positive as increase. /// - /// Value to convert. Can be obtained from CompareTo. - /// Effect. - protected ModEffect CalculateForSign(int comparison) + /// Value to convert. Will arrive from comparison between bindable once it changes and it's . + /// Effect of the value. + protected virtual ModEffect CalculateEffectForComparison(int comparison) { if (comparison == 0) return ModEffect.NotChanged; @@ -209,8 +194,6 @@ namespace osu.Game.Overlays.Mods return ModEffect.DifficultyIncrease; } - #endregion - protected enum ModEffect { NotChanged, From 8400de4b2ea1922138fc0d581f0ac8c3a531bd12 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 12 Sep 2022 17:50:11 +0200 Subject: [PATCH 105/583] invoking hitobject updated before invoking removed --- .../TestSceneSelectionBlueprintDeselection.cs | 35 +++++++++++++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 45 +++++++++++++------ 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs index fdc05e9456..b00582d6f3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs @@ -1,10 +1,12 @@ // 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 NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Editor { @@ -16,13 +18,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestSingleDeleteAtSameTime() { HitCircle? circle1 = null; - HitCircle? circle2 = null; AddStep("add two circles at the same time", () => { - circle1 = new HitCircle(); - circle2 = new HitCircle(); EditorClock.Seek(0); + circle1 = new HitCircle(); + var circle2 = new HitCircle(); EditorBeatmap.Add(circle1); EditorBeatmap.Add(circle2); EditorBeatmap.SelectedHitObjects.Add(circle1); @@ -31,5 +32,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("delete the first circle", () => EditorBeatmap.Remove(circle1)); } + + [Test] + public void TestBigStackDeleteAtSameTime() + { + AddStep("add 20 circles at the same time", () => + { + EditorClock.Seek(0); + + for (int i = 0; i < 20; i++) + { + EditorBeatmap.Add(new HitCircle()); + } + }); + + AddStep("select half of the circles", () => + { + foreach (var hitObject in EditorBeatmap.HitObjects.SkipLast(10).Reverse()) + { + EditorBeatmap.SelectedHitObjects.Add(hitObject); + } + }); + + AddStep("delete all selected circles", () => + { + InputManager.PressKey(Key.Delete); + InputManager.ReleaseKey(Key.Delete); + }); + } } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 8aa754b305..95b848fec0 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; namespace osu.Game.Screens.Edit @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit private readonly IBeatmapProcessor beatmapProcessor; - private readonly Dictionary> startTimeBindables = new Dictionary>(); + private readonly Dictionary> hitObjectBindables = new Dictionary>(); public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null) { @@ -97,7 +98,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); foreach (var obj in HitObjects) - trackStartTime(obj); + trackBindables(obj); } /// @@ -222,7 +223,7 @@ namespace osu.Game.Screens.Edit /// The to insert. public void Insert(int index, HitObject hitObject) { - trackStartTime(hitObject); + trackBindables(hitObject); mutableHitObjects.Insert(index, hitObject); @@ -299,9 +300,9 @@ namespace osu.Game.Screens.Edit mutableHitObjects.RemoveAt(index); - var bindable = startTimeBindables[hitObject]; - bindable.UnbindAll(); - startTimeBindables.Remove(hitObject); + var bindables = hitObjectBindables[hitObject]; + bindables.ForEach(b => b.UnbindAll()); + hitObjectBindables.Remove(hitObject); BeginChange(); batchPendingDeletes.Add(hitObject); @@ -325,25 +326,25 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PreProcess(); + foreach (var h in batchPendingUpdates) processHitObject(h); foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); - foreach (var h in batchPendingUpdates) processHitObject(h); beatmapProcessor?.PostProcess(); // callbacks may modify the lists so let's be safe about it + var updates = batchPendingUpdates.ToArray(); + batchPendingUpdates.Clear(); + var deletes = batchPendingDeletes.ToArray(); batchPendingDeletes.Clear(); var inserts = batchPendingInserts.ToArray(); batchPendingInserts.Clear(); - var updates = batchPendingUpdates.ToArray(); - batchPendingUpdates.Clear(); - + foreach (var h in updates) HitObjectUpdated?.Invoke(h); foreach (var h in deletes) HitObjectRemoved?.Invoke(h); foreach (var h in inserts) HitObjectAdded?.Invoke(h); - foreach (var h in updates) HitObjectUpdated?.Invoke(h); updateInProgress.Value = false; } @@ -355,10 +356,12 @@ namespace osu.Game.Screens.Edit private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty); - private void trackStartTime(HitObject hitObject) + private void trackBindables(HitObject hitObject) { - startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindables[hitObject].ValueChanged += _ => + var bindables = new List(3); + + var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.ValueChanged += _ => { // For now we'll remove and re-add the hitobject. This is not optimal and can be improved if required. mutableHitObjects.Remove(hitObject); @@ -368,6 +371,20 @@ namespace osu.Game.Screens.Edit Update(hitObject); }; + bindables.Add(startTimeBindable); + + if (hitObject is IHasComboInformation hasCombo) + { + var comboIndexBindable = hasCombo.ComboIndexBindable.GetBoundCopy(); + comboIndexBindable.ValueChanged += _ => Update(hitObject); + bindables.Add(comboIndexBindable); + + var indexInCurrentComboBindable = hasCombo.IndexInCurrentComboBindable.GetBoundCopy(); + indexInCurrentComboBindable.ValueChanged += _ => Update(hitObject); + bindables.Add(indexInCurrentComboBindable); + } + + hitObjectBindables[hitObject] = bindables; } private int findInsertionIndex(IReadOnlyList list, double startTime) From a5b962d9a3bc55beac3b4f89aa1544879adf6c20 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Sep 2022 01:37:23 +0300 Subject: [PATCH 106/583] Add failing test case --- .../Online/TestSceneBeatmapListingOverlay.cs | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 8ef120d252..2c9b34e4a7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Online private OsuConfigManager localConfig; + private bool returnCursorOnResponse; + [BackgroundDependencyLoader] private void load() { @@ -61,6 +63,7 @@ namespace osu.Game.Tests.Visual.Online searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse { BeatmapSets = setsForResponse, + Cursor = returnCursorOnResponse ? new Cursor() : null, }); return true; @@ -106,7 +109,7 @@ namespace osu.Game.Tests.Visual.Online { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); - AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray())); AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); @@ -127,10 +130,10 @@ namespace osu.Game.Tests.Visual.Online { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); - AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray())); assertAllCardsOfType(100); - AddStep("show more results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 30).ToArray())); + AddStep("show more results", () => fetchFor(getManyBeatmaps(30).ToArray())); assertAllCardsOfType(30); } @@ -139,7 +142,7 @@ namespace osu.Game.Tests.Visual.Online { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); - AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray())); assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Extra, viaConfig); @@ -161,7 +164,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); placeholderShown(); - AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray())); AddUntilStep("wait for loaded", () => this.ChildrenOfType().Count() == 100); AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); @@ -180,6 +183,32 @@ namespace osu.Game.Tests.Visual.Online }); } + /// + /// During pagination, the first beatmap of the second page may be a duplicate of the last beatmap from the previous page. + /// This is currently the case with osu!web API due to ES relevance score's presence in the response cursor. + /// See: https://github.com/ppy/osu-web/issues/9270 + /// + [Test] + public void TestDuplicatedBeatmapOnlyShowsOnce() + { + APIBeatmapSet beatmapSet = null; + + AddStep("show many results", () => + { + beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSet.Title = "last beatmap of first page"; + + fetchFor(getManyBeatmaps(49).Append(beatmapSet).ToArray(), true); + }); + AddUntilStep("wait for loaded", () => this.ChildrenOfType().Count() == 50); + + AddStep("set next page", () => setSearchResponse(getManyBeatmaps(49).Prepend(beatmapSet).ToArray(), false)); + AddStep("scroll to end", () => overlay.ChildrenOfType().Single().ScrollToEnd()); + AddUntilStep("wait for loaded", () => this.ChildrenOfType().Count() == 99); + + AddAssert("beatmap not duplicated", () => overlay.ChildrenOfType().Count(c => c.BeatmapSet.Equals(beatmapSet)) == 1); + } + [Test] public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults() { @@ -336,15 +365,25 @@ namespace osu.Game.Tests.Visual.Online private static int searchCount; - private void fetchFor(params APIBeatmapSet[] beatmaps) + private APIBeatmapSet[] getManyBeatmaps(int count) => Enumerable.Range(0, count).Select(_ => CreateAPIBeatmapSet(Ruleset.Value)).ToArray(); + + private void fetchFor(params APIBeatmapSet[] beatmaps) => fetchFor(beatmaps, false); + + private void fetchFor(APIBeatmapSet[] beatmaps, bool hasNextPage) { - setsForResponse.Clear(); - setsForResponse.AddRange(beatmaps); + setSearchResponse(beatmaps, hasNextPage); // trigger arbitrary change for fetching. searchControl.Query.Value = $"search {searchCount++}"; } + private void setSearchResponse(APIBeatmapSet[] beatmaps, bool hasNextPage) + { + setsForResponse.Clear(); + setsForResponse.AddRange(beatmaps); + returnCursorOnResponse = hasNextPage; + } + private void setRankAchievedFilter(ScoreRank[] ranks) { AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () => From 07f577a0c67314035588ba33856e135643632a02 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Sep 2022 01:38:52 +0300 Subject: [PATCH 107/583] Fix beatmap listing potentially showing duplicate beatmap cards --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 13 ++++++++++++- osu.Game/Overlays/BeatmapListingOverlay.cs | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index b9e0a4e6cb..70312a1535 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays; namespace osu.Game.Beatmaps.Drawables.Cards { - public abstract class BeatmapCard : OsuClickableContainer + public abstract class BeatmapCard : OsuClickableContainer, IEquatable { public const float TRANSITION_DURATION = 400; public const float CORNER_RADIUS = 10; @@ -96,5 +96,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards throw new ArgumentOutOfRangeException(nameof(size), size, @"Unsupported card size"); } } + + public bool Equals(BeatmapCard? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return BeatmapSet.Equals(other.BeatmapSet); + } + + public override bool Equals(object obj) => obj is BeatmapCard other && Equals(other); + public override int GetHashCode() => BeatmapSet.GetHashCode(); } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 3136492af0..70c6c60ecd 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -176,6 +176,8 @@ namespace osu.Game.Overlays } else { + newCards = newCards.Except(foundContent); + panelLoadTask = LoadComponentsAsync(newCards, loaded => { lastFetchDisplayedTime = Time.Current; @@ -185,7 +187,7 @@ namespace osu.Game.Overlays } } - private BeatmapCard[] createCardsFor(IEnumerable beatmapSets) => beatmapSets.Select(set => BeatmapCard.Create(set, filterControl.CardSize.Value).With(c => + private IEnumerable createCardsFor(IEnumerable beatmapSets) => beatmapSets.Select(set => BeatmapCard.Create(set, filterControl.CardSize.Value).With(c => { c.Anchor = Anchor.TopCentre; c.Origin = Anchor.TopCentre; From 718f8c4ee206bfeaa52177730fc74cb0c7d404b6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Sep 2022 01:09:42 +0200 Subject: [PATCH 108/583] revert the fix --- osu.Game/Screens/Edit/EditorBeatmap.cs | 45 ++++++++------------------ 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 95b848fec0..8aa754b305 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -16,7 +16,6 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; namespace osu.Game.Screens.Edit @@ -80,7 +79,7 @@ namespace osu.Game.Screens.Edit private readonly IBeatmapProcessor beatmapProcessor; - private readonly Dictionary> hitObjectBindables = new Dictionary>(); + private readonly Dictionary> startTimeBindables = new Dictionary>(); public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null) { @@ -98,7 +97,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); foreach (var obj in HitObjects) - trackBindables(obj); + trackStartTime(obj); } /// @@ -223,7 +222,7 @@ namespace osu.Game.Screens.Edit /// The to insert. public void Insert(int index, HitObject hitObject) { - trackBindables(hitObject); + trackStartTime(hitObject); mutableHitObjects.Insert(index, hitObject); @@ -300,9 +299,9 @@ namespace osu.Game.Screens.Edit mutableHitObjects.RemoveAt(index); - var bindables = hitObjectBindables[hitObject]; - bindables.ForEach(b => b.UnbindAll()); - hitObjectBindables.Remove(hitObject); + var bindable = startTimeBindables[hitObject]; + bindable.UnbindAll(); + startTimeBindables.Remove(hitObject); BeginChange(); batchPendingDeletes.Add(hitObject); @@ -326,25 +325,25 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PreProcess(); - foreach (var h in batchPendingUpdates) processHitObject(h); foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); + foreach (var h in batchPendingUpdates) processHitObject(h); beatmapProcessor?.PostProcess(); // callbacks may modify the lists so let's be safe about it - var updates = batchPendingUpdates.ToArray(); - batchPendingUpdates.Clear(); - var deletes = batchPendingDeletes.ToArray(); batchPendingDeletes.Clear(); var inserts = batchPendingInserts.ToArray(); batchPendingInserts.Clear(); - foreach (var h in updates) HitObjectUpdated?.Invoke(h); + var updates = batchPendingUpdates.ToArray(); + batchPendingUpdates.Clear(); + foreach (var h in deletes) HitObjectRemoved?.Invoke(h); foreach (var h in inserts) HitObjectAdded?.Invoke(h); + foreach (var h in updates) HitObjectUpdated?.Invoke(h); updateInProgress.Value = false; } @@ -356,12 +355,10 @@ namespace osu.Game.Screens.Edit private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty); - private void trackBindables(HitObject hitObject) + private void trackStartTime(HitObject hitObject) { - var bindables = new List(3); - - var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindable.ValueChanged += _ => + startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindables[hitObject].ValueChanged += _ => { // For now we'll remove and re-add the hitobject. This is not optimal and can be improved if required. mutableHitObjects.Remove(hitObject); @@ -371,20 +368,6 @@ namespace osu.Game.Screens.Edit Update(hitObject); }; - bindables.Add(startTimeBindable); - - if (hitObject is IHasComboInformation hasCombo) - { - var comboIndexBindable = hasCombo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.ValueChanged += _ => Update(hitObject); - bindables.Add(comboIndexBindable); - - var indexInCurrentComboBindable = hasCombo.IndexInCurrentComboBindable.GetBoundCopy(); - indexInCurrentComboBindable.ValueChanged += _ => Update(hitObject); - bindables.Add(indexInCurrentComboBindable); - } - - hitObjectBindables[hitObject] = bindables; } private int findInsertionIndex(IReadOnlyList list, double startTime) From 5fba21d658335f88010cc1748891a9be65f6ea01 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Sep 2022 02:21:01 +0300 Subject: [PATCH 109/583] Rename parameter to clarify purpose --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 7e7f0f027d..3f4d13c082 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -18,14 +18,13 @@ namespace osu.Game.Skinning private readonly HitResult result; private readonly float finalScale; + private readonly bool forceTransforms; - private readonly bool allowTransforms; - - public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool allowTransforms = false) + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) { this.result = result; this.finalScale = finalScale; - this.allowTransforms = allowTransforms; + this.forceTransforms = forceTransforms; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; @@ -47,7 +46,7 @@ namespace osu.Game.Skinning this.Delay(fade_out_delay).FadeOut(fade_out_length); // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece. - if (!allowTransforms && animation?.FrameCount > 1) + if (animation?.FrameCount > 1 && !forceTransforms) return; switch (result) From a1f4724685c356272ca11342b0eff4eff3470b46 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Sep 2022 01:38:29 +0200 Subject: [PATCH 110/583] moved the location of the tests --- .../Editing}/TestSceneSelectionBlueprintDeselection.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) rename {osu.Game.Rulesets.Osu.Tests/Editor => osu.Game.Tests/Visual/Editing}/TestSceneSelectionBlueprintDeselection.cs (88%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs b/osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs similarity index 88% rename from osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs rename to osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs index b00582d6f3..6141e5f31c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs @@ -4,14 +4,18 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests.Editor +namespace osu.Game.Tests.Visual.Editing { - public class TestSceneSelectionBlueprintDeselection : TestSceneOsuEditor + public class TestSceneSelectionBlueprintDeselection : EditorTestScene { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); [Test] From a6f68e4a908dc8a6593445f6ee639b1912577b66 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Sep 2022 03:08:13 +0300 Subject: [PATCH 111/583] Fix NRT inspections --- osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 4510fda11d..1042341337 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); - var difficultyAdjustMod = ruleset.CreateMod(); + var difficultyAdjustMod = ruleset.CreateMod().AsNonNull(); difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); - var difficultyAdjustMod = ruleset.CreateMod(); + var difficultyAdjustMod = ruleset.CreateMod().AsNonNull(); var originalDifficulty = advancedStats.BeatmapInfo.Difficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); From fd48249eef42c8af74fb50373744d58bc80eb3f7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Sep 2022 02:20:52 +0200 Subject: [PATCH 112/583] fix with new event --- .../Components/HitObjectOrderedSelectionContainer.cs | 6 ++---- osu.Game/Screens/Edit/EditorBeatmap.cs | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index d6fd07c998..2d8dca9c2d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -24,11 +24,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - editorBeatmap.HitObjectUpdated += hitObjectUpdated; + editorBeatmap.SelectionBlueprintsShouldBeSorted += SortInternal; } - private void hitObjectUpdated(HitObject _) => SortInternal(); - public override void Add(SelectionBlueprint drawable) { SortInternal(); @@ -72,7 +70,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.Dispose(isDisposing); if (editorBeatmap != null) - editorBeatmap.HitObjectUpdated -= hitObjectUpdated; + editorBeatmap.SelectionBlueprintsShouldBeSorted -= SortInternal; } } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 8aa754b305..82f2187901 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -48,6 +48,11 @@ namespace osu.Game.Screens.Edit /// public event Action HitObjectUpdated; + /// + /// Invoked after is updated during and blueprints need to be sorted immediately to prevent a crash. + /// + public event Action SelectionBlueprintsShouldBeSorted; + /// /// All currently selected s. /// @@ -331,6 +336,9 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); + // Signal selection blueprint sorting because it is possible that the beatmap processor changed the order of the selection blueprints + SelectionBlueprintsShouldBeSorted?.Invoke(); + // callbacks may modify the lists so let's be safe about it var deletes = batchPendingDeletes.ToArray(); batchPendingDeletes.Clear(); From 0ca4be5e5bf43db46950b56c900891e1fd1097d4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Sep 2022 03:45:31 +0300 Subject: [PATCH 113/583] Fix one more inspection --- .../UI/DrawableRulesetDependencies.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index cffbf2c9a1..52387a5740 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -53,20 +53,17 @@ namespace osu.Game.Rulesets.UI { var resources = ruleset.CreateResourceStore(); - if (resources != null) - { - var host = parent.Get(); + var host = parent.Get(); - TextureStore = new TextureStore(host.Renderer, parent.Get().CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); - CacheAs(TextureStore = new FallbackTextureStore(host.Renderer, TextureStore, parent.Get())); + TextureStore = new TextureStore(host.Renderer, parent.Get().CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + CacheAs(TextureStore = new FallbackTextureStore(host.Renderer, TextureStore, parent.Get())); - SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); - SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; - CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get())); + SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); + SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; + CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get())); - ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore(resources, @"Shaders")); - CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get())); - } + ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore(resources, @"Shaders")); + CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get())); RulesetConfigManager = parent.Get().GetConfigFor(ruleset); if (RulesetConfigManager != null) From 6bf6b7e125639aa3a20d972bb87cf2d712975b41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 13:07:23 +0900 Subject: [PATCH 114/583] Fix null considerations in `DrawableRulesetDependencies` --- .../UI/DrawableRulesetDependencies.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index 52387a5740..ad52b4affc 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -12,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; @@ -44,9 +43,9 @@ namespace osu.Game.Rulesets.UI public ShaderManager ShaderManager { get; } /// - /// The ruleset config manager. + /// The ruleset config manager. May be null if ruleset does not expose a configuration manager. /// - public IRulesetConfigManager RulesetConfigManager { get; private set; } + public IRulesetConfigManager? RulesetConfigManager { get; } public DrawableRulesetDependencies(Ruleset ruleset, IReadOnlyDependencyContainer parent) : base(parent) @@ -93,10 +92,9 @@ namespace osu.Game.Rulesets.UI isDisposed = true; - SampleStore?.Dispose(); - TextureStore?.Dispose(); - ShaderManager?.Dispose(); - RulesetConfigManager = null; + if (ShaderManager.IsNotNull()) SampleStore.Dispose(); + if (TextureStore.IsNotNull()) TextureStore.Dispose(); + if (ShaderManager.IsNotNull()) ShaderManager.Dispose(); } #endregion @@ -157,7 +155,7 @@ namespace osu.Game.Rulesets.UI public void Dispose() { - primary?.Dispose(); + if (primary.IsNotNull()) primary.Dispose(); } } @@ -182,7 +180,7 @@ namespace osu.Game.Rulesets.UI protected override void Dispose(bool disposing) { base.Dispose(disposing); - primary?.Dispose(); + if (primary.IsNotNull()) primary.Dispose(); } } @@ -198,12 +196,12 @@ namespace osu.Game.Rulesets.UI this.fallback = fallback; } - public override byte[] LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name); + public override byte[]? LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name); protected override void Dispose(bool disposing) { base.Dispose(disposing); - primary?.Dispose(); + if (primary.IsNotNull()) primary.Dispose(); } } } From d368f37c3ab8c360cd1a31138543118f90fd1625 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 13:50:59 +0900 Subject: [PATCH 115/583] Remove redundant second `Current` set operation --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 13249e37ca..4787b07af8 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -76,8 +76,6 @@ namespace osu.Game.Overlays.Settings.Sections { base.LoadComplete(); - skinDropdown.Current = skins.CurrentSkinInfo; - realmSubscription = realm.RegisterForNotifications(_ => realm.Realm.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. From 0fcd9e02f632230d3d1b884e88a5d4c0891f2c9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 14:01:40 +0900 Subject: [PATCH 116/583] Fix skin dropdown not updating correctly when skin is changed externally --- osu.Game/Database/Live.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index 52e1d420f7..a21b7f606b 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -53,6 +53,8 @@ namespace osu.Game.Database public bool Equals(Live? other) => ID == other?.ID; + public override int GetHashCode() => HashCode.Combine(ID); + public override string ToString() => PerformRead(i => i.ToString()); } } From ac034bffebe3597716bdaf14395452925881549d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 14:21:54 +0900 Subject: [PATCH 117/583] Fix potential crash if multiplayer spectator load is aborted early --- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 8e79c89685..6e939c3916 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Game.Beatmaps; @@ -34,8 +35,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken cancellationToken) { + // HUD overlay may not be loaded if load has been cancelled early. + if (cancellationToken.IsCancellationRequested) + return; + HUDOverlay.PlayerSettingsOverlay.Expire(); HUDOverlay.HoldToQuit.Expire(); } From f53507828c0bfdb7909c895a0d921c6de31ef9ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 14:59:30 +0900 Subject: [PATCH 118/583] Rename event to be more generic (and add comprehensive xmldoc) --- .../Components/HitObjectOrderedSelectionContainer.cs | 4 ++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 2d8dca9c2d..18bb6284b8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - editorBeatmap.SelectionBlueprintsShouldBeSorted += SortInternal; + editorBeatmap.BeatmapReprocessed += SortInternal; } public override void Add(SelectionBlueprint drawable) @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.Dispose(isDisposing); if (editorBeatmap != null) - editorBeatmap.SelectionBlueprintsShouldBeSorted -= SortInternal; + editorBeatmap.BeatmapReprocessed -= SortInternal; } } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 82f2187901..16c0064e80 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -49,9 +49,13 @@ namespace osu.Game.Screens.Edit public event Action HitObjectUpdated; /// - /// Invoked after is updated during and blueprints need to be sorted immediately to prevent a crash. + /// Invoked after any state changes occurred which triggered a beatmap reprocess via an . /// - public event Action SelectionBlueprintsShouldBeSorted; + /// + /// Beatmap processing may change the order of hitobjects. This event gives external components a chance to handle any changes + /// not covered by the / / events. + /// + public event Action BeatmapReprocessed; /// /// All currently selected s. @@ -336,8 +340,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); - // Signal selection blueprint sorting because it is possible that the beatmap processor changed the order of the selection blueprints - SelectionBlueprintsShouldBeSorted?.Invoke(); + BeatmapReprocessed?.Invoke(); // callbacks may modify the lists so let's be safe about it var deletes = batchPendingDeletes.ToArray(); From 608c893b23f2007e6793de9cff1e72aec92fe70b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 15:03:13 +0900 Subject: [PATCH 119/583] Add basic test guarantees --- .../Editing/TestSceneSelectionBlueprintDeselection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs b/osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs index 6141e5f31c..5c933468be 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSelectionBlueprintDeselection.cs @@ -28,13 +28,17 @@ namespace osu.Game.Tests.Visual.Editing EditorClock.Seek(0); circle1 = new HitCircle(); var circle2 = new HitCircle(); + EditorBeatmap.Add(circle1); EditorBeatmap.Add(circle2); + EditorBeatmap.SelectedHitObjects.Add(circle1); EditorBeatmap.SelectedHitObjects.Add(circle2); }); AddStep("delete the first circle", () => EditorBeatmap.Remove(circle1)); + AddAssert("one hitobject remains", () => EditorBeatmap.HitObjects.Count == 1); + AddAssert("one hitobject selected", () => EditorBeatmap.SelectedHitObjects.Count == 1); } [Test] @@ -63,6 +67,9 @@ namespace osu.Game.Tests.Visual.Editing InputManager.PressKey(Key.Delete); InputManager.ReleaseKey(Key.Delete); }); + + AddAssert("10 hitobjects remain", () => EditorBeatmap.HitObjects.Count == 10); + AddAssert("no hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); } } } From 3490b160563b74ab4a227d1f3a1885d233ab16da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Sep 2022 01:10:55 +0300 Subject: [PATCH 120/583] Add simplified `SoloGameplayLeaderboard` implementation Note that this doesn't support waiting until leaderboard finishes fetching scores. --- .../Play/HUD/SoloGameplayLeaderboard.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs new file mode 100644 index 0000000000..20af036ec9 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play.HUD +{ + public class SoloGameplayLeaderboard : GameplayLeaderboard + { + [BackgroundDependencyLoader] + private void load(IAPIProvider api, ScoreProcessor processor, ILeaderboard leaderboard) + { + var local = Add(api.LocalUser.Value, true); + local.TotalScore.BindTarget = processor.TotalScore; + local.Accuracy.BindTarget = processor.Accuracy; + local.Combo.BindTarget = processor.Combo; + + foreach (var player in leaderboard.Scores) + { + // todo: APIUser is pain for IScoreInfo. + var score = Add(new APIUser + { + Id = player.User.OnlineID, + Username = player.User.Username, + }, false); + + score.TotalScore.Value = player.TotalScore; + score.Accuracy.Value = player.Accuracy; + score.Combo.Value = player.MaxCombo; + } + } + } +} From fed9a47866664a689ac6dd0c916e578b7a653970 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Sep 2022 01:12:57 +0300 Subject: [PATCH 121/583] Add test coverage --- .../TestSceneSoloGameplayLeaderboard.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs new file mode 100644 index 0000000000..0ea2271fbf --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSoloGameplayLeaderboard : OsuTestScene + { + [Cached] + private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); + + private SoloGameplayLeaderboard leaderboard = null!; + + [SetUp] + public void SetUp() => Schedule(() => + { + var trackingUser = new APIUser + { + Username = "local user", + Id = 2, + }; + + Child = leaderboard = new SoloGameplayLeaderboard(trackingUser) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Expanded = { Value = true }, + }; + + leaderboard.ShowScores(createSampleScores()); + }); + + [Test] + public void TestLocalUser() + { + AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v); + AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v); + AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v); + } + + private static List createSampleScores() + { + return new[] + { + new ScoreInfo { User = new APIUser { Username = @"peppy" }, TotalScore = RNG.Next(500000, 1000000) }, + new ScoreInfo { User = new APIUser { Username = @"smoogipoo" }, TotalScore = RNG.Next(500000, 1000000) }, + new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) }, + new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) }, + new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) }, + }.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 500000 + i * 10000 })).ToList(); + } + } +} From 4c669e2bce8889bef580d5936fa7320f89e83358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 16:04:38 +0900 Subject: [PATCH 122/583] Track local `Player.Score`'s user rather than using `APIProvider` --- .../Screens/Play/HUD/GameplayLeaderboard.cs | 9 +++------ .../Play/HUD/SoloGameplayLeaderboard.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index f21ce5e36a..16b36a7468 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; @@ -31,7 +28,7 @@ namespace osu.Game.Screens.Play.HUD private bool requiresScroll; private readonly OsuScrollContainer scroll; - private GameplayLeaderboardScore trackedScore; + private GameplayLeaderboardScore? trackedScore; /// /// Create a new leaderboard. @@ -77,7 +74,7 @@ namespace osu.Game.Screens.Play.HUD /// Whether the player should be tracked on the leaderboard. /// Set to true for the local player or a player whose replay is currently being played. /// - public ILeaderboardScore Add([CanBeNull] APIUser user, bool isTracked) + public ILeaderboardScore Add(APIUser? user, bool isTracked) { var drawable = CreateLeaderboardScoreDrawable(user, isTracked); @@ -108,7 +105,7 @@ namespace osu.Game.Screens.Play.HUD scroll.ScrollToStart(false); } - protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser user, bool isTracked) => + protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser? user, bool isTracked) => new GameplayLeaderboardScore(user, isTracked); protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 20af036ec9..cfe1b4c86a 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; -using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; @@ -14,25 +11,26 @@ namespace osu.Game.Screens.Play.HUD public class SoloGameplayLeaderboard : GameplayLeaderboard { [BackgroundDependencyLoader] - private void load(IAPIProvider api, ScoreProcessor processor, ILeaderboard leaderboard) + private void load(Player player, ScoreProcessor processor, ILeaderboard leaderboard) { - var local = Add(api.LocalUser.Value, true); + ILeaderboardScore local = Add(player.Score.ScoreInfo.User, true); + local.TotalScore.BindTarget = processor.TotalScore; local.Accuracy.BindTarget = processor.Accuracy; local.Combo.BindTarget = processor.Combo; - foreach (var player in leaderboard.Scores) + foreach (var s in leaderboard.Scores) { // todo: APIUser is pain for IScoreInfo. var score = Add(new APIUser { - Id = player.User.OnlineID, - Username = player.User.Username, + Id = s.User.OnlineID, + Username = s.User.Username, }, false); - score.TotalScore.Value = player.TotalScore; - score.Accuracy.Value = player.Accuracy; - score.Combo.Value = player.MaxCombo; + score.TotalScore.Value = s.TotalScore; + score.Accuracy.Value = s.Accuracy; + score.Combo.Value = s.MaxCombo; } } } From 368faa00847b9d33011af1d3c331339c0445475b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 16:07:31 +0900 Subject: [PATCH 123/583] Make constructor `protected` in base implementation --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 16b36a7468..8bcc831de7 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -16,9 +16,8 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { - public class GameplayLeaderboard : CompositeDrawable + public abstract class GameplayLeaderboard : CompositeDrawable { - private readonly int maxPanels; private readonly Cached sorting = new Cached(); public Bindable Expanded = new Bindable(); @@ -30,14 +29,13 @@ namespace osu.Game.Screens.Play.HUD private GameplayLeaderboardScore? trackedScore; + private const int max_panels = 8; + /// /// Create a new leaderboard. /// - /// The maximum panels to show at once. Defines the maximum height of this component. - public GameplayLeaderboard(int maxPanels = 8) + protected GameplayLeaderboard() { - this.maxPanels = maxPanels; - Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH; InternalChildren = new Drawable[] @@ -91,7 +89,7 @@ namespace osu.Game.Screens.Play.HUD Flow.Add(drawable); drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true); - int displayCount = Math.Min(Flow.Count, maxPanels); + int displayCount = Math.Min(Flow.Count, max_panels); Height = displayCount * (GameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y); requiresScroll = displayCount != Flow.Count; From d251c0b2acbc5e9e52f0b5f4d800780ac61d35ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 16:36:09 +0900 Subject: [PATCH 124/583] Move leaderboard implementation to `Player` itself --- .../Multiplayer/MultiplayerPlayer.cs | 58 ++++++++----------- osu.Game/Screens/Play/Player.cs | 36 ++++++++++++ 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 773e68162e..d6d381d721 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -41,15 +41,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); - private MultiplayerGameplayLeaderboard leaderboard; - private readonly MultiplayerRoomUser[] users; - private readonly Bindable leaderboardExpanded = new BindableBool(); - private LoadingLayer loadingDisplay; private FillFlowContainer leaderboardFlow; + private MultiplayerGameplayLeaderboard multiplayerLeaderboard; + /// /// Construct a multiplayer player. /// @@ -81,38 +79,33 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Spacing = new Vector2(5) }); - HUDOverlay.HoldingForHUD.BindValueChanged(_ => updateLeaderboardExpandedState()); - LocalUserPlaying.BindValueChanged(_ => updateLeaderboardExpandedState(), true); - - // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(users), l => - { - if (!LoadedBeatmapSuccessfully) - return; - - leaderboard.Expanded.BindTo(leaderboardExpanded); - - leaderboardFlow.Insert(0, l); - - if (leaderboard.TeamScores.Count >= 2) - { - LoadComponentAsync(new GameplayMatchScoreDisplay - { - Team1Score = { BindTarget = leaderboard.TeamScores.First().Value }, - Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value }, - Expanded = { BindTarget = HUDOverlay.ShowHud }, - }, scoreDisplay => leaderboardFlow.Insert(1, scoreDisplay)); - } - }); - LoadComponentAsync(new GameplayChatDisplay(Room) { - Expanded = { BindTarget = leaderboardExpanded }, + Expanded = { BindTarget = LeaderboardExpandedState }, }, chat => leaderboardFlow.Insert(2, chat)); HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue }); } + protected override GameplayLeaderboard CreateGameplayLeaderboard() => multiplayerLeaderboard = new MultiplayerGameplayLeaderboard(users); + + protected override void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) + { + Debug.Assert(leaderboard == multiplayerLeaderboard); + + leaderboardFlow.Insert(0, leaderboard); + + if (multiplayerLeaderboard.TeamScores.Count >= 2) + { + LoadComponentAsync(new GameplayMatchScoreDisplay + { + Team1Score = { BindTarget = multiplayerLeaderboard.TeamScores.First().Value }, + Team2Score = { BindTarget = multiplayerLeaderboard.TeamScores.Last().Value }, + Expanded = { BindTarget = HUDOverlay.ShowHud }, + }, scoreDisplay => leaderboardFlow.Insert(1, scoreDisplay)); + } + } + protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); @@ -167,9 +160,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } - private void updateLeaderboardExpandedState() => - leaderboardExpanded.Value = !LocalUserPlaying.Value || HUDOverlay.HoldingForHUD.Value; - private void failAndBail(string message = null) { if (!string.IsNullOrEmpty(message)) @@ -232,8 +222,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(Room.RoomID.Value != null); - return leaderboard.TeamScores.Count == 2 - ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, leaderboard.TeamScores) + return multiplayerLeaderboard.TeamScores.Count == 2 + ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) : new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 91e9c3b58f..3fd57496bb 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,6 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -375,6 +376,8 @@ namespace osu.Game.Screens.Play if (Configuration.AutomaticallySkipIntro) skipIntroOverlay.SkipWhenReady(); + + loadLeaderboard(); } protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); @@ -820,6 +823,39 @@ namespace osu.Game.Screens.Play return mouseWheelDisabled.Value && !e.AltPressed; } + #region Gameplay leaderboard + + protected readonly Bindable LeaderboardExpandedState = new BindableBool(); + + private void loadLeaderboard() + { + HUDOverlay.HoldingForHUD.BindValueChanged(_ => updateLeaderboardExpandedState()); + LocalUserPlaying.BindValueChanged(_ => updateLeaderboardExpandedState(), true); + + LoadComponentAsync(CreateGameplayLeaderboard(), leaderboard => + { + if (!LoadedBeatmapSuccessfully) + return; + + leaderboard.Expanded.BindTo(LeaderboardExpandedState); + AddLeaderboardToHUD(leaderboard); + }); + } + + protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Bottom = 75, Left = 20 }, + }; + + protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.Add(leaderboard); + + private void updateLeaderboardExpandedState() => + LeaderboardExpandedState.Value = !LocalUserPlaying.Value || HUDOverlay.HoldingForHUD.Value; + + #endregion + #region Fail Logic protected FailOverlay FailOverlay { get; private set; } From ac58c222b9d798cc9163b466ca69c092ca95929f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 16:54:03 +0900 Subject: [PATCH 125/583] Allow `DrawableAvatar` to accept an `IUser` for now --- osu.Game/Users/Drawables/DrawableAvatar.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 483106d3f4..155f63dc18 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -14,13 +14,13 @@ namespace osu.Game.Users.Drawables [LongRunningLoad] public class DrawableAvatar : Sprite { - private readonly APIUser user; + private readonly IUser user; /// /// A simple, non-interactable avatar sprite for the specified user. /// /// The user. A null value will get a placeholder avatar. - public DrawableAvatar(APIUser user = null) + public DrawableAvatar(IUser user = null) { this.user = user; @@ -33,10 +33,10 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - if (user != null && user.Id > 1) + if (user != null && user.OnlineID > 1) // TODO: The fallback here should not need to exist. Users should be looked up and populated via UserLookupCache or otherwise // in remaining cases where this is required (chat tabs, local leaderboard), at which point this should be removed. - Texture = textures.Get(user.AvatarUrl ?? $@"https://a.ppy.sh/{user.Id}"); + Texture = textures.Get((user as APIUser)?.AvatarUrl ?? $@"https://a.ppy.sh/{user.OnlineID}"); Texture ??= textures.Get(@"Online/avatar-guest"); } From d2b80645ab50a75736f39e4ae652c73cd687fae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 16:56:19 +0900 Subject: [PATCH 126/583] Change `Leaderboard` to use `IUser` instead of `APIUser` --- .../Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 6 +++--- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 6 +++--- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 5 +++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 9e6941738a..4fda4c1c50 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -120,6 +120,6 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time); private void assertCombo(int userId, int expectedCombo) - => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); + => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.OnlineID == userId).Combo.Value == expectedCombo); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 70f498e7f2..13fde4fd72 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -522,7 +522,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); - private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); + private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.OnlineID == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); } diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 8bcc831de7..8b37e0bacb 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play.HUD /// Whether the player should be tracked on the leaderboard. /// Set to true for the local player or a player whose replay is currently being played. /// - public ILeaderboardScore Add(APIUser? user, bool isTracked) + public ILeaderboardScore Add(IUser? user, bool isTracked) { var drawable = CreateLeaderboardScoreDrawable(user, isTracked); @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Play.HUD scroll.ScrollToStart(false); } - protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser? user, bool isTracked) => + protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(IUser? user, bool isTracked) => new GameplayLeaderboardScore(user, isTracked); protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 0f007cd1cb..64a470311f 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; using osu.Game.Users.Drawables; using osu.Game.Utils; using osuTK; @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Play.HUD } [CanBeNull] - public APIUser User { get; } + public IUser User { get; } /// /// Whether this score is the local user or a replay player (and should be focused / always visible). @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Play.HUD /// /// The score's player. /// Whether the player is the local user or a replay player. - public GameplayLeaderboardScore([CanBeNull] APIUser user, bool tracked) + public GameplayLeaderboardScore([CanBeNull] IUser user, bool tracked) { User = user; Tracked = tracked; diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index ac58325060..56756249b3 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -21,6 +21,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; +using osu.Game.Users; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD @@ -125,11 +126,11 @@ namespace osu.Game.Screens.Play.HUD playingUserIds.BindCollectionChanged(playingUsersChanged); } - protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser user, bool isTracked) + protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(IUser user, bool isTracked) { var leaderboardScore = base.CreateLeaderboardScoreDrawable(user, isTracked); - if (UserScores[user.Id].Team is int team) + if (UserScores[user.OnlineID].Team is int team) { leaderboardScore.BackgroundColour = getTeamColour(team).Lighten(1.2f); leaderboardScore.TextColour = Color4.White; From 70e6b595f1a7801a2b39e727d378f33dff3e1e5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 17:07:13 +0900 Subject: [PATCH 127/583] Refactor `SoloGameplayLeaderboard` to not read scores via DI Also allows updating scores if they arrive late. --- .../Play/HUD/SoloGameplayLeaderboard.cs | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index cfe1b4c86a..61be602d53 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -1,32 +1,43 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Screens.Play.HUD { public class SoloGameplayLeaderboard : GameplayLeaderboard { - [BackgroundDependencyLoader] - private void load(Player player, ScoreProcessor processor, ILeaderboard leaderboard) + private readonly IUser trackingUser; + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + public SoloGameplayLeaderboard(IUser trackingUser) { - ILeaderboardScore local = Add(player.Score.ScoreInfo.User, true); + this.trackingUser = trackingUser; + } - local.TotalScore.BindTarget = processor.TotalScore; - local.Accuracy.BindTarget = processor.Accuracy; - local.Combo.BindTarget = processor.Combo; + public void ShowScores(IEnumerable scores) + { + Clear(); - foreach (var s in leaderboard.Scores) + if (!scores.Any()) + return; + + ILeaderboardScore local = Add(trackingUser, true); + + local.TotalScore.BindTarget = scoreProcessor.TotalScore; + local.Accuracy.BindTarget = scoreProcessor.Accuracy; + local.Combo.BindTarget = scoreProcessor.Combo; + + foreach (var s in scores) { - // todo: APIUser is pain for IScoreInfo. - var score = Add(new APIUser - { - Id = s.User.OnlineID, - Username = s.User.Username, - }, false); + var score = Add(s.User, false); score.TotalScore.Value = s.TotalScore; score.Accuracy.Value = s.Accuracy; From 6d167070f8629558dcbc7f83390c9906e7f4b272 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 17:49:53 +0900 Subject: [PATCH 128/583] Add back DI leaderboard retrieval via bindable pathway --- .../TestSceneSoloGameplayLeaderboard.cs | 42 ++++++++++++------- .../Leaderboards/ILeaderboardScoreSource.cs | 15 +++++++ osu.Game/Online/Leaderboards/Leaderboard.cs | 15 ++++--- .../Play/HUD/SoloGameplayLeaderboard.cs | 15 ++++++- .../Select/Leaderboards/BeatmapLeaderboard.cs | 5 ++- osu.Game/Screens/Select/PlaySongSelect.cs | 23 ++++++++-- 6 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index 0ea2271fbf..6839fafa93 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -5,9 +5,12 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -15,31 +18,36 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSoloGameplayLeaderboard : OsuTestScene + public class TestSceneSoloGameplayLeaderboard : OsuTestScene, ILeaderboardScoreSource { [Cached] private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); - private SoloGameplayLeaderboard leaderboard = null!; + private readonly BindableList scores = new BindableList(); - [SetUp] - public void SetUp() => Schedule(() => + [SetUpSteps] + public void SetUpSteps() { - var trackingUser = new APIUser - { - Username = "local user", - Id = 2, - }; + AddStep("clear scores", () => scores.Clear()); - Child = leaderboard = new SoloGameplayLeaderboard(trackingUser) + AddStep("create component", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Expanded = { Value = true }, - }; + var trackingUser = new APIUser + { + Username = "local user", + Id = 2, + }; - leaderboard.ShowScores(createSampleScores()); - }); + Child = new SoloGameplayLeaderboard(trackingUser) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Expanded = { Value = true }, + }; + }); + + AddStep("add scores", () => scores.AddRange(createSampleScores())); + } [Test] public void TestLocalUser() @@ -49,6 +57,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v); } + IBindableList ILeaderboardScoreSource.Scores => scores; + private static List createSampleScores() { return new[] diff --git a/osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs b/osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs new file mode 100644 index 0000000000..e7ed43633e --- /dev/null +++ b/osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs @@ -0,0 +1,15 @@ +// 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.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + [Cached] + public interface ILeaderboardScoreSource + { + IBindableList Scores { get; } + } +} diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 58b1ea62aa..9b6e9fbec7 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -39,7 +39,9 @@ namespace osu.Game.Online.Leaderboards /// /// The currently displayed scores. /// - public IEnumerable Scores => scores; + public IBindableList Scores => scores; + + private readonly BindableList scores = new BindableList(); /// /// Whether the current scope should refetch in response to changes in API connectivity state. @@ -68,8 +70,6 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); - private ICollection scores; - private TScope scope; public TScope Scope @@ -169,7 +169,7 @@ namespace osu.Game.Online.Leaderboards throw new InvalidOperationException($"State {state} cannot be set by a leaderboard implementation."); } - Debug.Assert(scores?.Any() != true); + Debug.Assert(scores.Any() != true); setState(state); } @@ -181,7 +181,10 @@ namespace osu.Game.Online.Leaderboards /// The user top score, if any. protected void SetScores(IEnumerable scores, TScoreInfo userScore = default) { - this.scores = scores?.ToList(); + this.scores.Clear(); + if (scores != null) + this.scores.AddRange(scores); + userScoreContainer.Score.Value = userScore; if (userScore == null) @@ -247,7 +250,7 @@ namespace osu.Game.Online.Leaderboards .Expire(); scoreFlowContainer = null; - if (scores?.Any() != true) + if (scores.Any() != true) { setState(LeaderboardState.NoScores); return; diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 61be602d53..a67e59df05 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; @@ -14,6 +16,8 @@ namespace osu.Game.Screens.Play.HUD { private readonly IUser trackingUser; + private readonly IBindableList scores = new BindableList(); + [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; @@ -22,7 +26,16 @@ namespace osu.Game.Screens.Play.HUD this.trackingUser = trackingUser; } - public void ShowScores(IEnumerable scores) + [BackgroundDependencyLoader(true)] + private void load(ILeaderboardScoreSource? scoreSource) + { + if (scoreSource != null) + scores.BindTo(scoreSource.Scores); + + scores.BindCollectionChanged((_, __) => Scheduler.AddOnce(showScores, scores), true); + } + + private void showScores(IEnumerable scores) { Clear(); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 343b815e9f..ee62160a21 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -23,7 +23,7 @@ using Realms; namespace osu.Game.Screens.Select.Leaderboards { - public class BeatmapLeaderboard : Leaderboard + public class BeatmapLeaderboard : Leaderboard, ILeaderboardScoreSource { public Action ScoreSelected; @@ -152,7 +152,8 @@ namespace osu.Game.Screens.Select.Leaderboards { SetScores( scoreManager.OrderByTotalScore(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), - r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)); + r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) + ); }); return req; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index c24ca9a7cf..a4c797a034 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -6,10 +6,12 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -22,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Screens.Select { - public class PlaySongSelect : SongSelect + public class PlaySongSelect : SongSelect, ILeaderboardScoreSource { private OsuScreen playerLoader; @@ -37,14 +39,25 @@ namespace osu.Game.Screens.Select private void load(OsuColour colours) { BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit()); - - ((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += PresentScore; } protected void PresentScore(ScoreInfo score) => FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false))); - protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); + protected override BeatmapDetailArea CreateBeatmapDetailArea() + { + var playBeatmapDetailArea = new PlayBeatmapDetailArea + { + Leaderboard = + { + ScoreSelected = PresentScore + } + }; + + Scores.BindTo(playBeatmapDetailArea.Leaderboard.Scores); + + return playBeatmapDetailArea; + } protected override bool OnKeyDown(KeyDownEvent e) { @@ -121,5 +134,7 @@ namespace osu.Game.Screens.Select playerLoader = null; } } + + IBindableList ILeaderboardScoreSource.Scores { get; } = new BindableList(); } } From 5894d2f0bca96e4af328dab2c7604b671943c529 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 18:12:49 +0900 Subject: [PATCH 129/583] Ensure gameplay leaderboard hides with rest of HUD when it should --- .../OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 3 ++- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++++++++++-- osu.Game/Screens/Play/Player.cs | 3 ++- osu.Game/Screens/Play/PlayerConfiguration.cs | 5 +++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d6d381d721..655aa08a72 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -60,7 +60,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AllowPause = false, AllowRestart = false, AllowSkipping = room.AutoSkip.Value, - AutomaticallySkipIntro = room.AutoSkip.Value + AutomaticallySkipIntro = room.AutoSkip.Value, + AlwaysShowLeaderboard = true, }) { this.users = users; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f9f3693385..a727b8925b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -9,7 +9,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -80,7 +79,7 @@ namespace osu.Game.Screens.Play private readonly SkinnableTargetContainer mainComponents; - private IEnumerable hideTargets => new Drawable[] { mainComponents, KeyCounter, topRightElements }; + private readonly List hideTargets; public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods) { @@ -129,6 +128,8 @@ namespace osu.Game.Screens.Play }, clicksPerSecondCalculator = new ClicksPerSecondCalculator() }; + + hideTargets = new List { mainComponents, KeyCounter, topRightElements }; } [BackgroundDependencyLoader(true)] @@ -173,6 +174,13 @@ namespace osu.Game.Screens.Play replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } + public void Add(Drawable drawable, bool hideWithHUD) + { + base.Add(drawable); + if (hideWithHUD) + hideTargets.Add(drawable); + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3fd57496bb..659dbd06ef 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -838,6 +838,7 @@ namespace osu.Game.Screens.Play return; leaderboard.Expanded.BindTo(LeaderboardExpandedState); + AddLeaderboardToHUD(leaderboard); }); } @@ -849,7 +850,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Bottom = 75, Left = 20 }, }; - protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.Add(leaderboard); + protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.Add(leaderboard, !Configuration.AlwaysShowLeaderboard); private void updateLeaderboardExpandedState() => LeaderboardExpandedState.Value = !LocalUserPlaying.Value || HUDOverlay.HoldingForHUD.Value; diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs index b1b0e01d80..b82925ccb8 100644 --- a/osu.Game/Screens/Play/PlayerConfiguration.cs +++ b/osu.Game/Screens/Play/PlayerConfiguration.cs @@ -36,5 +36,10 @@ namespace osu.Game.Screens.Play /// Whether the intro should be skipped by default. /// public bool AutomaticallySkipIntro { get; set; } + + /// + /// Whether the gameplay leaderboard should always be shown (usually in a contracted state). + /// + public bool AlwaysShowLeaderboard { get; set; } } } From 678eec1c674d0d591d08c35e017d197e55f35503 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 18:23:47 +0900 Subject: [PATCH 130/583] Move `LeaderboardFlow` to `HUDOverlay` to share positioning logic --- .../Multiplayer/MultiplayerPlayer.cs | 34 ++----------------- osu.Game/Screens/Play/HUDOverlay.cs | 34 ++++++++++++++----- osu.Game/Screens/Play/Player.cs | 11 ++---- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 655aa08a72..a2c43898f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -9,8 +9,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.UserInterface; @@ -21,7 +19,6 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Users; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { @@ -44,7 +41,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private readonly MultiplayerRoomUser[] users; private LoadingLayer loadingDisplay; - private FillFlowContainer leaderboardFlow; private MultiplayerGameplayLeaderboard multiplayerLeaderboard; @@ -73,17 +69,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!LoadedBeatmapSuccessfully) return; - HUDOverlay.Add(leaderboardFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(5) - }); - LoadComponentAsync(new GameplayChatDisplay(Room) { Expanded = { BindTarget = LeaderboardExpandedState }, - }, chat => leaderboardFlow.Insert(2, chat)); + }, chat => HUDOverlay.LeaderboardFlow.Insert(2, chat)); HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue }); } @@ -94,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(leaderboard == multiplayerLeaderboard); - leaderboardFlow.Insert(0, leaderboard); + HUDOverlay.LeaderboardFlow.Insert(0, leaderboard); if (multiplayerLeaderboard.TeamScores.Count >= 2) { @@ -103,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Team1Score = { BindTarget = multiplayerLeaderboard.TeamScores.First().Value }, Team2Score = { BindTarget = multiplayerLeaderboard.TeamScores.Last().Value }, Expanded = { BindTarget = HUDOverlay.ShowHud }, - }, scoreDisplay => leaderboardFlow.Insert(1, scoreDisplay)); + }, scoreDisplay => HUDOverlay.LeaderboardFlow.Insert(1, scoreDisplay)); } } @@ -169,23 +158,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Schedule(() => PerformExit(false)); } - protected override void Update() - { - base.Update(); - - if (!LoadedBeatmapSuccessfully) - return; - - adjustLeaderboardPosition(); - } - - private void adjustLeaderboardPosition() - { - const float padding = 44; // enough margin to avoid the hit error display. - - leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight); - } - private void onGameplayStarted() => Scheduler.Add(() => { if (!this.IsCurrentScreen()) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a727b8925b..d08f3fc07f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -79,9 +79,15 @@ namespace osu.Game.Screens.Play private readonly SkinnableTargetContainer mainComponents; + /// + /// A flow which sits at the left side of the screen to house leaderboard (and related) components. + /// Will automatically be positioned to avoid colliding with top scoring elements. + /// + public readonly FillFlowContainer LeaderboardFlow; + private readonly List hideTargets; - public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods) + public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { this.drawableRuleset = drawableRuleset; this.mods = mods; @@ -126,10 +132,19 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - clicksPerSecondCalculator = new ClicksPerSecondCalculator() + LeaderboardFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5) + }, + clicksPerSecondCalculator = new ClicksPerSecondCalculator(), }; hideTargets = new List { mainComponents, KeyCounter, topRightElements }; + + if (alwaysShowLeaderboard) + hideTargets.Add(LeaderboardFlow); } [BackgroundDependencyLoader(true)] @@ -174,13 +189,6 @@ namespace osu.Game.Screens.Play replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } - public void Add(Drawable drawable, bool hideWithHUD) - { - base.Add(drawable); - if (hideWithHUD) - hideTargets.Add(drawable); - } - protected override void Update() { base.Update(); @@ -220,6 +228,14 @@ namespace osu.Game.Screens.Play bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight); else bottomRightElements.Y = 0; + + adjustLeaderboardPosition(); + } + + private void adjustLeaderboardPosition() + { + const float padding = 44; // enough margin to avoid the hit error display. + LeaderboardFlow.Position = new Vector2(padding, padding + TopScoringElementsHeight); } private void updateVisibility() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 659dbd06ef..95e3e75c8a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -420,7 +420,7 @@ namespace osu.Game.Screens.Play // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods) + HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration.AlwaysShowLeaderboard) { HoldToQuit = { @@ -843,14 +843,9 @@ namespace osu.Game.Screens.Play }); } - protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 75, Left = 20 }, - }; + protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User); - protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.Add(leaderboard, !Configuration.AlwaysShowLeaderboard); + protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.LeaderboardFlow.Add(leaderboard); private void updateLeaderboardExpandedState() => LeaderboardExpandedState.Value = !LocalUserPlaying.Value || HUDOverlay.HoldingForHUD.Value; From e15a25ea492061306cff82ef09e58d4f95490b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 18:42:57 +0900 Subject: [PATCH 131/583] Fix leaderboard positioning logic to actually consider elements on the left side --- osu.Game/Screens/Play/HUDOverlay.cs | 47 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d08f3fc07f..c340f262ed 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -34,11 +34,6 @@ namespace osu.Game.Screens.Play public const Easing FADE_EASING = Easing.OutQuint; - /// - /// The total height of all the top of screen scoring elements. - /// - public float TopScoringElementsHeight { get; private set; } - /// /// The total height of all the bottom of screen scoring elements. /// @@ -136,6 +131,7 @@ namespace osu.Game.Screens.Play { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, + Padding = new MarginPadding(44), // enough margin to avoid the hit error display Spacing = new Vector2(5) }, clicksPerSecondCalculator = new ClicksPerSecondCalculator(), @@ -193,22 +189,36 @@ namespace osu.Game.Screens.Play { base.Update(); - Vector2? lowestTopScreenSpace = null; + float? lowestTopScreenSpaceLeft = null; + float? lowestTopScreenSpaceRight = null; + Vector2? highestBottomScreenSpace = null; // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. foreach (var element in mainComponents.Components.Cast()) { // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. - if (element.Anchor.HasFlagFast(Anchor.TopRight) || (element.Anchor.HasFlagFast(Anchor.y0) && element.RelativeSizeAxes == Axes.X)) + if (element.Anchor.HasFlagFast(Anchor.y0)) { // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. if (element is LegacyHealthDisplay) continue; - var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; - if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y) - lowestTopScreenSpace = bottomRight; + float bottom = element.ScreenSpaceDrawQuad.BottomRight.Y; + + bool isRelativeX = element.RelativeSizeAxes == Axes.X; + + if (element.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX) + { + if (lowestTopScreenSpaceRight == null || bottom > lowestTopScreenSpaceRight.Value) + lowestTopScreenSpaceRight = bottom; + } + + if (element.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX) + { + if (lowestTopScreenSpaceLeft == null || bottom > lowestTopScreenSpaceLeft.Value) + lowestTopScreenSpaceLeft = bottom; + } } // and align bottom-right components with the top-edge of the highest bottom-anchored hud element. else if (element.Anchor.HasFlagFast(Anchor.BottomRight) || (element.Anchor.HasFlagFast(Anchor.y2) && element.RelativeSizeAxes == Axes.X)) @@ -219,23 +229,20 @@ namespace osu.Game.Screens.Play } } - if (lowestTopScreenSpace.HasValue) - topRightElements.Y = TopScoringElementsHeight = MathHelper.Clamp(ToLocalSpace(lowestTopScreenSpace.Value).Y, 0, DrawHeight - topRightElements.DrawHeight); + if (lowestTopScreenSpaceRight.HasValue) + topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight); else topRightElements.Y = 0; + if (lowestTopScreenSpaceLeft.HasValue) + LeaderboardFlow.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceLeft.Value)).Y, 0, DrawHeight - LeaderboardFlow.DrawHeight); + else + LeaderboardFlow.Y = 0; + if (highestBottomScreenSpace.HasValue) bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight); else bottomRightElements.Y = 0; - - adjustLeaderboardPosition(); - } - - private void adjustLeaderboardPosition() - { - const float padding = 44; // enough margin to avoid the hit error display. - LeaderboardFlow.Position = new Vector2(padding, padding + TopScoringElementsHeight); } private void updateVisibility() From 9226f0abbca34bd14e6c3571ea7a4426cf9abf57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 19:16:25 +0900 Subject: [PATCH 132/583] Implement equality correctly in `Live` --- osu.Game/Database/Live.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index a21b7f606b..3bb11c3a50 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -51,7 +51,13 @@ namespace osu.Game.Database ID = id; } - public bool Equals(Live? other) => ID == other?.ID; + public bool Equals(Live? other) + { + if (ReferenceEquals(this, other)) return true; + if (other == null) return false; + + return ID == other.ID; + } public override int GetHashCode() => HashCode.Combine(ID); From 94693a466767179a7307b469e584547e9fe7d990 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 19:55:57 +0900 Subject: [PATCH 133/583] Fix oversight when making interface implementation explicit --- osu.Game/Screens/Select/PlaySongSelect.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index a4c797a034..619ec97535 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -26,15 +24,17 @@ namespace osu.Game.Screens.Select { public class PlaySongSelect : SongSelect, ILeaderboardScoreSource { - private OsuScreen playerLoader; + private OsuScreen? playerLoader; [Resolved(CanBeNull = true)] - private INotificationOverlay notifications { get; set; } + private INotificationOverlay? notifications { get; set; } public override bool AllowExternalScreenChange => true; protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); + private PlayBeatmapDetailArea playBeatmapDetailArea = null!; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Select protected override BeatmapDetailArea CreateBeatmapDetailArea() { - var playBeatmapDetailArea = new PlayBeatmapDetailArea + playBeatmapDetailArea = new PlayBeatmapDetailArea { Leaderboard = { @@ -54,8 +54,6 @@ namespace osu.Game.Screens.Select } }; - Scores.BindTo(playBeatmapDetailArea.Leaderboard.Scores); - return playBeatmapDetailArea; } @@ -74,9 +72,9 @@ namespace osu.Game.Screens.Select return base.OnKeyDown(e); } - private IReadOnlyList modsAtGameplayStart; + private IReadOnlyList? modsAtGameplayStart; - private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod(); + private ModAutoplay? getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod(); protected override bool OnStart() { @@ -135,6 +133,6 @@ namespace osu.Game.Screens.Select } } - IBindableList ILeaderboardScoreSource.Scores { get; } = new BindableList(); + IBindableList ILeaderboardScoreSource.Scores => playBeatmapDetailArea.Leaderboard.Scores; } } From 81b5e4a8654513937945301029f596abc8ee123a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Sep 2022 19:57:40 +0900 Subject: [PATCH 134/583] Fix back-to-front condition on leaderboard always show configuration --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index c340f262ed..3e661e76f4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -139,7 +139,7 @@ namespace osu.Game.Screens.Play hideTargets = new List { mainComponents, KeyCounter, topRightElements }; - if (alwaysShowLeaderboard) + if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); } From 04dbb6fc1b61e8e3be64484b079d7c20d0b26127 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Sep 2022 00:49:02 +0300 Subject: [PATCH 135/583] Add inline comment --- osu.Game/Overlays/BeatmapListingOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 70c6c60ecd..2be328427b 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -176,6 +176,9 @@ namespace osu.Game.Overlays } else { + // new results may contain beatmaps from a previous page, + // this is dodgy but matches web behaviour for now. + // see: https://github.com/ppy/osu-web/issues/9270 newCards = newCards.Except(foundContent); panelLoadTask = LoadComponentsAsync(newCards, loaded => From b4e6a20846d10ddd8a2590d0fd5b8701ef9e5294 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Sep 2022 12:59:04 +0900 Subject: [PATCH 136/583] Fix song select status prefix matching no longer working Regressed in #19275 due to weird logic. Closes #20289. --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 10 ++++++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index ac16c59e5b..da32edb8fb 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -191,6 +191,16 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsTrue(filterCriteria.BeatDivisor.IsUpperInclusive); } + [Test] + public void TestPartialStatusMatch() + { + const string query = "status=r"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); + Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); + } + [Test] public void TestApplyStatusQueries() { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index a7c1aec361..c86554ddbc 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -122,14 +122,17 @@ namespace osu.Game.Screens.Select private static bool tryParseEnum(string value, out TEnum result) where TEnum : struct { - if (Enum.TryParse(value, true, out result)) return true; + // First try an exact match. + if (Enum.TryParse(value, true, out result)) + return true; + // Then try a prefix match. string? prefixMatch = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); if (prefixMatch == null) return false; - return Enum.TryParse(value, true, out result); + return Enum.TryParse(prefixMatch, true, out result); } private static GroupCollection? tryMatchRegex(string value, string regex) From 85e3a681b12e79b8cb12b6b8551bb3eea7e28566 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Sep 2022 21:07:30 -0700 Subject: [PATCH 137/583] Fix notification overlay toast tray background absorbing input from behind --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index e3f8734581..329379de4a 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -26,6 +26,8 @@ namespace osu.Game.Overlays { public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => toastFlow.ReceivePositionalInputAt(screenSpacePos); + public bool IsDisplayingToasts => toastFlow.Count > 0; private FillFlowContainer toastFlow = null!; From 8a6977213a285344f81a987af80cd0d6337ac243 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Sep 2022 14:02:35 +0900 Subject: [PATCH 138/583] Fix displayed scores in gameplay leaderboard not tracking display mode changes --- .../Play/HUD/SoloGameplayLeaderboard.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index a67e59df05..2282277c3c 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -18,9 +18,15 @@ namespace osu.Game.Screens.Play.HUD private readonly IBindableList scores = new BindableList(); + // hold references to ensure bindables are updated. + private readonly List> scoreBindables = new List>(); + [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + public SoloGameplayLeaderboard(IUser trackingUser) { this.trackingUser = trackingUser; @@ -32,12 +38,13 @@ namespace osu.Game.Screens.Play.HUD if (scoreSource != null) scores.BindTo(scoreSource.Scores); - scores.BindCollectionChanged((_, __) => Scheduler.AddOnce(showScores, scores), true); + scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); } - private void showScores(IEnumerable scores) + private void showScores() { Clear(); + scoreBindables.Clear(); if (!scores.Any()) return; @@ -52,7 +59,12 @@ namespace osu.Game.Screens.Play.HUD { var score = Add(s.User, false); - score.TotalScore.Value = s.TotalScore; + var bindableTotal = scoreManager.GetBindableTotalScore(s); + + // Direct binding not possible due to differing types (see https://github.com/ppy/osu/issues/20298). + bindableTotal.BindValueChanged(total => score.TotalScore.Value = total.NewValue, true); + scoreBindables.Add(bindableTotal); + score.Accuracy.Value = s.Accuracy; score.Combo.Value = s.MaxCombo; } From fcf54f1bc80ce018e9247fe5ecd7835e6488fb6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Sep 2022 14:18:12 +0900 Subject: [PATCH 139/583] Fix gameplay leaderboard not being sorted correctly in tie situations --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 6 +++++- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 1 + osu.Game/Screens/Play/HUD/ILeaderboardScore.cs | 6 ++++++ osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs | 4 ++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 8b37e0bacb..325253e9d4 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -88,6 +88,7 @@ namespace osu.Game.Screens.Play.HUD Flow.Add(drawable); drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true); + drawable.DisplayOrder.BindValueChanged(_ => sorting.Invalidate(), true); int displayCount = Math.Min(Flow.Count, max_panels); Height = displayCount * (GameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y); @@ -160,7 +161,10 @@ namespace osu.Game.Screens.Play.HUD if (sorting.IsValid) return; - var orderedByScore = Flow.OrderByDescending(i => i.TotalScore.Value).ToList(); + var orderedByScore = Flow + .OrderByDescending(i => i.TotalScore.Value) + .ThenBy(i => i.DisplayOrder.Value) + .ToList(); for (int i = 0; i < Flow.Count; i++) { diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 64a470311f..29354e610d 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -55,6 +55,7 @@ namespace osu.Game.Screens.Play.HUD public BindableDouble Accuracy { get; } = new BindableDouble(1); public BindableInt Combo { get; } = new BindableInt(); public BindableBool HasQuit { get; } = new BindableBool(); + public Bindable DisplayOrder { get; } = new Bindable(); public Color4? BackgroundColour { get; set; } diff --git a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs index 20bf7045b8..aa06bb08a5 100644 --- a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs @@ -14,5 +14,11 @@ namespace osu.Game.Screens.Play.HUD BindableInt Combo { get; } BindableBool HasQuit { get; } + + /// + /// An optional value to guarantee stable ordering. + /// Lower numbers will appear higher in cases of ties. + /// + Bindable DisplayOrder { get; } } } diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 2282277c3c..6141260beb 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -55,6 +55,9 @@ namespace osu.Game.Screens.Play.HUD local.Accuracy.BindTarget = scoreProcessor.Accuracy; local.Combo.BindTarget = scoreProcessor.Combo; + // Local score should always show lower than any existing scores in cases of ties. + local.DisplayOrder.Value = long.MaxValue; + foreach (var s in scores) { var score = Add(s.User, false); @@ -67,6 +70,7 @@ namespace osu.Game.Screens.Play.HUD score.Accuracy.Value = s.Accuracy; score.Combo.Value = s.MaxCombo; + score.DisplayOrder.Value = s.OnlineID > 0 ? s.OnlineID : s.Date.ToUnixTimeSeconds(); } } } From bc07513c3c19f4440676aff013b111eec321ad50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Sep 2022 14:19:53 +0900 Subject: [PATCH 140/583] Fix local scores potentially not being stable-sorted for leaderboard display --- osu.Game/Scoring/ScoreManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6bb31eb4db..8342d3bcc1 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -58,7 +58,10 @@ namespace osu.Game.Scoring /// The array of s to reorder. /// The given ordered by decreasing total score. public IEnumerable OrderByTotalScore(IEnumerable scores) - => scores.OrderByDescending(s => GetTotalScore(s)).ThenBy(s => s.OnlineID); + => scores.OrderByDescending(s => GetTotalScore(s)) + .ThenBy(s => s.OnlineID) + // Local scores may not have an online ID. Fall back to date in these cases. + .ThenBy(s => s.Date); /// /// Retrieves a bindable that represents the total score of a . From 30cf14e824bc0eeb9f01f0eeb63eb91462c7954e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Sep 2022 14:40:45 +0900 Subject: [PATCH 141/583] Update ReSharper CLI to 2022.2.3 --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 57694d7f57..1f937e1837 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2022.1.1", + "version": "2022.2.3", "commands": [ "jb" ] From a9eba2712991229a99e1f1b80c85d92e720a8264 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 19:41:37 +0900 Subject: [PATCH 142/583] Factor out pooling logic from `Playfield` --- .../Objects/Pooling/HitObjectEntryManager.cs | 69 +++++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 49 +++++++------ 2 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs new file mode 100644 index 0000000000..129c4cab44 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace osu.Game.Rulesets.Objects.Pooling +{ + /// + /// Manages a mapping between and + /// + internal class HitObjectEntryManager + { + /// + /// All entries, including entries of the nested hit objects. + /// + public IEnumerable AllEntries => entryMap.Values; + + public event Action? OnEntryAdded; + public event Action? OnEntryRemoved; + + private readonly Func createLifetimeEntry; + + private readonly Dictionary entryMap = new Dictionary(); + private readonly Dictionary parentMap = new Dictionary(); + + public HitObjectEntryManager(Func createLifetimeEntry) + { + this.createLifetimeEntry = createLifetimeEntry; + } + + public HitObjectLifetimeEntry Add(HitObject hitObject, HitObject? parentHitObject) + { + if (parentHitObject != null && !entryMap.TryGetValue(parentHitObject, out var parentEntry)) + throw new InvalidOperationException($@"The parent {nameof(HitObject)} must be added to this {nameof(HitObjectEntryManager)} before nested {nameof(HitObject)} is added."); + + if (entryMap.ContainsKey(hitObject)) + throw new InvalidOperationException($@"The {nameof(HitObject)} is already added to this {nameof(HitObjectEntryManager)}."); + + if (parentHitObject != null) + parentMap[hitObject] = parentHitObject; + + var entry = createLifetimeEntry(hitObject); + entryMap[hitObject] = entry; + + OnEntryAdded?.Invoke(entry, parentHitObject); + return entry; + } + + public bool Remove(HitObject hitObject) + { + if (!entryMap.TryGetValue(hitObject, out var entry)) + return false; + + parentMap.Remove(hitObject, out var parentHitObject); + + OnEntryRemoved?.Invoke(entry, parentHitObject); + return true; + } + + public bool TryGet(HitObject hitObject, [MaybeNullWhen(false)] out HitObjectLifetimeEntry entry) + { + return entryMap.TryGetValue(hitObject, out entry); + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 60d1555052..5aaa7682db 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; +using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.UI { @@ -94,6 +95,8 @@ namespace osu.Game.Rulesets.UI [Resolved(CanBeNull = true)] private IReadOnlyList mods { get; set; } + private readonly HitObjectEntryManager entryManager; + /// /// Creates a new . /// @@ -108,6 +111,10 @@ namespace osu.Game.Rulesets.UI h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); + + entryManager = new HitObjectEntryManager(CreateLifetimeEntry); + entryManager.OnEntryAdded += onEntryAdded; + entryManager.OnEntryRemoved += onEntryRemoved; } [BackgroundDependencyLoader] @@ -171,6 +178,7 @@ namespace osu.Game.Rulesets.UI /// The added . protected virtual void OnHitObjectAdded(HitObject hitObject) { + preloadSamples(hitObject); } /// @@ -263,13 +271,7 @@ namespace osu.Game.Rulesets.UI /// public virtual void Add(HitObject hitObject) { - var entry = CreateLifetimeEntry(hitObject); - lifetimeEntryMap[entry.HitObject] = entry; - - preloadSamples(hitObject); - - HitObjectContainer.Add(entry); - OnHitObjectAdded(entry.HitObject); + entryManager.Add(hitObject, null); } private void preloadSamples(HitObject hitObject) @@ -292,14 +294,23 @@ namespace osu.Game.Rulesets.UI /// Whether the was successfully removed. public virtual bool Remove(HitObject hitObject) { - if (lifetimeEntryMap.Remove(hitObject, out var entry)) - { - HitObjectContainer.Remove(entry); - OnHitObjectRemoved(hitObject); - return true; - } + return entryManager.Remove(hitObject) || nestedPlayfields.Any(p => p.Remove(hitObject)); + } - return nestedPlayfields.Any(p => p.Remove(hitObject)); + private void onEntryAdded(HitObjectLifetimeEntry entry, [CanBeNull] HitObject parentHitObject) + { + if (parentHitObject != null) return; + + HitObjectContainer.Add(entry); + OnHitObjectAdded(entry.HitObject); + } + + private void onEntryRemoved(HitObjectLifetimeEntry entry, [CanBeNull] HitObject parentHitObject) + { + if (parentHitObject != null) return; + + HitObjectContainer.Remove(entry); + OnHitObjectRemoved(entry.HitObject); } /// @@ -366,8 +377,8 @@ namespace osu.Game.Rulesets.UI } } - if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) - lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); + if (!entryManager.TryGet(hitObject, out var entry)) + entry = entryManager.Add(hitObject, parent?.HitObject); dho.ParentHitObject = parent; dho.Apply(entry); @@ -442,8 +453,6 @@ namespace osu.Game.Rulesets.UI /// internal event Action HitObjectUsageFinished; - private readonly Dictionary lifetimeEntryMap = new Dictionary(); - /// /// Sets whether to keep a given always alive within this or any nested . /// @@ -451,7 +460,7 @@ namespace osu.Game.Rulesets.UI /// Whether to keep always alive. internal void SetKeepAlive(HitObject hitObject, bool keepAlive) { - if (lifetimeEntryMap.TryGetValue(hitObject, out var entry)) + if (entryManager.TryGet(hitObject, out var entry)) { entry.KeepAlive = keepAlive; return; @@ -466,7 +475,7 @@ namespace osu.Game.Rulesets.UI /// internal void KeepAllAlive() { - foreach (var (_, entry) in lifetimeEntryMap) + foreach (var entry in entryManager.AllEntries) entry.KeepAlive = true; foreach (var p in nestedPlayfields) From bde390828b017f3d62698f263ec14a962d05ad19 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 20:48:00 +0900 Subject: [PATCH 143/583] Manage entries of nested hit objects in `HitObjectEntryManager` - Fix nested hit objects are leaked when parent hit object is edited or deleted --- .../Objects/Pooling/HitObjectEntryManager.cs | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index 129c4cab44..f14f8b6f61 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -25,39 +25,54 @@ namespace osu.Game.Rulesets.Objects.Pooling private readonly Func createLifetimeEntry; private readonly Dictionary entryMap = new Dictionary(); - private readonly Dictionary parentMap = new Dictionary(); + private readonly Dictionary parentMap = new Dictionary(); + private readonly Dictionary> childrenMap = new Dictionary>(); public HitObjectEntryManager(Func createLifetimeEntry) { this.createLifetimeEntry = createLifetimeEntry; } - public HitObjectLifetimeEntry Add(HitObject hitObject, HitObject? parentHitObject) + public HitObjectLifetimeEntry Add(HitObject hitObject, HitObject? parent) { - if (parentHitObject != null && !entryMap.TryGetValue(parentHitObject, out var parentEntry)) - throw new InvalidOperationException($@"The parent {nameof(HitObject)} must be added to this {nameof(HitObjectEntryManager)} before nested {nameof(HitObject)} is added."); - if (entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObject)} is already added to this {nameof(HitObjectEntryManager)}."); - if (parentHitObject != null) - parentMap[hitObject] = parentHitObject; - var entry = createLifetimeEntry(hitObject); entryMap[hitObject] = entry; + parentMap[entry] = parent; - OnEntryAdded?.Invoke(entry, parentHitObject); + if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) + parentChildEntries.Add(entry); + + hitObject.DefaultsApplied += onDefaultsApplied; + + childrenMap[entry.HitObject] = new List(); + + OnEntryAdded?.Invoke(entry, parent); return entry; } public bool Remove(HitObject hitObject) { - if (!entryMap.TryGetValue(hitObject, out var entry)) + if (!entryMap.Remove(hitObject, out var entry)) return false; - parentMap.Remove(hitObject, out var parentHitObject); + parentMap.Remove(entry, out var parent); - OnEntryRemoved?.Invoke(entry, parentHitObject); + if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) + parentChildEntries.Remove(entry); + + hitObject.DefaultsApplied -= onDefaultsApplied; + + // Remove all entries of the nested hit objects + if (childrenMap.Remove(entry.HitObject, out var childEntries)) + { + foreach (var childEntry in childEntries) + Remove(childEntry.HitObject); + } + + OnEntryRemoved?.Invoke(entry, parent); return true; } @@ -65,5 +80,20 @@ namespace osu.Game.Rulesets.Objects.Pooling { return entryMap.TryGetValue(hitObject, out entry); } + + /// + /// As nested hit objects are recreated, remove entries of the old nested hit objects. + /// + private void onDefaultsApplied(HitObject hitObject) + { + if (!childrenMap.Remove(hitObject, out var childEntries)) + return; + + foreach (var entry in childEntries) + Remove(entry.HitObject); + + childEntries.Clear(); + childrenMap[hitObject] = childEntries; + } } } From 47539e2129d85e50866e659e850d8bc769927057 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 15:09:01 +0900 Subject: [PATCH 144/583] Add doc comments to `HitObjectEntryManager` --- .../Objects/Pooling/HitObjectEntryManager.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index f14f8b6f61..71c0a88616 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -19,13 +19,38 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public IEnumerable AllEntries => entryMap.Values; + /// + /// Invoked when a new is added to this .. + /// The second parameter of the event is the parent hit object. + /// public event Action? OnEntryAdded; + + /// + /// Invoked when a is removed from this . + /// The second parameter of the event is the parent hit object. + /// public event Action? OnEntryRemoved; private readonly Func createLifetimeEntry; + /// + /// Provides the reverse mapping of for each entry. + /// private readonly Dictionary entryMap = new Dictionary(); + + /// + /// Stores the parent hit object for entries of the nested hit objects. + /// A null is stored for entries of the top-level hit objects. + /// + /// + /// The parent hit object of a pooled hit object may be non-pooled. + /// In that case, no corresponding is stored in this . + /// private readonly Dictionary parentMap = new Dictionary(); + + /// + /// Stores the list of entries managed by this for each hit object managed by this . + /// private readonly Dictionary> childrenMap = new Dictionary>(); public HitObjectEntryManager(Func createLifetimeEntry) From c59aa574509d153883fc6dd6f4627058a7089e27 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 15:15:33 +0900 Subject: [PATCH 145/583] Remove `createLifetimeEntry` from `HitObjectEntryManager` --- .../Objects/Pooling/HitObjectEntryManager.cs | 33 ++++++++----------- osu.Game/Rulesets/UI/Playfield.cs | 19 ++++++++--- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index 71c0a88616..d8dd6fab7e 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -31,8 +31,6 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public event Action? OnEntryRemoved; - private readonly Func createLifetimeEntry; - /// /// Provides the reverse mapping of for each entry. /// @@ -53,37 +51,33 @@ namespace osu.Game.Rulesets.Objects.Pooling /// private readonly Dictionary> childrenMap = new Dictionary>(); - public HitObjectEntryManager(Func createLifetimeEntry) + public void Add(HitObjectLifetimeEntry entry, HitObject? parent) { - this.createLifetimeEntry = createLifetimeEntry; - } + if (parentMap.ContainsKey(entry)) + throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}."); - public HitObjectLifetimeEntry Add(HitObject hitObject, HitObject? parent) - { - if (entryMap.ContainsKey(hitObject)) - throw new InvalidOperationException($@"The {nameof(HitObject)} is already added to this {nameof(HitObjectEntryManager)}."); - - var entry = createLifetimeEntry(hitObject); - entryMap[hitObject] = entry; + var hitObject = entry.HitObject; parentMap[entry] = parent; + entryMap[hitObject] = entry; if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Add(entry); hitObject.DefaultsApplied += onDefaultsApplied; - childrenMap[entry.HitObject] = new List(); + childrenMap[hitObject] = new List(); OnEntryAdded?.Invoke(entry, parent); - return entry; } - public bool Remove(HitObject hitObject) + public void Remove(HitObjectLifetimeEntry entry) { - if (!entryMap.Remove(hitObject, out var entry)) - return false; + if (!parentMap.ContainsKey(entry)) + throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectLifetimeEntry)}."); + var hitObject = entry.HitObject; parentMap.Remove(entry, out var parent); + entryMap.Remove(hitObject); if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Remove(entry); @@ -94,11 +88,10 @@ namespace osu.Game.Rulesets.Objects.Pooling if (childrenMap.Remove(entry.HitObject, out var childEntries)) { foreach (var childEntry in childEntries) - Remove(childEntry.HitObject); + Remove(childEntry); } OnEntryRemoved?.Invoke(entry, parent); - return true; } public bool TryGet(HitObject hitObject, [MaybeNullWhen(false)] out HitObjectLifetimeEntry entry) @@ -115,7 +108,7 @@ namespace osu.Game.Rulesets.Objects.Pooling return; foreach (var entry in childEntries) - Remove(entry.HitObject); + Remove(entry); childEntries.Clear(); childrenMap[hitObject] = childEntries; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 5aaa7682db..2ec72d8fe3 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.UI [Resolved(CanBeNull = true)] private IReadOnlyList mods { get; set; } - private readonly HitObjectEntryManager entryManager; + private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); /// /// Creates a new . @@ -112,7 +112,6 @@ namespace osu.Game.Rulesets.UI h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); - entryManager = new HitObjectEntryManager(CreateLifetimeEntry); entryManager.OnEntryAdded += onEntryAdded; entryManager.OnEntryRemoved += onEntryRemoved; } @@ -271,7 +270,8 @@ namespace osu.Game.Rulesets.UI /// public virtual void Add(HitObject hitObject) { - entryManager.Add(hitObject, null); + var entry = CreateLifetimeEntry(hitObject); + entryManager.Add(entry, null); } private void preloadSamples(HitObject hitObject) @@ -294,7 +294,13 @@ namespace osu.Game.Rulesets.UI /// Whether the was successfully removed. public virtual bool Remove(HitObject hitObject) { - return entryManager.Remove(hitObject) || nestedPlayfields.Any(p => p.Remove(hitObject)); + if (entryManager.TryGet(hitObject, out var entry)) + { + entryManager.Remove(entry); + return true; + } + + return nestedPlayfields.Any(p => p.Remove(hitObject)); } private void onEntryAdded(HitObjectLifetimeEntry entry, [CanBeNull] HitObject parentHitObject) @@ -378,7 +384,10 @@ namespace osu.Game.Rulesets.UI } if (!entryManager.TryGet(hitObject, out var entry)) - entry = entryManager.Add(hitObject, parent?.HitObject); + { + entry = CreateLifetimeEntry(hitObject); + entryManager.Add(entry, parent?.HitObject); + } dho.ParentHitObject = parent; dho.Apply(entry); From 5dc0d32e4207199f93c0d2f054f3d145952ab066 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Sep 2022 15:24:20 +0900 Subject: [PATCH 146/583] Apply NRT to hitobject lifetime related classes --- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 4 +--- osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 9af4cf3544..fedf419973 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; @@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Objects /// The result that was judged with. /// This is set by the accompanying , and reused when required for rewinding. /// - internal JudgementResult Result; + internal JudgementResult? Result; private readonly IBindable startTimeBindable = new BindableDouble(); diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index d8dd6fab7e..30a8e8f549 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; From 1801ae3c6a16b20fd4ec7ad7e7741919566c083c Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 14 Sep 2022 17:40:22 +0300 Subject: [PATCH 147/583] Move flashlight TD difficulty reduction to diffcalc --- .../Difficulty/OsuDifficultyCalculator.cs | 3 +++ .../Difficulty/OsuPerformanceCalculator.cs | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 295fefffa3..fd4a21bb76 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -46,7 +46,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; if (mods.Any(m => m is OsuModTouchDevice)) + { aimRating = Math.Pow(aimRating, 0.8); + flashlightRating = Math.Pow(flashlightRating, 0.8); + } if (mods.Any(h => h is OsuModRelax)) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 2b0e2db898..30b56ff769 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -228,12 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double rawFlashlight = attributes.FlashlightDifficulty; - - if (score.Mods.Any(m => m is OsuModTouchDevice)) - rawFlashlight = Math.Pow(rawFlashlight, 0.8); - - double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; + double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) From 87384db8729401b7b6824880cd5cc95eeefa9126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 00:51:02 +0900 Subject: [PATCH 148/583] Fix slider rotation causing thousands of new drawables to be created --- .../Sliders/Components/PathControlPointPiece.cs | 7 +++++-- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 9 ++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index ab4b492767..59edbe8dc6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -66,11 +66,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components cachePoints(slider); sliderVersion = slider.Path.Version.GetBoundCopy(); - sliderVersion.BindValueChanged(_ => + + // schedule ensure that updates are only applied after all operations from a single frame are applied. + // this avoids inadvertently changing the slider path type for bach operations. + sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() => { cachePoints(slider); updatePathType(); - }); + })); controlPoint.Changed += updateMarkerDisplay; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 57d67acad5..280a35500a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -186,13 +186,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (h is IHasPath path) { - var controlPoints = path.Path.ControlPoints.Select(p => - new PathControlPoint(RotatePointAroundOrigin(p.Position, Vector2.Zero, delta), p.Type)).ToArray(); - - // Importantly, update as a single operation so automatic adjustment of control points to different - // curve types does not unexpectedly trigger and change the slider's shape. - path.Path.ControlPoints.Clear(); - path.Path.ControlPoints.AddRange(controlPoints); + foreach (PathControlPoint t in path.Path.ControlPoints) + t.Position = RotatePointAroundOrigin(t.Position, Vector2.Zero, delta); } } From d6db82283a806ab62114ee3c830cd15152a5a1ef Mon Sep 17 00:00:00 2001 From: Mk-56spn Date: Wed, 14 Sep 2022 18:01:29 +0200 Subject: [PATCH 149/583] Removes unused class --- osu.Game/Screens/Play/KeyCounterState.cs | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 osu.Game/Screens/Play/KeyCounterState.cs diff --git a/osu.Game/Screens/Play/KeyCounterState.cs b/osu.Game/Screens/Play/KeyCounterState.cs deleted file mode 100644 index 45d027e5ec..0000000000 --- a/osu.Game/Screens/Play/KeyCounterState.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -namespace osu.Game.Screens.Play -{ - public class KeyCounterState - { - public KeyCounterState(double time, int count) - { - Time = time; - Count = count; - } - - public readonly double Time; - public readonly int Count; - } -} From c203a030c1bdec9b40168b1ca8844c802436b4d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 04:28:07 +0900 Subject: [PATCH 150/583] Update osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs Co-authored-by: apollo <83023433+apollo-dw@users.noreply.github.com> --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 59edbe8dc6..5dec5d1cb0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components sliderVersion = slider.Path.Version.GetBoundCopy(); // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the slider path type for bach operations. + // this avoids inadvertently changing the slider path type for batch operations. sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() => { cachePoints(slider); From de7dd29d791304fe588ae1f297ecff8e128168c4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Sep 2022 03:57:05 +0300 Subject: [PATCH 151/583] Add "Nominations" and "Updated" sorting criteria in beatmap listing --- osu.Game/Overlays/BeatmapListing/SortCriteria.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs index ff6e62b274..6c010c7504 100644 --- a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs +++ b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs @@ -19,6 +19,9 @@ namespace osu.Game.Overlays.BeatmapListing [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingDifficulty))] Difficulty, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingUpdated))] + Updated, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingRanked))] Ranked, @@ -32,6 +35,9 @@ namespace osu.Game.Overlays.BeatmapListing Favourites, [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingRelevance))] - Relevance + Relevance, + + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingNominations))] + Nominations, } } From efebe55d228f9a669bc500a822bf211ca34e6ba3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Sep 2022 03:58:32 +0300 Subject: [PATCH 152/583] Display certain sort criterias based on selected category and query --- .../BeatmapListingFilterControl.cs | 21 ++++---- .../BeatmapListingSortTabControl.cs | 53 +++++++++++++++++-- osu.Game/Overlays/OverlaySortTabControl.cs | 4 +- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 2ca369d459..ff9a46ed4f 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -151,19 +151,20 @@ namespace osu.Game.Overlays.BeatmapListing config.BindWith(OsuSetting.BeatmapListingCardSize, cardSize); - var sortCriteria = sortControl.Current; - var sortDirection = sortControl.SortDirection; - - searchControl.Query.BindValueChanged(query => + searchControl.Query.BindValueChanged(_ => { - sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? SortCriteria.Ranked : SortCriteria.Relevance; - sortDirection.Value = SortDirection.Descending; + resetSortControl(); queueUpdateSearch(true); }); + searchControl.Category.BindValueChanged(_ => + { + resetSortControl(); + queueUpdateSearch(); + }); + searchControl.General.CollectionChanged += (_, _) => queueUpdateSearch(); searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.CollectionChanged += (_, _) => queueUpdateSearch(); @@ -171,8 +172,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); searchControl.ExplicitContent.BindValueChanged(_ => queueUpdateSearch()); - sortCriteria.BindValueChanged(_ => queueUpdateSearch()); - sortDirection.BindValueChanged(_ => queueUpdateSearch()); + sortControl.Current.BindValueChanged(_ => queueUpdateSearch()); + sortControl.SortDirection.BindValueChanged(_ => queueUpdateSearch()); apiUser = api.LocalUser.GetBoundCopy(); apiUser.BindValueChanged(_ => queueUpdateSearch()); @@ -199,6 +200,8 @@ namespace osu.Game.Overlays.BeatmapListing performRequest(); } + private void resetSortControl() => sortControl.Reset(searchControl.Category.Value, !string.IsNullOrEmpty(searchControl.Query.Value)); + private void queueUpdateSearch(bool queryTextChanged = false) { SearchStarted?.Invoke(); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs index 454f381c60..bc1f30dcaf 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs @@ -17,18 +17,65 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly Bindable SortDirection = new Bindable(Overlays.SortDirection.Descending); - public BeatmapListingSortTabControl() + private SearchCategory? lastCategory; + private bool? lastHasQuery; + + protected override void LoadComplete() { - Current.Value = SortCriteria.Ranked; + base.LoadComplete(); + Reset(SearchCategory.Leaderboard, false); + } + + public void Reset(SearchCategory category, bool hasQuery) + { + if (category != lastCategory || hasQuery != lastHasQuery) + { + TabControl.Clear(); + + TabControl.AddItem(SortCriteria.Title); + TabControl.AddItem(SortCriteria.Artist); + TabControl.AddItem(SortCriteria.Difficulty); + + if (category == SearchCategory.Any || category > SearchCategory.Loved) + TabControl.AddItem(SortCriteria.Updated); + + if (category < SearchCategory.Pending || category == SearchCategory.Mine) + TabControl.AddItem(SortCriteria.Ranked); + + TabControl.AddItem(SortCriteria.Rating); + TabControl.AddItem(SortCriteria.Plays); + TabControl.AddItem(SortCriteria.Favourites); + + if (hasQuery) + TabControl.AddItem(SortCriteria.Relevance); + + if (category == SearchCategory.Pending) + TabControl.AddItem(SortCriteria.Nominations); + } + + var nonQueryCriteria = category >= SearchCategory.Pending ? SortCriteria.Updated : SortCriteria.Ranked; + + Current.Value = hasQuery ? SortCriteria.Relevance : nonQueryCriteria; + SortDirection.Value = Overlays.SortDirection.Descending; + + // if the new criteria isn't different from the previous one, + // then re-adding tab items will not mark the current tab as selected. + // see: https://github.com/ppy/osu-framework/issues/5412 + TabControl.Current.TriggerChange(); + + lastCategory = category; + lastHasQuery = hasQuery; } protected override SortTabControl CreateControl() => new BeatmapSortTabControl { - SortDirection = { BindTarget = SortDirection } + SortDirection = { BindTarget = SortDirection }, }; private class BeatmapSortTabControl : SortTabControl { + protected override bool AddEnumEntriesAutomatically => false; + public readonly Bindable SortDirection = new Bindable(); protected override TabItem CreateTabItem(SortCriteria value) => new BeatmapSortTabItem(value) diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index f02d2b388f..befb011353 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -26,6 +26,8 @@ namespace osu.Game.Overlays { public class OverlaySortTabControl : CompositeDrawable, IHasCurrentValue { + public TabControl TabControl { get; } + private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current @@ -59,7 +61,7 @@ namespace osu.Game.Overlays Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = SortStrings.Default }, - CreateControl().With(c => + TabControl = CreateControl().With(c => { c.Anchor = Anchor.CentreLeft; c.Origin = Anchor.CentreLeft; From 9824d09d490341424a52cfa54cb594036710de82 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Sep 2022 03:58:42 +0300 Subject: [PATCH 153/583] Add test coverage --- .../TestSceneBeatmapListingSortTabControl.cs | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs index 3247461fba..0ef13385ec 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs @@ -3,9 +3,13 @@ #nullable disable +using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; @@ -15,12 +19,13 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneBeatmapListingSortTabControl : OsuTestScene { + private readonly BeatmapListingSortTabControl control; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); public TestSceneBeatmapListingSortTabControl() { - BeatmapListingSortTabControl control; OsuSpriteText current; OsuSpriteText direction; @@ -45,5 +50,83 @@ namespace osu.Game.Tests.Visual.UserInterface control.SortDirection.BindValueChanged(sortDirection => direction.Text = $"Sort direction: {sortDirection.NewValue}", true); control.Current.BindValueChanged(criteria => current.Text = $"Criteria: {criteria.NewValue}", true); } + + [Test] + public void TestRankedSort() + { + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Any); + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Leaderboard); + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Ranked); + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Qualified); + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Loved); + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Favourites); + criteriaShowsOnCategory(false, SortCriteria.Ranked, SearchCategory.Pending); + criteriaShowsOnCategory(false, SortCriteria.Ranked, SearchCategory.Wip); + criteriaShowsOnCategory(false, SortCriteria.Ranked, SearchCategory.Graveyard); + criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Mine); + } + + [Test] + public void TestUpdatedSort() + { + criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Any); + criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Leaderboard); + criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Ranked); + criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Qualified); + criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Loved); + criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Favourites); + criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Pending); + criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Wip); + criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Graveyard); + criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Mine); + } + + [Test] + public void TestNominationsSort() + { + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Any); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Leaderboard); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Ranked); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Qualified); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Loved); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Favourites); + criteriaShowsOnCategory(true, SortCriteria.Nominations, SearchCategory.Pending); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Wip); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Graveyard); + criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Mine); + } + + [Test] + public void TestResetNoQuery() + { + resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Any); + resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Leaderboard); + resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Ranked); + resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Qualified); + resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Loved); + resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Favourites); + resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Pending); + resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Wip); + resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Graveyard); + resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine); + } + + private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category) + { + AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () => + { + control.Reset(category, false); + return control.ChildrenOfType>().Single().Items.Contains(criteria) == expected; + }); + } + + private void resetUsesCriteriaOnCategory(SortCriteria criteria, SearchCategory category) + { + AddAssert($"reset uses {criteria.ToString().ToLowerInvariant()} on {category.ToString().ToLowerInvariant()}", () => + { + control.Reset(category, false); + return control.Current.Value == criteria; + }); + } } } From 31bc067dd1f9a194729580875ef0a82f191988fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Sep 2022 15:38:44 +0900 Subject: [PATCH 154/583] Don't store nulls to parentMap --- .../Objects/Pooling/HitObjectEntryManager.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index 30a8e8f549..66aa2cfaf7 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -36,13 +36,12 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// Stores the parent hit object for entries of the nested hit objects. - /// A null is stored for entries of the top-level hit objects. /// /// /// The parent hit object of a pooled hit object may be non-pooled. /// In that case, no corresponding is stored in this . /// - private readonly Dictionary parentMap = new Dictionary(); + private readonly Dictionary parentMap = new Dictionary(); /// /// Stores the list of entries managed by this for each hit object managed by this . @@ -55,16 +54,17 @@ namespace osu.Game.Rulesets.Objects.Pooling throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}."); var hitObject = entry.HitObject; - parentMap[entry] = parent; entryMap[hitObject] = entry; - - if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) - parentChildEntries.Add(entry); - - hitObject.DefaultsApplied += onDefaultsApplied; - childrenMap[hitObject] = new List(); + if (parent != null) + { + parentMap[entry] = parent; + if (childrenMap.TryGetValue(parent, out var parentChildEntries)) + parentChildEntries.Add(entry); + } + + hitObject.DefaultsApplied += onDefaultsApplied; OnEntryAdded?.Invoke(entry, parent); } @@ -74,14 +74,11 @@ namespace osu.Game.Rulesets.Objects.Pooling throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectLifetimeEntry)}."); var hitObject = entry.HitObject; - parentMap.Remove(entry, out var parent); entryMap.Remove(hitObject); - if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) + if (parentMap.Remove(entry, out var parent) && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Remove(entry); - hitObject.DefaultsApplied -= onDefaultsApplied; - // Remove all entries of the nested hit objects if (childrenMap.Remove(entry.HitObject, out var childEntries)) { @@ -89,6 +86,7 @@ namespace osu.Game.Rulesets.Objects.Pooling Remove(childEntry); } + hitObject.DefaultsApplied -= onDefaultsApplied; OnEntryRemoved?.Invoke(entry, parent); } From 68e4d2289814dedf98fbe48dfe7116c3a30c9df6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Sep 2022 15:40:03 +0900 Subject: [PATCH 155/583] Precondition against entryMap intead of parentMap --- .../Objects/Pooling/HitObjectEntryManager.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index 66aa2cfaf7..11caf79d8b 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -50,10 +50,11 @@ namespace osu.Game.Rulesets.Objects.Pooling public void Add(HitObjectLifetimeEntry entry, HitObject? parent) { - if (parentMap.ContainsKey(entry)) + HitObject hitObject = entry.HitObject; + + if (entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}."); - var hitObject = entry.HitObject; entryMap[hitObject] = entry; childrenMap[hitObject] = new List(); @@ -70,17 +71,18 @@ namespace osu.Game.Rulesets.Objects.Pooling public void Remove(HitObjectLifetimeEntry entry) { - if (!parentMap.ContainsKey(entry)) + HitObject hitObject = entry.HitObject; + + if (!entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectLifetimeEntry)}."); - var hitObject = entry.HitObject; entryMap.Remove(hitObject); if (parentMap.Remove(entry, out var parent) && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Remove(entry); // Remove all entries of the nested hit objects - if (childrenMap.Remove(entry.HitObject, out var childEntries)) + if (childrenMap.Remove(hitObject, out var childEntries)) { foreach (var childEntry in childEntries) Remove(childEntry); From c847cc521e34022b9146aa45b96aa069cd2715a1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Sep 2022 16:00:35 +0900 Subject: [PATCH 156/583] Add some inline comments --- .../Rulesets/Objects/Pooling/HitObjectEntryManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index 11caf79d8b..0b30ec09f9 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -55,9 +55,11 @@ namespace osu.Game.Rulesets.Objects.Pooling if (entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}."); + // Add the entry. entryMap[hitObject] = entry; childrenMap[hitObject] = new List(); + // If the entry has a parent, set it and add the entry to the parent's children. if (parent != null) { parentMap[entry] = parent; @@ -74,14 +76,15 @@ namespace osu.Game.Rulesets.Objects.Pooling HitObject hitObject = entry.HitObject; if (!entryMap.ContainsKey(hitObject)) - throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectLifetimeEntry)}."); + throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectEntryManager)}."); entryMap.Remove(hitObject); + // If the entry has a parent, unset it and remove the entry from the parents' children. if (parentMap.Remove(entry, out var parent) && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Remove(entry); - // Remove all entries of the nested hit objects + // Remove all the entries' children. if (childrenMap.Remove(hitObject, out var childEntries)) { foreach (var childEntry in childEntries) @@ -105,9 +108,11 @@ namespace osu.Game.Rulesets.Objects.Pooling if (!childrenMap.Remove(hitObject, out var childEntries)) return; + // Remove all the entries' children. At this point the parents' (this entries') children list has been removed from the map, so this does not cause upwards traversal. foreach (var entry in childEntries) Remove(entry); + // The removed children list needs to be added back to the map for the entry to potentially receive children. childEntries.Clear(); childrenMap[hitObject] = childEntries; } From e56ea58e71ca78b6f284e37e0c68def4a5889eb5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Sep 2022 16:06:21 +0900 Subject: [PATCH 157/583] Fix up xmldoc --- osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index 0b30ec09f9..6c39ea44da 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Objects.Pooling private readonly Dictionary parentMap = new Dictionary(); /// - /// Stores the list of entries managed by this for each hit object managed by this . + /// Stores the list of child entries for each hit object managed by this . /// private readonly Dictionary> childrenMap = new Dictionary>(); From 0329601e84c1a26929f0b89c3b86eb1e1ebf350e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Sep 2022 16:09:27 +0900 Subject: [PATCH 158/583] Fix inspection --- .../Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 79e3cf000e..b6da562bd0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("hit first hitobject", () => { InputManager.Click(MouseButton.Left); - return nextObjectEntry.Result.HasResult; + return nextObjectEntry.Result?.HasResult == true; }); AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); From 40a60f7145312992a29f0496fc9bb8c08827e1bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:31:00 +0900 Subject: [PATCH 159/583] Remove all entity framework code and migrations --- CodeAnalysis/BannedSymbols.txt | 2 - osu.Desktop/osu.Desktop.csproj | 4 - .../Navigation/TestEFToRealmMigration.cs | 63 -- .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 2 +- osu.Game/Database/DatabaseContextFactory.cs | 218 ------- osu.Game/Database/DatabaseWriteUsage.cs | 60 -- osu.Game/Database/EFToRealmMigrator.cs | 595 ------------------ osu.Game/Database/IDatabaseContextFactory.cs | 23 - osu.Game/Database/OsuDbContext.cs | 214 ------- .../20171019041408_InitialCreate.Designer.cs | 293 --------- .../20171019041408_InitialCreate.cs | 314 --------- ...025071459_AddMissingIndexRules.Designer.cs | 299 --------- .../20171025071459_AddMissingIndexRules.cs | 83 --- ...eatmapOnlineIDUniqueConstraint.Designer.cs | 302 --------- ...5731_AddBeatmapOnlineIDUniqueConstraint.cs | 26 - ...034410_AddRulesetInfoShortName.Designer.cs | 307 --------- .../20171209034410_AddRulesetInfoShortName.cs | 36 -- .../20180125143340_Settings.Designer.cs | 329 ---------- .../Migrations/20180125143340_Settings.cs | 58 -- .../20180131154205_AddMuteBinding.cs | 26 - .../20180219060912_AddSkins.Designer.cs | 379 ----------- .../Migrations/20180219060912_AddSkins.cs | 74 --- ...54_RemoveUniqueHashConstraints.Designer.cs | 377 ----------- ...80529055154_RemoveUniqueHashConstraints.cs | 54 -- ...111_UpdateTaikoDefaultBindings.Designer.cs | 376 ----------- ...180621044111_UpdateTaikoDefaultBindings.cs | 20 - ...628011956_RemoveNegativeSetIDs.Designer.cs | 376 ----------- .../20180628011956_RemoveNegativeSetIDs.cs | 21 - .../20180913080842_AddRankStatus.Designer.cs | 380 ----------- .../20180913080842_AddRankStatus.cs | 36 -- ...0181007180454_StandardizePaths.Designer.cs | 380 ----------- .../20181007180454_StandardizePaths.cs | 27 - ...20181128100659_AddSkinInfoHash.Designer.cs | 387 ------------ .../20181128100659_AddSkinInfoHash.cs | 44 -- ...81130113755_AddScoreInfoTables.Designer.cs | 484 -------------- .../20181130113755_AddScoreInfoTables.cs | 115 ---- ...20190225062029_AddUserIDColumn.Designer.cs | 487 -------------- .../20190225062029_AddUserIDColumn.cs | 25 - .../20190525060824_SkinSettings.Designer.cs | 498 --------------- .../Migrations/20190525060824_SkinSettings.cs | 57 -- ...AddDateAddedColumnToBeatmapSet.Designer.cs | 489 -------------- ...05091246_AddDateAddedColumnToBeatmapSet.cs | 27 - ...8070844_AddBPMAndLengthColumns.Designer.cs | 504 --------------- .../20190708070844_AddBPMAndLengthColumns.cs | 36 -- ...20190913104727_AddBeatmapVideo.Designer.cs | 506 --------------- .../20190913104727_AddBeatmapVideo.cs | 25 - ...02094919_RefreshVolumeBindings.Designer.cs | 506 --------------- .../20200302094919_RefreshVolumeBindings.cs | 19 - ...01019224408_AddEpilepsyWarning.Designer.cs | 508 --------------- .../20201019224408_AddEpilepsyWarning.cs | 26 - ...700_RefreshVolumeBindingsAgain.Designer.cs | 506 --------------- ...210412045700_RefreshVolumeBindingsAgain.cs | 19 - ...60743_AddSkinInstantiationInfo.Designer.cs | 508 --------------- ...20210511060743_AddSkinInstantiationInfo.cs | 25 - ...9_AddAuthorIdToBeatmapMetadata.Designer.cs | 511 --------------- ...0514062639_AddAuthorIdToBeatmapMetadata.cs | 26 - ...824185035_AddCountdownSettings.Designer.cs | 513 --------------- .../20210824185035_AddCountdownSettings.cs | 26 - ...11_AddSamplesMatchPlaybackRate.Designer.cs | 515 --------------- ...10912144011_AddSamplesMatchPlaybackRate.cs | 26 - .../20211020081609_ResetSkinHashes.cs | 23 - .../Migrations/OsuDbContextModelSnapshot.cs | 513 --------------- osu.Game/OsuGameBase.cs | 10 +- osu.Game/Screens/Loader.cs | 22 +- osu.sln.DotSettings | 1 - 65 files changed, 3 insertions(+), 13738 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs delete mode 100644 osu.Game/Database/DatabaseContextFactory.cs delete mode 100644 osu.Game/Database/DatabaseWriteUsage.cs delete mode 100644 osu.Game/Database/EFToRealmMigrator.cs delete mode 100644 osu.Game/Database/IDatabaseContextFactory.cs delete mode 100644 osu.Game/Database/OsuDbContext.cs delete mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs delete mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.cs delete mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs delete mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs delete mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs delete mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs delete mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs delete mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs delete mode 100644 osu.Game/Migrations/20180125143340_Settings.Designer.cs delete mode 100644 osu.Game/Migrations/20180125143340_Settings.cs delete mode 100644 osu.Game/Migrations/20180131154205_AddMuteBinding.cs delete mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs delete mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs delete mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs delete mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs delete mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs delete mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs delete mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs delete mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs delete mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs delete mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.cs delete mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs delete mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.cs delete mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs delete mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs delete mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs delete mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs delete mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs delete mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.cs delete mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs delete mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.cs delete mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs delete mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs delete mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs delete mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs delete mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs delete mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs delete mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs delete mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs delete mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs delete mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs delete mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs delete mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs delete mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs delete mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs delete mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs delete mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs delete mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs delete mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.cs delete mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs delete mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs delete mode 100644 osu.Game/Migrations/20211020081609_ResetSkinHashes.cs delete mode 100644 osu.Game/Migrations/OsuDbContextModelSnapshot.cs diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index e779ee6658..022da0a2ea 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -6,8 +6,6 @@ T:System.IComparable;Don't use non-generic IComparable. Use generic version inst T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) -T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. -T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods. M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index c67017f175..8ef480d4e3 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,10 +28,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs b/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs deleted file mode 100644 index c3559589ed..0000000000 --- a/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Linq; -using System.Runtime.InteropServices; -using NUnit.Framework; -using osu.Framework; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Models; -using osu.Game.Scoring; -using osu.Game.Skinning; -using osu.Game.Tests.Resources; - -namespace osu.Game.Tests.Visual.Navigation -{ - public class TestEFToRealmMigration : OsuGameTestScene - { - public override void RecycleLocalStorage(bool isDisposing) - { - base.RecycleLocalStorage(isDisposing); - - if (isDisposing) - return; - - using (var outStream = LocalStorage.CreateFileSafely(DatabaseContextFactory.DATABASE_NAME)) - using (var stream = TestResources.OpenResource(DatabaseContextFactory.DATABASE_NAME)) - stream.CopyTo(outStream); - } - - [SetUp] - public void SetUp() - { - if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64) - Assert.Ignore("EF-to-realm migrations are not supported on M1 ARM architectures."); - } - - public override void SetUpSteps() - { - // base SetUpSteps are executed before the above SetUp, therefore early-return to allow ignoring test properly. - // attempting to ignore here would yield a TargetInvocationException instead. - if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64) - return; - - base.SetUpSteps(); - } - - [Test] - public void TestMigration() - { - // Numbers are taken from the test database (see commit f03de16ee5a46deac3b5f2ca1edfba5c4c5dca7d). - AddAssert("Check beatmaps", () => Game.Dependencies.Get().Run(r => r.All().Count(s => !s.Protected) == 1)); - AddAssert("Check skins", () => Game.Dependencies.Get().Run(r => r.All().Count(s => !s.Protected) == 1)); - AddAssert("Check scores", () => Game.Dependencies.Get().Run(r => r.All().Count() == 1)); - - // One extra file is created during realm migration / startup due to the circles intro import. - AddAssert("Check files", () => Game.Dependencies.Get().Run(r => r.All().Count() == 271)); - } - } -} diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index f96fcc2630..aa773da6d3 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -192,7 +192,7 @@ namespace osu.Game.Beatmaps try { - using (var db = new SqliteConnection(DatabaseContextFactory.CreateDatabaseConnectionString("online.db", storage))) + using (var db = new SqliteConnection(string.Concat("Data Source=", storage.GetFullPath($@"{"online.db"}", true)))) { db.Open(); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs deleted file mode 100644 index af91fb4971..0000000000 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.IO; -using System.Linq; -using System.Threading; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Framework.Statistics; - -namespace osu.Game.Database -{ - public class DatabaseContextFactory : IDatabaseContextFactory - { - private readonly Storage storage; - - public const string DATABASE_NAME = @"client.db"; - - private ThreadLocal threadContexts; - - private readonly object writeLock = new object(); - - private bool currentWriteDidWrite; - private bool currentWriteDidError; - - private int currentWriteUsages; - - private IDbContextTransaction currentWriteTransaction; - - public DatabaseContextFactory(Storage storage) - { - this.storage = storage; - recycleThreadContexts(); - } - - private static readonly GlobalStatistic reads = GlobalStatistics.Get("Database", "Get (Read)"); - private static readonly GlobalStatistic writes = GlobalStatistics.Get("Database", "Get (Write)"); - private static readonly GlobalStatistic commits = GlobalStatistics.Get("Database", "Commits"); - private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Database", "Rollbacks"); - - /// - /// Get a context for the current thread for read-only usage. - /// If a is in progress, the existing write-safe context will be returned. - /// - public OsuDbContext Get() - { - reads.Value++; - return threadContexts.Value; - } - - /// - /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). - /// This method may block if a write is already active on a different thread. - /// - /// Whether to start a transaction for this write. - /// A usage containing a usable context. - public DatabaseWriteUsage GetForWrite(bool withTransaction = true) - { - writes.Value++; - Monitor.Enter(writeLock); - OsuDbContext context; - - try - { - if (currentWriteTransaction == null && withTransaction) - { - // this mitigates the fact that changes on tracked entities will not be rolled back with the transaction by ensuring write operations are always executed in isolated contexts. - // if this results in sub-optimal efficiency, we may need to look into removing Database-level transactions in favour of running SaveChanges where we currently commit the transaction. - if (threadContexts.IsValueCreated) - recycleThreadContexts(); - - context = threadContexts.Value; - currentWriteTransaction = context.Database.BeginTransaction(); - } - else - { - // we want to try-catch the retrieval of the context because it could throw an error (in CreateContext). - context = threadContexts.Value; - } - } - catch - { - // retrieval of a context could trigger a fatal error. - Monitor.Exit(writeLock); - throw; - } - - Interlocked.Increment(ref currentWriteUsages); - - return new DatabaseWriteUsage(context, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; - } - - private void usageCompleted(DatabaseWriteUsage usage) - { - int usages = Interlocked.Decrement(ref currentWriteUsages); - - try - { - currentWriteDidWrite |= usage.PerformedWrite; - currentWriteDidError |= usage.Errors.Any(); - - if (usages == 0) - { - if (currentWriteDidError) - { - rollbacks.Value++; - currentWriteTransaction?.Rollback(); - } - else - { - commits.Value++; - currentWriteTransaction?.Commit(); - } - - if (currentWriteDidWrite || currentWriteDidError) - { - // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged). - usage.Context.Dispose(); - - // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches. - recycleThreadContexts(); - } - - currentWriteTransaction = null; - currentWriteDidWrite = false; - currentWriteDidError = false; - } - } - finally - { - Monitor.Exit(writeLock); - } - } - - private void recycleThreadContexts() - { - // Contexts for other threads are not disposed as they may be in use elsewhere. Instead, fresh contexts are exposed - // for other threads to use, and we rely on the finalizer inside OsuDbContext to handle their previous contexts - threadContexts?.Value.Dispose(); - threadContexts = new ThreadLocal(CreateContext, true); - } - - protected virtual OsuDbContext CreateContext() => new OsuDbContext(CreateDatabaseConnectionString(DATABASE_NAME, storage)) - { - Database = { AutoTransactionsEnabled = false } - }; - - public void CreateBackup(string backupFilename) - { - Logger.Log($"Creating full EF database backup at {backupFilename}", LoggingTarget.Database); - - using (var source = storage.GetStream(DATABASE_NAME, mode: FileMode.Open)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - } - - public void ResetDatabase() - { - lock (writeLock) - { - recycleThreadContexts(); - - try - { - int attempts = 10; - - // Retry logic taken from MigratableStorage.AttemptOperation. - while (true) - { - try - { - storage.Delete(DATABASE_NAME); - return; - } - catch (Exception) - { - if (attempts-- == 0) - throw; - } - - Thread.Sleep(250); - } - } - catch - { - // for now we are not sure why file handles are kept open by EF, but this is generally only used in testing - } - } - } - - public void FlushConnections() - { - if (threadContexts != null) - { - foreach (var context in threadContexts.Values) - context.Dispose(); - } - - recycleThreadContexts(); - } - - public static string CreateDatabaseConnectionString(string filename, Storage storage) => string.Concat("Data Source=", storage.GetFullPath($@"{filename}", true)); - - private readonly ManualResetEventSlim migrationComplete = new ManualResetEventSlim(); - - public void SetMigrationCompletion() => migrationComplete.Set(); - - public void WaitForMigrationCompletion() - { - if (!migrationComplete.Wait(300000)) - throw new TimeoutException("Migration took too long (likely stuck)."); - } - } -} diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs deleted file mode 100644 index b5f25eae20..0000000000 --- a/osu.Game/Database/DatabaseWriteUsage.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; - -namespace osu.Game.Database -{ - public class DatabaseWriteUsage : IDisposable - { - public readonly OsuDbContext Context; - private readonly Action usageCompleted; - - public DatabaseWriteUsage(OsuDbContext context, Action onCompleted) - { - Context = context; - usageCompleted = onCompleted; - } - - public bool PerformedWrite { get; private set; } - - private bool isDisposed; - public List Errors = new List(); - - /// - /// Whether this write usage will commit a transaction on completion. - /// If false, there is a parent usage responsible for transaction commit. - /// - public bool IsTransactionLeader; - - protected void Dispose(bool disposing) - { - if (isDisposed) return; - - isDisposed = true; - - try - { - PerformedWrite |= Context.SaveChanges() > 0; - } - catch (Exception e) - { - Errors.Add(e); - throw; - } - finally - { - usageCompleted?.Invoke(this); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs deleted file mode 100644 index 294a8cd3ed..0000000000 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ /dev/null @@ -1,595 +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.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using osu.Framework; -using osu.Framework.Allocation; -using osu.Framework.Development; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Models; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Skinning; -using osuTK; -using Realms; -using SharpCompress.Archives; -using SharpCompress.Archives.Zip; -using SharpCompress.Common; -using SharpCompress.Writers.Zip; - -namespace osu.Game.Database -{ - internal class EFToRealmMigrator : CompositeDrawable - { - public Task MigrationCompleted => migrationCompleted.Task; - - private readonly TaskCompletionSource migrationCompleted = new TaskCompletionSource(); - - [Resolved] - private DatabaseContextFactory efContextFactory { get; set; } = null!; - - [Resolved] - private RealmAccess realm { get; set; } = null!; - - [Resolved] - private OsuConfigManager config { get; set; } = null!; - - [Resolved] - private INotificationOverlay notificationOverlay { get; set; } = null!; - - [Resolved] - private OsuGame game { get; set; } = null!; - - [Resolved] - private Storage storage { get; set; } = null!; - - private readonly OsuTextFlowContainer currentOperationText; - - public EFToRealmMigrator() - { - RelativeSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(10), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Database migration in progress", - Font = OsuFont.Default.With(size: 40) - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "This could take a few minutes depending on the speed of your disk(s).", - Font = OsuFont.Default.With(size: 30) - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Please keep the window open until this completes!", - Font = OsuFont.Default.With(size: 30) - }, - new LoadingSpinner(true) - { - State = { Value = Visibility.Visible } - }, - currentOperationText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 30)) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - TextAnchor = Anchor.TopCentre, - }, - } - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - beginMigration(); - } - - private void beginMigration() - { - const string backup_folder = "backups"; - - string backupSuffix = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - - // required for initial backup. - var realmBlockOperations = realm.BlockAllOperations("EF migration"); - - Task.Factory.StartNew(() => - { - try - { - realm.CreateBackup(Path.Combine(backup_folder, $"client.{backupSuffix}.realm")); - } - finally - { - // Once the backup is created, we need to stop blocking operations so the migration can complete. - realmBlockOperations.Dispose(); - // Clean up here so we don't accidentally dispose twice. - realmBlockOperations = null; - } - - efContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{backupSuffix}.db")); - - using (var ef = efContextFactory.Get()) - { - realm.Write(r => - { - // Before beginning, ensure realm is in an empty state. - // Migrations which are half-completed could lead to issues if the user tries a second time. - // Note that we only do this for beatmaps and scores since the other migrations are yonks old. - r.RemoveAll(); - r.RemoveAll(); - r.RemoveAll(); - r.RemoveAll(); - }); - - ef.Migrate(); - - migrateSettings(ef); - migrateSkins(ef); - migrateBeatmaps(ef); - migrateScores(ef); - } - }, TaskCreationOptions.LongRunning).ContinueWith(t => - { - if (t.Exception == null) - { - log("Migration successful!"); - - if (DebugUtils.IsDebugBuild) - { - Logger.Log( - "Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", - level: LogLevel.Important); - } - } - else - { - log("Migration failed!"); - Logger.Log(t.Exception.ToString(), LoggingTarget.Database); - - if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && t.Exception.Flatten().InnerException is TypeInitializationException) - { - // Not guaranteed to be the only cause of exception, but let's roll with it for now. - log("Please download and run the intel version of osu! once\nto allow data migration to complete!"); - efContextFactory.SetMigrationCompletion(); - return; - } - - notificationOverlay.Post(new SimpleErrorNotification - { - Text = - "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", - Activated = () => - { - game.OpenUrlExternally( - $@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", - true); - - const string attachment_filename = "attach_me.zip"; - - var backupStorage = storage.GetStorageForDirectory(backup_folder); - - backupStorage.Delete(attachment_filename); - - try - { - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); - zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); - } - } - catch { } - - backupStorage.PresentFileExternally(attachment_filename); - - return true; - } - }); - } - - // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. - // If we were to not do this, the migration would run another time the next time the user starts the game. - deletePreRealmData(); - - // If something went wrong and the disposal token wasn't invoked above, ensure it is here. - realmBlockOperations?.Dispose(); - - migrationCompleted.SetResult(true); - efContextFactory.SetMigrationCompletion(); - }); - } - - private void deletePreRealmData() - { - // Delete the database permanently. - // Will cause future startups to not attempt migration. - efContextFactory.ResetDatabase(); - } - - private void log(string message) - { - Logger.Log(message, LoggingTarget.Database); - Scheduler.AddOnce(m => currentOperationText.Text = m, message); - } - - private void migrateBeatmaps(OsuDbContext ef) - { - // can be removed 20220730. - var existingBeatmapSets = ef.EFBeatmapSetInfo - .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(s => s.Metadata) - .AsSplitQuery(); - - log("Beginning beatmaps migration to realm"); - - // previous entries in EF are removed post migration. - if (!existingBeatmapSets.Any()) - { - log("No beatmaps found to migrate"); - return; - } - - int count = existingBeatmapSets.Count(); - - realm.Run(r => - { - log($"Found {count} beatmaps in EF"); - - var transaction = r.BeginWrite(); - int written = 0; - int missing = 0; - - try - { - foreach (var beatmapSet in existingBeatmapSets) - { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = r.BeginWrite(); - log($"Migrated {written}/{count} beatmaps..."); - } - - var realmBeatmapSet = new BeatmapSetInfo - { - OnlineID = beatmapSet.OnlineID ?? -1, - DateAdded = beatmapSet.DateAdded, - Status = beatmapSet.Status, - DeletePending = beatmapSet.DeletePending, - Hash = beatmapSet.Hash, - Protected = beatmapSet.Protected, - }; - - migrateFiles(beatmapSet, r, realmBeatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - var ruleset = r.Find(beatmap.RulesetInfo.ShortName); - var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); - - if (ruleset == null) - { - log($"Skipping {++missing} beatmaps with missing ruleset"); - continue; - } - - var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) - { - DifficultyName = beatmap.DifficultyName, - Status = beatmap.Status, - OnlineID = beatmap.OnlineID ?? -1, - Length = beatmap.Length, - BPM = beatmap.BPM, - Hash = beatmap.Hash, - StarRating = beatmap.StarRating, - MD5Hash = beatmap.MD5Hash, - Hidden = beatmap.Hidden, - AudioLeadIn = beatmap.AudioLeadIn, - StackLeniency = beatmap.StackLeniency, - SpecialStyle = beatmap.SpecialStyle, - LetterboxInBreaks = beatmap.LetterboxInBreaks, - WidescreenStoryboard = beatmap.WidescreenStoryboard, - EpilepsyWarning = beatmap.EpilepsyWarning, - SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, - DistanceSpacing = beatmap.DistanceSpacing, - BeatDivisor = beatmap.BeatDivisor, - GridSize = beatmap.GridSize, - TimelineZoom = beatmap.TimelineZoom, - Countdown = beatmap.Countdown, - CountdownOffset = beatmap.CountdownOffset, - Bookmarks = beatmap.Bookmarks, - BeatmapSet = realmBeatmapSet, - }; - - realmBeatmapSet.Beatmaps.Add(realmBeatmap); - } - - r.Add(realmBeatmapSet); - } - } - finally - { - transaction.Commit(); - } - - log($"Successfully migrated {count} beatmaps to realm"); - }); - } - - private BeatmapMetadata getBestMetadata(EFBeatmapMetadata? beatmapMetadata, EFBeatmapMetadata? beatmapSetMetadata) - { - var metadata = beatmapMetadata ?? beatmapSetMetadata ?? new EFBeatmapMetadata(); - - return new BeatmapMetadata - { - Title = metadata.Title, - TitleUnicode = metadata.TitleUnicode, - Artist = metadata.Artist, - ArtistUnicode = metadata.ArtistUnicode, - Author = - { - OnlineID = metadata.Author.Id, - Username = metadata.Author.Username, - }, - Source = metadata.Source, - Tags = metadata.Tags, - PreviewTime = metadata.PreviewTime, - AudioFile = metadata.AudioFile, - BackgroundFile = metadata.BackgroundFile, - }; - } - - private void migrateScores(OsuDbContext db) - { - // can be removed 20220730. - var existingScores = db.ScoreInfo - .Include(s => s.Ruleset) - .Include(s => s.BeatmapInfo) - .Include(s => s.Files) - .ThenInclude(f => f.FileInfo) - .AsSplitQuery(); - - log("Beginning scores migration to realm"); - - // previous entries in EF are removed post migration. - if (!existingScores.Any()) - { - log("No scores found to migrate"); - return; - } - - int count = existingScores.Count(); - - realm.Run(r => - { - log($"Found {count} scores in EF"); - - var transaction = r.BeginWrite(); - int written = 0; - int missing = 0; - - try - { - foreach (var score in existingScores) - { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = r.BeginWrite(); - log($"Migrated {written}/{count} scores..."); - } - - var beatmap = r.All().FirstOrDefault(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = r.Find(score.Ruleset.ShortName); - - if (beatmap == null || ruleset == null) - { - log($"Skipping {++missing} scores with missing ruleset or beatmap"); - continue; - } - - var user = new RealmUser - { - OnlineID = score.User.OnlineID, - Username = score.User.Username - }; - - var realmScore = new ScoreInfo(beatmap, ruleset, user) - { - Hash = score.Hash, - DeletePending = score.DeletePending, - OnlineID = score.OnlineID ?? -1, - ModsJson = score.ModsJson, - StatisticsJson = score.StatisticsJson, - TotalScore = score.TotalScore, - MaxCombo = score.MaxCombo, - Accuracy = score.Accuracy, - Date = score.Date, - PP = score.PP, - Rank = score.Rank, - HitEvents = score.HitEvents, - Passed = score.Passed, - Combo = score.Combo, - Position = score.Position, - Statistics = score.Statistics, - Mods = score.Mods, - APIMods = score.APIMods, - }; - - migrateFiles(score, r, realmScore); - - r.Add(realmScore); - } - } - finally - { - transaction.Commit(); - } - - log($"Successfully migrated {count} scores to realm"); - }); - } - - private void migrateSkins(OsuDbContext db) - { - // can be removed 20220530. - var existingSkins = db.SkinInfo - .Include(s => s.Files) - .ThenInclude(f => f.FileInfo) - .AsSplitQuery() - .ToList(); - - // previous entries in EF are removed post migration. - if (!existingSkins.Any()) - return; - - var userSkinChoice = config.GetBindable(OsuSetting.Skin); - int.TryParse(userSkinChoice.Value, out int userSkinInt); - - switch (userSkinInt) - { - case EFSkinInfo.DEFAULT_SKIN: - userSkinChoice.Value = SkinInfo.DEFAULT_SKIN.ToString(); - break; - - case EFSkinInfo.CLASSIC_SKIN: - userSkinChoice.Value = SkinInfo.CLASSIC_SKIN.ToString(); - break; - } - - realm.Run(r => - { - using (var transaction = r.BeginWrite()) - { - // only migrate data if the realm database is empty. - // note that this cannot be written as: `r.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!r.All().Any(s => !s.Protected)) - { - log($"Migrating {existingSkins.Count} skins"); - - foreach (var skin in existingSkins) - { - var realmSkin = new SkinInfo - { - Name = skin.Name, - Creator = skin.Creator, - Hash = skin.Hash, - Protected = false, - InstantiationInfo = skin.InstantiationInfo, - }; - - migrateFiles(skin, r, realmSkin); - - r.Add(realmSkin); - - if (skin.ID == userSkinInt) - userSkinChoice.Value = realmSkin.ID.ToString(); - } - } - - transaction.Commit(); - } - }); - } - - private static void migrateFiles(IHasFiles fileSource, Realm realm, IHasRealmFiles realmObject) where T : INamedFileInfo - { - foreach (var file in fileSource.Files) - { - var realmFile = realm.Find(file.FileInfo.Hash); - - if (realmFile == null) - realm.Add(realmFile = new RealmFile { Hash = file.FileInfo.Hash }); - - realmObject.Files.Add(new RealmNamedFileUsage(realmFile, file.Filename)); - } - } - - private void migrateSettings(OsuDbContext db) - { - // migrate ruleset settings. can be removed 20220315. - var existingSettings = db.DatabasedSetting.ToList(); - - // previous entries in EF are removed post migration. - if (!existingSettings.Any()) - return; - - log("Beginning settings migration to realm"); - - realm.Run(r => - { - using (var transaction = r.BeginWrite()) - { - // only migrate data if the realm database is empty. - if (!r.All().Any()) - { - log($"Migrating {existingSettings.Count} settings"); - - foreach (var dkb in existingSettings) - { - if (dkb.RulesetID == null) - continue; - - string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); - - if (string.IsNullOrEmpty(shortName)) - continue; - - r.Add(new RealmRulesetSetting - { - Key = dkb.Key, - Value = dkb.StringValue, - RulesetName = shortName, - Variant = dkb.Variant ?? 0, - }); - } - } - - transaction.Commit(); - } - }); - } - - private string? getRulesetShortNameFromLegacyID(long rulesetId) => - efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - } -} diff --git a/osu.Game/Database/IDatabaseContextFactory.cs b/osu.Game/Database/IDatabaseContextFactory.cs deleted file mode 100644 index bddabad586..0000000000 --- a/osu.Game/Database/IDatabaseContextFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -namespace osu.Game.Database -{ - public interface IDatabaseContextFactory - { - /// - /// Get a context for read-only usage. - /// - OsuDbContext Get(); - - /// - /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). - /// This method may block if a write is already active on a different thread. - /// - /// Whether to start a transaction for this write. - /// A usage containing a usable context. - DatabaseWriteUsage GetForWrite(bool withTransaction = true); - } -} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs deleted file mode 100644 index 118a8ee469..0000000000 --- a/osu.Game/Database/OsuDbContext.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using osu.Framework.Logging; -using osu.Framework.Statistics; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.IO; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Skinning; -using SQLitePCL; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -namespace osu.Game.Database -{ - public class OsuDbContext : DbContext - { - public DbSet EFBeatmapInfo { get; set; } - public DbSet BeatmapDifficulty { get; set; } - public DbSet BeatmapMetadata { get; set; } - public DbSet EFBeatmapSetInfo { get; set; } - public DbSet FileInfo { get; set; } - public DbSet RulesetInfo { get; set; } - public DbSet SkinInfo { get; set; } - public DbSet ScoreInfo { get; set; } - - // migrated to realm - public DbSet DatabasedSetting { get; set; } - - private readonly string connectionString; - - private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory()); - - private static readonly GlobalStatistic contexts = GlobalStatistics.Get("Database", "Contexts"); - - static OsuDbContext() - { - // required to initialise native SQLite libraries on some platforms. - Batteries_V2.Init(); - - // https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678 - raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); - } - - /// - /// Create a new in-memory OsuDbContext instance. - /// - public OsuDbContext() - : this("DataSource=:memory:") - { - // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0). - - Migrate(); - } - - /// - /// Create a new OsuDbContext instance. - /// - /// A valid SQLite connection string. - public OsuDbContext(string connectionString) - { - this.connectionString = connectionString; - - var connection = Database.GetDbConnection(); - - try - { - connection.Open(); - - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "PRAGMA journal_mode=WAL;"; - cmd.ExecuteNonQuery(); - - cmd.CommandText = "PRAGMA foreign_keys=OFF;"; - cmd.ExecuteNonQuery(); - } - } - catch - { - connection.Close(); - throw; - } - - contexts.Value++; - } - - ~OsuDbContext() - { - // DbContext does not contain a finalizer (https://github.com/aspnet/EntityFrameworkCore/issues/8872) - // This is used to clean up previous contexts when fresh contexts are exposed via DatabaseContextFactory - Dispose(); - } - - private bool isDisposed; - - public override void Dispose() - { - if (isDisposed) return; - - isDisposed = true; - - base.Dispose(); - - contexts.Value--; - GC.SuppressFinalize(this); - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder - // this is required for the time being due to the way we are querying in places like BeatmapStore. - // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. - .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) - .UseLoggerFactory(logger.Value); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.MD5Hash); - modelBuilder.Entity().HasIndex(b => b.Hash); - - modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.DeletePending); - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.DeletePending); - modelBuilder.Entity().HasMany(s => s.Files).WithOne(f => f.SkinInfo); - - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); - - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.ReferenceCount); - - modelBuilder.Entity().HasIndex(b => b.Available); - modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); - - modelBuilder.Entity().HasOne(b => b.BaseDifficulty); - - modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); - } - - private class OsuDbLoggerFactory : ILoggerFactory - { - #region Disposal - - public void Dispose() - { - } - - #endregion - - public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); - - public void AddProvider(ILoggerProvider provider) - { - // no-op. called by tooling. - } - - private class OsuDbLogger : ILogger - { - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - if (logLevel < LogLevel.Information) - return; - - Framework.Logging.LogLevel frameworkLogLevel; - - switch (logLevel) - { - default: - frameworkLogLevel = Framework.Logging.LogLevel.Debug; - break; - - case LogLevel.Warning: - frameworkLogLevel = Framework.Logging.LogLevel.Important; - break; - - case LogLevel.Error: - case LogLevel.Critical: - frameworkLogLevel = Framework.Logging.LogLevel.Error; - break; - } - - Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel); - } - - public bool IsEnabled(LogLevel logLevel) - { -#if DEBUG_DATABASE - return logLevel > LogLevel.Debug; -#else - return logLevel > LogLevel.Information; -#endif - } - - public IDisposable BeginScope(TState state) => null; - } - } - - public void Migrate() => Database.Migrate(); - } -} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs deleted file mode 100644 index c751530bf4..0000000000 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ /dev/null @@ -1,293 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171019041408_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash"); - - b.HasIndex("MetadataID"); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs deleted file mode 100644 index 08ab64fd08..0000000000 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ /dev/null @@ -1,314 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "BeatmapDifficulty", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapMetadata", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "FileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Hash = table.Column(type: "TEXT", nullable: true), - ReferenceCount = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_FileInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "KeyBinding", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Action = table.Column(type: "INTEGER", nullable: false), - Keys = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: true), - Variant = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_KeyBinding", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "RulesetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Available = table.Column(type: "INTEGER", nullable: false), - InstantiationInfo = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RulesetInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapSetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", - column: x => x.BaseDifficultyID, - principalTable: "BeatmapDifficulty", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BeatmapSetFileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - FileInfoID = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_BaseDifficultyID", - table: "BeatmapInfo", - column: "BaseDifficultyID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_BeatmapSetInfoID", - table: "BeatmapInfo", - column: "BeatmapSetInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MetadataID", - table: "BeatmapInfo", - column: "MetadataID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_RulesetID", - table: "BeatmapInfo", - column: "RulesetID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", - table: "BeatmapSetFileInfo", - column: "BeatmapSetInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetFileInfo_FileInfoID", - table: "BeatmapSetFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_DeletePending", - table: "BeatmapSetInfo", - column: "DeletePending"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_MetadataID", - table: "BeatmapSetInfo", - column: "MetadataID"); - - migrationBuilder.CreateIndex( - name: "IX_FileInfo_Hash", - table: "FileInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_FileInfo_ReferenceCount", - table: "FileInfo", - column: "ReferenceCount"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Action", - table: "KeyBinding", - column: "Action"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding", - column: "Variant"); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_Available", - table: "RulesetInfo", - column: "Available"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BeatmapInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetFileInfo"); - - migrationBuilder.DropTable( - name: "KeyBinding"); - - migrationBuilder.DropTable( - name: "BeatmapDifficulty"); - - migrationBuilder.DropTable( - name: "RulesetInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetInfo"); - - migrationBuilder.DropTable( - name: "FileInfo"); - - migrationBuilder.DropTable( - name: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs deleted file mode 100644 index 4cd234f2ef..0000000000 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ /dev/null @@ -1,299 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171025071459_AddMissingIndexRules")] - partial class AddMissingIndexRules - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs deleted file mode 100644 index 4ec3952941..0000000000 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs +++ /dev/null @@ -1,83 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddMissingIndexRules : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", - table: "BeatmapSetInfo", - column: "OnlineBeatmapSetID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - } - } -} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs deleted file mode 100644 index 006acf12cd..0000000000 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ /dev/null @@ -1,302 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] - partial class AddBeatmapOnlineIDUniqueConstraint - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs deleted file mode 100644 index 6aba12f86f..0000000000 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs +++ /dev/null @@ -1,26 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBeatmapOnlineIDUniqueConstraint : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_OnlineBeatmapID", - table: "BeatmapInfo", - column: "OnlineBeatmapID", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_OnlineBeatmapID", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs deleted file mode 100644 index fc2496bc24..0000000000 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ /dev/null @@ -1,307 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171209034410_AddRulesetInfoShortName")] - partial class AddRulesetInfoShortName - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs deleted file mode 100644 index 5688455f79..0000000000 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs +++ /dev/null @@ -1,36 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddRulesetInfoShortName : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ShortName", - table: "RulesetInfo", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_ShortName", - table: "RulesetInfo", - column: "ShortName", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_RulesetInfo_ShortName", - table: "RulesetInfo"); - - migrationBuilder.DropColumn( - name: "ShortName", - table: "RulesetInfo"); - } - } -} diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs deleted file mode 100644 index 4bb599eec1..0000000000 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ /dev/null @@ -1,329 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180125143340_Settings")] - partial class Settings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs deleted file mode 100644 index 1feb37531f..0000000000 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ /dev/null @@ -1,58 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class Settings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding"); - - migrationBuilder.CreateTable( - name: "Settings", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Key = table.Column(type: "TEXT", nullable: false), - RulesetID = table.Column(type: "INTEGER", nullable: true), - Value = table.Column(type: "TEXT", nullable: true), - Variant = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Settings", x => x.ID); - }); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_RulesetID_Variant", - table: "KeyBinding", - columns: new[] { "RulesetID", "Variant" }); - - migrationBuilder.CreateIndex( - name: "IX_Settings_RulesetID_Variant", - table: "Settings", - columns: new[] { "RulesetID", "Variant" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Settings"); - - migrationBuilder.DropIndex( - name: "IX_KeyBinding_RulesetID_Variant", - table: "KeyBinding"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding", - column: "Variant"); - } - } -} diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs deleted file mode 100644 index 8646d1d76b..0000000000 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ /dev/null @@ -1,26 +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 Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using osu.Game.Database; -using osu.Game.Input.Bindings; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180131154205_AddMuteBinding")] - public partial class AddMuteBinding : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); - migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); - } - } -} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs deleted file mode 100644 index cdc4ef2e66..0000000000 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ /dev/null @@ -1,379 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180219060912_AddSkins")] - partial class AddSkins - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs deleted file mode 100644 index 319748bed6..0000000000 --- a/osu.Game/Migrations/20180219060912_AddSkins.cs +++ /dev/null @@ -1,74 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSkins : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "SkinInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Creator = table.Column(type: "TEXT", nullable: true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_SkinInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "SkinFileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - FileInfoID = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false), - SkinInfoID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SkinFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_SkinFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", - column: x => x.SkinInfoID, - principalTable: "SkinInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_SkinFileInfo_FileInfoID", - table: "SkinFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_SkinFileInfo_SkinInfoID", - table: "SkinFileInfo", - column: "SkinInfoID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "SkinFileInfo"); - - migrationBuilder.DropTable( - name: "SkinInfo"); - } - } -} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs deleted file mode 100644 index f28408bfb3..0000000000 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs +++ /dev/null @@ -1,377 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180529055154_RemoveUniqueHashConstraints")] - partial class RemoveUniqueHashConstraints - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs deleted file mode 100644 index 91eabe8868..0000000000 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs +++ /dev/null @@ -1,54 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RemoveUniqueHashConstraints : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash", - unique: true); - } - } -} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs deleted file mode 100644 index aaa11e88b6..0000000000 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180621044111_UpdateTaikoDefaultBindings")] - partial class UpdateTaikoDefaultBindings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs deleted file mode 100644 index d888ccd5a2..0000000000 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs +++ /dev/null @@ -1,20 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class UpdateTaikoDefaultBindings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - // we can't really tell if these should be restored or not, so let's just not do so. - } - } -} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs deleted file mode 100644 index 7eeacd56d7..0000000000 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180628011956_RemoveNegativeSetIDs")] - partial class RemoveNegativeSetIDs - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs deleted file mode 100644 index fdea636ac6..0000000000 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ /dev/null @@ -1,21 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RemoveNegativeSetIDs : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - // There was a change that beatmaps were being loaded with "-1" online IDs, which is completely incorrect. - // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps. - migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs deleted file mode 100644 index 5ab43da046..0000000000 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs +++ /dev/null @@ -1,380 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180913080842_AddRankStatus")] - partial class AddRankStatus - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs deleted file mode 100644 index bb147dff84..0000000000 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.cs +++ /dev/null @@ -1,36 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddRankStatus : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Status", - table: "BeatmapSetInfo", - nullable: false, - defaultValue: -3); // NONE - - migrationBuilder.AddColumn( - name: "Status", - table: "BeatmapInfo", - nullable: false, - defaultValue: -3); // NONE - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Status", - table: "BeatmapSetInfo"); - - migrationBuilder.DropColumn( - name: "Status", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs deleted file mode 100644 index b387a45ecf..0000000000 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs +++ /dev/null @@ -1,380 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20181007180454_StandardizePaths")] - partial class StandardizePaths - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs deleted file mode 100644 index 30f27043a0..0000000000 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ /dev/null @@ -1,27 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class StandardizePaths : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - string windowsStyle = @"\"; - string standardized = "/"; - - // Escaping \ does not seem to be needed. - migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs deleted file mode 100644 index 120674671a..0000000000 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs +++ /dev/null @@ -1,387 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20181128100659_AddSkinInfoHash")] - partial class AddSkinInfoHash - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs deleted file mode 100644 index ee825a1e9c..0000000000 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs +++ /dev/null @@ -1,44 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSkinInfoHash : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Hash", - table: "SkinInfo", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_SkinInfo_DeletePending", - table: "SkinInfo", - column: "DeletePending"); - - migrationBuilder.CreateIndex( - name: "IX_SkinInfo_Hash", - table: "SkinInfo", - column: "Hash", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_SkinInfo_DeletePending", - table: "SkinInfo"); - - migrationBuilder.DropIndex( - name: "IX_SkinInfo_Hash", - table: "SkinInfo"); - - migrationBuilder.DropColumn( - name: "Hash", - table: "SkinInfo"); - } - } -} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs deleted file mode 100644 index eee53182ce..0000000000 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs +++ /dev/null @@ -1,484 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20181130113755_AddScoreInfoTables")] - partial class AddScoreInfoTables - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany() - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs deleted file mode 100644 index 58980132f3..0000000000 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs +++ /dev/null @@ -1,115 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddScoreInfoTables : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ScoreInfo", - columns: table => new - { - ID = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Rank = table.Column(nullable: false), - TotalScore = table.Column(nullable: false), - Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), - PP = table.Column(nullable: true), - MaxCombo = table.Column(nullable: false), - Combo = table.Column(nullable: false), - RulesetID = table.Column(nullable: false), - Mods = table.Column(nullable: true), - User = table.Column(nullable: true), - BeatmapInfoID = table.Column(nullable: false), - OnlineScoreID = table.Column(nullable: true), - Date = table.Column(nullable: false), - Statistics = table.Column(nullable: true), - Hash = table.Column(nullable: true), - DeletePending = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ScoreInfo", x => x.ID); - table.ForeignKey( - name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", - column: x => x.BeatmapInfoID, - principalTable: "BeatmapInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_ScoreInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ScoreFileInfo", - columns: table => new - { - ID = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - FileInfoID = table.Column(nullable: false), - Filename = table.Column(nullable: false), - ScoreInfoID = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_ScoreFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", - column: x => x.ScoreInfoID, - principalTable: "ScoreInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_ScoreFileInfo_FileInfoID", - table: "ScoreFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_ScoreFileInfo_ScoreInfoID", - table: "ScoreFileInfo", - column: "ScoreInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_ScoreInfo_BeatmapInfoID", - table: "ScoreInfo", - column: "BeatmapInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_ScoreInfo_OnlineScoreID", - table: "ScoreInfo", - column: "OnlineScoreID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ScoreInfo_RulesetID", - table: "ScoreInfo", - column: "RulesetID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ScoreFileInfo"); - - migrationBuilder.DropTable( - name: "ScoreInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs deleted file mode 100644 index 8e1e3a59f3..0000000000 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs +++ /dev/null @@ -1,487 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190225062029_AddUserIDColumn")] - partial class AddUserIDColumn - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs deleted file mode 100644 index f2eef600dc..0000000000 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs +++ /dev/null @@ -1,25 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddUserIDColumn : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "UserID", - table: "ScoreInfo", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "UserID", - table: "ScoreInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs deleted file mode 100644 index 348c42adb9..0000000000 --- a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs +++ /dev/null @@ -1,498 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190525060824_SkinSettings")] - partial class SkinSettings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs deleted file mode 100644 index 7779b55bb7..0000000000 --- a/osu.Game/Migrations/20190525060824_SkinSettings.cs +++ /dev/null @@ -1,57 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class SkinSettings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@"create table Settings_dg_tmp - ( - ID INTEGER not null - constraint PK_Settings - primary key autoincrement, - Key TEXT not null, - RulesetID INTEGER, - Value TEXT, - Variant INTEGER, - SkinInfoID int - constraint Settings_SkinInfo_ID_fk - references SkinInfo - on delete restrict - ); - - insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings; - - drop table Settings; - - alter table Settings_dg_tmp rename to Settings; - - create index IX_Settings_RulesetID_Variant - on Settings (RulesetID, Variant); - - create index Settings_SkinInfoID_index - on Settings (SkinInfoID); - - "); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Settings_SkinInfo_SkinInfoID", - table: "Settings"); - - migrationBuilder.DropIndex( - name: "IX_Settings_SkinInfoID", - table: "Settings"); - - migrationBuilder.DropColumn( - name: "SkinInfoID", - table: "Settings"); - } - } -} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs deleted file mode 100644 index 9477369aa0..0000000000 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs +++ /dev/null @@ -1,489 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190605091246_AddDateAddedColumnToBeatmapSet")] - partial class AddDateAddedColumnToBeatmapSet - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs deleted file mode 100644 index 0620a0624f..0000000000 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs +++ /dev/null @@ -1,27 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddDateAddedColumnToBeatmapSet : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "DateAdded", - table: "BeatmapSetInfo", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "DateAdded", - table: "BeatmapSetInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs deleted file mode 100644 index c5fcc16f84..0000000000 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs +++ /dev/null @@ -1,504 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190708070844_AddBPMAndLengthColumns")] - partial class AddBPMAndLengthColumns - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs deleted file mode 100644 index f8ce354aa1..0000000000 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs +++ /dev/null @@ -1,36 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBPMAndLengthColumns : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "BPM", - table: "BeatmapInfo", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "Length", - table: "BeatmapInfo", - nullable: false, - defaultValue: 0.0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "BPM", - table: "BeatmapInfo"); - - migrationBuilder.DropColumn( - name: "Length", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs deleted file mode 100644 index 826233a2b0..0000000000 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190913104727_AddBeatmapVideo")] - partial class AddBeatmapVideo - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs deleted file mode 100644 index af82b4db20..0000000000 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs +++ /dev/null @@ -1,25 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBeatmapVideo : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "VideoFile", - table: "BeatmapMetadata", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "VideoFile", - table: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs deleted file mode 100644 index 22316b0380..0000000000 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20200302094919_RefreshVolumeBindings")] - partial class RefreshVolumeBindings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs deleted file mode 100644 index 3d2ddbf6fc..0000000000 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs +++ /dev/null @@ -1,19 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RefreshVolumeBindings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs deleted file mode 100644 index 1c05de832e..0000000000 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20201019224408_AddEpilepsyWarning")] - partial class AddEpilepsyWarning - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs deleted file mode 100644 index 58a35a7bf3..0000000000 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs +++ /dev/null @@ -1,26 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddEpilepsyWarning : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "EpilepsyWarning", - table: "BeatmapInfo", - nullable: false, - defaultValue: false); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "EpilepsyWarning", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs deleted file mode 100644 index 2c100d39b9..0000000000 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210412045700_RefreshVolumeBindingsAgain")] - partial class RefreshVolumeBindingsAgain - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs deleted file mode 100644 index 4d3941dd20..0000000000 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs +++ /dev/null @@ -1,19 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RefreshVolumeBindingsAgain : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs deleted file mode 100644 index b808c648da..0000000000 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210511060743_AddSkinInstantiationInfo")] - partial class AddSkinInstantiationInfo - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs deleted file mode 100644 index 887635fa85..0000000000 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs +++ /dev/null @@ -1,25 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSkinInstantiationInfo : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "InstantiationInfo", - table: "SkinInfo", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "InstantiationInfo", - table: "SkinInfo"); - } - } -} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs deleted file mode 100644 index 89bab3a0fa..0000000000 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs +++ /dev/null @@ -1,511 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210514062639_AddAuthorIdToBeatmapMetadata")] - partial class AddAuthorIdToBeatmapMetadata - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs deleted file mode 100644 index 7b579e27b9..0000000000 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs +++ /dev/null @@ -1,26 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddAuthorIdToBeatmapMetadata : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "AuthorID", - table: "BeatmapMetadata", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "AuthorID", - table: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs deleted file mode 100644 index afeb42130d..0000000000 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs +++ /dev/null @@ -1,513 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210824185035_AddCountdownSettings")] - partial class AddCountdownSettings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("CountdownOffset"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs deleted file mode 100644 index d1b09e2c1d..0000000000 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs +++ /dev/null @@ -1,26 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddCountdownSettings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CountdownOffset", - table: "BeatmapInfo", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CountdownOffset", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs deleted file mode 100644 index 6e53d7fae0..0000000000 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs +++ /dev/null @@ -1,515 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210912144011_AddSamplesMatchPlaybackRate")] - partial class AddSamplesMatchPlaybackRate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("CountdownOffset"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SamplesMatchPlaybackRate"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs deleted file mode 100644 index f6fc1f4420..0000000000 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs +++ /dev/null @@ -1,26 +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 Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSamplesMatchPlaybackRate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "SamplesMatchPlaybackRate", - table: "BeatmapInfo", - nullable: false, - defaultValue: false); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "SamplesMatchPlaybackRate", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs deleted file mode 100644 index 6d53c019ec..0000000000 --- a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs +++ /dev/null @@ -1,23 +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 Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20211020081609_ResetSkinHashes")] - public partial class ResetSkinHashes : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs deleted file mode 100644 index 036c26cb0a..0000000000 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ /dev/null @@ -1,513 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - partial class OsuDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("CountdownOffset"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SamplesMatchPlaybackRate"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 97142d5472..b30a065371 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -199,11 +199,6 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); - /// - /// A legacy EF context factory if migration has not been performed to realm yet. - /// - protected DatabaseContextFactory EFContextFactory { get; private set; } - /// /// Number of unhandled exceptions to allow before aborting execution. /// @@ -242,10 +237,7 @@ namespace osu.Game Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); - if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME)) - dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage)); - - dependencies.Cache(realm = new RealmAccess(Storage, CLIENT_DATABASE_FILENAME, Host.UpdateThread, EFContextFactory)); + dependencies.Cache(realm = new RealmAccess(Storage, CLIENT_DATABASE_FILENAME, Host.UpdateThread)); dependencies.CacheAs(RulesetStore = new RealmRulesetStore(realm, Storage)); dependencies.CacheAs(RulesetStore); diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index dbb1278d76..afba00274c 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -14,7 +14,6 @@ using osu.Game.Screens.Menu; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Graphics.UserInterface; using IntroSequence = osu.Game.Configuration.IntroSequence; @@ -66,32 +65,13 @@ namespace osu.Game.Screens protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); - [Resolved(canBeNull: true)] - private DatabaseContextFactory efContextFactory { get; set; } - - private EFToRealmMigrator realmMigrator; - public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); - // A non-null context factory means there's still content to migrate. - if (efContextFactory != null) - { - LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); - realmMigrator.MigrationCompleted.ContinueWith(_ => Schedule(() => - { - // Delay initial screen loading to ensure that the migration is in a complete and sane state - // before the intro screen may import the game intro beatmap. - LoadComponentAsync(loadableScreen = CreateLoadableScreen()); - })); - } - else - { - LoadComponentAsync(loadableScreen = CreateLoadableScreen()); - } + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); LoadComponentAsync(spinner = new LoadingSpinner(true, true) { diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 3ad29ea6db..44df495929 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -807,7 +807,6 @@ See the LICENCE file in the repository root for full licence text. True True - True True True True From 0aa92c78ec0ae000f8f241db3b5a65a6ea28b7bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:34:35 +0900 Subject: [PATCH 160/583] Add local sqlite initialisation to `BeatmapUpdaterMetadataLookup` for now --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index aa773da6d3..007bec46bb 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -19,6 +19,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; +using SQLitePCL; namespace osu.Game.Beatmaps { @@ -41,6 +42,10 @@ namespace osu.Game.Beatmaps public BeatmapUpdaterMetadataLookup(IAPIProvider api, Storage storage) { + // required to initialise native SQLite libraries on some platforms. + Batteries_V2.Init(); + raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); + this.api = api; this.storage = storage; From 581a4d2d6d033069af55d972e345f0d63b29a2fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:39:59 +0900 Subject: [PATCH 161/583] Use `APIBeatmap` for realm migration requiring `ShortName` mapping --- osu.Game/Database/RealmAccess.cs | 20 ++++++++++++------- .../API/Requests/Responses/APIBeatmap.cs | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e23fc912df..cefc7da503 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -24,6 +24,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Models; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -45,8 +46,6 @@ namespace osu.Game.Database /// public readonly string Filename; - private readonly IDatabaseContextFactory? efContextFactory; - private readonly SynchronizationContext? updateThreadSyncContext; /// @@ -162,11 +161,9 @@ namespace osu.Game.Database /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. /// The game update thread, used to post realm operations into a thread-safe context. - /// An EF factory used only for migration purposes. - public RealmAccess(Storage storage, string filename, GameThread? updateThread = null, IDatabaseContextFactory? efContextFactory = null) + public RealmAccess(Storage storage, string filename, GameThread? updateThread = null) { this.storage = storage; - this.efContextFactory = efContextFactory; updateThreadSyncContext = updateThread?.SynchronizationContext ?? SynchronizationContext.Current; @@ -876,8 +873,17 @@ namespace osu.Game.Database } } - private string? getRulesetShortNameFromLegacyID(long rulesetId) => - efContextFactory?.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; + private string? getRulesetShortNameFromLegacyID(long rulesetId) + { + try + { + return new APIBeatmap.APIRuleset { OnlineID = (int)rulesetId }.ShortName; + } + catch + { + return null; + } + } /// /// Create a full realm backup. diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 3fee81cf33..8a77801c3a 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.API.Requests.Responses public bool Equals(IBeatmapInfo? other) => other is APIBeatmap b && this.MatchesOnlineID(b); - private class APIRuleset : IRulesetInfo + public class APIRuleset : IRulesetInfo { public int OnlineID { get; set; } = -1; From 65d1c40dd54ec556ca28e30f01bfb16a56a5e048 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:47:26 +0900 Subject: [PATCH 162/583] Remove remaining package references and replace with `SQLitePCLRaw` direct reference This may require further consideration for test and iOS projects. --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - osu.Desktop/osu.Desktop.csproj | 1 - .../osu.Game.Rulesets.Catch.Tests.csproj | 1 - .../osu.Game.Rulesets.Mania.Tests.csproj | 1 - .../osu.Game.Rulesets.Osu.Tests.csproj | 1 - .../osu.Game.Rulesets.Taiko.Tests.csproj | 1 - osu.Game.Tests/osu.Game.Tests.csproj | 1 - osu.Game/osu.Game.csproj | 4 ++-- osu.iOS.props | 2 -- 12 files changed, 2 insertions(+), 14 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 011a37cbdc..b8604169aa 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index c04f6132f3..4117452579 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 529054fd4f..0b119c8680 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index c04f6132f3..4117452579 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 8ef480d4e3..3f926ed45a 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,7 +27,6 @@ - diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 3ac1491946..d45f8a9692 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,6 @@ - WinExe diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index d07df75864..2951076591 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,6 @@ - WinExe diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 36c40c0fe2..c2973644cf 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,6 @@ - WinExe diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 51d4bbc630..e8eaff4368 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,6 @@ - WinExe diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 65679deb01..2572864e09 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,6 @@ - diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fabef87c28..953087f9c8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,8 +26,7 @@ - - + @@ -41,6 +40,7 @@ + diff --git a/osu.iOS.props b/osu.iOS.props index 89166f924c..496bfbb85c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -81,8 +81,6 @@ - - From 28b15e232df3d8b6b9cc38b14d22819510e7b2f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:59:03 +0900 Subject: [PATCH 163/583] Remove all EF models --- osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 31 --- osu.Game/Beatmaps/EFBeatmapDifficulty.cs | 80 ------- osu.Game/Beatmaps/EFBeatmapInfo.cs | 183 --------------- osu.Game/Beatmaps/EFBeatmapMetadata.cs | 89 -------- osu.Game/Beatmaps/EFBeatmapSetInfo.cs | 108 --------- osu.Game/Rulesets/EFRulesetInfo.cs | 87 -------- osu.Game/Scoring/EFScoreInfo.cs | 272 ----------------------- osu.Game/Scoring/ScoreFileInfo.cs | 31 --- osu.Game/Skinning/EFSkinInfo.cs | 65 ------ osu.Game/Skinning/SkinFileInfo.cs | 31 --- 10 files changed, 977 deletions(-) delete mode 100644 osu.Game/Beatmaps/BeatmapSetFileInfo.cs delete mode 100644 osu.Game/Beatmaps/EFBeatmapDifficulty.cs delete mode 100644 osu.Game/Beatmaps/EFBeatmapInfo.cs delete mode 100644 osu.Game/Beatmaps/EFBeatmapMetadata.cs delete mode 100644 osu.Game/Beatmaps/EFBeatmapSetInfo.cs delete mode 100644 osu.Game/Rulesets/EFRulesetInfo.cs delete mode 100644 osu.Game/Scoring/EFScoreInfo.cs delete mode 100644 osu.Game/Scoring/ScoreFileInfo.cs delete mode 100644 osu.Game/Skinning/EFSkinInfo.cs delete mode 100644 osu.Game/Skinning/SkinFileInfo.cs diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs deleted file mode 100644 index 051366fb99..0000000000 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.ComponentModel.DataAnnotations; -using osu.Game.Database; -using osu.Game.IO; - -namespace osu.Game.Beatmaps -{ - public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public int BeatmapSetInfoID { get; set; } - - public EFBeatmapSetInfo BeatmapSetInfo { get; set; } - - public int FileInfoID { get; set; } - - public FileInfo FileInfo { get; set; } - - [Required] - public string Filename { get; set; } - - IFileInfo INamedFileUsage.File => FileInfo; - } -} diff --git a/osu.Game/Beatmaps/EFBeatmapDifficulty.cs b/osu.Game/Beatmaps/EFBeatmapDifficulty.cs deleted file mode 100644 index 8bcac24ca1..0000000000 --- a/osu.Game/Beatmaps/EFBeatmapDifficulty.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.ComponentModel.DataAnnotations.Schema; -using osu.Game.Database; - -namespace osu.Game.Beatmaps -{ - [Table(@"BeatmapDifficulty")] - public class EFBeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo - { - /// - /// The default value used for all difficulty settings except and . - /// - public const float DEFAULT_DIFFICULTY = 5; - - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; - public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; - public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; - - private float? approachRate; - - public EFBeatmapDifficulty() - { - } - - public EFBeatmapDifficulty(IBeatmapDifficultyInfo source) - { - CopyFrom(source); - } - - public float ApproachRate - { - get => approachRate ?? OverallDifficulty; - set => approachRate = value; - } - - public double SliderMultiplier { get; set; } = 1; - public double SliderTickRate { get; set; } = 1; - - /// - /// Returns a shallow-clone of this . - /// - public EFBeatmapDifficulty Clone() - { - var diff = (EFBeatmapDifficulty)Activator.CreateInstance(GetType()); - CopyTo(diff); - return diff; - } - - public virtual void CopyFrom(IBeatmapDifficultyInfo other) - { - ApproachRate = other.ApproachRate; - DrainRate = other.DrainRate; - CircleSize = other.CircleSize; - OverallDifficulty = other.OverallDifficulty; - - SliderMultiplier = other.SliderMultiplier; - SliderTickRate = other.SliderTickRate; - } - - public virtual void CopyTo(EFBeatmapDifficulty other) - { - other.ApproachRate = ApproachRate; - other.DrainRate = DrainRate; - other.CircleSize = CircleSize; - other.OverallDifficulty = OverallDifficulty; - - other.SliderMultiplier = SliderMultiplier; - other.SliderTickRate = SliderTickRate; - } - } -} diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs deleted file mode 100644 index 20abdc686a..0000000000 --- a/osu.Game/Beatmaps/EFBeatmapInfo.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; -using osu.Framework.Testing; -using osu.Game.Database; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; -using osu.Game.Scoring; - -namespace osu.Game.Beatmaps -{ - [ExcludeFromDynamicCompile] - [Serializable] - [Table(@"BeatmapInfo")] - public class EFBeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public int BeatmapVersion; - - private int? onlineID; - - [JsonProperty("id")] - [Column("OnlineBeatmapID")] - public int? OnlineID - { - get => onlineID; - set => onlineID = value > 0 ? value : null; - } - - [JsonIgnore] - public int BeatmapSetInfoID { get; set; } - - public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None; - - [Required] - public EFBeatmapSetInfo BeatmapSetInfo { get; set; } - - public EFBeatmapMetadata Metadata { get; set; } - - [JsonIgnore] - public int BaseDifficultyID { get; set; } - - public EFBeatmapDifficulty BaseDifficulty { get; set; } - - [NotMapped] - public APIBeatmap OnlineInfo { get; set; } - - /// - /// The playable length in milliseconds of this beatmap. - /// - public double Length { get; set; } - - /// - /// The most common BPM of this beatmap. - /// - public double BPM { get; set; } - - public string Path { get; set; } - - [JsonProperty("file_sha2")] - public string Hash { get; set; } - - [JsonIgnore] - public bool Hidden { get; set; } - - /// - /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). - /// - [JsonProperty("file_md5")] - public string MD5Hash { get; set; } - - // General - public double AudioLeadIn { get; set; } - public float StackLeniency { get; set; } = 0.7f; - public bool SpecialStyle { get; set; } - - [Column("RulesetID")] - public int RulesetInfoID { get; set; } - - public EFRulesetInfo RulesetInfo { get; set; } - - public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } - public bool EpilepsyWarning { get; set; } - - /// - /// Whether or not sound samples should change rate when playing with speed-changing mods. - /// TODO: only read/write supported for now, requires implementation in gameplay. - /// - public bool SamplesMatchPlaybackRate { get; set; } - - public CountdownType Countdown { get; set; } = CountdownType.Normal; - - /// - /// The number of beats to move the countdown backwards (compared to its default location). - /// - public int CountdownOffset { get; set; } - - [NotMapped] - public int[] Bookmarks { get; set; } = Array.Empty(); - - public double DistanceSpacing { get; set; } - public int BeatDivisor { get; set; } - public int GridSize { get; set; } - public double TimelineZoom { get; set; } - - // Metadata - [Column("Version")] - public string DifficultyName { get; set; } - - [JsonProperty("difficulty_rating")] - [Column("StarDifficulty")] - public double StarRating { get; set; } - - /// - /// Currently only populated for beatmap deletion. Use to query scores. - /// - public List Scores { get; set; } - - [JsonIgnore] - public DifficultyRating DifficultyRating => StarDifficulty.GetDifficultyRating(StarRating); - - public override string ToString() => this.GetDisplayTitle(); - - public bool Equals(EFBeatmapInfo other) - { - if (ReferenceEquals(this, other)) return true; - if (other == null) return false; - - if (ID != 0 && other.ID != 0) - return ID == other.ID; - - return false; - } - - public bool Equals(IBeatmapInfo other) => other is EFBeatmapInfo b && Equals(b); - - public bool AudioEquals(EFBeatmapInfo other) => other != null && BeatmapSetInfo != null && other.BeatmapSetInfo != null && - BeatmapSetInfo.Hash == other.BeatmapSetInfo.Hash && - (Metadata ?? BeatmapSetInfo.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSetInfo.Metadata).AudioFile; - - public bool BackgroundEquals(EFBeatmapInfo other) => other != null && BeatmapSetInfo != null && other.BeatmapSetInfo != null && - BeatmapSetInfo.Hash == other.BeatmapSetInfo.Hash && - (Metadata ?? BeatmapSetInfo.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSetInfo.Metadata).BackgroundFile; - - /// - /// Returns a shallow-clone of this . - /// - public EFBeatmapInfo Clone() => (EFBeatmapInfo)MemberwiseClone(); - - #region Implementation of IHasOnlineID - - int IHasOnlineID.OnlineID => OnlineID ?? -1; - - #endregion - - #region Implementation of IBeatmapInfo - - [JsonIgnore] - IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSetInfo?.Metadata ?? new EFBeatmapMetadata(); - - [JsonIgnore] - IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty; - - [JsonIgnore] - IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSetInfo; - - [JsonIgnore] - IRulesetInfo IBeatmapInfo.Ruleset => RulesetInfo; - - #endregion - } -} diff --git a/osu.Game/Beatmaps/EFBeatmapMetadata.cs b/osu.Game/Beatmaps/EFBeatmapMetadata.cs deleted file mode 100644 index c0588f128c..0000000000 --- a/osu.Game/Beatmaps/EFBeatmapMetadata.cs +++ /dev/null @@ -1,89 +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.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; -using osu.Framework.Testing; -using osu.Game.Database; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Users; - -namespace osu.Game.Beatmaps -{ - [ExcludeFromDynamicCompile] - [Serializable] - [Table(@"BeatmapMetadata")] - public class EFBeatmapMetadata : IEquatable, IHasPrimaryKey, IBeatmapMetadataInfo - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public string Title { get; set; } = string.Empty; - - [JsonProperty("title_unicode")] - public string TitleUnicode { get; set; } = string.Empty; - - public string Artist { get; set; } = string.Empty; - - [JsonProperty("artist_unicode")] - public string ArtistUnicode { get; set; } = string.Empty; - - [JsonIgnore] - public List Beatmaps { get; set; } = new List(); - - [JsonIgnore] - public List BeatmapSets { get; set; } = new List(); - - /// - /// The author of the beatmaps in this set. - /// - [JsonIgnore] - public APIUser Author = new APIUser(); - - /// - /// Helper property to deserialize a username to . - /// - [JsonProperty(@"user_id")] - [Column("AuthorID")] - public int AuthorID - { - get => Author.Id; // This should not be used, but is required to make EF work correctly. - set => Author.Id = value; - } - - /// - /// Helper property to deserialize a username to . - /// - [JsonProperty(@"creator")] - [Column("Author")] - public string AuthorString - { - get => Author.Username; // This should not be used, but is required to make EF work correctly. - set => Author.Username = value; - } - - public string Source { get; set; } = string.Empty; - - [JsonProperty(@"tags")] - public string Tags { get; set; } = string.Empty; - - /// - /// The time in milliseconds to begin playing the track for preview purposes. - /// If -1, the track should begin playing at 40% of its length. - /// - public int PreviewTime { get; set; } = -1; - - public string AudioFile { get; set; } = string.Empty; - - public string BackgroundFile { get; set; } = string.Empty; - - public bool Equals(EFBeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other); - - public override string ToString() => this.GetDisplayTitle(); - - IUser IBeatmapMetadataInfo.Author => Author; - } -} diff --git a/osu.Game/Beatmaps/EFBeatmapSetInfo.cs b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs deleted file mode 100644 index 1f41d3727c..0000000000 --- a/osu.Game/Beatmaps/EFBeatmapSetInfo.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using JetBrains.Annotations; -using Newtonsoft.Json; -using osu.Framework.Testing; -using osu.Game.Database; -using osu.Game.Extensions; - -namespace osu.Game.Beatmaps -{ - [ExcludeFromDynamicCompile] - [Serializable] - [Table(@"BeatmapSetInfo")] - public class EFBeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - private int? onlineID; - - [Column("OnlineBeatmapSetID")] - public int? OnlineID - { - get => onlineID; - set => onlineID = value > 0 ? value : null; - } - - public DateTimeOffset DateAdded { get; set; } - - public EFBeatmapMetadata Metadata { get; set; } - - [NotNull] - public List Beatmaps { get; } = new List(); - - public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None; - - public List Files { get; } = new List(); - - /// - /// The maximum star difficulty of all beatmaps in this set. - /// - [JsonIgnore] - public double MaxStarDifficulty => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.StarRating); - - /// - /// The maximum playable length in milliseconds of all beatmaps in this set. - /// - [JsonIgnore] - public double MaxLength => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.Length); - - /// - /// The maximum BPM of all beatmaps in this set. - /// - [JsonIgnore] - public double MaxBPM => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.BPM); - - [NotMapped] - public bool DeletePending { get; set; } - - public string Hash { get; set; } - - /// - /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. - /// The path returned is relative to the user file storage. - /// - /// The name of the file to get the storage path of. - public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath(); - - public override string ToString() => Metadata?.ToString() ?? base.ToString(); - - public bool Protected { get; set; } - - public bool Equals(EFBeatmapSetInfo other) - { - if (ReferenceEquals(this, other)) return true; - if (other == null) return false; - - if (ID != 0 && other.ID != 0) - return ID == other.ID; - - return false; - } - - public bool Equals(IBeatmapSetInfo other) => other is EFBeatmapSetInfo b && Equals(b); - - #region Implementation of IHasOnlineID - - int IHasOnlineID.OnlineID => OnlineID ?? -1; - - #endregion - - #region Implementation of IBeatmapSetInfo - - IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata ?? Beatmaps.FirstOrDefault()?.Metadata ?? new EFBeatmapMetadata(); - IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; - IEnumerable IHasNamedFiles.Files => Files; - - #endregion - } -} diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs deleted file mode 100644 index bd67bdb93c..0000000000 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.ComponentModel.DataAnnotations.Schema; -using System.Diagnostics.CodeAnalysis; -using Newtonsoft.Json; -using osu.Framework.Testing; - -namespace osu.Game.Rulesets -{ - [ExcludeFromDynamicCompile] - [Table(@"RulesetInfo")] - public sealed class EFRulesetInfo : IEquatable, IComparable, IRulesetInfo - { - public int? ID { get; set; } - - public string Name { get; set; } - - public string ShortName { get; set; } - - public string InstantiationInfo { get; set; } - - [JsonIgnore] - public bool Available { get; set; } - - // TODO: this should probably be moved to RulesetStore. - public Ruleset CreateInstance() - { - if (!Available) - return null; - - var type = Type.GetType(InstantiationInfo); - - if (type == null) - return null; - - var ruleset = Activator.CreateInstance(type) as Ruleset; - - return ruleset; - } - - public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - - public int CompareTo(EFRulesetInfo other) => OnlineID.CompareTo(other.OnlineID); - - public int CompareTo(IRulesetInfo other) - { - if (!(other is EFRulesetInfo ruleset)) - throw new ArgumentException($@"Object is not of type {nameof(EFRulesetInfo)}.", nameof(other)); - - return CompareTo(ruleset); - } - - public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); - - public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b); - - [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] - public override int GetHashCode() - { - unchecked - { - int hashCode = ID.HasValue ? ID.GetHashCode() : 0; - hashCode = (hashCode * 397) ^ (InstantiationInfo != null ? InstantiationInfo.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ Available.GetHashCode(); - return hashCode; - } - } - - public override string ToString() => Name ?? $"{Name} ({ShortName}) ID: {ID}"; - - #region Implementation of IHasOnlineID - - [NotMapped] - public int OnlineID - { - get => ID ?? -1; - set => ID = value >= 0 ? value : null; - } - - #endregion - } -} diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs deleted file mode 100644 index 8a5bbbf0da..0000000000 --- a/osu.Game/Scoring/EFScoreInfo.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Localisation; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Users; -using osu.Game.Utils; - -namespace osu.Game.Scoring -{ - [Table(@"ScoreInfo")] - public class EFScoreInfo : IScoreInfo, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public ScoreRank Rank { get; set; } - - public long TotalScore { get; set; } - - [Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database. - public double Accuracy { get; set; } - - public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); - - public double? PP { get; set; } - - public int MaxCombo { get; set; } - - public int Combo { get; set; } // Todo: Shouldn't exist in here - - public int RulesetID { get; set; } - - [NotMapped] - public bool Passed { get; set; } = true; - - public EFRulesetInfo Ruleset { get; set; } - - private APIMod[] localAPIMods; - - private Mod[] mods; - - [NotMapped] - public Mod[] Mods - { - get - { - var rulesetInstance = Ruleset?.CreateInstance(); - if (rulesetInstance == null) - return mods ?? Array.Empty(); - - Mod[] scoreMods = Array.Empty(); - - if (mods != null) - scoreMods = mods; - else if (localAPIMods != null) - scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); - - return scoreMods; - } - set - { - localAPIMods = null; - mods = value; - } - } - - // Used for API serialisation/deserialisation. - [NotMapped] - public APIMod[] APIMods - { - get - { - if (localAPIMods != null) - return localAPIMods; - - if (mods == null) - return Array.Empty(); - - return localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); - } - set - { - localAPIMods = value; - - // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. - mods = null; - } - } - - // Used for database serialisation/deserialisation. - [Column("Mods")] - public string ModsJson - { - get => JsonConvert.SerializeObject(APIMods); - set => APIMods = !string.IsNullOrEmpty(value) ? JsonConvert.DeserializeObject(value) : Array.Empty(); - } - - [NotMapped] - public APIUser User { get; set; } - - [Column("User")] - public string UserString - { - get => User?.Username; - set - { - User ??= new APIUser(); - User.Username = value; - } - } - - [Column("UserID")] - public int? UserID - { - get => User?.Id ?? 1; - set - { - User ??= new APIUser(); - User.Id = value ?? 1; - } - } - - public int BeatmapInfoID { get; set; } - - [Column("Beatmap")] - public EFBeatmapInfo BeatmapInfo { get; set; } - - private long? onlineID; - - [JsonProperty("id")] - [Column("OnlineScoreID")] - public long? OnlineID - { - get => onlineID; - set => onlineID = value > 0 ? value : null; - } - - public DateTimeOffset Date { get; set; } - - [NotMapped] - public Dictionary Statistics { get; set; } = new Dictionary(); - - [Column("Statistics")] - public string StatisticsJson - { - get => JsonConvert.SerializeObject(Statistics); - set - { - if (value == null) - { - Statistics.Clear(); - return; - } - - Statistics = JsonConvert.DeserializeObject>(value); - } - } - - [NotMapped] - public List HitEvents { get; set; } - - public List Files { get; } = new List(); - - public string Hash { get; set; } - - public bool DeletePending { get; set; } - - /// - /// The position of this score, starting at 1. - /// - [NotMapped] - public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. - - /// - /// Whether this represents a legacy (osu!stable) score. - /// - [NotMapped] - public bool IsLegacyScore => Mods.OfType().Any(); - - public IEnumerable GetStatisticsForDisplay() - { - foreach (var r in Ruleset.CreateInstance().GetHitResults()) - { - int value = Statistics.GetValueOrDefault(r.result); - - switch (r.result) - { - case HitResult.SmallTickHit: - { - int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - if (total > 0) - yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); - - break; - } - - case HitResult.LargeTickHit: - { - int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (total > 0) - yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); - - break; - } - - case HitResult.SmallTickMiss: - case HitResult.LargeTickMiss: - break; - - default: - yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); - - break; - } - } - } - - public EFScoreInfo DeepClone() - { - var clone = (EFScoreInfo)MemberwiseClone(); - - clone.Statistics = new Dictionary(clone.Statistics); - - return clone; - } - - public override string ToString() => this.GetDisplayTitle(); - - public bool Equals(EFScoreInfo other) - { - if (ReferenceEquals(this, other)) return true; - if (other == null) return false; - - if (ID != 0 && other.ID != 0) - return ID == other.ID; - - return false; - } - - #region Implementation of IHasOnlineID - - long IHasOnlineID.OnlineID => OnlineID ?? -1; - - #endregion - - #region Implementation of IScoreInfo - - IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; - IRulesetInfo IScoreInfo.Ruleset => Ruleset; - IUser IScoreInfo.User => User; - bool IScoreInfo.HasReplay => Files.Any(); - - #endregion - - IEnumerable IHasNamedFiles.Files => Files; - } -} diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs deleted file mode 100644 index 6c3509bccd..0000000000 --- a/osu.Game/Scoring/ScoreFileInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.ComponentModel.DataAnnotations; -using osu.Game.Database; -using osu.Game.IO; - -namespace osu.Game.Scoring -{ - public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public int ScoreInfoID { get; set; } - - public EFScoreInfo ScoreInfo { get; set; } - - public int FileInfoID { get; set; } - - public FileInfo FileInfo { get; set; } - - [Required] - public string Filename { get; set; } - - IFileInfo INamedFileUsage.File => FileInfo; - } -} diff --git a/osu.Game/Skinning/EFSkinInfo.cs b/osu.Game/Skinning/EFSkinInfo.cs deleted file mode 100644 index 4204364c50..0000000000 --- a/osu.Game/Skinning/EFSkinInfo.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Game.Database; -using osu.Game.Extensions; -using osu.Game.IO; - -namespace osu.Game.Skinning -{ - [Table(@"SkinInfo")] - public class EFSkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete - { - internal const int DEFAULT_SKIN = 0; - internal const int CLASSIC_SKIN = -1; - internal const int RANDOM_SKIN = -2; - - public int ID { get; set; } - - public string Name { get; set; } = string.Empty; - - public string Creator { get; set; } = string.Empty; - - public string Hash { get; set; } - - public string InstantiationInfo { get; set; } - - public virtual Skin CreateInstance(IStorageResourceProvider resources) - { - var type = string.IsNullOrEmpty(InstantiationInfo) - // handle the case of skins imported before InstantiationInfo was added. - ? typeof(LegacySkin) - : Type.GetType(InstantiationInfo).AsNonNull(); - - return (Skin)Activator.CreateInstance(type, this, resources); - } - - public List Files { get; set; } = new List(); - - public bool DeletePending { get; set; } - - public static EFSkinInfo Default { get; } = new EFSkinInfo - { - ID = DEFAULT_SKIN, - Name = "osu! (triangles)", - Creator = "team osu!", - InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo() - }; - - public bool Equals(EFSkinInfo other) => other != null && ID == other.ID; - - public override string ToString() - { - string author = Creator == null ? string.Empty : $"({Creator})"; - return $"{Name} {author}".Trim(); - } - - public bool IsManaged => ID > 0; - } -} diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs deleted file mode 100644 index f3243d3f24..0000000000 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.ComponentModel.DataAnnotations; -using osu.Game.Database; -using osu.Game.IO; - -namespace osu.Game.Skinning -{ - public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage - { - public int ID { get; set; } - - public bool IsManaged => ID > 0; - - public int SkinInfoID { get; set; } - - public EFSkinInfo SkinInfo { get; set; } - - public int FileInfoID { get; set; } - - public FileInfo FileInfo { get; set; } - - [Required] - public string Filename { get; set; } - - IFileInfo INamedFileUsage.File => FileInfo; - } -} From 8286ab0d04d7e6fb27cac79b26f2fbc1d161ddb5 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Thu, 15 Sep 2022 11:11:58 +0100 Subject: [PATCH 164/583] Revert "Fix `SongProgress` invalidating too often" This reverts commit 2b4b14ca9990a4626fefcbecf624ca2d97bf4244. --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index b3d5066a9e..d0eb8f8ca1 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -47,10 +47,7 @@ namespace osu.Game.Screens.Play.HUD if (clock != null) gameplayClock = clock; - // Lock height so changes in text autosize (if character height changes) - // don't cause parent invalidation. - Height = 14; - + AutoSizeAxes = Axes.Y; Children = new Drawable[] { new Container From 2092008251cf5d0cc4846763b86719155f59a8bd Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Thu, 15 Sep 2022 11:16:00 +0100 Subject: [PATCH 165/583] Set InvalidationSource to Self --- osu.Game/Screens/Play/SquareGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 00d6ede3bf..a1e004bfcc 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play } } - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize, InvalidationSource.Self); private ScheduledDelegate scheduledCreate; protected override void Update() From 4af76b9f48fd1d4602319af4552bee120b89f2ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 19:55:18 +0900 Subject: [PATCH 166/583] Apply the same change to `HandleFlip` --- .../Edit/OsuSelectionHandler.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 280a35500a..eddc1390f0 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -127,16 +127,13 @@ namespace osu.Game.Rulesets.Osu.Edit { didFlip = true; - var controlPoints = slider.Path.ControlPoints.Select(p => - new PathControlPoint(new Vector2( - (direction == Direction.Horizontal ? -1 : 1) * p.Position.X, - (direction == Direction.Vertical ? -1 : 1) * p.Position.Y - ), p.Type)).ToArray(); - - // Importantly, update as a single operation so automatic adjustment of control points to different - // curve types does not unexpectedly trigger and change the slider's shape. - slider.Path.ControlPoints.Clear(); - slider.Path.ControlPoints.AddRange(controlPoints); + foreach (var cp in slider.Path.ControlPoints) + { + cp.Position = new Vector2( + (direction == Direction.Horizontal ? -1 : 1) * cp.Position.X, + (direction == Direction.Vertical ? -1 : 1) * cp.Position.Y + ); + } } } @@ -186,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (h is IHasPath path) { - foreach (PathControlPoint t in path.Path.ControlPoints) - t.Position = RotatePointAroundOrigin(t.Position, Vector2.Zero, delta); + foreach (PathControlPoint cp in path.Path.ControlPoints) + cp.Position = RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta); } } From c7f8f948b928c9a240e6fc5f791ad07c7dd26746 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 17:36:14 +0900 Subject: [PATCH 167/583] Rename `CreateLegacySkinProvider` to `CreateSkinTransformer` and apply to all skins --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 11 +++++++++- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 11 +++++++++- .../TestSceneSliderApplication.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 11 +++++++++- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 11 +++++++++- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 8 +++++++- .../Skinning/RulesetSkinProvidingContainer.cs | 20 +++++++++---------- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- 9 files changed, 60 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 999b9f6e2c..5c9c95827a 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -182,7 +182,16 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(RulesetInfo, beatmap); - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); + public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) + { + switch (skin) + { + case LegacySkin: + return new CatchLegacySkinTransformer(skin); + } + + return null; + } public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c6a5e8bdaa..061dedb07a 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -62,7 +62,16 @@ namespace osu.Game.Rulesets.Mania public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap); + public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) + { + switch (skin) + { + case LegacySkin: + return new ManiaLegacySkinTransformer(skin, beatmap); + } + + return null; + } public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index 08a62fe3ae..0169627867 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo()); tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1"; - var provider = Ruleset.Value.CreateInstance().CreateLegacySkinProvider(tintingSkin, Beatmap.Value.Beatmap); + var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(tintingSkin, Beatmap.Value.Beatmap); Child = new SkinProvidingContainer(provider) { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6af765361d..0a5b3139fc 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -231,7 +231,16 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new OsuLegacySkinTransformer(skin); + public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) + { + switch (skin) + { + case LegacySkin: + return new OsuLegacySkinTransformer(skin); + } + + return null; + } public int LegacyID => 0; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 6beb83575d..dc36bc0320 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -43,7 +43,16 @@ namespace osu.Game.Rulesets.Taiko public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this); - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TaikoLegacySkinTransformer(skin); + public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) + { + switch (skin) + { + case LegacySkin: + return new TaikoLegacySkinTransformer(skin); + } + + return null; + } public const string SHORT_NAME = "taiko"; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 5c7321fb24..e12be6d3b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestOsuRuleset : OsuRuleset { - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin); + public override ISkin CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin); private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 4bc82a5f7e..a446210c8a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -200,7 +200,13 @@ namespace osu.Game.Rulesets public ModAutoplay? GetAutoplayMod() => CreateMod(); - public virtual ISkin? CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; + /// + /// Create a transformer which adds lookups specific to a ruleset to skin sources. + /// + /// The source skin. + /// The current beatmap. + /// A skin with a transformer applied, or null if no transformation is provided by this ruleset. + public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 937cf20bc6..d5690710bb 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin is LegacySkin ? GetLegacyRulesetTransformedSkin(beatmapSkin) : beatmapSkin) + InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin is LegacySkin ? GetRulesetTransformedSkin(beatmapSkin) : beatmapSkin) { Child = Content = new Container { @@ -67,16 +67,16 @@ namespace osu.Game.Skinning Debug.Assert(ParentSource != null); - foreach (var skin in ParentSource.AllSources) + foreach (var source in ParentSource.AllSources) { - switch (skin) + switch (source) { - case LegacySkin legacySkin: - sources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + case Skin skin: + sources.Add(GetRulesetTransformedSkin(skin)); break; default: - sources.Add(skin); + sources.Add(source); break; } } @@ -94,16 +94,16 @@ namespace osu.Game.Skinning SetSources(sources); } - protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) + protected ISkin GetRulesetTransformedSkin(ISkin skin) { - if (legacySkin == null) + if (skin == null) return null; - var rulesetTransformed = Ruleset.CreateLegacySkinProvider(legacySkin, Beatmap); + var rulesetTransformed = Ruleset.CreateSkinTransformer(skin, Beatmap); if (rulesetTransformed != null) return rulesetTransformed; - return legacySkin; + return skin; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index ffdde782a5..7278e1e93f 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual ISkin provider = skin; if (provider is LegacySkin legacyProvider) - provider = Ruleset.Value.CreateInstance().CreateLegacySkinProvider(legacyProvider, beatmap); + provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(legacyProvider, beatmap); var children = new Container { From 5ce67345ae129f4d0fd80ca15bbb7130af15f8b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 01:02:38 +0900 Subject: [PATCH 168/583] Catch against sqlite initialisation failures --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 007bec46bb..2fd1d06b7b 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -42,9 +42,16 @@ namespace osu.Game.Beatmaps public BeatmapUpdaterMetadataLookup(IAPIProvider api, Storage storage) { - // required to initialise native SQLite libraries on some platforms. - Batteries_V2.Init(); - raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); + try + { + // required to initialise native SQLite libraries on some platforms. + Batteries_V2.Init(); + raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); + } + catch + { + // may fail if platform not supported. + } this.api = api; this.storage = storage; From db822696eda362206817b1881c17e7613f79f4d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 01:02:46 +0900 Subject: [PATCH 169/583] Use latest version of sqlite3 bundle --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 953087f9c8..fed7c27f07 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -40,7 +40,7 @@ - + From 4f208416bedfd8cb3cd11474d7de2dca5aa40c8d Mon Sep 17 00:00:00 2001 From: Drison64 Date: Fri, 16 Sep 2022 13:50:18 +1400 Subject: [PATCH 170/583] Fixed irresponsiveness of score panel timestamp to time format --- .../Expanded/ExpandedPanelMiddleContent.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index b496f4242d..e212dad65f 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -7,12 +7,14 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -34,6 +36,7 @@ namespace osu.Game.Screens.Ranking.Expanded private const float padding = 10; private readonly ScoreInfo score; + private Bindable bindableUse24HourDisplay; private readonly bool withFlair; private readonly List statisticDisplays = new List(); @@ -61,8 +64,9 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache beatmapDifficultyCache) + private void load(BeatmapDifficultyCache beatmapDifficultyCache, OsuConfigManager config) { + bindableUse24HourDisplay = config.GetBindable(OsuSetting.Prefer24HourTime); var beatmap = score.BeatmapInfo; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; string creator = metadata.Author.Username; @@ -224,7 +228,7 @@ namespace osu.Game.Screens.Ranking.Expanded }); if (score.Date != default) - AddInternal(new PlayedOnText(score.Date)); + AddInternal(new PlayedOnText(score.Date, bindableUse24HourDisplay)); var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely(); @@ -280,12 +284,25 @@ namespace osu.Game.Screens.Ranking.Expanded public class PlayedOnText : OsuSpriteText { - public PlayedOnText(DateTimeOffset time) + private bool use24HourDisplay; + + public PlayedOnText(DateTimeOffset time, Bindable bindableUse24HourDisplay) { + use24HourDisplay = bindableUse24HourDisplay.Value; + bindableUse24HourDisplay.BindValueChanged(prefer24H => + { + use24HourDisplay = prefer24H.NewValue; + UpdateHourDisplay(time); + }, true); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold); - Text = $"Played on {time.ToLocalTime():d MMMM yyyy HH:mm}"; + UpdateHourDisplay(time); + } + + public void UpdateHourDisplay(DateTimeOffset time) + { + Text = use24HourDisplay ? $"Played on {time.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {time.ToLocalTime():d MMMM yyyy h:mm tt}"; } } } From d9499abc3387367b4922abda985c2ee90ff76c07 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 16 Sep 2022 13:14:25 +0900 Subject: [PATCH 171/583] Refactor to avoid passing around bindables --- .../Expanded/ExpandedPanelMiddleContent.cs | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index e212dad65f..829ba83696 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -36,7 +36,6 @@ namespace osu.Game.Screens.Ranking.Expanded private const float padding = 10; private readonly ScoreInfo score; - private Bindable bindableUse24HourDisplay; private readonly bool withFlair; private readonly List statisticDisplays = new List(); @@ -64,9 +63,8 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache beatmapDifficultyCache, OsuConfigManager config) + private void load(BeatmapDifficultyCache beatmapDifficultyCache) { - bindableUse24HourDisplay = config.GetBindable(OsuSetting.Prefer24HourTime); var beatmap = score.BeatmapInfo; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; string creator = metadata.Author.Username; @@ -228,7 +226,7 @@ namespace osu.Game.Screens.Ranking.Expanded }); if (score.Date != default) - AddInternal(new PlayedOnText(score.Date, bindableUse24HourDisplay)); + AddInternal(new PlayedOnText(score.Date)); var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely(); @@ -284,25 +282,34 @@ namespace osu.Game.Screens.Ranking.Expanded public class PlayedOnText : OsuSpriteText { - private bool use24HourDisplay; + private readonly DateTimeOffset time; + private readonly Bindable prefer24HourTime = new Bindable(); - public PlayedOnText(DateTimeOffset time, Bindable bindableUse24HourDisplay) + public PlayedOnText(DateTimeOffset time) { - use24HourDisplay = bindableUse24HourDisplay.Value; - bindableUse24HourDisplay.BindValueChanged(prefer24H => - { - use24HourDisplay = prefer24H.NewValue; - UpdateHourDisplay(time); - }, true); + this.time = time; + Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold); - UpdateHourDisplay(time); } - public void UpdateHourDisplay(DateTimeOffset time) + [BackgroundDependencyLoader] + private void load(OsuConfigManager configManager) { - Text = use24HourDisplay ? $"Played on {time.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {time.ToLocalTime():d MMMM yyyy h:mm tt}"; + configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + prefer24HourTime.BindValueChanged(_ => updateDisplay(), true); + } + + private void updateDisplay() + { + Text = prefer24HourTime.Value ? $"Played on {time.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {time.ToLocalTime():d MMMM yyyy h:mm tt}"; } } } From 21ac3fd88be99c7b057e420b2d3d75dd5b592dff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 14:00:05 +0900 Subject: [PATCH 172/583] Catch any unobserved exceptions during beatmap metadata harvesting The process will retry when a connection is successful, so this doesn't need to be as loud as it was. Addresses https://github.com/ppy/osu/discussions/20331. --- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 95228c380f..06d24a82f3 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -1,6 +1,7 @@ // 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.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -80,6 +81,10 @@ namespace osu.Game.Online.Metadata await ProcessChanges(catchUpChanges.BeatmapSetIDs); } } + catch (Exception e) + { + Logger.Log($"Error while processing catch-up of metadata ({e.Message})"); + } finally { catchingUp = false; From 289e6ad977f6e073ca61e20aafe961b3c0e479f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 15:12:05 +0900 Subject: [PATCH 173/583] Fix follow point animations in legacy skins not always starting at correct point in time --- .../Connections/FollowPointConnection.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 0306c99fd5..1a9d12e860 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; @@ -21,32 +20,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public const int SPACING = 32; public const double PREEMPT = 800; - public DrawablePool Pool; + public DrawablePool? Pool { private get; set; } protected override void OnApply(FollowPointLifetimeEntry entry) { base.OnApply(entry); - entry.Invalidated += onEntryInvalidated; - refreshPoints(); + entry.Invalidated += scheduleRefresh; + + // Our clock may not be correct at this point if `LoadComplete` has not run yet. + // Without a schedule, animations referencing FollowPoint's clock (see `IAnimationTimeReference`) would be incorrect on first pool usage. + scheduleRefresh(); } protected override void OnFree(FollowPointLifetimeEntry entry) { base.OnFree(entry); - entry.Invalidated -= onEntryInvalidated; + entry.Invalidated -= scheduleRefresh; // Return points to the pool. ClearInternal(false); } - private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints); - - private void refreshPoints() + private void scheduleRefresh() => Scheduler.AddOnce(() => { + Debug.Assert(Pool != null); + ClearInternal(false); var entry = Entry; + if (entry?.End == null) return; OsuHitObject start = entry.Start; @@ -95,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } entry.LifetimeEnd = finalTransformEndTime; - } + }); /// /// Computes the fade time of follow point positioned between two hitobjects. From 0ff4e343f87c82b04692584c0e61c1ef26ba56d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 16:29:53 +0900 Subject: [PATCH 174/583] Add failing test showing incorrect unread notification count --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index e978b57ba4..a97096e143 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface displayedCount = new OsuSpriteText() }; - notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; + notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"unread count: {count.NewValue}"; }; }); [Test] @@ -270,6 +270,8 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed); AddAssert("Completion toast shown", () => notificationOverlay.ToastCount == 1); + AddUntilStep("wait forwarded", () => notificationOverlay.ToastCount == 0); + AddAssert("only one unread", () => notificationOverlay.UnreadCount.Value == 1); } [Test] From 0d24fda4b9cbaaa107717feab3fc9adc4635cdd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 16:36:56 +0900 Subject: [PATCH 175/583] Fire `Notification.Closed` immediately to ensure off-screen notifications are closed --- osu.Game/Overlays/NotificationOverlay.cs | 4 ++-- .../Overlays/Notifications/Notification.cs | 23 +++++++++++-------- .../Notifications/ProgressNotification.cs | 5 ++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 15573f99af..fad8afd371 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -210,14 +210,14 @@ namespace osu.Game.Overlays mainContent.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); } - private void notificationClosed() + private void notificationClosed() => Schedule(() => { updateCounts(); // this debounce is currently shared between popin/popout sounds, which means one could potentially not play when the user is expecting it. // popout is constant across all notification types, and should therefore be handled using playback concurrency instead, but seems broken at the moment. playDebouncedSample("UI/overlay-pop-out"); - } + }); private void playDebouncedSample(string sampleName) { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 885bb2fc6c..4e7cebf0ae 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -26,7 +26,8 @@ namespace osu.Game.Overlays.Notifications public abstract class Notification : Container { /// - /// User requested close. + /// Notification was closed, either by user or otherwise. + /// Importantly, this event may be fired from a non-update thread. /// public event Action? Closed; @@ -55,6 +56,8 @@ namespace osu.Game.Overlays.Notifications protected Container IconContent; + public bool WasClosed { get; private set; } + private readonly Container content; protected override Container Content => content; @@ -245,21 +248,23 @@ namespace osu.Game.Overlays.Notifications initialFlash.FadeOutFromOne(2000, Easing.OutQuart); } - public bool WasClosed; - public virtual void Close(bool runFlingAnimation) { if (WasClosed) return; WasClosed = true; - if (runFlingAnimation && dragContainer.FlingLeft()) - this.FadeOut(600, Easing.In); - else - this.FadeOut(100); - Closed?.Invoke(); - Expire(); + + Schedule(() => + { + if (runFlingAnimation && dragContainer.FlingLeft()) + this.FadeOut(600, Easing.In); + else + this.FadeOut(100); + + Expire(); + }); } private class DragContainer : Container diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index c4d402e5b9..58c9814781 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -141,8 +141,6 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); - attemptPostCompletion(); - base.Close(false); break; } } @@ -166,6 +164,8 @@ namespace osu.Game.Overlays.Notifications CompletionTarget.Invoke(CreateCompletionNotification()); completionSent = true; + + Close(false); } private ProgressNotificationState state; @@ -239,6 +239,7 @@ namespace osu.Game.Overlays.Notifications { switch (State) { + case ProgressNotificationState.Completed: case ProgressNotificationState.Cancelled: base.Close(runFlingAnimation); break; From 38d8d457d97a43d64deb14cec6f6e8b11b493d53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 17:54:44 +0900 Subject: [PATCH 176/583] Add back second completion post attempt for case when notification overlay isn't loaded yet --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 58c9814781..61bb22041e 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -141,6 +141,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); + attemptPostCompletion(); break; } } From 4c4fdfd15379afe3906b69512b367e8bf1b403c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 18:15:17 +0900 Subject: [PATCH 177/583] Provide scores directly to `Player` instance rather than relying on DI --- .../TestSceneSoloGameplayLeaderboard.cs | 6 ++---- .../Leaderboards/ILeaderboardScoreSource.cs | 15 --------------- .../Screens/Play/HUD/SoloGameplayLeaderboard.cs | 16 ++++++---------- osu.Game/Screens/Play/Player.cs | 7 ++++++- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 17 +++++++++-------- 6 files changed, 24 insertions(+), 39 deletions(-) delete mode 100644 osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index 6839fafa93..2b8eb3a80c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -18,7 +17,7 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSoloGameplayLeaderboard : OsuTestScene, ILeaderboardScoreSource + public class TestSceneSoloGameplayLeaderboard : OsuTestScene { [Cached] private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); @@ -40,6 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SoloGameplayLeaderboard(trackingUser) { + Scores = { BindTarget = scores }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Expanded = { Value = true }, @@ -57,8 +57,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v); } - IBindableList ILeaderboardScoreSource.Scores => scores; - private static List createSampleScores() { return new[] diff --git a/osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs b/osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs deleted file mode 100644 index e7ed43633e..0000000000 --- a/osu.Game/Online/Leaderboards/ILeaderboardScoreSource.cs +++ /dev/null @@ -1,15 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Scoring; - -namespace osu.Game.Online.Leaderboards -{ - [Cached] - public interface ILeaderboardScoreSource - { - IBindableList Scores { get; } - } -} diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 6141260beb..00efcb1ea9 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; @@ -16,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD { private readonly IUser trackingUser; - private readonly IBindableList scores = new BindableList(); + public readonly IBindableList Scores = new BindableList(); // hold references to ensure bindables are updated. private readonly List> scoreBindables = new List>(); @@ -32,13 +31,10 @@ namespace osu.Game.Screens.Play.HUD this.trackingUser = trackingUser; } - [BackgroundDependencyLoader(true)] - private void load(ILeaderboardScoreSource? scoreSource) + protected override void LoadComplete() { - if (scoreSource != null) - scores.BindTo(scoreSource.Scores); - - scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); + base.LoadComplete(); + Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); } private void showScores() @@ -46,7 +42,7 @@ namespace osu.Game.Screens.Play.HUD Clear(); scoreBindables.Clear(); - if (!scores.Any()) + if (!Scores.Any()) return; ILeaderboardScore local = Add(trackingUser, true); @@ -58,7 +54,7 @@ namespace osu.Game.Screens.Play.HUD // Local score should always show lower than any existing scores in cases of ties. local.DisplayOrder.Value = long.MaxValue; - foreach (var s in scores) + foreach (var s in Scores) { var score = Add(s.User, false); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 95e3e75c8a..e1530d7ea2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -843,7 +843,12 @@ namespace osu.Game.Screens.Play }); } - protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User); + public readonly BindableList LeaderboardScores = new BindableList(); + + protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User) + { + Scores = { BindTarget = LeaderboardScores } + }; protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.LeaderboardFlow.Add(leaderboard); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index ee62160a21..ed4da5e848 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -23,7 +23,7 @@ using Realms; namespace osu.Game.Screens.Select.Leaderboards { - public class BeatmapLeaderboard : Leaderboard, ILeaderboardScoreSource + public class BeatmapLeaderboard : Leaderboard { public Action ScoreSelected; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 619ec97535..068f78172b 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -22,7 +21,7 @@ using osuTK.Input; namespace osu.Game.Screens.Select { - public class PlaySongSelect : SongSelect, ILeaderboardScoreSource + public class PlaySongSelect : SongSelect { private OsuScreen? playerLoader; @@ -111,14 +110,18 @@ namespace osu.Game.Screens.Select Player createPlayer() { + Player player; + var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); if (replayGeneratingMod != null) - { - return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)); - } + player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)); + else + player = new SoloPlayer(); - return new SoloPlayer(); + ((IBindableList)player.LeaderboardScores).BindTo(playBeatmapDetailArea.Leaderboard.Scores); + + return player; } } @@ -132,7 +135,5 @@ namespace osu.Game.Screens.Select playerLoader = null; } } - - IBindableList ILeaderboardScoreSource.Scores => playBeatmapDetailArea.Leaderboard.Scores; } } From c0e2ba419e60bf72affedb4dcb5a8d0cc7f73424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=BA=E3=82=BF=E3=82=B1?= Date: Fri, 16 Sep 2022 18:31:02 +0900 Subject: [PATCH 178/583] Localize non-localizable setting items --- osu.Game/Localisation/DebugSettingsStrings.cs | 10 ++ .../Localisation/GeneralSettingsStrings.cs | 5 + .../MaintenanceSettingsStrings.cs | 111 ++++++++++++++++++ .../Localisation/TabletSettingsStrings.cs | 5 + .../Sections/DebugSettings/MemorySettings.cs | 4 +- .../Sections/General/UpdateSettings.cs | 2 +- .../Settings/Sections/Input/TabletSettings.cs | 7 +- .../Sections/Maintenance/BeatmapSettings.cs | 2 +- .../Maintenance/CollectionsSettings.cs | 4 +- .../MassDeleteConfirmationDialog.cs | 3 +- .../MassVideoDeleteConfirmationDialog.cs | 3 +- .../Maintenance/MigrationRunScreen.cs | 9 +- .../Maintenance/MigrationSelectScreen.cs | 7 +- .../Sections/Maintenance/ModPresetSettings.cs | 6 +- .../Sections/Maintenance/ScoreSettings.cs | 2 +- .../Sections/Maintenance/SkinSettings.cs | 2 +- .../StableDirectoryLocationDialog.cs | 5 +- .../StableDirectorySelectScreen.cs | 3 +- 18 files changed, 163 insertions(+), 27 deletions(-) diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index 74b2c8d892..66ce0fa109 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -49,6 +49,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm"); + /// + /// "Block realm" + /// + public static LocalisableString BlockRealm => new TranslatableString(getKey(@"block_realm"), @"Block realm"); + + /// + /// "Unblock realm" + /// + public static LocalisableString UnblockRealm => new TranslatableString(getKey(@"unblock_realm"), @"Unblock realm"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 2aa91f5245..bbad80976e 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -64,6 +64,11 @@ namespace osu.Game.Localisation /// public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard"); + /// + /// "You are running the latest release ({0})" + /// + public static LocalisableString RunningLatestRelease(string arg0) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", arg0); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index a398eced07..8b7ca8d93a 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -14,11 +14,72 @@ namespace osu.Game.Localisation /// public static LocalisableString MaintenanceSectionHeader => new TranslatableString(getKey(@"maintenance_section_header"), @"Maintenance"); + /// + /// "Beatmaps" + /// + public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"Beatmaps"); + + /// + /// "Skins" + /// + public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"Skins"); + + /// + /// "Collections" + /// + public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); + + + /// + /// "Scores" + /// + public static LocalisableString Scores => new TranslatableString(getKey(@"scores"), @"Scores"); + + /// + /// "Mod presets" + /// + public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Mod presets"); + /// /// "Select directory" /// public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory"); + /// + /// "Migration in progress" + /// + public static LocalisableString MigrationInProgress => new TranslatableString(getKey(@"migration_in_progress"), @"Migration in progress"); + + /// + /// "This could take a few minutes depending on the speed of your disk(s)." + /// + public static LocalisableString MigrationDescription => new TranslatableString(getKey(@"migration_description"), @"This could take a few minutes depending on the speed of your disk(s)."); + + /// + /// "Please avoid interacting with the game!" + /// + public static LocalisableString ProhibitedInteractDuringMigration => new TranslatableString(getKey(@"prohibited_interact_during_migration"), @"Please avoid interacting with the game!"); + + /// + /// "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up." + /// + public static LocalisableString FailedCleanupNotification => new TranslatableString(getKey(@"failed_cleanup_notification"), @"Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up."); + + /// + /// "Please select a new location" + /// + public static LocalisableString SelectNewLocation => new TranslatableString(getKey(@"select_new_location"), @"Please select a new location"); + + /// + /// "The target directory already seems to have an osu! install. Use that data instead?" + /// + public static LocalisableString TargetDirectoryAlreadyInstalledOsu => new TranslatableString(getKey(@"target_directory_already_installed_osu"), @"The target directory already seems to have an osu! install. Use that data instead?"); + + /// + /// "To complete this operation, osu! will close. Please open it again to use the new data location." + /// + public static LocalisableString RestartAndReOpenRequiredForCompletion => new TranslatableString(getKey(@"restart_and_re_open_required_for_completion"), @"To complete this operation, osu! will close. Please open it again to use the new data location."); + /// /// "Import beatmaps from stable" /// @@ -84,6 +145,56 @@ namespace osu.Game.Localisation /// public static LocalisableString RestoreAllRecentlyDeletedModPresets => new TranslatableString(getKey(@"restore_all_recently_deleted_mod_presets"), @"Restore all recently deleted mod presets"); + /// + /// "Deleted all collections!" + /// + public static LocalisableString DeletedAllCollections => new TranslatableString(getKey(@"deleted_all_collections"), @"Deleted all collections!"); + + /// + /// "Deleted all mod presets!" + /// + public static LocalisableString DeletedAllModPresets => new TranslatableString(getKey(@"deleted_all_mod_presets"), @"Deleted all mod presets!"); + + /// + /// "Restored all deleted mod presets!" + /// + public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!"); + + /// + /// "Everything?" + /// + public static LocalisableString MassDeleteConfirmation => new TranslatableString(getKey(@"mass_delete_confirmation"), @"Everything?"); + + /// + /// "All beatmap videos? This cannot be undone!" + /// + public static LocalisableString MassVideoDeleteConfirmation => new TranslatableString(getKey(@"mass_video_delete_confirmation"), @"All beatmap videos? This cannot be undone!"); + + /// + /// "Failed to automatically locate an osu!stable installation." + /// + public static LocalisableString StableDirectoryLocationHeader => new TranslatableString(getKey(@"stable_directory_location_header"), @"Failed to automatically locate an osu!stable installation."); + + /// + /// "An existing install could not be located. If you know where it is, you can help locate it." + /// + public static LocalisableString StableDirectoryLocationBody => new TranslatableString(getKey(@"stable_directory_location_body"), @"An existing install could not be located. If you know where it is, you can help locate it."); + + /// + /// "Sure! I know where it is located!" + /// + public static LocalisableString StableDirectoryLocationOk => new TranslatableString(getKey(@"stable_directory_location_ok"), @"Sure! I know where it is located!"); + + /// + /// "Actually I don't have osu!stable installed." + /// + public static LocalisableString StableDirectoryLocationCancel => new TranslatableString(getKey(@"stable_directory_location_cancel"), @"Actually I don't have osu!stable installed."); + + /// + /// "Please select your osu!stable install location" + /// + public static LocalisableString StableDirectorySelectHeader => new TranslatableString(getKey(@"stable_directory_select_header"), @"Please select your osu!stable install location"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/TabletSettingsStrings.cs b/osu.Game/Localisation/TabletSettingsStrings.cs index d62d348df9..6c2e3c1f9c 100644 --- a/osu.Game/Localisation/TabletSettingsStrings.cs +++ b/osu.Game/Localisation/TabletSettingsStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString NoTabletDetected => new TranslatableString(getKey(@"no_tablet_detected"), @"No tablet detected!"); + /// + /// "If your tablet is not detected, please read [this FAQ]({0}) for troubleshooting steps." + /// + public static LocalisableString NoTabletDetectedDescription(string url) => new TranslatableString(getKey(@"no_tablet_detected_description"), @"If your tablet is not detected, please read [this FAQ]({0}) for troubleshooting steps.", url); + /// /// "Reset to full area" /// diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 42ac4adb34..5ec09adfda 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -46,11 +46,11 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings }, blockAction = new SettingsButton { - Text = "Block realm", + Text = DebugSettingsStrings.BlockRealm, }, unblockAction = new SettingsButton { - Text = "Unblock realm", + Text = DebugSettingsStrings.UnblockRealm, }, }; diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index b68a4fed48..d97cf699e5 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { notifications?.Post(new SimpleNotification { - Text = $"You are running the latest release ({game.Version})", + Text = GeneralSettingsStrings.RunningLatestRelease(game.Version), Icon = FontAwesome.Solid.CheckCircle, }); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 271438ed14..43676c5bbe 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -110,11 +110,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) { t.NewLine(); - t.AddText("If your tablet is not detected, please read "); - t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows + var formattedSource = MessageFormatter.FormatText(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" - : @"https://opentabletdriver.net/Wiki/FAQ/Linux"); - t.AddText(" for troubleshooting steps."); + : @"https://opentabletdriver.net/Wiki/FAQ/Linux").ToString()); + t.AddLinks(formattedSource.Text, formattedSource.Links); } }), } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 453dbd2e18..00342faf3b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class BeatmapSettings : SettingsSubsection { - protected override LocalisableString Header => "Beatmaps"; + protected override LocalisableString Header => MaintenanceSettingsStrings.Beatmaps; private SettingsButton importBeatmapsButton = null!; private SettingsButton deleteBeatmapsButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 5a91213eb8..9ec6f59fb5 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class CollectionsSettings : SettingsSubsection { - protected override LocalisableString Header => "Collections"; + protected override LocalisableString Header => MaintenanceSettingsStrings.Collections; private SettingsButton importCollectionsButton = null!; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void deleteAllCollections() { realm.Write(r => r.RemoveAll()); - notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllCollections }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs index 19e6f83dac..bcfccaa5e2 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Localisation; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance @@ -10,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public MassDeleteConfirmationDialog(Action deleteAction) { - BodyText = "Everything?"; + BodyText = MaintenanceSettingsStrings.MassDeleteConfirmation; DeleteAction = deleteAction; } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs index fc8c9d497b..a386c64806 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -10,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public MassVideoDeleteConfirmationDialog(Action deleteAction) : base(deleteAction) { - BodyText = "All beatmap videos? This cannot be undone!"; + BodyText = MaintenanceSettingsStrings.MassVideoDeleteConfirmation; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index d565576d09..158e1a8aa0 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays.Notifications; using osu.Game.Screens; using osuTK; @@ -71,14 +72,14 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "Migration in progress", + Text = MaintenanceSettingsStrings.MigrationInProgress, Font = OsuFont.Default.With(size: 40) }, new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "This could take a few minutes depending on the speed of your disk(s).", + Text = MaintenanceSettingsStrings.MigrationDescription, Font = OsuFont.Default.With(size: 30) }, new LoadingSpinner(true) @@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "Please avoid interacting with the game!", + Text = MaintenanceSettingsStrings.ProhibitedInteractDuringMigration, Font = OsuFont.Default.With(size: 30) }, } @@ -111,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { notifications.Post(new SimpleNotification { - Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.", + Text = MaintenanceSettingsStrings.FailedCleanupNotification, Activated = () => { originalStorage.PresentExternally(); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 0d32e33d87..2f4f04fbc2 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.IO; +using osu.Game.Localisation; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance @@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public override bool HideOverlaysOnEnter => true; - public override LocalisableString HeaderText => "Please select a new location"; + public override LocalisableString HeaderText => MaintenanceSettingsStrings.SelectNewLocation; protected override void OnSelection(DirectoryInfo directory) { @@ -51,9 +52,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance // Quick test for whether there's already an osu! install at the target path. if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME)) { - dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use that data instead?", () => + dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.TargetDirectoryAlreadyInstalledOsu.ToString(), () => { - dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () => + dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.RestartAndReOpenRequiredForCompletion.ToString(), () => { (storage as OsuStorage)?.ChangeDataPath(target.FullName); game.Exit(); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs index d35d3ff468..3e3138f041 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class ModPresetSettings : SettingsSubsection { - protected override LocalisableString Header => "Mod presets"; + protected override LocalisableString Header => MaintenanceSettingsStrings.ModPresets; [Resolved] private RealmAccess realm { get; set; } = null!; @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance deleteAllButton.Enabled.Value = true; if (deletionTask.IsCompletedSuccessfully) - notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all mod presets!" }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllModPresets }); else if (deletionTask.IsFaulted) Logger.Error(deletionTask.Exception, "Failed to delete all mod presets"); } @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance undeleteButton.Enabled.Value = true; if (undeletionTask.IsCompletedSuccessfully) - notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Restored all deleted mod presets!" }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.RestoredAllDeletedModPresets }); else if (undeletionTask.IsFaulted) Logger.Error(undeletionTask.Exception, "Failed to restore mod presets"); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index 70e11d45dc..6377d59e2a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class ScoreSettings : SettingsSubsection { - protected override LocalisableString Header => "Scores"; + protected override LocalisableString Header => MaintenanceSettingsStrings.Scores; private SettingsButton importScoresButton = null!; private SettingsButton deleteScoresButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index c95b1d4dd8..893981f3d9 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class SkinSettings : SettingsSubsection { - protected override LocalisableString Header => "Skins"; + protected override LocalisableString Header => MaintenanceSettingsStrings.Skins; private SettingsButton importSkinsButton = null!; private SettingsButton deleteSkinsButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs index 8aff4520b5..31e5b05596 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; +using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Screens; @@ -19,8 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public StableDirectoryLocationDialog(TaskCompletionSource taskCompletionSource) { - HeaderText = "Failed to automatically locate an osu!stable installation."; - BodyText = "An existing install could not be located. If you know where it is, you can help locate it."; + HeaderText = MaintenanceSettingsStrings.StableDirectoryLocationHeader; + BodyText = MaintenanceSettingsStrings.StableDirectoryLocationBody; Icon = FontAwesome.Solid.QuestionCircle; Buttons = new PopupDialogButton[] diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 047d589689..22cf2e7076 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Localisation; using osu.Framework.Screens; +using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override bool IsValidDirectory(DirectoryInfo info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; - public override LocalisableString HeaderText => "Please select your osu!stable install location"; + public override LocalisableString HeaderText => MaintenanceSettingsStrings.StableDirectorySelectHeader; public StableDirectorySelectScreen(TaskCompletionSource taskCompletionSource) { From b99b10e586e79367459983aa45aac55e33cd9654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=BA=E3=82=BF=E3=82=B1?= Date: Fri, 16 Sep 2022 18:34:13 +0900 Subject: [PATCH 179/583] fix code style problem --- osu.Game/Localisation/MaintenanceSettingsStrings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 8b7ca8d93a..4d7fdc60f7 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -29,7 +29,6 @@ namespace osu.Game.Localisation /// public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); - /// /// "Scores" /// From 3a62d292698aa279f765c154e6004317e3d92fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=BA=E3=82=BF=E3=82=B1?= Date: Fri, 16 Sep 2022 18:43:59 +0900 Subject: [PATCH 180/583] fix tab spacing --- osu.Game/Localisation/GeneralSettingsStrings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index bbad80976e..8506971756 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -65,9 +65,9 @@ namespace osu.Game.Localisation public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard"); /// - /// "You are running the latest release ({0})" - /// - public static LocalisableString RunningLatestRelease(string arg0) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", arg0); + /// "You are running the latest release ({0})" + /// + public static LocalisableString RunningLatestRelease(string arg0) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", arg0); private static string getKey(string key) => $"{prefix}:{key}"; } From 85bb6f0beb859cf9f57d6ef6e68f28f557cb9c64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 00:54:16 +0900 Subject: [PATCH 181/583] Update framework Update framework (again) Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2c186a52dd..77c29a5d6e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fed7c27f07..29e690a024 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 496bfbb85c..83410b08f6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -82,7 +82,7 @@ - + From 433bb5ae2492aeee65446eb44b29adcd4cfda61c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Sep 2022 20:54:06 +0900 Subject: [PATCH 182/583] Add ServerShuttingDownCountdown --- .../Online/Multiplayer/MultiplayerClient.cs | 26 +++++++++++++++++++ .../Multiplayer/MultiplayerCountdown.cs | 1 + .../ServerShuttingDownCountdown.cs | 20 ++++++++++++++ osu.Game/Online/SignalRWorkaroundTypes.cs | 3 ++- osu.Game/OsuGame.cs | 2 ++ osu.Game/OsuGameBase.cs | 6 ++--- 6 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index c398d72118..20efe5662d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Utils; @@ -26,6 +27,8 @@ namespace osu.Game.Online.Multiplayer { public abstract class MultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer { + public Action? PostNotification { protected get; set; } + /// /// Invoked when any change occurs to the multiplayer room. /// @@ -554,6 +557,14 @@ namespace osu.Game.Online.Multiplayer { case CountdownStartedEvent countdownStartedEvent: Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown); + + switch (countdownStartedEvent.Countdown) + { + case ServerShuttingDownCountdown: + postServerShuttingDownNotification(); + break; + } + break; case CountdownStoppedEvent countdownStoppedEvent: @@ -569,6 +580,21 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } + private void postServerShuttingDownNotification() + { + ServerShuttingDownCountdown? countdown = room?.ActiveCountdowns.OfType().FirstOrDefault(); + + if (countdown == null) + return; + + PostNotification?.Invoke(new SimpleNotification + { + Text = countdown.FinalNotification + ? $"The multiplayer server is restarting in {countdown.TimeRemaining:hh\\:mm\\:ss}. This multiplayer room will be closed shortly." + : $"The multiplayer server is restarting in {countdown.TimeRemaining:hh\\:mm\\:ss}." + }); + } + Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability) { Scheduler.Add(() => diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index fd22420b99..c59f5937b0 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -13,6 +13,7 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] [Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. [Union(1, typeof(ForceGameplayStartCountdown))] + [Union(2, typeof(ServerShuttingDownCountdown))] public abstract class MultiplayerCountdown { /// diff --git a/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs b/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs new file mode 100644 index 0000000000..4def3acc5e --- /dev/null +++ b/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using MessagePack; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A countdown that indicates the current multiplayer server is shutting down. + /// + [MessagePackObject] + public class ServerShuttingDownCountdown : MultiplayerCountdown + { + /// + /// If this is the final notification, no more events will be sent after this. + /// + [Key(2)] + public bool FinalNotification { get; set; } + } +} diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index 3518fbb4fe..0b545821ee 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -28,7 +28,8 @@ namespace osu.Game.Online (typeof(TeamVersusRoomState), typeof(MatchRoomState)), (typeof(TeamVersusUserState), typeof(MatchUserState)), (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)), - (typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)) + (typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)), + (typeof(ServerShuttingDownCountdown), typeof(MultiplayerCountdown)), }; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9e2384322a..63a46e63d6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -730,6 +730,8 @@ namespace osu.Game ScoreManager.PostNotification = n => Notifications.Post(n); ScoreManager.PresentImport = items => PresentScore(items.First().Value); + MultiplayerClient.PostNotification = n => Notifications.Post(n); + // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown"; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b30a065371..8b016e8eb0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -179,7 +179,7 @@ namespace osu.Game private SpectatorClient spectatorClient; - private MultiplayerClient multiplayerClient; + protected MultiplayerClient MultiplayerClient { get; private set; } private MetadataClient metadataClient; @@ -284,7 +284,7 @@ namespace osu.Game // TODO: OsuGame or OsuGameBase? dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); - dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); + dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -329,7 +329,7 @@ namespace osu.Game AddInternal(apiAccess); AddInternal(spectatorClient); - AddInternal(multiplayerClient); + AddInternal(MultiplayerClient); AddInternal(metadataClient); AddInternal(rulesetConfigCache); From 81d582c051280ee3dbe6de607d654c7788ac7ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=BA=E3=82=BF=E3=82=B1?= Date: Fri, 16 Sep 2022 21:08:25 +0900 Subject: [PATCH 183/583] fix review points and fine tuning --- osu.Game/Localisation/CommonStrings.cs | 5 ++++ osu.Game/Localisation/DebugSettingsStrings.cs | 15 ----------- .../MaintenanceSettingsStrings.cs | 25 ------------------- osu.Game/Overlays/Dialog/ConfirmDialog.cs | 3 ++- .../Sections/DebugSettings/MemorySettings.cs | 6 ++--- .../Settings/Sections/Input/TabletSettings.cs | 6 ++--- .../Sections/Maintenance/BeatmapSettings.cs | 2 +- .../Maintenance/CollectionsSettings.cs | 2 +- .../Maintenance/MigrationSelectScreen.cs | 4 +-- .../Sections/Maintenance/ModPresetSettings.cs | 2 +- .../Sections/Maintenance/ScoreSettings.cs | 2 +- .../Sections/Maintenance/SkinSettings.cs | 2 +- .../StableDirectoryLocationDialog.cs | 4 +-- 13 files changed, 22 insertions(+), 56 deletions(-) diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 93e3276f59..385ebd0593 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -89,6 +89,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); + /// + /// "Mod presets" + /// + public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Mod presets"); + /// /// "Name" /// diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index 66ce0fa109..dd21739096 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -44,21 +44,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches"); - /// - /// "Compact realm" - /// - public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm"); - - /// - /// "Block realm" - /// - public static LocalisableString BlockRealm => new TranslatableString(getKey(@"block_realm"), @"Block realm"); - - /// - /// "Unblock realm" - /// - public static LocalisableString UnblockRealm => new TranslatableString(getKey(@"unblock_realm"), @"Unblock realm"); - private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 4d7fdc60f7..4648682e64 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -14,31 +14,6 @@ namespace osu.Game.Localisation /// public static LocalisableString MaintenanceSectionHeader => new TranslatableString(getKey(@"maintenance_section_header"), @"Maintenance"); - /// - /// "Beatmaps" - /// - public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"Beatmaps"); - - /// - /// "Skins" - /// - public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"Skins"); - - /// - /// "Collections" - /// - public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); - - /// - /// "Scores" - /// - public static LocalisableString Scores => new TranslatableString(getKey(@"scores"), @"Scores"); - - /// - /// "Mod presets" - /// - public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Mod presets"); - /// /// "Select directory" /// diff --git a/osu.Game/Overlays/Dialog/ConfirmDialog.cs b/osu.Game/Overlays/Dialog/ConfirmDialog.cs index 8be865ee16..c17080f602 100644 --- a/osu.Game/Overlays/Dialog/ConfirmDialog.cs +++ b/osu.Game/Overlays/Dialog/ConfirmDialog.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Dialog @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Dialog /// The description of the action to be displayed to the user. /// An action to perform on confirmation. /// An optional action to perform on cancel. - public ConfirmDialog(string message, Action onConfirm, Action onCancel = null) + public ConfirmDialog(LocalisableString message, Action onConfirm, Action onCancel = null) { HeaderText = message; BodyText = "Last chance to turn back"; diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 5ec09adfda..3afb060e49 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings }, new SettingsButton { - Text = DebugSettingsStrings.CompactRealm, + Text = "Compact realm", Action = () => { // Blocking operations implicitly causes a Compact(). @@ -46,11 +46,11 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings }, blockAction = new SettingsButton { - Text = DebugSettingsStrings.BlockRealm, + Text = "Block realm", }, unblockAction = new SettingsButton { - Text = DebugSettingsStrings.UnblockRealm, + Text = "Unblock realm", }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 43676c5bbe..e32639f476 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, LocalisationManager localisation) { Children = new Drawable[] { @@ -110,9 +110,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) { t.NewLine(); - var formattedSource = MessageFormatter.FormatText(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows + var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" - : @"https://opentabletdriver.net/Wiki/FAQ/Linux").ToString()); + : @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value); t.AddLinks(formattedSource.Text, formattedSource.Links); } }), diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 00342faf3b..beae5a6aad 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class BeatmapSettings : SettingsSubsection { - protected override LocalisableString Header => MaintenanceSettingsStrings.Beatmaps; + protected override LocalisableString Header => CommonStrings.Beatmaps; private SettingsButton importBeatmapsButton = null!; private SettingsButton deleteBeatmapsButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 9ec6f59fb5..17fef37e40 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class CollectionsSettings : SettingsSubsection { - protected override LocalisableString Header => MaintenanceSettingsStrings.Collections; + protected override LocalisableString Header => CommonStrings.Collections; private SettingsButton importCollectionsButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 2f4f04fbc2..5de33fdd55 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -52,9 +52,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance // Quick test for whether there's already an osu! install at the target path. if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME)) { - dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.TargetDirectoryAlreadyInstalledOsu.ToString(), () => + dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.TargetDirectoryAlreadyInstalledOsu, () => { - dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.RestartAndReOpenRequiredForCompletion.ToString(), () => + dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.RestartAndReOpenRequiredForCompletion, () => { (storage as OsuStorage)?.ChangeDataPath(target.FullName); game.Exit(); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs index 3e3138f041..51f6e1bf60 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class ModPresetSettings : SettingsSubsection { - protected override LocalisableString Header => MaintenanceSettingsStrings.ModPresets; + protected override LocalisableString Header => CommonStrings.ModPresets; [Resolved] private RealmAccess realm { get; set; } = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index 6377d59e2a..eb2d3171ea 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class ScoreSettings : SettingsSubsection { - protected override LocalisableString Header => MaintenanceSettingsStrings.Scores; + protected override LocalisableString Header => CommonStrings.Scores; private SettingsButton importScoresButton = null!; private SettingsButton deleteScoresButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 893981f3d9..93c65513b7 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class SkinSettings : SettingsSubsection { - protected override LocalisableString Header => MaintenanceSettingsStrings.Skins; + protected override LocalisableString Header => CommonStrings.Skins; private SettingsButton importSkinsButton = null!; private SettingsButton deleteSkinsButton = null!; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs index 31e5b05596..7b7ea7cee0 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs @@ -28,12 +28,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { new PopupDialogOkButton { - Text = "Sure! I know where it is located!", + Text = MaintenanceSettingsStrings.StableDirectoryLocationOk, Action = () => Schedule(() => performer.PerformFromScreen(screen => screen.Push(new StableDirectorySelectScreen(taskCompletionSource)))) }, new PopupDialogCancelButton { - Text = "Actually I don't have osu!stable installed.", + Text = MaintenanceSettingsStrings.StableDirectoryLocationCancel, Action = () => taskCompletionSource.TrySetCanceled() } }; From 92b2417d4c7afbde92896e234bcae9b3fcc6f717 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 16 Sep 2022 21:10:11 +0900 Subject: [PATCH 184/583] Post notification when room joined --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 20efe5662d..f9236cbfac 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -210,6 +210,8 @@ namespace osu.Game.Online.Multiplayer updateLocalRoomSettings(joinedRoom.Settings); + postServerShuttingDownNotification(); + OnRoomJoined(); }, cancellationSource.Token).ConfigureAwait(false); }, cancellationSource.Token).ConfigureAwait(false); From 877165eb98f02fa0531cac2d63e86cb3626ff868 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Sep 2022 16:16:53 +0300 Subject: [PATCH 185/583] Allow specifying icon colour on simple notifications --- osu.Game/Overlays/Notifications/SimpleNotification.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index 1dba60fb5f..f3bb6a0578 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -41,6 +42,12 @@ namespace osu.Game.Overlays.Notifications } } + public ColourInfo IconColour + { + get => IconContent.Colour; + set => IconContent.Colour = value; + } + private TextFlowContainer? textDrawable; private SpriteIcon? iconDrawable; From 110652592fc3a96c36e2d6e9b2f0639ea5c2f192 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Sep 2022 16:17:24 +0300 Subject: [PATCH 186/583] Display readable tablet notifications and link to supported list page --- osu.Game/OsuGame.cs | 49 +++++++++++++++++++++++++++++++++++++++-- osu.Game/OsuGameBase.cs | 4 +++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0d115f62fe..ce0a961190 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -24,6 +24,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Screens; @@ -187,7 +188,8 @@ namespace osu.Game { this.args = args; - forwardLoggedErrorsToNotifications(); + forwardRuntimeLogsToNotifications(); + forwardTabletLogsToNotifications(); SentryLogger = new SentryLogger(this); } @@ -992,7 +994,7 @@ namespace osu.Game overlay.Depth = (float)-Clock.CurrentTime; } - private void forwardLoggedErrorsToNotifications() + private void forwardRuntimeLogsToNotifications() { int recentLogCount = 0; @@ -1033,6 +1035,49 @@ namespace osu.Game }; } + private void forwardTabletLogsToNotifications() + { + bool notifyOnWarning = true; + + Logger.NewEntry += entry => + { + if (entry.Level < LogLevel.Important || entry.LoggerName != ITabletHandler.LOGGER_NAME) + return; + + if (entry.Level == LogLevel.Error) + { + Schedule(() => Notifications.Post(new SimpleNotification + { + Text = $"Encountered tablet error: \"{entry.Message}\"", + Icon = FontAwesome.Solid.PenSquare, + IconColour = Colours.RedDark, + })); + } + else if (notifyOnWarning) + { + Schedule(() => Notifications.Post(new SimpleNotification + { + Text = @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.", + Icon = FontAwesome.Solid.PenSquare, + IconColour = Colours.YellowDark, + Activated = () => + { + OpenUrlExternally("https://opentabletdriver.net/Tablets", true); + return true; + } + })); + + notifyOnWarning = false; + } + }; + + Schedule(() => + { + ITabletHandler tablet = Host.AvailableInputHandlers.OfType().SingleOrDefault(); + tablet?.Tablet.BindValueChanged(_ => notifyOnWarning = true, true); + }); + } + private Task asyncLoadStream; /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b30a065371..9bc4508e42 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -124,6 +124,8 @@ namespace osu.Game protected SessionStatics SessionStatics { get; private set; } + protected OsuColour Colours { get; private set; } + protected BeatmapManager BeatmapManager { get; private set; } protected BeatmapModelDownloader BeatmapDownloader { get; private set; } @@ -308,7 +310,7 @@ namespace osu.Game dependencies.CacheAs(powerStatus); dependencies.Cache(SessionStatics = new SessionStatics()); - dependencies.Cache(new OsuColour()); + dependencies.Cache(Colours = new OsuColour()); RegisterImportHandler(BeatmapManager); RegisterImportHandler(ScoreManager); From f512106bee0ae1adc2faa16eb3abc09d3552b7a4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Sep 2022 20:00:38 +0300 Subject: [PATCH 187/583] Adjust test scene to match expectations - Adds a scenario where no button is pressed before second hitobject. - Adjusts press time to not conflict with break start time. --- .../Mods/TestSceneOsuModAlternate.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 5e46498aca..8994abcea5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; +using AutoMapper.Internal; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; @@ -125,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods /// Ensures alternation is reset before the first hitobject after a break. /// [Test] - public void TestInputSingularWithBreak() => CreateModTest(new ModTestData + public void TestInputSingularWithBreak([Values] bool pressBeforeSecondObject) => CreateModTest(new ModTestData { Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, @@ -155,21 +157,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, } }, - ReplayFrames = new List + ReplayFrames = new ReplayFrame[] { // first press to start alternate lock. - new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), - new OsuReplayFrame(501, new Vector2(100)), - // press same key after break but before hit object. - new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton), - new OsuReplayFrame(2251, new Vector2(300, 100)), + new OsuReplayFrame(450, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(451, new Vector2(100)), // press same key at second hitobject and ensure it has been hit. - new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton), - new OsuReplayFrame(2501, new Vector2(500, 100)), + new OsuReplayFrame(2450, new Vector2(500, 100), OsuAction.LeftButton), + new OsuReplayFrame(2451, new Vector2(500, 100)), // press same key at third hitobject and ensure it has been missed. - new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.LeftButton), - new OsuReplayFrame(3001, new Vector2(500, 100)), - } + new OsuReplayFrame(2950, new Vector2(500, 100), OsuAction.LeftButton), + new OsuReplayFrame(2951, new Vector2(500, 100)), + }.Concat(!pressBeforeSecondObject + ? Enumerable.Empty() + : new ReplayFrame[] + { + // press same key after break but before hit object. + new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(2251, new Vector2(300, 100)), + } + ).ToList() }); } } From 557b39b69b051cc4bc3e74ac9d7fc775f0dc1d21 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Sep 2022 19:48:24 +0300 Subject: [PATCH 188/583] Fix `InputBlockingMod` not always clearing last action on break periods --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index a7aca8257b..e4e8905722 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -18,7 +18,7 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset + public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset, IUpdatableByPlayfield { public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) }; @@ -62,15 +62,18 @@ namespace osu.Game.Rulesets.Osu.Mods gameplayClock = drawableRuleset.FrameStableClock; } + public void Update(Playfield playfield) + { + if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) + LastAcceptedAction = null; + } + protected abstract bool CheckValidNewAction(OsuAction action); private bool checkCorrectAction(OsuAction action) { if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) - { - LastAcceptedAction = null; return true; - } switch (action) { From fe7223711890b614cdc2eabf9933d6400da73b9e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Sep 2022 20:08:50 +0300 Subject: [PATCH 189/583] Remove unused using --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 8994abcea5..521c10c10c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using AutoMapper.Internal; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; From 288cc7b201b8a404dc677017fe002220c4b2ab97 Mon Sep 17 00:00:00 2001 From: vegguid <75315940+vegguid@users.noreply.github.com> Date: Fri, 16 Sep 2022 21:36:17 +0200 Subject: [PATCH 190/583] Fixed Leaderboard tooltip not following time format setting --- .../Leaderboards/LeaderboardScoreTooltip.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 2f3ece0e3b..7123a274d4 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -15,6 +15,8 @@ using osu.Framework.Localisation; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +using osu.Framework.Bindables; +using osu.Game.Configuration; namespace osu.Game.Online.Leaderboards { @@ -24,7 +26,7 @@ namespace osu.Game.Online.Leaderboards private FillFlowContainer topScoreStatistics = null!; private FillFlowContainer bottomScoreStatistics = null!; private FillFlowContainer modStatistics = null!; - + private readonly Bindable prefer24HourTime = new Bindable(); public LeaderboardScoreTooltip() { AutoSizeAxes = Axes.Both; @@ -36,8 +38,9 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OsuConfigManager configManager) { + configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime); InternalChildren = new Drawable[] { new Box @@ -92,6 +95,13 @@ namespace osu.Game.Online.Leaderboards }; } + private void updateDisplay() + { + if (displayedScore != null) + { + timestampLabel.Text = prefer24HourTime.Value ? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}"; + } + } private ScoreInfo? displayedScore; public void SetContent(ScoreInfo score) @@ -101,7 +111,7 @@ namespace osu.Game.Online.Leaderboards displayedScore = score; - timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; + prefer24HourTime.BindValueChanged(_ => updateDisplay(), true); modStatistics.Clear(); topScoreStatistics.Clear(); From 48527f2d076faddf0288903ac06ecf0daa16b8c6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Sep 2022 22:50:49 +0300 Subject: [PATCH 191/583] Ignore case during logger name comparison --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ce0a961190..9a63ccbf48 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1041,7 +1041,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.LoggerName != ITabletHandler.LOGGER_NAME) + if (entry.Level < LogLevel.Important || !entry.LoggerName.Equals(ITabletHandler.LOGGER_NAME, StringComparison.OrdinalIgnoreCase)) return; if (entry.Level == LogLevel.Error) From 0d76f4501d4cb47d7a0f7880b9c9c19755315d12 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sat, 17 Sep 2022 10:10:20 +0100 Subject: [PATCH 192/583] Revert "Set InvalidationSource to Self" This reverts commit 2092008251cf5d0cc4846763b86719155f59a8bd. --- osu.Game/Screens/Play/SquareGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index a1e004bfcc..00d6ede3bf 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play } } - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize, InvalidationSource.Self); + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); private ScheduledDelegate scheduledCreate; protected override void Update() From e3d5ba530147644677938b74f68de09359c6c776 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sat, 17 Sep 2022 10:45:04 +0100 Subject: [PATCH 193/583] Check if graph DrawSize changed --- osu.Game/Screens/Play/SquareGraph.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 00d6ede3bf..092e056c85 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -78,11 +78,13 @@ namespace osu.Game.Screens.Play private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); private ScheduledDelegate scheduledCreate; + private Vector2 previousDrawSize; + protected override void Update() { base.Update(); - if (values != null && !layout.IsValid) + if (values != null && !layout.IsValid && DrawSize != previousDrawSize) { columns?.FadeOut(500, Easing.OutQuint).Expire(); @@ -90,6 +92,7 @@ namespace osu.Game.Screens.Play scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500); layout.Validate(); + previousDrawSize = DrawSize; } } From 0bfe967452a0ae9a910d4ca31318aed155c58608 Mon Sep 17 00:00:00 2001 From: vegguid <75315940+vegguid@users.noreply.github.com> Date: Sat, 17 Sep 2022 12:58:23 +0200 Subject: [PATCH 194/583] moved BindValueChanged to LoadComplete --- .../Leaderboards/LeaderboardScoreTooltip.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7123a274d4..0651fe0072 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -94,13 +94,11 @@ namespace osu.Game.Online.Leaderboards } }; } - - private void updateDisplay() + protected override void LoadComplete() { - if (displayedScore != null) - { - timestampLabel.Text = prefer24HourTime.Value ? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}"; - } + base.LoadComplete(); + + prefer24HourTime.BindValueChanged(_ => updateTimestampLabel(), true); } private ScoreInfo? displayedScore; @@ -111,7 +109,7 @@ namespace osu.Game.Online.Leaderboards displayedScore = score; - prefer24HourTime.BindValueChanged(_ => updateDisplay(), true); + updateTimestampLabel(); modStatistics.Clear(); topScoreStatistics.Clear(); @@ -130,6 +128,13 @@ namespace osu.Game.Online.Leaderboards topScoreStatistics.Add(new HitResultCell(result)); } } + private void updateTimestampLabel() + { + if (displayedScore != null) + { + timestampLabel.Text = prefer24HourTime.Value ? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}"; + } + } protected override void PopIn() => this.FadeIn(20, Easing.OutQuint); protected override void PopOut() => this.FadeOut(80, Easing.OutQuint); From a1d830e47f3728329353a39be1fce94de727c483 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Sep 2022 22:22:56 +0900 Subject: [PATCH 195/583] Fix formatting --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 0651fe0072..f51f57c031 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -27,6 +27,7 @@ namespace osu.Game.Online.Leaderboards private FillFlowContainer bottomScoreStatistics = null!; private FillFlowContainer modStatistics = null!; private readonly Bindable prefer24HourTime = new Bindable(); + public LeaderboardScoreTooltip() { AutoSizeAxes = Axes.Both; @@ -94,12 +95,14 @@ namespace osu.Game.Online.Leaderboards } }; } + protected override void LoadComplete() { base.LoadComplete(); prefer24HourTime.BindValueChanged(_ => updateTimestampLabel(), true); } + private ScoreInfo? displayedScore; public void SetContent(ScoreInfo score) @@ -128,11 +131,14 @@ namespace osu.Game.Online.Leaderboards topScoreStatistics.Add(new HitResultCell(result)); } } + private void updateTimestampLabel() { if (displayedScore != null) { - timestampLabel.Text = prefer24HourTime.Value ? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}"; + timestampLabel.Text = prefer24HourTime.Value + ? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}" + : $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}"; } } From d580b07063f2c83bd165809f210c82c64ee7b19e Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sat, 17 Sep 2022 14:40:36 +0100 Subject: [PATCH 196/583] Refactor update condition logic --- osu.Game/Screens/Play/SquareGraph.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 092e056c85..210ae21156 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -15,7 +15,6 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; -using osu.Framework.Layout; using osu.Framework.Threading; namespace osu.Game.Screens.Play @@ -24,11 +23,6 @@ namespace osu.Game.Screens.Play { private BufferedContainer columns; - public SquareGraph() - { - AddLayout(layout); - } - public int ColumnCount => columns?.Children.Count ?? 0; private int progress; @@ -57,7 +51,7 @@ namespace osu.Game.Screens.Play if (value == values) return; values = value; - layout.Invalidate(); + graphNeedsUpdate = true; } } @@ -75,24 +69,25 @@ namespace osu.Game.Screens.Play } } - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); private ScheduledDelegate scheduledCreate; + private bool graphNeedsUpdate = false; + private Vector2 previousDrawSize; protected override void Update() { base.Update(); - if (values != null && !layout.IsValid && DrawSize != previousDrawSize) + if (graphNeedsUpdate || (values != null && DrawSize != previousDrawSize)) { columns?.FadeOut(500, Easing.OutQuint).Expire(); scheduledCreate?.Cancel(); scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500); - layout.Validate(); previousDrawSize = DrawSize; + graphNeedsUpdate = false; } } From 0b6a77bc8bd90c76aa9fbf491d6542e5e7dff887 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Sat, 17 Sep 2022 15:59:42 +0200 Subject: [PATCH 197/583] EditorBeatmap's TimelineZoom should never be zero --- osu.Game/Screens/Edit/EditorBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 16c0064e80..356276039c 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -96,6 +96,7 @@ namespace osu.Game.Screens.Edit PlayableBeatmap.ControlPointInfo = ConvertControlPoints(PlayableBeatmap.ControlPointInfo); this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo; + if (this.beatmapInfo.TimelineZoom <= 0) this.beatmapInfo.TimelineZoom = 1; if (beatmapSkin is Skin skin) { From 2dfa89c62e255c4d806db3ea8ab92aa028afedca Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sat, 17 Sep 2022 15:05:25 +0100 Subject: [PATCH 198/583] Clean up --- osu.Game/Screens/Play/SquareGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 210ae21156..9ac673ae52 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate scheduledCreate; - private bool graphNeedsUpdate = false; + private bool graphNeedsUpdate; private Vector2 previousDrawSize; From 51841988bf32532267a65c3e0bb3f28d335ae1f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 15:40:20 +0900 Subject: [PATCH 199/583] Rename references to `DefaultSkin` to have `Triangles` suffix --- .../TestSceneCatchSkinConfiguration.cs | 2 +- .../Editor/TestSceneManiaComposeScreen.cs | 2 +- .../TestSceneHitCircleArea.cs | 2 +- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 4 ++-- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Navigation/TestSceneEditDefaultSkin.cs | 4 ++-- osu.Game/Configuration/OsuConfigManager.cs | 2 +- .../Backgrounds/BackgroundScreenDefault.cs | 2 +- ...DefaultSkin.cs => DefaultSkinTriangles.cs} | 10 +++++----- .../Skinning/RulesetSkinProvidingContainer.cs | 2 +- osu.Game/Skinning/SkinInfo.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 20 +++++++++---------- osu.Game/Skinning/SkinnableSprite.cs | 2 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 6 +++--- 14 files changed, 31 insertions(+), 31 deletions(-) rename osu.Game/Skinning/{DefaultSkin.cs => DefaultSkinTriangles.cs} (95%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs index 6ecbf58a52..06235d3744 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests }); } - private class TestSkin : DefaultSkin + private class TestSkin : DefaultSkinTriangles { public bool FlipCatcherPlate { get; set; } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 0354228cca..9e02424eb8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestDefaultSkin() { - AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged()); + AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged()); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 0cf2ec6b7e..d3f58ca868 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = new SkinProvidingContainer(new DefaultSkin(null)) + Child = new SkinProvidingContainer(new DefaultSkinTriangles(null)) { RelativeSizeAxes = Axes.Both, Child = drawableHitCircle = new DrawableHitCircle(hitCircle) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index c3c10215a5..57f2578200 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Skins.IO skinManager.CurrentSkinInfo.Value.PerformRead(s => { Assert.IsFalse(s.Protected); - Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType()); + Assert.AreEqual(typeof(DefaultSkinTriangles), s.CreateInstance(skinManager).GetType()); new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream); @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Skins.IO { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); - Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType()); + Assert.AreEqual(typeof(DefaultSkinTriangles), s.CreateInstance(skinManager).GetType()); }); return Task.CompletedTask; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index e12be6d3b4..b24a65820d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEmptyLegacyBeatmapSkinFallsBack() { - CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null)); + CreateSkinTest(DefaultSkinTriangles.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null)); AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs index 4ca55e8744..bc6458b62b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestEditDefaultSkin() { - AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.DEFAULT_SKIN); + AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.DEFAULT_SKIN_TRIANGLES); AddStep("open settings", () => { Game.Settings.Show(); }); @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("open skin editor", () => skinEditor.Show()); // Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part). - AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.DEFAULT_SKIN); + AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.DEFAULT_SKIN_TRIANGLES); AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected)); AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType().SingleOrDefault()?.Enabled.Value == true); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 5f49557685..ec97508522 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Configuration { // UI/selection defaults SetDefault(OsuSetting.Ruleset, string.Empty); - SetDefault(OsuSetting.Skin, SkinInfo.DEFAULT_SKIN.ToString()); + SetDefault(OsuSetting.Skin, SkinInfo.DEFAULT_SKIN_TRIANGLES.ToString()); SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); SetDefault(OsuSetting.BeatmapDetailModsFilter, false); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index c794c768c6..e04e6d4849 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Backgrounds case BackgroundSource.Skin: // default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them. - if (skin.Value is DefaultSkin || skin.Value is DefaultLegacySkin) + if (skin.Value is DefaultSkinTriangles || skin.Value is DefaultLegacySkin) break; newBackground = new SkinBackground(skin.Value, getBackgroundTextureName()); diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkinTriangles.cs similarity index 95% rename from osu.Game/Skinning/DefaultSkin.cs rename to osu.Game/Skinning/DefaultSkinTriangles.cs index f10e8412b1..26d933105a 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkinTriangles.cs @@ -22,26 +22,26 @@ using osuTK.Graphics; namespace osu.Game.Skinning { - public class DefaultSkin : Skin + public class DefaultSkinTriangles : Skin { public static SkinInfo CreateInfo() => new SkinInfo { - ID = osu.Game.Skinning.SkinInfo.DEFAULT_SKIN, + ID = osu.Game.Skinning.SkinInfo.DEFAULT_SKIN_TRIANGLES, Name = "osu! (triangles)", Creator = "team osu!", Protected = true, - InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo() + InstantiationInfo = typeof(DefaultSkinTriangles).GetInvariantInstantiationInfo() }; private readonly IStorageResourceProvider resources; - public DefaultSkin(IStorageResourceProvider resources) + public DefaultSkinTriangles(IStorageResourceProvider resources) : this(CreateInfo(), resources) { } [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] - public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) + public DefaultSkinTriangles(SkinInfo skin, IStorageResourceProvider resources) : base(skin, resources) { this.resources = resources; diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d5690710bb..9ece48a6cb 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Skinning } } - int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index bf3cf77257..2984381604 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning [JsonObject(MemberSerialization.OptIn)] public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasGuidPrimaryKey, ISoftDelete, IHasNamedFiles { - internal static readonly Guid DEFAULT_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); + internal static readonly Guid DEFAULT_SKIN_TRIANGLES = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); 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"); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 7ffea3b54f..bce58424d4 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -49,9 +49,9 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); - public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()) + public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged()) { - Default = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged() + Default = Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged() }; private readonly SkinImporter skinImporter; @@ -61,7 +61,7 @@ namespace osu.Game.Skinning /// /// The default skin. /// - public Skin DefaultSkin { get; } + public Skin DefaultSkinTriangles { get; } /// /// The default legacy skin. @@ -86,7 +86,7 @@ namespace osu.Game.Skinning var defaultSkins = new[] { DefaultLegacySkin = new DefaultLegacySkin(this), - DefaultSkin = new DefaultSkin(this), + DefaultSkinTriangles = new DefaultSkinTriangles(this), }; // Ensure the default entries are present. @@ -104,7 +104,7 @@ namespace osu.Game.Skinning CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin); }; - CurrentSkin.Value = DefaultSkin; + CurrentSkin.Value = DefaultSkinTriangles; CurrentSkin.ValueChanged += skin => { if (!skin.NewValue.SkinInfo.Equals(CurrentSkinInfo.Value)) @@ -125,7 +125,7 @@ namespace osu.Game.Skinning if (randomChoices.Length == 0) { - CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged(); + CurrentSkinInfo.Value = Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged(); return; } @@ -232,8 +232,8 @@ namespace osu.Game.Skinning if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultLegacySkin) yield return DefaultLegacySkin; - if (CurrentSkin.Value != DefaultSkin) - yield return DefaultSkin; + if (CurrentSkin.Value != DefaultSkinTriangles) + yield return DefaultSkinTriangles; } } @@ -294,7 +294,7 @@ namespace osu.Game.Skinning Guid currentUserSkin = CurrentSkinInfo.Value.ID; if (items.Any(s => s.ID == currentUserSkin)) - scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()); + scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged()); Delete(items.ToList(), silent); }); @@ -313,7 +313,7 @@ namespace osu.Game.Skinning skinInfo = DefaultLegacySkin.SkinInfo; } - CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.SkinInfo; + CurrentSkinInfo.Value = skinInfo ?? DefaultSkinTriangles.SkinInfo; } } } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 57beb6e803..cb4a91691e 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -112,7 +112,7 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) - => skin.GetType() == typeof(DefaultSkin) + => skin.GetType() == typeof(DefaultSkinTriangles) || skin.GetType() == typeof(DefaultLegacySkin) || skin.GetType() == typeof(LegacySkin); } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 7278e1e93f..21ecdf7dbe 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual public abstract class SkinnableTestScene : OsuGridTestScene, IStorageResourceProvider { private Skin metricsSkin; - private Skin defaultSkin; + private Skin defaultSkinTriangles; private Skin specialSkin; private Skin oldSkin; @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual var dllStore = new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); - defaultSkin = new DefaultLegacySkin(this); + defaultSkinTriangles = new DefaultLegacySkin(this); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore(dllStore, "Resources/special_skin"), this, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore(dllStore, "Resources/old_skin"), this, true); } @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual Cell(0).Child = createProvider(null, creationFunction, beatmap); Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap); - Cell(2).Child = createProvider(defaultSkin, creationFunction, beatmap); + Cell(2).Child = createProvider(defaultSkinTriangles, creationFunction, beatmap); Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap); Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap); } From 3e28ab72cefb4b28e1566662b75b72ed4fe1a016 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 15:49:15 +0900 Subject: [PATCH 200/583] Standardise default skin names --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- osu.Game/Skinning/DefaultSkinTriangles.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 2ebcc98c53..04f1286dc7 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning public static SkinInfo CreateInfo() => new SkinInfo { ID = Skinning.SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. - Name = "osu!classic", + Name = "osu! \"classic\" (2013)", Creator = "team osu!", Protected = true, InstantiationInfo = typeof(DefaultLegacySkin).GetInvariantInstantiationInfo() diff --git a/osu.Game/Skinning/DefaultSkinTriangles.cs b/osu.Game/Skinning/DefaultSkinTriangles.cs index 26d933105a..02df247c36 100644 --- a/osu.Game/Skinning/DefaultSkinTriangles.cs +++ b/osu.Game/Skinning/DefaultSkinTriangles.cs @@ -27,7 +27,7 @@ namespace osu.Game.Skinning public static SkinInfo CreateInfo() => new SkinInfo { ID = osu.Game.Skinning.SkinInfo.DEFAULT_SKIN_TRIANGLES, - Name = "osu! (triangles)", + Name = "osu! \"triangles\" (2017)", Creator = "team osu!", Protected = true, InstantiationInfo = typeof(DefaultSkinTriangles).GetInvariantInstantiationInfo() From 487378f732989463c2431c4075d873e623da86fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 15:49:26 +0900 Subject: [PATCH 201/583] Fallback to default skin if instantiation fails --- osu.Game/Skinning/SkinInfo.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 2984381604..9e268fd7ec 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -47,7 +47,17 @@ namespace osu.Game.Skinning ? typeof(LegacySkin) : Type.GetType(InstantiationInfo).AsNonNull(); - return (Skin)Activator.CreateInstance(type, this, resources); + try + { + return (Skin)Activator.CreateInstance(type, this, resources); + } + catch + { + // This defaults to triangles to catch the case where a user has a modified triangles skin. + // If we ever add more default skins in the future this will need some kind of proper migration rather than + // a single catch. + return new DefaultSkinTriangles(this, resources); + } } public IList Files { get; } = null!; From 64ee2108257326c9f188a966726732d097d531f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:17:48 +0900 Subject: [PATCH 202/583] Add realm migration to update skin names --- osu.Game/Database/RealmAccess.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cefc7da503..edcd020226 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -69,8 +69,9 @@ namespace osu.Game.Database /// 22 2022-07-31 Added ModPreset. /// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo. /// 24 2022-08-22 Added MaximumStatistics to ScoreInfo. + /// 25 2022-09-18 Remove skins to add with new naming. /// - private const int schema_version = 24; + private const int schema_version = 25; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -870,6 +871,11 @@ namespace osu.Game.Database } break; + + case 25: + // Remove the default skins so they can be added back by SkinManager with updated naming. + migration.NewRealm.RemoveRange(migration.NewRealm.All().Where(s => s.Protected)); + break; } } From 1b475f9360842a1704050a12ece8e1477a3006cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:21:39 +0900 Subject: [PATCH 203/583] Ensure skin `InstantiationInfo` is updated when saving --- osu.Game/Skinning/SkinImporter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index f270abd163..701dcdfc2d 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -232,6 +232,9 @@ namespace osu.Game.Skinning { skin.SkinInfo.PerformWrite(s => { + // Update for safety + s.InstantiationInfo = skin.GetType().GetInvariantInstantiationInfo(); + // Serialise out the SkinInfo itself. string skinInfoJson = JsonConvert.SerializeObject(s, new JsonSerializerSettings { Formatting = Formatting.Indented }); From 1c4e02fae8db6e16a58f26c99c4238626d3b4d13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Sep 2022 23:58:42 +0900 Subject: [PATCH 204/583] Explicitly order default skins in dropdown to get correct order --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 4787b07af8..f1a00dc958 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -78,8 +78,7 @@ namespace osu.Game.Overlays.Settings.Sections realmSubscription = realm.RegisterForNotifications(_ => realm.Realm.All() .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); + .OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); skinDropdown.Current.BindValueChanged(skin => { @@ -101,14 +100,17 @@ namespace osu.Game.Overlays.Settings.Sections if (!sender.Any()) return; - int protectedCount = sender.Count(s => s.Protected); - // For simplicity repopulate the full list. // In the future we should change this to properly handle ChangeSet events. dropdownItems.Clear(); - foreach (var skin in sender) + + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.DEFAULT_SKIN_TRIANGLES).ToLive(realm)); + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.CLASSIC_SKIN).ToLive(realm)); + + dropdownItems.Add(random_skin_info); + + foreach (var skin in sender.Where(s => !s.Protected)) dropdownItems.Add(skin.ToLive(realm)); - dropdownItems.Insert(protectedCount, random_skin_info); Schedule(() => skinDropdown.Items = dropdownItems); } From 41e69fbca69bd23f824660c883bf6d50f5e3e0eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Sep 2022 00:14:49 +0900 Subject: [PATCH 205/583] Remove "default" prefix from naming --- .../TestSceneCatchSkinConfiguration.cs | 2 +- .../Editor/TestSceneManiaComposeScreen.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs | 2 +- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 4 ++-- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Visual/Navigation/TestSceneEditDefaultSkin.cs | 4 ++-- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 +- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 2 +- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- osu.Game/Skinning/SkinInfo.cs | 4 ++-- osu.Game/Skinning/SkinManager.cs | 10 +++++----- osu.Game/Skinning/SkinnableSprite.cs | 2 +- .../{DefaultSkinTriangles.cs => TrianglesSkin.cs} | 10 +++++----- osu.Game/Tests/Visual/SkinnableTestScene.cs | 6 +++--- 15 files changed, 28 insertions(+), 28 deletions(-) rename osu.Game/Skinning/{DefaultSkinTriangles.cs => TrianglesSkin.cs} (95%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs index 06235d3744..a4b2b26624 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests }); } - private class TestSkin : DefaultSkinTriangles + private class TestSkin : TrianglesSkin { public bool FlipCatcherPlate { get; set; } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 9e02424eb8..e96a186ae4 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestDefaultSkin() { - AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged()); + AddStep("set default skin", () => skins.CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged()); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index d3f58ca868..57734236da 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = new SkinProvidingContainer(new DefaultSkinTriangles(null)) + Child = new SkinProvidingContainer(new TrianglesSkin(null)) { RelativeSizeAxes = Axes.Both, Child = drawableHitCircle = new DrawableHitCircle(hitCircle) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 57f2578200..d87bee0d05 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Skins.IO skinManager.CurrentSkinInfo.Value.PerformRead(s => { Assert.IsFalse(s.Protected); - Assert.AreEqual(typeof(DefaultSkinTriangles), s.CreateInstance(skinManager).GetType()); + Assert.AreEqual(typeof(TrianglesSkin), s.CreateInstance(skinManager).GetType()); new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream); @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Skins.IO { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); - Assert.AreEqual(typeof(DefaultSkinTriangles), s.CreateInstance(skinManager).GetType()); + Assert.AreEqual(typeof(TrianglesSkin), s.CreateInstance(skinManager).GetType()); }); return Task.CompletedTask; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index b24a65820d..01cc856a4a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEmptyLegacyBeatmapSkinFallsBack() { - CreateSkinTest(DefaultSkinTriangles.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null)); + CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null)); AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs index bc6458b62b..3757c49f18 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestEditDefaultSkin() { - AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.DEFAULT_SKIN_TRIANGLES); + AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.TRIANGLES_SKIN); AddStep("open settings", () => { Game.Settings.Show(); }); @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("open skin editor", () => skinEditor.Show()); // Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part). - AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.DEFAULT_SKIN_TRIANGLES); + AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.TRIANGLES_SKIN); AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected)); AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType().SingleOrDefault()?.Enabled.Value == true); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ec97508522..54c545e367 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Configuration { // UI/selection defaults SetDefault(OsuSetting.Ruleset, string.Empty); - SetDefault(OsuSetting.Skin, SkinInfo.DEFAULT_SKIN_TRIANGLES.ToString()); + SetDefault(OsuSetting.Skin, SkinInfo.TRIANGLES_SKIN.ToString()); SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); SetDefault(OsuSetting.BeatmapDetailModsFilter, false); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index f1a00dc958..8d491a980a 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Settings.Sections // In the future we should change this to properly handle ChangeSet events. dropdownItems.Clear(); - dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.DEFAULT_SKIN_TRIANGLES).ToLive(realm)); + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.TRIANGLES_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.CLASSIC_SKIN).ToLive(realm)); dropdownItems.Add(random_skin_info); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index e04e6d4849..dbc4e2b2e1 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Backgrounds case BackgroundSource.Skin: // default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them. - if (skin.Value is DefaultSkinTriangles || skin.Value is DefaultLegacySkin) + if (skin.Value is TrianglesSkin || skin.Value is DefaultLegacySkin) break; newBackground = new SkinBackground(skin.Value, getBackgroundTextureName()); diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 9ece48a6cb..4c35681f66 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Skinning } } - int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 9e268fd7ec..63f3fd12d1 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning [JsonObject(MemberSerialization.OptIn)] public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasGuidPrimaryKey, ISoftDelete, IHasNamedFiles { - internal static readonly Guid DEFAULT_SKIN_TRIANGLES = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); + internal static readonly Guid TRIANGLES_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); 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"); @@ -56,7 +56,7 @@ namespace osu.Game.Skinning // This defaults to triangles to catch the case where a user has a modified triangles skin. // If we ever add more default skins in the future this will need some kind of proper migration rather than // a single catch. - return new DefaultSkinTriangles(this, resources); + return new TrianglesSkin(this, resources); } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bce58424d4..20af72e4f6 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -49,9 +49,9 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); - public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged()) + public readonly Bindable> CurrentSkinInfo = new Bindable>(TrianglesSkin.CreateInfo().ToLiveUnmanaged()) { - Default = Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged() + Default = TrianglesSkin.CreateInfo().ToLiveUnmanaged() }; private readonly SkinImporter skinImporter; @@ -86,7 +86,7 @@ namespace osu.Game.Skinning var defaultSkins = new[] { DefaultLegacySkin = new DefaultLegacySkin(this), - DefaultSkinTriangles = new DefaultSkinTriangles(this), + DefaultSkinTriangles = new TrianglesSkin(this), }; // Ensure the default entries are present. @@ -125,7 +125,7 @@ namespace osu.Game.Skinning if (randomChoices.Length == 0) { - CurrentSkinInfo.Value = Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged(); + CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged(); return; } @@ -294,7 +294,7 @@ namespace osu.Game.Skinning Guid currentUserSkin = CurrentSkinInfo.Value.ID; if (items.Any(s => s.ID == currentUserSkin)) - scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkinTriangles.CreateInfo().ToLiveUnmanaged()); + scheduler.Add(() => CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged()); Delete(items.ToList(), silent); }); diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index cb4a91691e..69454de979 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -112,7 +112,7 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) - => skin.GetType() == typeof(DefaultSkinTriangles) + => skin.GetType() == typeof(TrianglesSkin) || skin.GetType() == typeof(DefaultLegacySkin) || skin.GetType() == typeof(LegacySkin); } diff --git a/osu.Game/Skinning/DefaultSkinTriangles.cs b/osu.Game/Skinning/TrianglesSkin.cs similarity index 95% rename from osu.Game/Skinning/DefaultSkinTriangles.cs rename to osu.Game/Skinning/TrianglesSkin.cs index 02df247c36..2c70963524 100644 --- a/osu.Game/Skinning/DefaultSkinTriangles.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -22,26 +22,26 @@ using osuTK.Graphics; namespace osu.Game.Skinning { - public class DefaultSkinTriangles : Skin + public class TrianglesSkin : Skin { public static SkinInfo CreateInfo() => new SkinInfo { - ID = osu.Game.Skinning.SkinInfo.DEFAULT_SKIN_TRIANGLES, + ID = osu.Game.Skinning.SkinInfo.TRIANGLES_SKIN, Name = "osu! \"triangles\" (2017)", Creator = "team osu!", Protected = true, - InstantiationInfo = typeof(DefaultSkinTriangles).GetInvariantInstantiationInfo() + InstantiationInfo = typeof(TrianglesSkin).GetInvariantInstantiationInfo() }; private readonly IStorageResourceProvider resources; - public DefaultSkinTriangles(IStorageResourceProvider resources) + public TrianglesSkin(IStorageResourceProvider resources) : this(CreateInfo(), resources) { } [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] - public DefaultSkinTriangles(SkinInfo skin, IStorageResourceProvider resources) + public TrianglesSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin, resources) { this.resources = resources; diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 21ecdf7dbe..0299a45238 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual public abstract class SkinnableTestScene : OsuGridTestScene, IStorageResourceProvider { private Skin metricsSkin; - private Skin defaultSkinTriangles; + private Skin trianglesSkin; private Skin specialSkin; private Skin oldSkin; @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual var dllStore = new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); - defaultSkinTriangles = new DefaultLegacySkin(this); + trianglesSkin = new DefaultLegacySkin(this); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore(dllStore, "Resources/special_skin"), this, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore(dllStore, "Resources/old_skin"), this, true); } @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual Cell(0).Child = createProvider(null, creationFunction, beatmap); Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap); - Cell(2).Child = createProvider(defaultSkinTriangles, creationFunction, beatmap); + Cell(2).Child = createProvider(trianglesSkin, creationFunction, beatmap); Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap); Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap); } From 9a10ecb3789630bd3715ce5e38ffd2cfe5bc1b24 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Sep 2022 19:37:30 +0300 Subject: [PATCH 206/583] Clarify purpose of `APIUserScoreAggregate` --- .../Online/API/Requests/Responses/APIUserScoreAggregate.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs index 30433ab8cd..c870157fec 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs @@ -10,6 +10,9 @@ using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { + /// + /// Represents an aggregate score for a user based off all beatmaps that have been played in the playlist. + /// public class APIUserScoreAggregate { [JsonProperty("attempts")] From 3c7ea5c8fadbc0348f5f84304c2872ff1d831a64 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Sat, 17 Sep 2022 18:57:09 +0200 Subject: [PATCH 207/583] Update --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 9e96a7386d..721f0c4e3b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); - float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); + float initialZoom = (float)(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom)); float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); float maximumZoom = getZoomLevelForVisibleMilliseconds(500); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 356276039c..16c0064e80 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -96,7 +96,6 @@ namespace osu.Game.Screens.Edit PlayableBeatmap.ControlPointInfo = ConvertControlPoints(PlayableBeatmap.ControlPointInfo); this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo; - if (this.beatmapInfo.TimelineZoom <= 0) this.beatmapInfo.TimelineZoom = 1; if (beatmapSkin is Skin skin) { From c8768eb39919c649556a4bb508f1bbde4863a765 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Sat, 17 Sep 2022 19:00:50 +0200 Subject: [PATCH 208/583] Changed == to <= on TimelineZoom check --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 721f0c4e3b..09fa2c8cee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); - float initialZoom = (float)(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom)); + float initialZoom = (float)(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom <= 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom)); float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); float maximumZoom = getZoomLevelForVisibleMilliseconds(500); From 9d94343317a00d8cf122b88baf7757cd4c584fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Vaj=C4=8F=C3=A1k?= Date: Sat, 17 Sep 2022 19:51:44 +0200 Subject: [PATCH 209/583] Update osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs Co-authored-by: Salman Ahmed --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 09fa2c8cee..721f0c4e3b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); - float initialZoom = (float)(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom <= 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom)); + float initialZoom = (float)(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom)); float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); float maximumZoom = getZoomLevelForVisibleMilliseconds(500); From c075d3de08accdd9f49d1e347a3aad4b6607a078 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Sat, 17 Sep 2022 22:43:40 +0200 Subject: [PATCH 210/583] Create a test and a check if initial zoom value of ZoomableScrollContainer is in range --- .../Editing/TestSceneZoomableScrollContainer.cs | 13 +++++++++++++ .../Components/Timeline/ZoomableScrollContainer.cs | 3 +++ 2 files changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index ce418f33f0..1858aee76b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -66,6 +67,18 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded); } + [Test] + public void TestInitialZoomOutOfRange() + { + AddStep("Invalid ZoomableScrollContainer throws ArgumentException", () => + { + Assert.Throws(() => + { + _ = new ZoomableScrollContainer(1, 60, 0); + }); + }); + } + [Test] public void TestWidthInitialization() { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 7d51284f46..0fb59a8a1f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -87,6 +87,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (minimum > maximum) throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})"); + if (initial < minimum || initial > maximum) + throw new ArgumentException($"{nameof(initial)} ({initial}) must be between {nameof(minimum)} ({minimum}) and {nameof(maximum)} ({maximum})"); + minZoom = minimum; maxZoom = maximum; CurrentZoom = zoomTarget = initial; From beef3b418ad1e1ba4023a55348992f69e40fbcde Mon Sep 17 00:00:00 2001 From: O Thiago Date: Sat, 17 Sep 2022 23:41:23 -0400 Subject: [PATCH 211/583] Simplifies size calculation --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 558605efc3..a34e55faae 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -50,6 +50,11 @@ namespace osu.Game.Rulesets.Mods where T : HitObject { public const double FLASHLIGHT_FADE_DURATION = 800; + + private const int flashlight_size_decrease_combo = 100; + private const int last_flashlight_size_decrease_combo = 200; + private const float decrease_flashlight_with_combo_multiplier = 0.1f; + protected readonly BindableInt Combo = new BindableInt(); public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) @@ -147,16 +152,12 @@ namespace osu.Game.Rulesets.Mods protected float GetSizeFor(int combo) { float size = defaultFlashlightSize * sizeMultiplier; + int comboForSize = Math.Min(combo, last_flashlight_size_decrease_combo); - if (comboBasedSize) - { - if (combo > 200) - size *= 0.8f; - else if (combo > 100) - size *= 0.9f; - } + if (!comboBasedSize) + comboForSize = 0; - return size; + return size * (1 - decrease_flashlight_with_combo_multiplier * MathF.Floor(MathF.Min(comboForSize, last_flashlight_size_decrease_combo) / flashlight_size_decrease_combo)); } private Vector2 flashlightPosition; From 8464a1941bef4c47a949a1c935bdc58aa2b8791f Mon Sep 17 00:00:00 2001 From: O Thiago Date: Sat, 17 Sep 2022 23:49:23 -0400 Subject: [PATCH 212/583] Use linq expression for handling breaks --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a34e55faae..f43f54541d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -132,13 +133,8 @@ namespace osu.Game.Rulesets.Mods using (BeginAbsoluteSequence(0)) { - foreach (var breakPeriod in Breaks) + foreach (BreakPeriod breakPeriod in Breaks.Where(breakPeriod => breakPeriod.HasEffect && breakPeriod.Duration >= FLASHLIGHT_FADE_DURATION * 2)) { - if (!breakPeriod.HasEffect) - continue; - - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); } From 3b87ecf56c0f473b7081bad10e715825becbc6b8 Mon Sep 17 00:00:00 2001 From: O Thiago Date: Sun, 18 Sep 2022 00:18:17 -0400 Subject: [PATCH 213/583] renames variables to make more logical sense --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index f43f54541d..4d24bdfa02 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -53,8 +53,8 @@ namespace osu.Game.Rulesets.Mods public const double FLASHLIGHT_FADE_DURATION = 800; private const int flashlight_size_decrease_combo = 100; - private const int last_flashlight_size_decrease_combo = 200; - private const float decrease_flashlight_with_combo_multiplier = 0.1f; + private const int flashlight_size_decrease_combo_max = 200; + private const float flashlight_size_decrease_with_combo_multiplier = 0.1f; protected readonly BindableInt Combo = new BindableInt(); @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Mods using (BeginAbsoluteSequence(0)) { - foreach (BreakPeriod breakPeriod in Breaks.Where(breakPeriod => breakPeriod.HasEffect && breakPeriod.Duration >= FLASHLIGHT_FADE_DURATION * 2)) + foreach (var breakPeriod in Breaks.Where(breakPeriod => breakPeriod.HasEffect && breakPeriod.Duration >= FLASHLIGHT_FADE_DURATION * 2)) { this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); @@ -148,12 +148,12 @@ namespace osu.Game.Rulesets.Mods protected float GetSizeFor(int combo) { float size = defaultFlashlightSize * sizeMultiplier; - int comboForSize = Math.Min(combo, last_flashlight_size_decrease_combo); + int comboForSize = Math.Min(combo, flashlight_size_decrease_combo_max); if (!comboBasedSize) comboForSize = 0; - return size * (1 - decrease_flashlight_with_combo_multiplier * MathF.Floor(MathF.Min(comboForSize, last_flashlight_size_decrease_combo) / flashlight_size_decrease_combo)); + return size * (1 - flashlight_size_decrease_with_combo_multiplier * MathF.Floor(MathF.Min(comboForSize, flashlight_size_decrease_combo_max) / flashlight_size_decrease_combo)); } private Vector2 flashlightPosition; From ac4229e3d4654b07758617e6c274d7cde550caad Mon Sep 17 00:00:00 2001 From: O Thiago Date: Sun, 18 Sep 2022 00:38:01 -0400 Subject: [PATCH 214/583] Removes overhead when not combo based size --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 4d24bdfa02..490f9120b1 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -148,12 +148,11 @@ namespace osu.Game.Rulesets.Mods protected float GetSizeFor(int combo) { float size = defaultFlashlightSize * sizeMultiplier; - int comboForSize = Math.Min(combo, flashlight_size_decrease_combo_max); - if (!comboBasedSize) - comboForSize = 0; + if (comboBasedSize) + size *= 1 - flashlight_size_decrease_with_combo_multiplier * MathF.Floor(MathF.Min(combo, flashlight_size_decrease_combo_max) / flashlight_size_decrease_combo); - return size * (1 - flashlight_size_decrease_with_combo_multiplier * MathF.Floor(MathF.Min(comboForSize, flashlight_size_decrease_combo_max) / flashlight_size_decrease_combo)); + return size; } private Vector2 flashlightPosition; From 2dcaf7cfd8ee9294cc6c7162d01520f24e9e0691 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 18 Sep 2022 14:25:31 +0900 Subject: [PATCH 215/583] Fix memory leak due to missing event unbind --- .../Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs index 1f80c47d13..5a297f18db 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs @@ -110,6 +110,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (Client != null) { Client.RoomUpdated -= invokeOnRoomUpdated; + Client.LoadRequested -= invokeOnRoomLoadRequested; Client.UserLeft -= invokeUserLeft; Client.UserKicked -= invokeUserKicked; Client.UserJoined -= invokeUserJoined; From d0b8409de5418fec79d0b21c64ce0d2c25ff3357 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Sep 2022 18:18:10 +0900 Subject: [PATCH 216/583] Apply more renames --- .../Skinning/Legacy/LegacyHitExplosion.cs | 2 +- .../TestSceneCursorParticles.cs | 2 +- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 2 +- .../Visual/Gameplay/TestSceneParticleSpewer.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 14 +++++++------- osu.Game/Tests/Visual/SkinnableTestScene.cs | 10 ++++++---- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs index 0630de9156..8f46bdbe6e 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy [BackgroundDependencyLoader] private void load(SkinManager skins) { - var defaultLegacySkin = skins.DefaultLegacySkin; + var defaultLegacySkin = skins.DefaultClassicSkin; // sprite names intentionally swapped to match stable member naming / ease of cross-referencing explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2"); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs index b5d1c4854c..7f0ecaca2b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("setup default legacy skin", () => { - skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo; + skinManager.CurrentSkinInfo.Value = skinManager.DefaultClassicSkin.SkinInfo; }); }); } diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index d87bee0d05..3cbc205eaf 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -226,7 +226,7 @@ namespace osu.Game.Tests.Skins.IO { var skinManager = osu.Dependencies.Get(); - skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo; + skinManager.CurrentSkinInfo.Value = skinManager.DefaultClassicSkin.SkinInfo; skinManager.EnsureMutableSkin(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index 26706d9465..66441c8bad 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay } private TestParticleSpewer createSpewer() => - new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) + new TestParticleSpewer(skinManager.DefaultClassicSkin.GetTexture("star2")) { Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 20af72e4f6..a9a01dfebf 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -59,14 +59,14 @@ namespace osu.Game.Skinning private readonly IResourceStore userFiles; /// - /// The default skin. + /// The default "triangles" skin. /// public Skin DefaultSkinTriangles { get; } /// - /// The default legacy skin. + /// The default "classic" skin. /// - public Skin DefaultLegacySkin { get; } + public Skin DefaultClassicSkin { get; } public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) : base(storage, realm) @@ -85,7 +85,7 @@ namespace osu.Game.Skinning var defaultSkins = new[] { - DefaultLegacySkin = new DefaultLegacySkin(this), + DefaultClassicSkin = new DefaultLegacySkin(this), DefaultSkinTriangles = new TrianglesSkin(this), }; @@ -229,8 +229,8 @@ namespace osu.Game.Skinning { yield return CurrentSkin.Value; - if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultLegacySkin) - yield return DefaultLegacySkin; + if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultClassicSkin) + yield return DefaultClassicSkin; if (CurrentSkin.Value != DefaultSkinTriangles) yield return DefaultSkinTriangles; @@ -310,7 +310,7 @@ namespace osu.Game.Skinning if (skinInfo == null) { if (guid == SkinInfo.CLASSIC_SKIN) - skinInfo = DefaultLegacySkin.SkinInfo; + skinInfo = DefaultClassicSkin.SkinInfo; } CurrentSkinInfo.Value = skinInfo ?? DefaultSkinTriangles.SkinInfo; diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 0299a45238..75224742a2 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -29,8 +29,9 @@ namespace osu.Game.Tests.Visual { public abstract class SkinnableTestScene : OsuGridTestScene, IStorageResourceProvider { + private TrianglesSkin trianglesSkin; private Skin metricsSkin; - private Skin trianglesSkin; + private Skin legacySkin; private Skin specialSkin; private Skin oldSkin; @@ -47,8 +48,9 @@ namespace osu.Game.Tests.Visual { var dllStore = new DllResourceStore(GetType().Assembly); + trianglesSkin = new TrianglesSkin(this); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); - trianglesSkin = new DefaultLegacySkin(this); + legacySkin = new DefaultLegacySkin(this); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore(dllStore, "Resources/special_skin"), this, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore(dllStore, "Resources/old_skin"), this, true); } @@ -61,9 +63,9 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmapForSkinProvider(); - Cell(0).Child = createProvider(null, creationFunction, beatmap); + Cell(0).Child = createProvider(trianglesSkin, creationFunction, beatmap); Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap); - Cell(2).Child = createProvider(trianglesSkin, creationFunction, beatmap); + Cell(2).Child = createProvider(legacySkin, creationFunction, beatmap); Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap); Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap); } From 6a06a5b1b5476deb96e8e405cbd83fef66071b48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Sep 2022 18:20:28 +0900 Subject: [PATCH 217/583] Reword fallback instantiation handling comment to hopefully explain things better --- osu.Game/Skinning/SkinInfo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 63f3fd12d1..34728245c8 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -53,7 +53,8 @@ namespace osu.Game.Skinning } catch { - // This defaults to triangles to catch the case where a user has a modified triangles skin. + // Since the class was renamed from "DefaultSkin" to "TrianglesSkin", the instantiation would fail + // for user modified skins. This aims to amicably handle that. // If we ever add more default skins in the future this will need some kind of proper migration rather than // a single catch. return new TrianglesSkin(this, resources); From 0ac28cbecc2c58d4acdb614fe291b42bf3d291b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Sep 2022 23:42:16 +0900 Subject: [PATCH 218/583] Fix potential crash from missing `DownloadProgress` --- .../Multiplayer/Participants/StateDisplay.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index 9c05c19d1b..ecef7509d9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -25,9 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { private const double fade_time = 50; - private SpriteIcon icon; - private OsuSpriteText text; - private ProgressBar progressBar; + private SpriteIcon icon = null!; + private OsuSpriteText text = null!; + private ProgressBar progressBar = null!; public StateDisplay() { @@ -86,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants }; } - private OsuColour colours; + private OsuColour colours = null!; public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability) { @@ -164,10 +161,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants break; case DownloadState.Downloading: - Debug.Assert(availability.DownloadProgress != null); - progressBar.FadeIn(fade_time); - progressBar.CurrentTime = availability.DownloadProgress.Value; + progressBar.CurrentTime = availability.DownloadProgress ?? 0; text.Text = "downloading map"; icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; From 6d7d80a4fbb09f2f89d82bfc3366b71d3d06b259 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Sun, 18 Sep 2022 17:29:08 +0200 Subject: [PATCH 219/583] Made X position value of CatchHitObject clamp to CatchPlayfield's width --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 6e01c44e1f..c6d63f6b81 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects [JsonIgnore] public float X { - set => originalX.Value = value; + set => originalX.Value = Math.Clamp(value, 0, CatchPlayfield.WIDTH); } private HitObjectProperty xOffset; From 493efd84a3900f4c439887f5db931fb699d143f7 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 12:08:34 -0700 Subject: [PATCH 220/583] Basic smoke path implementation --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 5 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/Default/DefaultSmoke.cs | 14 ++ .../Skinning/Legacy/LegacySmoke.cs | 26 ++++ .../Legacy/OsuLegacySkinTransformer.cs | 6 + osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 131 ++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 + osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 57 ++++++++ 9 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Smoke.cs create mode 100644 osu.Game.Rulesets.Osu/UI/SmokeContainer.cs diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 12256e93d0..dec965e567 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -80,6 +80,9 @@ namespace osu.Game.Rulesets.Osu LeftButton, [Description("Right button")] - RightButton + RightButton, + + [Description("Smoke")] + Smoke, } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 302194e91a..76465fe3d5 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Osu { new KeyBinding(InputKey.Z, OsuAction.LeftButton), new KeyBinding(InputKey.X, OsuAction.RightButton), + new KeyBinding(InputKey.C, OsuAction.Smoke), new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index fcf079b6aa..11daa26072 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, + Smoke, ApproachCircle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs new file mode 100644 index 0000000000..3c798c2afa --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultSmoke : Smoke + { + public DefaultSmoke() + { + Texture = null; + PathRadius = 2; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs new file mode 100644 index 0000000000..9494d31a63 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacySmoke : Smoke + { + private ISkin skin; + + public LegacySmoke(ISkin skin) + { + this.skin = skin; + + PathRadius = 8; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Texture = skin.GetTexture("cursor-smoke"); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 885a2c12fb..a9186f821e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -106,6 +106,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; + case OsuSkinComponents.Smoke: + if (GetTexture("cursor-smoke") != null) + return new LegacySmoke(this); + + return null; + case OsuSkinComponents.HitCircleText: if (!this.HasFont(LegacyFont.HitCircle)) return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs new file mode 100644 index 0000000000..6c60392c94 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -0,0 +1,131 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Lines; +using osu.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public abstract class Smoke : TexturedPath + { + protected bool IsActive { get; private set; } + protected double SmokeTimeStart { get; private set; } = double.MinValue; + protected double SmokeTimeEnd { get; private set; } = double.MinValue; + + protected readonly List SmokeVertexPositions = new List(); + protected readonly List SmokeVertexTimes = new List(); + + [Resolved(CanBeNull = true)] + private SmokeContainer? smokeContainer { get; set; } + + protected struct SmokePoint + { + public Vector2 Position; + public double Time; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (smokeContainer != null) + { + smokeContainer.SmokeMoved += guardOnSmokeMoved; + smokeContainer.SmokeEnded += guardOnSmokeEnded; + IsActive = true; + } + + Anchor = Anchor.TopLeft; + Origin = Anchor.TopLeft; + + SmokeTimeStart = Time.Current; + } + + private void guardOnSmokeMoved(Vector2 position, double time) + { + if (IsActive) + OnSmokeMoved(position, time); + } + + private void guardOnSmokeEnded(double time) + { + if (IsActive) + OnSmokeEnded(time); + } + + protected virtual void OnSmokeMoved(Vector2 position, double time) + { + addSmokeVertex(position, time); + } + + private void addSmokeVertex(Vector2 position, double time) + { + Debug.Assert(SmokeVertexTimes.Count == SmokeVertexPositions.Count); + + if (SmokeVertexTimes.Count > 0 && SmokeVertexTimes.Last() > time) + { + int index = ~SmokeVertexTimes.BinarySearch(time, new UpperBoundComparer()); + + SmokeVertexTimes.RemoveRange(index, SmokeVertexTimes.Count - index); + SmokeVertexPositions.RemoveRange(index, SmokeVertexPositions.Count - index); + } + + SmokeVertexTimes.Add(time); + SmokeVertexPositions.Add(position); + } + + protected virtual void OnSmokeEnded(double time) + { + IsActive = false; + SmokeTimeEnd = time; + } + + protected override void Update() + { + base.Update(); + + const double visible_duration = 8000; + const float disappear_speed = 3; + + int index = 0; + if (!IsActive) + { + double cutoffTime = SmokeTimeStart + disappear_speed * (Time.Current - (SmokeTimeEnd + visible_duration)); + index = ~SmokeVertexTimes.BinarySearch(cutoffTime, new UpperBoundComparer()); + } + Vertices = new List(SmokeVertexPositions.Skip(index)); + + Position = -PositionInBoundingBox(Vector2.Zero); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (smokeContainer != null) + { + smokeContainer.SmokeMoved -= guardOnSmokeMoved; + smokeContainer.SmokeEnded -= guardOnSmokeEnded; + } + } + + private struct UpperBoundComparer : IComparer + { + public int Compare(double x, double target) + { + // By returning -1 when the target value is equal to x, guarantees that the + // element at BinarySearch's returned index will always be the first element + // larger. Since 0 is never returned, the target is never "found", so the return + // value will be the index's complement. + + return x > target ? 1 : -1; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index fc2ba8ea2f..8085c07912 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.UI public class OsuPlayfield : Playfield { private readonly PlayfieldBorder playfieldBorder; + private readonly SmokeContainer smokeContainer; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; @@ -54,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI InternalChildren = new Drawable[] { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, + smokeContainer = new SmokeContainer { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs new file mode 100644 index 0000000000..865204f8cc --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -0,0 +1,57 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI +{ + [Cached] + public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler + { + public event Action? SmokeMoved; + public event Action? SmokeEnded; + + private bool isSmoking = false; + + public override bool ReceivePositionalInputAt(Vector2 _) => true; + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == OsuAction.Smoke) + { + isSmoking = true; + AddInternal(new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Smoke), _ => new DefaultSmoke())); + + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == OsuAction.Smoke) + { + isSmoking = false; + SmokeEnded?.Invoke(Time.Current); + } + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (isSmoking) + SmokeMoved?.Invoke(e.MousePosition, Time.Current); + + return base.OnMouseMove(e); + } + } +} From 613564b5b9ecdb56953aa093982b3c22a565f8e4 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 12:10:01 -0700 Subject: [PATCH 221/583] Full legacy smoke implementation and temp default smoke --- .../Skinning/Default/DefaultSmoke.cs | 51 +- .../Skinning/Legacy/LegacySmoke.cs | 93 +++- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 443 +++++++++++++++--- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 1 - 4 files changed, 521 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index 3c798c2afa..ecba9d4904 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -1,14 +1,61 @@ // 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Rendering; +using osuTK.Graphics; + namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultSmoke : Smoke { + private const double fade_out_delay = 8000; + private const double fade_out_speed = 3; + private const double fade_out_duration = 50; + private const float alpha = 0.5f; + + protected override double LifetimeAfterSmokeEnd => fade_out_delay + fade_out_duration + (SmokeEndTime - SmokeStartTime) / fade_out_speed; + public DefaultSmoke() { - Texture = null; - PathRadius = 2; + Radius = 2; + } + + protected override DrawNode CreateDrawNode() => new DefaultSmokeDrawNode(this); + + private class DefaultSmokeDrawNode : SmokeDrawNode + { + private double fadeOutTime; + + public DefaultSmokeDrawNode(ITexturedShaderDrawable source) + : base(source) + { + } + + protected override void UpdateDrawVariables(IRenderer renderer) + { + base.UpdateDrawVariables(renderer); + + fadeOutTime = SmokeStartTime + fade_out_speed * (CurrentTime - (SmokeEndTime + fade_out_delay)); + } + + protected override Color4 ColorAtTime(double pointTime) + { + var color = Color4.White; + color.A = alpha; + + double timeDoingFadeOut = fadeOutTime - pointTime; + if (timeDoingFadeOut > 0) + { + float fraction = Math.Clamp((float)(1 - (timeDoingFadeOut / fade_out_duration)), 0, 1); + fraction = MathF.Pow(fraction, 5); + color.A *= fraction; + } + + return color; + } } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index 9494d31a63..6c2c80f746 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -1,19 +1,42 @@ // 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Rendering; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacySmoke : Smoke { + private const double initial_fade_out_duration = 2500; + + private const double re_fade_in_speed = 3; + private const double re_fade_in_duration = 50; + + private const double final_fade_out_duration = 7500; + + private const float initial_alpha = 0.8f; + private const float re_fade_in_alpha = 1.4f; + + protected override double LifetimeAfterSmokeEnd + { + get + { + double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + return final_fade_out_duration + initialFadeOutDurationTrunc * (1 + re_fade_in_speed); + } + } + private ISkin skin; public LegacySmoke(ISkin skin) { this.skin = skin; - - PathRadius = 8; + Radius = 3; } protected override void LoadComplete() @@ -22,5 +45,71 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Texture = skin.GetTexture("cursor-smoke"); } + + protected override DrawNode CreateDrawNode() => new LegacySmokeDrawNode(this); + + protected class LegacySmokeDrawNode : SmokeDrawNode + { + private double initialFadeOutDurationTrunc; + private double initialFadeOutTime; + private double reFadeInTime; + private double finalFadeOutTime; + + public LegacySmokeDrawNode(ITexturedShaderDrawable source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + } + + protected override void UpdateDrawVariables(IRenderer renderer) + { + base.UpdateDrawVariables(renderer); + + initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime); + reFadeInTime = re_fade_in_speed * (CurrentTime - SmokeEndTime) + SmokeEndTime - initialFadeOutDurationTrunc; + finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc * (1 + 1 / re_fade_in_speed); + } + + protected override Color4 ColorAtTime(double pointTime) + { + var color = Color4.White; + + double timeDoingInitialFadeOut = initialFadeOutTime - pointTime; + + if (timeDoingInitialFadeOut > 0) + { + float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1); + fraction = MathF.Pow(fraction, 5); + color.A = (1 - fraction) * initial_alpha; + } + + if (color.A > 0) + { + double timeDoingReFadeIn = reFadeInTime - pointTime; + double timeDoingFinalFadeOut = finalFadeOutTime - pointTime; + + if (timeDoingFinalFadeOut > 0) + { + float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1); + fraction = MathF.Pow(fraction, 5); + color.A = (1 - fraction) * re_fade_in_alpha; + } + else if (timeDoingReFadeIn > 0) + { + float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1); + fraction = 1 - MathF.Pow(1 - fraction, 5); + color.A = fraction * (re_fade_in_alpha - color.A) + color.A; + } + } + + return color; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 6c60392c94..52de537098 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -1,33 +1,175 @@ // 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.Diagnostics; -using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Rendering.Vertices; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { - public abstract class Smoke : TexturedPath + public abstract class Smoke : Drawable, ITexturedShaderDrawable { - protected bool IsActive { get; private set; } - protected double SmokeTimeStart { get; private set; } = double.MinValue; - protected double SmokeTimeEnd { get; private set; } = double.MinValue; + public IShader? TextureShader { get; private set; } + public IShader? RoundedTextureShader { get; private set; } - protected readonly List SmokeVertexPositions = new List(); - protected readonly List SmokeVertexTimes = new List(); + private float radius = 1; + protected float Radius + { + get => radius; + set + { + if (radius == value) + return; + + radius = value; + Invalidate(Invalidation.DrawNode); + } + } + + + private int rotationSeed = RNG.Next(); + protected int RotationSeed + { + get => rotationSeed; + set + { + if (rotationSeed == value) + return; + + rotationSeed = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Texture? texture; + protected Texture? Texture + { + get => texture; + set + { + texture = value; + Invalidate(Invalidation.DrawNode); + } + } + + private double smokeTimeStart = double.MinValue; + protected double SmokeStartTime + { + get => smokeTimeStart; + private set + { + if (smokeTimeStart == value) + return; + + smokeTimeStart = value; + Invalidate(Invalidation.DrawNode); + } + } + + private double smokeTimeEnd = double.MaxValue; + protected double SmokeEndTime + { + get => smokeTimeEnd; + private set + { + if (smokeTimeEnd == value) + return; + + smokeTimeEnd = value; + Invalidate(Invalidation.DrawNode); + } + } + + public override IFrameBasedClock Clock + { + get => base.Clock; + set + { + base.Clock = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Vector2 topLeft; + protected Vector2 TopLeft + { + get => topLeft; + set + { + if (topLeft == value) + return; + + topLeft = value; + Invalidate(Invalidation.All); + } + } + + private Vector2 bottomRight; + protected Vector2 BottomRight + { + get => bottomRight; + set + { + if (bottomRight == value) + return; + + bottomRight = value; + Invalidate(Invalidation.Layout); + } + } + + protected abstract double LifetimeAfterSmokeEnd { get; } + protected virtual float PointInterval => Radius * 7f / 8; + protected bool IsActive { get; private set; } + + protected readonly List SmokePoints = new List(); + + private float totalDistance; + private Vector2? lastPosition = null; + + private const double max_duration = 60_000; + + public override float Height + { + get => base.Height = BottomRight.Y - TopLeft.Y; + set => throw new InvalidOperationException($"Cannot manually set {nameof(Height)} of {nameof(Smoke)}."); + } + + public override float Width + { + get => base.Width = BottomRight.X - TopLeft.X; + set => throw new InvalidOperationException($"Cannot manually set {nameof(Width)} of {nameof(Smoke)}."); + } + + public override Vector2 Size + { + get => base.Size = BottomRight - TopLeft; + set => throw new InvalidOperationException($"Cannot manually set {nameof(Size)} of {nameof(Smoke)}."); + } [Resolved(CanBeNull = true)] private SmokeContainer? smokeContainer { get; set; } - protected struct SmokePoint + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) { - public Vector2 Position; - public double Time; + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); } protected override void LoadComplete() @@ -36,72 +178,107 @@ namespace osu.Game.Rulesets.Osu.Skinning if (smokeContainer != null) { - smokeContainer.SmokeMoved += guardOnSmokeMoved; - smokeContainer.SmokeEnded += guardOnSmokeEnded; + smokeContainer.SmokeMoved += onSmokeMoved; + smokeContainer.SmokeEnded += onSmokeEnded; IsActive = true; } Anchor = Anchor.TopLeft; Origin = Anchor.TopLeft; - SmokeTimeStart = Time.Current; + SmokeStartTime = Time.Current; } - private void guardOnSmokeMoved(Vector2 position, double time) + private void onSmokeMoved(Vector2 position, double time) { - if (IsActive) - OnSmokeMoved(position, time); - } + if (!IsActive) + return; - private void guardOnSmokeEnded(double time) - { - if (IsActive) - OnSmokeEnded(time); - } + lastPosition ??= position; - protected virtual void OnSmokeMoved(Vector2 position, double time) - { - addSmokeVertex(position, time); - } + float delta = (position - (Vector2)lastPosition).LengthFast; + totalDistance += delta; + int count = (int)(totalDistance / PointInterval); - private void addSmokeVertex(Vector2 position, double time) - { - Debug.Assert(SmokeVertexTimes.Count == SmokeVertexPositions.Count); - - if (SmokeVertexTimes.Count > 0 && SmokeVertexTimes.Last() > time) + if (count > 0) { - int index = ~SmokeVertexTimes.BinarySearch(time, new UpperBoundComparer()); + Vector2 increment = position - (Vector2)lastPosition; + increment.NormalizeFast(); - SmokeVertexTimes.RemoveRange(index, SmokeVertexTimes.Count - index); - SmokeVertexPositions.RemoveRange(index, SmokeVertexPositions.Count - index); + Vector2 pointPos = (PointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition; + increment *= PointInterval; + + if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time) + { + int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer()); + SmokePoints.RemoveRange(index, SmokePoints.Count - index); + recalculateBounds(); + } + + totalDistance %= PointInterval; + for (int i = 0; i < count; i++) + { + SmokePoints.Add(new SmokePoint + { + Position = pointPos, + Time = time, + }); + + pointPos += increment; + } + + Invalidate(Invalidation.DrawNode); + adaptBounds(position); } - SmokeVertexTimes.Add(time); - SmokeVertexPositions.Add(position); + lastPosition = position; + + if (time - SmokeStartTime > max_duration) + onSmokeEnded(time); } - protected virtual void OnSmokeEnded(double time) + private void recalculateBounds() { - IsActive = false; - SmokeTimeEnd = time; + TopLeft = BottomRight = Vector2.Zero; + + foreach (var point in SmokePoints) + adaptBounds(point.Position); } + private void adaptBounds(Vector2 position) + { + if (position.X < TopLeft.X) + TopLeft = new Vector2(position.X, TopLeft.Y); + else if (position.X > BottomRight.X) + BottomRight = new Vector2(position.X, BottomRight.Y); + + if (position.Y < TopLeft.Y) + TopLeft = new Vector2(TopLeft.X, position.Y); + else if (position.Y > BottomRight.Y) + BottomRight = new Vector2(BottomRight.X, position.Y); + } + + private void onSmokeEnded(double time) + { + if (!IsActive) + return; + + IsActive = false; + SmokeEndTime = time; + LifetimeEnd = time + LifetimeAfterSmokeEnd + 100; + + // TODO: HYPER MEGA JANK WTF?? + if (Parent is SkinnableDrawable) + Parent.LifetimeEnd = LifetimeEnd; + } + + protected abstract override DrawNode CreateDrawNode(); + protected override void Update() { base.Update(); - const double visible_duration = 8000; - const float disappear_speed = 3; - - int index = 0; - if (!IsActive) - { - double cutoffTime = SmokeTimeStart + disappear_speed * (Time.Current - (SmokeTimeEnd + visible_duration)); - index = ~SmokeVertexTimes.BinarySearch(cutoffTime, new UpperBoundComparer()); - } - Vertices = new List(SmokeVertexPositions.Skip(index)); - - Position = -PositionInBoundingBox(Vector2.Zero); + Position = TopLeft; } protected override void Dispose(bool isDisposing) @@ -110,21 +287,163 @@ namespace osu.Game.Rulesets.Osu.Skinning if (smokeContainer != null) { - smokeContainer.SmokeMoved -= guardOnSmokeMoved; - smokeContainer.SmokeEnded -= guardOnSmokeEnded; + smokeContainer.SmokeMoved -= onSmokeMoved; + smokeContainer.SmokeEnded -= onSmokeEnded; } } - private struct UpperBoundComparer : IComparer + protected struct SmokePoint { - public int Compare(double x, double target) - { - // By returning -1 when the target value is equal to x, guarantees that the - // element at BinarySearch's returned index will always be the first element - // larger. Since 0 is never returned, the target is never "found", so the return - // value will be the index's complement. + public Vector2 Position; + public double Time; - return x > target ? 1 : -1; + public struct UpperBoundComparer : IComparer + { + public int Compare(SmokePoint x, SmokePoint target) + { + // By returning -1 when the target value is equal to x, guarantees that the + // element at BinarySearch's returned index will always be the first element + // larger. Since 0 is never returned, the target is never "found", so the return + // value will be the index's complement. + + return x.Time > target.Time ? 1 : -1; + } + } + } + + protected abstract class SmokeDrawNode : TexturedShaderDrawNode + { + protected new Smoke Source => (Smoke)base.Source; + + protected IVertexBatch? QuadBatch; + + protected readonly List Points = new List(); + + protected float Radius; + protected Vector2 DrawSize; + protected Vector2 PositionOffset; + protected Texture? Texture; + + protected double SmokeStartTime; + protected double SmokeEndTime; + protected double CurrentTime; + + protected RectangleF TextureRect; + + private IFrameBasedClock? clock; + + private int rotationSeed; + private Random rotationRNG = new Random(); + + public SmokeDrawNode(ITexturedShaderDrawable source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + Points.Clear(); + Points.AddRange(Source.SmokePoints); + + Radius = Source.Radius; + DrawSize = Source.DrawSize; + PositionOffset = Source.TopLeft; + Texture = Source.Texture; + clock = Source.Clock; + + SmokeStartTime = Source.SmokeStartTime; + SmokeEndTime = Source.SmokeEndTime; + + rotationSeed = Source.RotationSeed; + } + + public sealed override void Draw(IRenderer renderer) + { + base.Draw(renderer); + + if (Points.Count == 0) + return; + + QuadBatch ??= renderer.CreateQuadBatch(7200, 10); + Texture ??= renderer.WhitePixel; + + var shader = GetAppropriateShader(renderer); + shader.Bind(); + Texture.Bind(); + + UpdateDrawVariables(renderer); + UpdateVertexBuffer(); + + shader.Unbind(); + } + + protected Color4 ColorAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour + ? ((SRGBColour)DrawColourInfo.Colour).Linear + : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, DrawSize)).Linear; + + protected abstract Color4 ColorAtTime(double pointTime); + + protected virtual void UpdateDrawVariables(IRenderer renderer) + { + Debug.Assert(clock != null); + Debug.Assert(Texture != null); + + CurrentTime = clock.CurrentTime; + TextureRect = Texture.GetTextureRect(); + rotationRNG = new Random(rotationSeed); + } + + protected virtual void UpdateVertexBuffer() + { + foreach (var point in Points) + drawPointQuad(point); + } + + private Vector2 nextTextureDirection() + { + float angle = (float)rotationRNG.NextDouble() * 2 * MathF.PI; + return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); + } + + private void drawPointQuad(SmokePoint point) + { + Debug.Assert(QuadBatch != null); + + var color = ColorAtTime(point.Time); + var dir = nextTextureDirection(); + var ortho = dir.PerpendicularLeft; + + var localTopLeft = point.Position + (Radius * (-ortho - dir)) - PositionOffset; + var localTopRight = point.Position + (Radius * (-ortho + dir)) - PositionOffset; + var localBotLeft = point.Position + (Radius * (ortho - dir)) - PositionOffset; + var localBotRight = point.Position + (Radius * (ortho + dir)) - PositionOffset; + + QuadBatch.Add(new TexturedVertex2D + { + Position = Vector2Extensions.Transform(localTopLeft, DrawInfo.Matrix), + TexturePosition = TextureRect.TopLeft, + Colour = Color4Extensions.Multiply(ColorAtPosition(localTopLeft), color), + }); + QuadBatch.Add(new TexturedVertex2D + { + Position = Vector2Extensions.Transform(localTopRight, DrawInfo.Matrix), + TexturePosition = TextureRect.TopRight, + Colour = Color4Extensions.Multiply(ColorAtPosition(localTopRight), color), + }); + QuadBatch.Add(new TexturedVertex2D + { + Position = Vector2Extensions.Transform(localBotRight, DrawInfo.Matrix), + TexturePosition = TextureRect.BottomRight, + Colour = Color4Extensions.Multiply(ColorAtPosition(localBotRight), color), + }); + QuadBatch.Add(new TexturedVertex2D + { + Position = Vector2Extensions.Transform(localBotLeft, DrawInfo.Matrix), + TexturePosition = TextureRect.BottomLeft, + Colour = Color4Extensions.Multiply(ColorAtPosition(localBotLeft), color), + }); } } } diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 865204f8cc..b8897f2cf1 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; From ca9fe9f24000b40ef60422867d82ed25b0fd6e16 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Sun, 18 Sep 2022 22:05:09 +0200 Subject: [PATCH 222/583] Fixed TestCatcherCatchWidth failing --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 956d0e0c14..11fa751daf 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -108,14 +108,14 @@ namespace osu.Game.Rulesets.Catch.Tests float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; AddStep("catch fruit", () => { - attemptCatch(new Fruit { X = -halfWidth + 1 }); - attemptCatch(new Fruit { X = halfWidth - 1 }); + attemptCatch(new Fruit { OriginalX = -halfWidth + 1 }); + attemptCatch(new Fruit { OriginalX = halfWidth - 1 }); }); checkPlate(2); AddStep("miss fruit", () => { - attemptCatch(new Fruit { X = -halfWidth - 1 }); - attemptCatch(new Fruit { X = halfWidth + 1 }); + attemptCatch(new Fruit { OriginalX = -halfWidth - 1 }); + attemptCatch(new Fruit { OriginalX = halfWidth + 1 }); }); checkPlate(2); } From 4263933c9e4431afd2c085a7d40aa256c241eb22 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 18 Sep 2022 23:43:14 +0200 Subject: [PATCH 223/583] Add settings `AngleSharpness` and `Angle variety` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 52 +++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 056a325dce..fca42dedee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,9 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -25,6 +28,26 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); + [SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider))] + public BindableFloat AngleSharpness { get; } = new BindableFloat + { + Default = 7, + Value = 7, + MinValue = 1, + MaxValue = 10, + Precision = 0.1f + }; + + [SettingSource("Angle variety", "The amount of variety in how sharp angles are", SettingControlType = typeof(SettingsSlider))] + public BindableFloat AngleVariety { get; } = new BindableFloat + { + Default = 3, + Value = 3, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f + }; + private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; private Random random = null!; @@ -50,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (shouldStartNewSection(osuBeatmap, positionInfos, i)) { - sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f); + sectionOffset = getRandomOffset(0.0008f); flowDirection = !flowDirection; } @@ -65,11 +88,11 @@ namespace osu.Game.Rulesets.Osu.Mods float flowChangeOffset = 0; // Offsets only the angle of the current hit object. - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f); + float oneTimeOffset = getRandomOffset(0.002f); if (shouldApplyFlowChange(positionInfos, i)) { - flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f); + flowChangeOffset = getRandomOffset(0.002f); flowDirection = !flowDirection; } @@ -86,13 +109,32 @@ namespace osu.Game.Rulesets.Osu.Mods osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); } + private float getRandomOffset(float stdDev) + { + float customMultiplier = AngleVariety.Value / AngleVariety.Default; + return OsuHitObjectGenerationUtils.RandomGaussian(random, 0, stdDev * customMultiplier); + } + /// The target distance between the previous and the current . /// The angle (in rad) by which the target angle should be offset. /// Whether the relative angle should be positive or negative. - private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) + private float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset); + // Range 0..1 + float angleSharpness = AngleSharpness.Value / AngleSharpness.MaxValue; + float angleWideness = 1 - angleSharpness; + + // Range: -70..30 + float customOffsetX = angleSharpness * 100 - 70; + // Range: -0.075..0.175 + float customOffsetY = angleWideness * 0.25f - 0.075f; + + targetDistance += customOffsetX; + float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310 + customOffsetX))) + 0.5); + angle += offset + customOffsetY; + float relativeAngle = (float)Math.PI - angle; + return flowDirection ? -relativeAngle : relativeAngle; } From 9eb0a21d753b80082937d6abe82ae9c4d8be5ce6 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 19 Sep 2022 00:45:23 +0200 Subject: [PATCH 224/583] Correct comments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index fca42dedee..c829d1f696 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -120,13 +120,14 @@ namespace osu.Game.Rulesets.Osu.Mods /// Whether the relative angle should be positive or negative. private float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - // Range 0..1 + // Range [0.1;1] float angleSharpness = AngleSharpness.Value / AngleSharpness.MaxValue; + // Range [0;0.9] float angleWideness = 1 - angleSharpness; - // Range: -70..30 + // Range: [-60;30] float customOffsetX = angleSharpness * 100 - 70; - // Range: -0.075..0.175 + // Range: [-0.075;0.15] float customOffsetY = angleWideness * 0.25f - 0.075f; targetDistance += customOffsetX; From 407b104116c45f99d21d9cd9bde842d5984b8178 Mon Sep 17 00:00:00 2001 From: O Thiago Date: Sun, 18 Sep 2022 18:55:47 -0400 Subject: [PATCH 225/583] Revert changes This reverts commit beef3b418ad1e1ba4023a55348992f69e40fbcde. Revert "Use linq expression for handling breaks" This reverts commit 8464a1941bef4c47a949a1c935bdc58aa2b8791f. Revert "renames variables to make more logical sense" This reverts commit 3b87ecf56c0f473b7081bad10e715825becbc6b8. Revert "Removes overhead when not combo based size" This reverts commit ac4229e3d4654b07758617e6c274d7cde550caad. --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 490f9120b1..558605efc3 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -51,11 +50,6 @@ namespace osu.Game.Rulesets.Mods where T : HitObject { public const double FLASHLIGHT_FADE_DURATION = 800; - - private const int flashlight_size_decrease_combo = 100; - private const int flashlight_size_decrease_combo_max = 200; - private const float flashlight_size_decrease_with_combo_multiplier = 0.1f; - protected readonly BindableInt Combo = new BindableInt(); public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) @@ -133,8 +127,13 @@ namespace osu.Game.Rulesets.Mods using (BeginAbsoluteSequence(0)) { - foreach (var breakPeriod in Breaks.Where(breakPeriod => breakPeriod.HasEffect && breakPeriod.Duration >= FLASHLIGHT_FADE_DURATION * 2)) + foreach (var breakPeriod in Breaks) { + if (!breakPeriod.HasEffect) + continue; + + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); } @@ -150,7 +149,12 @@ namespace osu.Game.Rulesets.Mods float size = defaultFlashlightSize * sizeMultiplier; if (comboBasedSize) - size *= 1 - flashlight_size_decrease_with_combo_multiplier * MathF.Floor(MathF.Min(combo, flashlight_size_decrease_combo_max) / flashlight_size_decrease_combo); + { + if (combo > 200) + size *= 0.8f; + else if (combo > 100) + size *= 0.9f; + } return size; } From a4e9c7711c4b042101d6499a8b30d5fe27bf567c Mon Sep 17 00:00:00 2001 From: O Thiago Date: Sun, 18 Sep 2022 18:59:17 -0400 Subject: [PATCH 226/583] Fix when flashlight shrinking takes place --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 558605efc3..6d7706cde2 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -150,9 +150,9 @@ namespace osu.Game.Rulesets.Mods if (comboBasedSize) { - if (combo > 200) + if (combo >= 200) size *= 0.8f; - else if (combo > 100) + else if (combo >= 100) size *= 0.9f; } From 06178104c8953a059f5f26b3b04a5e8978ed551b Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 17:55:06 -0700 Subject: [PATCH 227/583] Show smoke in replays --- osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs | 3 +++ osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 85060261fe..8082c5aef4 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Replays Position = currentFrame.Position; if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); + if (currentFrame.Smoke) Actions.Add(OsuAction.Smoke); } public LegacyReplayFrame ToLegacy(IBeatmap beatmap) @@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Replays state |= ReplayButtonState.Left1; if (Actions.Contains(OsuAction.RightButton)) state |= ReplayButtonState.Right1; + if (Actions.Contains(OsuAction.Smoke)) + state |= ReplayButtonState.Smoke; return new LegacyReplayFrame(Time, Position.X, Position.Y, state); } diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index f6abf259e8..f345504ca1 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -46,6 +46,10 @@ namespace osu.Game.Replays.Legacy [IgnoreMember] public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2); + [JsonIgnore] + [IgnoreMember] + public bool Smoke => ButtonState.HasFlagFast(ReplayButtonState.Smoke); + [Key(3)] public ReplayButtonState ButtonState; From 0138663bdcb3d534dd892a68f545f310eb2dfe4d Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 18:32:33 -0700 Subject: [PATCH 228/583] Fix InspectCode errors --- .../Skinning/Default/DefaultSmoke.cs | 2 +- .../Skinning/Legacy/LegacySmoke.cs | 3 +-- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 15 +++++++++++---- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +-- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index ecba9d4904..e7f526da31 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Rendering; using osuTK.Graphics; @@ -47,6 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default color.A = alpha; double timeDoingFadeOut = fadeOutTime - pointTime; + if (timeDoingFadeOut > 0) { float fraction = Math.Clamp((float)(1 - (timeDoingFadeOut / fade_out_duration)), 0, 1); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index 6c2c80f746..23ab2d186f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Rendering; using osu.Game.Skinning; @@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } - private ISkin skin; + private readonly ISkin skin; public LegacySmoke(ISkin skin) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 52de537098..382e54e2ab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public IShader? RoundedTextureShader { get; private set; } private float radius = 1; + protected float Radius { get => radius; @@ -41,8 +42,8 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - private int rotationSeed = RNG.Next(); + protected int RotationSeed { get => rotationSeed; @@ -57,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } private Texture? texture; + protected Texture? Texture { get => texture; @@ -68,6 +70,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } private double smokeTimeStart = double.MinValue; + protected double SmokeStartTime { get => smokeTimeStart; @@ -82,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } private double smokeTimeEnd = double.MaxValue; + protected double SmokeEndTime { get => smokeTimeEnd; @@ -106,6 +110,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } private Vector2 topLeft; + protected Vector2 TopLeft { get => topLeft; @@ -115,11 +120,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return; topLeft = value; - Invalidate(Invalidation.All); + Invalidate(); } } private Vector2 bottomRight; + protected Vector2 BottomRight { get => bottomRight; @@ -140,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected readonly List SmokePoints = new List(); private float totalDistance; - private Vector2? lastPosition = null; + private Vector2? lastPosition; private const double max_duration = 60_000; @@ -216,6 +222,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } totalDistance %= PointInterval; + for (int i = 0; i < count; i++) { SmokePoints.Add(new SmokePoint @@ -335,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private int rotationSeed; private Random rotationRNG = new Random(); - public SmokeDrawNode(ITexturedShaderDrawable source) + protected SmokeDrawNode(ITexturedShaderDrawable source) : base(source) { } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 8085c07912..2e67e91460 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -32,7 +32,6 @@ namespace osu.Game.Rulesets.Osu.UI public class OsuPlayfield : Playfield { private readonly PlayfieldBorder playfieldBorder; - private readonly SmokeContainer smokeContainer; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; @@ -55,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI InternalChildren = new Drawable[] { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, - smokeContainer = new SmokeContainer { RelativeSizeAxes = Axes.Both }, + new SmokeContainer { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index b8897f2cf1..07a073c3e5 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI public event Action? SmokeMoved; public event Action? SmokeEnded; - private bool isSmoking = false; + private bool isSmoking; public override bool ReceivePositionalInputAt(Vector2 _) => true; From 6852577dad463994280fbd8a171f4f6098b1bdd1 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 19:08:01 -0700 Subject: [PATCH 229/583] Remove smoke from key overlay --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 15 +++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index dec965e567..6500ade7ea 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu { @@ -56,6 +57,20 @@ namespace osu.Game.Rulesets.Osu return base.HandleMouseTouchStateChange(e); } + public override void Attach(KeyCounterDisplay keyCounter) + { + var receptor = new ActionReceptor(keyCounter); + + KeyBindingContainer.Add(receptor); + + keyCounter.SetReceptor(receptor); + keyCounter.AddRange(new[] + { + new KeyCounterAction(OsuAction.LeftButton), + new KeyCounterAction(OsuAction.RightButton), + }); + } + private class OsuKeyBindingContainer : RulesetKeyBindingContainer { public bool AllowUserPresses = true; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7c37913576..902579d9d2 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public void Attach(KeyCounterDisplay keyCounter) + public virtual void Attach(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); From 454125123d3aa7a0e06b179b519eb8d67cab1576 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 11:31:38 +0900 Subject: [PATCH 230/583] Add failing test coverage showing mod reference leak path --- .../TestScenePlayerLocalScoreImport.cs | 33 +++++++++++++++++++ osu.Game/Tests/Visual/PlayerTestScene.cs | 12 ++++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 247b822dc3..db9d2bfc1d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -15,8 +15,10 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -101,6 +103,37 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for last played to update", () => getLastPlayed() != null); } + [Test] + public void TestModReferenceNotRetained() + { + AddStep("allow fail", () => allowFail = false); + + Mod[] originalMods = { new OsuModDaycore { SpeedChange = { Value = 0.8 } } }; + Mod[] playerMods = null!; + + AddStep($"Load player with mods", () => LoadPlayer(originalMods)); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + + AddStep("get mods at start of gameplay", () => playerMods = Player.Score.ScoreInfo.Mods.ToArray()); + + // Player creates new instance of mods during load. + AddAssert("player score has copied mods", () => playerMods.First(), () => Is.Not.SameAs(originalMods.First())); + AddAssert("player score has matching mods", () => playerMods.First(), () => Is.EqualTo(originalMods.First())); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + // Player creates new instance of mods after gameplay to ensure any runtime references to drawables etc. are not retained. + AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.Not.SameAs(playerMods.First())); + AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First())); + + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)).Mods.First(), () => Is.EqualTo(playerMods.First())); + } + [Test] public void TestScoreStoredLocally() { diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index a9decbae57..9bad867206 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -7,7 +7,6 @@ using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -57,7 +56,9 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; - protected void LoadPlayer() + protected void LoadPlayer() => LoadPlayer(Array.Empty()); + + protected void LoadPlayer(Mod[] mods) { var ruleset = CreatePlayerRuleset(); Ruleset.Value = ruleset.RulesetInfo; @@ -65,20 +66,21 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); - SelectedMods.Value = Array.Empty(); + + SelectedMods.Value = mods; if (!AllowFail) { var noFailMod = ruleset.CreateMod(); if (noFailMod != null) - SelectedMods.Value = new[] { noFailMod }; + SelectedMods.Value = SelectedMods.Value.Append(noFailMod).ToArray(); } if (Autoplay) { var mod = ruleset.GetAutoplayMod(); if (mod != null) - SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray(); + SelectedMods.Value = SelectedMods.Value.Append(mod).ToArray(); } Player = CreatePlayer(ruleset); From 0a625bd879d8b525da798f04b00fac1e161515c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Sep 2022 23:48:03 +0900 Subject: [PATCH 231/583] Fix mods potentially keeping reference to runtime gameplay elements As noticed by smoogipoo during multiplayer match on the weekend. --- osu.Game/Scoring/ScoreInfo.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 25a7bad9e8..1b36ae176d 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -137,6 +137,11 @@ namespace osu.Game.Scoring clone.Statistics = new Dictionary(clone.Statistics); clone.MaximumStatistics = new Dictionary(clone.MaximumStatistics); + + // Ensure we have fresh mods to avoid any references (ie. after gameplay). + clone.clearAllMods(); + clone.ModsJson = ModsJson; + clone.RealmUser = new RealmUser { OnlineID = RealmUser.OnlineID, From 2b1c5b2c4a11ffbafbf3fb0361647527de2baaac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 12:34:50 +0900 Subject: [PATCH 232/583] Fix test failure due to triangle skin no longer being null intests --- .../Visual/Gameplay/SkinnableHUDComponentTestScene.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs index 3ecf560eb1..1bf1bd4c81 100644 --- a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.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. -#nullable disable - using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { @@ -19,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(skin => { - var implementation = skin != null + var implementation = skin is not TrianglesSkin ? CreateLegacyImplementation() : CreateDefaultImplementation(); From 494790294acdd01e3c25127451cc36a08806f329 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 17:43:36 +0900 Subject: [PATCH 233/583] Update `SkinnableTestScene` to show new default skin --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 75224742a2..4ef9a733c4 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual private TrianglesSkin trianglesSkin; private Skin metricsSkin; private Skin legacySkin; + private Skin argonSkin; private Skin specialSkin; private Skin oldSkin; @@ -48,6 +49,7 @@ namespace osu.Game.Tests.Visual { var dllStore = new DllResourceStore(GetType().Assembly); + argonSkin = new ArgonSkin(this); trianglesSkin = new TrianglesSkin(this); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); legacySkin = new DefaultLegacySkin(this); @@ -63,11 +65,12 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmapForSkinProvider(); - Cell(0).Child = createProvider(trianglesSkin, creationFunction, beatmap); - Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap); - Cell(2).Child = createProvider(legacySkin, creationFunction, beatmap); - Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap); - Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap); + Cell(0).Child = createProvider(argonSkin, creationFunction, beatmap); + Cell(1).Child = createProvider(trianglesSkin, creationFunction, beatmap); + Cell(2).Child = createProvider(metricsSkin, creationFunction, beatmap); + Cell(3).Child = createProvider(legacySkin, creationFunction, beatmap); + Cell(4).Child = createProvider(specialSkin, creationFunction, beatmap); + Cell(5).Child = createProvider(oldSkin, creationFunction, beatmap); } protected IEnumerable CreatedDrawables => createdDrawables; @@ -82,10 +85,7 @@ namespace osu.Game.Tests.Visual OutlineBox outlineBox; SkinProvidingContainer skinProvider; - ISkin provider = skin; - - if (provider is LegacySkin legacyProvider) - provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(legacyProvider, beatmap); + ISkin provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(skin, beatmap) ?? skin; var children = new Container { From f6b29d8ebcaf468d43972ed66ccd605e21438b9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 19:17:54 +0900 Subject: [PATCH 234/583] Make `TestSceneHitCircle` show more than one cirle to make testing easier --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index cc69054e23..be224b88ce 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -58,10 +58,11 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { - var drawable = createSingle(circleSize, auto, timeOffset, positionOffset); - var playfield = new TestOsuPlayfield(); - playfield.Add(drawable); + + for (double t = timeOffset; t < timeOffset + 60000; t += 2000) + playfield.Add(createSingle(circleSize, auto, t, positionOffset)); + return playfield; } From a0e31018a18af7eeec7c4608cd5dcce7387d0f99 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 22:06:07 -0700 Subject: [PATCH 235/583] Copy stable smoke's fade/alpha values, blending, scale, and rotation --- .../Skinning/Default/DefaultSmoke.cs | 15 +++- .../Skinning/Legacy/LegacySmoke.cs | 83 ++++++++++++++++--- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 57 +++++-------- 3 files changed, 108 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index e7f526da31..0ba0143466 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Rendering; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default @@ -40,12 +41,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default fadeOutTime = SmokeStartTime + fade_out_speed * (CurrentTime - (SmokeEndTime + fade_out_delay)); } - protected override Color4 ColorAtTime(double pointTime) + protected override Color4 PointColor(SmokePoint point) { var color = Color4.White; color.A = alpha; - double timeDoingFadeOut = fadeOutTime - pointTime; + double timeDoingFadeOut = fadeOutTime - point.Time; if (timeDoingFadeOut > 0) { @@ -56,6 +57,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default return color; } + + protected override float PointScale(SmokePoint point) + { + throw new NotImplementedException(); + } + + protected override Vector2 PointDirection(SmokePoint point) + { + throw new NotImplementedException(); + } } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index 23ab2d186f..093d8f87eb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -4,29 +4,59 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Rendering; +using osu.Framework.Utils; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacySmoke : Smoke { - private const double initial_fade_out_duration = 2500; + // fade values + private const double initial_fade_out_duration = 4000; private const double re_fade_in_speed = 3; private const double re_fade_in_duration = 50; - private const double final_fade_out_duration = 7500; + private const double final_fade_out_speed = 2; + private const double final_fade_out_duration = 8000; - private const float initial_alpha = 0.8f; - private const float re_fade_in_alpha = 1.4f; + private const float initial_alpha = 0.6f; + private const float re_fade_in_alpha = 1f; + + // scale values + private const double scale_duration = 1200; + + private const float initial_scale = 0.65f; + private const float final_scale = 1f; + + // rotation values + private const double rotation_duration = 500; + + private const float max_rotation = 0.25f; + + private int rotationSeed = RNG.Next(); + + protected int RotationSeed + { + get => rotationSeed; + set + { + if (rotationSeed == value) + return; + + rotationSeed = value; + Invalidate(Invalidation.DrawNode); + } + } protected override double LifetimeAfterSmokeEnd { get { double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); - return final_fade_out_duration + initialFadeOutDurationTrunc * (1 + re_fade_in_speed); + return final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; } } @@ -49,11 +79,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected class LegacySmokeDrawNode : SmokeDrawNode { + protected new LegacySmoke Source => (LegacySmoke)base.Source; + private double initialFadeOutDurationTrunc; private double initialFadeOutTime; private double reFadeInTime; private double finalFadeOutTime; + private int rotationSeed; + private Random rotationRNG = new Random(); + public LegacySmokeDrawNode(ITexturedShaderDrawable source) : base(source) { @@ -64,34 +99,35 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy base.ApplyState(); initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + rotationSeed = Source.RotationSeed; } protected override void UpdateDrawVariables(IRenderer renderer) { base.UpdateDrawVariables(renderer); + rotationRNG = new Random(rotationSeed); initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime); reFadeInTime = re_fade_in_speed * (CurrentTime - SmokeEndTime) + SmokeEndTime - initialFadeOutDurationTrunc; - finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc * (1 + 1 / re_fade_in_speed); + finalFadeOutTime = final_fade_out_speed * (CurrentTime - SmokeEndTime) + SmokeEndTime - initialFadeOutDurationTrunc * (1 + 1 / re_fade_in_speed); } - protected override Color4 ColorAtTime(double pointTime) + protected override Color4 PointColor(SmokePoint point) { var color = Color4.White; - double timeDoingInitialFadeOut = initialFadeOutTime - pointTime; + double timeDoingInitialFadeOut = initialFadeOutTime - point.Time; if (timeDoingInitialFadeOut > 0) { float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1); - fraction = MathF.Pow(fraction, 5); color.A = (1 - fraction) * initial_alpha; } if (color.A > 0) { - double timeDoingReFadeIn = reFadeInTime - pointTime; - double timeDoingFinalFadeOut = finalFadeOutTime - pointTime; + double timeDoingReFadeIn = reFadeInTime - point.Time; + double timeDoingFinalFadeOut = finalFadeOutTime - point.Time; if (timeDoingFinalFadeOut > 0) { @@ -109,6 +145,31 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return color; } + + protected override float PointScale(SmokePoint point) + { + double timeDoingScale = CurrentTime - point.Time; + float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1); + fraction = 1 - MathF.Pow(1 - fraction, 5); + return fraction * (final_scale - initial_scale) + initial_scale; + } + + protected override Vector2 PointDirection(SmokePoint point) + { + float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X); + float finalAngle = initialAngle + nextRotation(); + + double timeDoingRotation = CurrentTime - point.Time; + float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1); + fraction = 1 - MathF.Pow(1 - fraction, 5); + float angle = fraction * (finalAngle - initialAngle) + initialAngle; + + return toVector2(angle); + } + + private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1); + + private Vector2 toVector2(float angle) => new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 382e54e2ab..b725cc028b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -42,21 +42,6 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - private int rotationSeed = RNG.Next(); - - protected int RotationSeed - { - get => rotationSeed; - set - { - if (rotationSeed == value) - return; - - rotationSeed = value; - Invalidate(Invalidation.DrawNode); - } - } - private Texture? texture; protected Texture? Texture @@ -195,6 +180,12 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokeStartTime = Time.Current; } + private Vector2 nextPointDirection() + { + float angle = RNG.NextSingle(0, 2 * MathF.PI); + return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); + } + private void onSmokeMoved(Vector2 position, double time) { if (!IsActive) @@ -229,6 +220,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { Position = pointPos, Time = time, + Direction = nextPointDirection(), }); pointPos += increment; @@ -303,6 +295,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { public Vector2 Position; public double Time; + public Vector2 Direction; public struct UpperBoundComparer : IComparer { @@ -339,9 +332,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private IFrameBasedClock? clock; - private int rotationSeed; - private Random rotationRNG = new Random(); - protected SmokeDrawNode(ITexturedShaderDrawable source) : base(source) { @@ -362,8 +352,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokeStartTime = Source.SmokeStartTime; SmokeEndTime = Source.SmokeEndTime; - - rotationSeed = Source.RotationSeed; } public sealed override void Draw(IRenderer renderer) @@ -377,6 +365,9 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture ??= renderer.WhitePixel; var shader = GetAppropriateShader(renderer); + + renderer.SetBlend(BlendingParameters.Additive); + shader.Bind(); Texture.Bind(); @@ -390,7 +381,11 @@ namespace osu.Game.Rulesets.Osu.Skinning ? ((SRGBColour)DrawColourInfo.Colour).Linear : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, DrawSize)).Linear; - protected abstract Color4 ColorAtTime(double pointTime); + protected abstract Color4 PointColor(SmokePoint point); + + protected abstract float PointScale(SmokePoint point); + + protected abstract Vector2 PointDirection(SmokePoint point); protected virtual void UpdateDrawVariables(IRenderer renderer) { @@ -399,7 +394,6 @@ namespace osu.Game.Rulesets.Osu.Skinning CurrentTime = clock.CurrentTime; TextureRect = Texture.GetTextureRect(); - rotationRNG = new Random(rotationSeed); } protected virtual void UpdateVertexBuffer() @@ -408,24 +402,19 @@ namespace osu.Game.Rulesets.Osu.Skinning drawPointQuad(point); } - private Vector2 nextTextureDirection() - { - float angle = (float)rotationRNG.NextDouble() * 2 * MathF.PI; - return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); - } - private void drawPointQuad(SmokePoint point) { Debug.Assert(QuadBatch != null); - var color = ColorAtTime(point.Time); - var dir = nextTextureDirection(); + var color = PointColor(point); + float scale = PointScale(point); + var dir = PointDirection(point); var ortho = dir.PerpendicularLeft; - var localTopLeft = point.Position + (Radius * (-ortho - dir)) - PositionOffset; - var localTopRight = point.Position + (Radius * (-ortho + dir)) - PositionOffset; - var localBotLeft = point.Position + (Radius * (ortho - dir)) - PositionOffset; - var localBotRight = point.Position + (Radius * (ortho + dir)) - PositionOffset; + var localTopLeft = point.Position + (Radius * scale * (-ortho - dir)) - PositionOffset; + var localTopRight = point.Position + (Radius * scale * (-ortho + dir)) - PositionOffset; + var localBotLeft = point.Position + (Radius * scale * (ortho - dir)) - PositionOffset; + var localBotRight = point.Position + (Radius * scale * (ortho + dir)) - PositionOffset; QuadBatch.Add(new TexturedVertex2D { From 8474335aeaf520b61a82b3800f473728e77b402d Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 22:08:45 -0700 Subject: [PATCH 236/583] Remove hacky LifetimeEnd workaround --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index b725cc028b..822dc113f1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -265,10 +264,6 @@ namespace osu.Game.Rulesets.Osu.Skinning IsActive = false; SmokeEndTime = time; LifetimeEnd = time + LifetimeAfterSmokeEnd + 100; - - // TODO: HYPER MEGA JANK WTF?? - if (Parent is SkinnableDrawable) - Parent.LifetimeEnd = LifetimeEnd; } protected abstract override DrawNode CreateDrawNode(); From 3eb28881e4b0052894f9b60c0ebe649e02921118 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 18 Sep 2022 22:14:54 -0700 Subject: [PATCH 237/583] Temp default smoke scale/rotation anims --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index 0ba0143466..6eebd18305 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -58,15 +58,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default return color; } - protected override float PointScale(SmokePoint point) - { - throw new NotImplementedException(); - } + protected override float PointScale(SmokePoint point) => 1f; - protected override Vector2 PointDirection(SmokePoint point) - { - throw new NotImplementedException(); - } + protected override Vector2 PointDirection(SmokePoint point) => point.Direction; } } } From 8204090e471f6784c9d1a520ac97b61e7391ff66 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 19 Sep 2022 00:07:22 -0700 Subject: [PATCH 238/583] Scale smoke radius based on texture width --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs | 5 +---- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index 093d8f87eb..d8c3ff3521 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -65,7 +65,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public LegacySmoke(ISkin skin) { this.skin = skin; - Radius = 3; } protected override void LoadComplete() @@ -164,12 +163,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy fraction = 1 - MathF.Pow(1 - fraction, 5); float angle = fraction * (finalAngle - initialAngle) + initialAngle; - return toVector2(angle); + return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); } private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1); - - private Vector2 toVector2(float angle) => new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 822dc113f1..bada67816b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -26,11 +26,11 @@ namespace osu.Game.Rulesets.Osu.Skinning public IShader? TextureShader { get; private set; } public IShader? RoundedTextureShader { get; private set; } - private float radius = 1; + private float? radius; protected float Radius { - get => radius; + get => radius ?? Texture?.DisplayWidth * 0.165f ?? 3; set { if (radius == value) From 41e7d271d764bb9f0d5c77b7f192fa1592212f4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 21:51:54 +0900 Subject: [PATCH 239/583] Remove redundant string interpolation --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index db9d2bfc1d..38a091dd85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay Mod[] originalMods = { new OsuModDaycore { SpeedChange = { Value = 0.8 } } }; Mod[] playerMods = null!; - AddStep($"Load player with mods", () => LoadPlayer(originalMods)); + AddStep("load player with mods", () => LoadPlayer(originalMods)); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); AddStep("get mods at start of gameplay", () => playerMods = Player.Score.ScoreInfo.Mods.ToArray()); From 0f7b38f4c3c463bc4f1a39f4577d6cccf30e835b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 16:02:57 +0900 Subject: [PATCH 240/583] Add new default skin "argon" --- .../Overlays/Settings/Sections/SkinSection.cs | 1 + .../Backgrounds/BackgroundScreenDefault.cs | 16 +- osu.Game/Skinning/ArgonSkin.cs | 198 ++++++++++++++++++ .../Skinning/RulesetSkinProvidingContainer.cs | 1 + osu.Game/Skinning/SkinInfo.cs | 1 + osu.Game/Skinning/SkinManager.cs | 31 +-- osu.Game/Skinning/SkinnableSprite.cs | 1 + osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- 8 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Skinning/ArgonSkin.cs diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 8d491a980a..f602b73065 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -104,6 +104,7 @@ namespace osu.Game.Overlays.Settings.Sections // In the future we should change this to properly handle ChangeSet events. dropdownItems.Clear(); + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.TRIANGLES_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.CLASSIC_SKIN).ToLive(realm)); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index dbc4e2b2e1..f8546d6ed0 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -129,11 +129,19 @@ namespace osu.Game.Screens.Backgrounds } case BackgroundSource.Skin: - // default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them. - if (skin.Value is TrianglesSkin || skin.Value is DefaultLegacySkin) - break; + switch (skin.Value) + { + case TrianglesSkin: + case ArgonSkin: + case DefaultLegacySkin: + // default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them. + break; + + default: + newBackground = new SkinBackground(skin.Value, getBackgroundTextureName()); + break; + } - newBackground = new SkinBackground(skin.Value, getBackgroundTextureName()); break; } } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs new file mode 100644 index 0000000000..2c17b28b01 --- /dev/null +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps.Formats; +using osu.Game.Extensions; +using osu.Game.IO; +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Skinning +{ + public class ArgonSkin : Skin + { + public static SkinInfo CreateInfo() => new SkinInfo + { + ID = osu.Game.Skinning.SkinInfo.ARGON_SKIN, + Name = "osu! \"argon\" (2022)", + Creator = "team osu!", + Protected = true, + InstantiationInfo = typeof(ArgonSkin).GetInvariantInstantiationInfo() + }; + + private readonly IStorageResourceProvider resources; + + public ArgonSkin(IStorageResourceProvider resources) + : this(CreateInfo(), resources) + { + } + + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] + public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin, resources) + { + this.resources = resources; + } + + public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT); + + public override ISample GetSample(ISampleInfo sampleInfo) + { + foreach (string lookup in sampleInfo.LookupNames) + { + var sample = Samples?.Get(lookup) ?? resources.AudioManager?.Samples.Get(lookup); + if (sample != null) + return sample; + } + + return null; + } + + public override Drawable GetDrawableComponent(ISkinComponent component) + { + if (base.GetDrawableComponent(component) is Drawable c) + return c; + + switch (component) + { + case SkinnableTargetComponent target: + switch (target.Target) + { + case SkinnableTarget.SongSelect: + var songSelectComponents = new SkinnableTargetComponentsContainer(_ => + { + // do stuff when we need to. + }); + + return songSelectComponents; + + case SkinnableTarget.MainHUDComponents: + var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); + var combo = container.OfType().FirstOrDefault(); + var ppCounter = container.OfType().FirstOrDefault(); + + if (score != null) + { + score.Anchor = Anchor.TopCentre; + score.Origin = Anchor.TopCentre; + + // elements default to beneath the health bar + const float vertical_offset = 30; + + const float horizontal_padding = 20; + + score.Position = new Vector2(0, vertical_offset); + + if (ppCounter != null) + { + ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4; + ppCounter.Origin = Anchor.TopCentre; + ppCounter.Anchor = Anchor.TopCentre; + } + + if (accuracy != null) + { + accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5); + accuracy.Origin = Anchor.TopRight; + accuracy.Anchor = Anchor.TopCentre; + + if (combo != null) + { + combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); + combo.Anchor = Anchor.TopCentre; + } + } + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.CentreLeft; + hitError.Origin = Anchor.CentreLeft; + } + + var hitError2 = container.OfType().LastOrDefault(); + + if (hitError2 != null) + { + hitError2.Anchor = Anchor.CentreRight; + hitError2.Scale = new Vector2(-1, 1); + // origin flipped to match scale above. + hitError2.Origin = Anchor.CentreLeft; + } + } + }) + { + Children = new Drawable[] + { + new DefaultComboCounter(), + new DefaultScoreCounter(), + new DefaultAccuracyCounter(), + new DefaultHealthDisplay(), + new DefaultSongProgress(), + new BarHitErrorMeter(), + new BarHitErrorMeter(), + new PerformancePointsCounter() + } + }; + + return skinnableTargetWrapper; + } + + return null; + } + + switch (component.LookupName) + { + // Temporary until default skin has a valid hit lighting. + case @"lighting": + return Drawable.Empty(); + } + + if (GetTexture(component.LookupName) is Texture t) + return new Sprite { Texture = t }; + + return null; + } + + public override IBindable GetConfig(TLookup lookup) + { + // todo: this code is pulled from LegacySkin and should not exist. + // will likely change based on how databased storage of skin configuration goes. + switch (lookup) + { + case GlobalSkinColours global: + switch (global) + { + case GlobalSkinColours.ComboColours: + return SkinUtils.As(new Bindable>(Configuration.ComboColours)); + } + + break; + + case SkinComboColourLookup comboColour: + return SkinUtils.As(new Bindable(getComboColour(Configuration, comboColour.ColourIndex))); + } + + return null; + } + + private static Color4 getComboColour(IHasComboColours source, int colourIndex) + => source.ComboColours[colourIndex % source.ComboColours.Count]; + } +} diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4c35681f66..6ad5d64e4b 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -81,6 +81,7 @@ namespace osu.Game.Skinning } } + // TODO: check int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 34728245c8..08d8a81d4c 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -20,6 +20,7 @@ namespace osu.Game.Skinning public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasGuidPrimaryKey, ISoftDelete, IHasNamedFiles { internal static readonly Guid TRIANGLES_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); + internal static readonly Guid ARGON_SKIN = new Guid("CFFA69DE-B3E3-4DEE-8563-3C4F425C05D0"); 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"); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index a9a01dfebf..059767d8f8 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -49,10 +49,7 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); - public readonly Bindable> CurrentSkinInfo = new Bindable>(TrianglesSkin.CreateInfo().ToLiveUnmanaged()) - { - Default = TrianglesSkin.CreateInfo().ToLiveUnmanaged() - }; + public readonly Bindable> CurrentSkinInfo = new Bindable>(ArgonSkin.CreateInfo().ToLiveUnmanaged()); private readonly SkinImporter skinImporter; @@ -61,7 +58,12 @@ namespace osu.Game.Skinning /// /// The default "triangles" skin. /// - public Skin DefaultSkinTriangles { get; } + private Skin argonSkin { get; } + + /// + /// The default skin (old). + /// + private Skin trianglesSkin { get; } /// /// The default "classic" skin. @@ -86,7 +88,8 @@ namespace osu.Game.Skinning var defaultSkins = new[] { DefaultClassicSkin = new DefaultLegacySkin(this), - DefaultSkinTriangles = new TrianglesSkin(this), + trianglesSkin = new TrianglesSkin(this), + argonSkin = new ArgonSkin(this), }; // Ensure the default entries are present. @@ -104,7 +107,7 @@ namespace osu.Game.Skinning CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin); }; - CurrentSkin.Value = DefaultSkinTriangles; + CurrentSkin.Value = argonSkin; CurrentSkin.ValueChanged += skin => { if (!skin.NewValue.SkinInfo.Equals(CurrentSkinInfo.Value)) @@ -125,7 +128,7 @@ namespace osu.Game.Skinning if (randomChoices.Length == 0) { - CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged(); + CurrentSkinInfo.Value = ArgonSkin.CreateInfo().ToLiveUnmanaged(); return; } @@ -229,11 +232,15 @@ namespace osu.Game.Skinning { yield return CurrentSkin.Value; + // Skin manager provides default fallbacks. + // This handles cases where a user skin doesn't have the required resources for complete display of + // certain elements. + if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultClassicSkin) yield return DefaultClassicSkin; - if (CurrentSkin.Value != DefaultSkinTriangles) - yield return DefaultSkinTriangles; + if (CurrentSkin.Value != trianglesSkin) + yield return trianglesSkin; } } @@ -294,7 +301,7 @@ namespace osu.Game.Skinning Guid currentUserSkin = CurrentSkinInfo.Value.ID; if (items.Any(s => s.ID == currentUserSkin)) - scheduler.Add(() => CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged()); + scheduler.Add(() => CurrentSkinInfo.Value = ArgonSkin.CreateInfo().ToLiveUnmanaged()); Delete(items.ToList(), silent); }); @@ -313,7 +320,7 @@ namespace osu.Game.Skinning skinInfo = DefaultClassicSkin.SkinInfo; } - CurrentSkinInfo.Value = skinInfo ?? DefaultSkinTriangles.SkinInfo; + CurrentSkinInfo.Value = skinInfo ?? trianglesSkin.SkinInfo; } } } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 69454de979..f8a9aaa6fb 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -113,6 +113,7 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) => skin.GetType() == typeof(TrianglesSkin) + || skin.GetType() == typeof(ArgonSkin) || skin.GetType() == typeof(DefaultLegacySkin) || skin.GetType() == typeof(LegacySkin); } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 4ef9a733c4..a8193701ef 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual }, new OsuSpriteText { - Text = skin?.SkinInfo.Value.Name ?? "none", + Text = skin.SkinInfo.Value.Name, Scale = new Vector2(1.5f), Padding = new MarginPadding(5), }, From b766493776071d9ecc6660fb8df200f1066abcad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Sep 2022 17:51:10 +0900 Subject: [PATCH 241/583] Add basic argon skin transformer for osu! ruleset --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 + .../Skinning/Argon/ArgonMainCirclePiece.cs | 157 ++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 49 ++++++ .../Skinning/Default/RingPiece.cs | 4 +- 4 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 0a5b3139fc..3f5e728651 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.Skinning.Argon; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.UI; @@ -237,6 +238,9 @@ namespace osu.Game.Rulesets.Osu { case LegacySkin: return new OsuLegacySkinTransformer(skin); + + case ArgonSkin: + return new OsuArgonSkinTransformer(skin); } return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs new file mode 100644 index 0000000000..7f645f1452 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -0,0 +1,157 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonMainCirclePiece : CompositeDrawable + { + private readonly Circle outerFill; + private readonly Circle outerGradient; + private readonly Circle innerGradient; + private readonly Circle innerFill; + + private readonly RingPiece ring; + private readonly OsuSpriteText number; + + private readonly IBindable accentColour = new Bindable(); + private readonly IBindable indexInCurrentCombo = new Bindable(); + + [Resolved] + private DrawableHitObject drawableObject { get; set; } = null!; + + public ArgonMainCirclePiece() + { + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + const float border_thickness = 7; + const float fill_thickness = 24; + + InternalChildren = new Drawable[] + { + outerFill = new Circle // renders white outer border and dark fill + { + Size = Size, + Alpha = 1, + }, + outerGradient = new Circle // renders the outer bright gradient + { + Size = outerFill.Size - new Vector2(border_thickness * 3), + Alpha = 1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + innerGradient = new Circle // renders the inner bright gradient + { + Size = outerGradient.Size - new Vector2(fill_thickness), + Alpha = 1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + innerFill = new Circle // renders the inner dark fill + { + Size = innerGradient.Size - new Vector2(fill_thickness), + Alpha = 1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + number = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -2, + Text = @"1", + }, + ring = new RingPiece(border_thickness), + }; + } + + [BackgroundDependencyLoader] + private void load() + { + var drawableOsuObject = (DrawableOsuHitObject)drawableObject; + + accentColour.BindTo(drawableObject.AccentColour); + indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + accentColour.BindValueChanged(colour => + { + outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); + outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); + innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); + }, true); + + indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); + + drawableObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableObject, drawableObject.State.Value); + } + + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) + { + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + switch (state) + { + case ArmedState.Hit: + const double fade_out_time = 600; + const double flash_in = 200; + + number.FadeOut(flash_in / 4); + + outerGradient.ResizeTo(Size, fade_out_time / 2, Easing.OutQuint); + outerGradient.FadeOut(flash_in); + + innerGradient.ResizeTo(Size, fade_out_time * 2, Easing.OutQuint); + innerGradient.FadeOut(flash_in); + + innerFill.ResizeTo(Size, fade_out_time * 1.5, Easing.OutQuint); + innerFill.FadeOut(flash_in); + + outerFill.FadeOut(flash_in); + + ring.ResizeTo(Size + new Vector2(ring.BorderThickness * 1.5f), fade_out_time / 1.5f, Easing.OutQuint); + ring.FadeOut(fade_out_time); + + using (BeginDelayedSequence(flash_in)) + this.FadeOut(fade_out_time, Easing.OutQuint); + + break; + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject.IsNotNull()) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs new file mode 100644 index 0000000000..6aced740bc --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -0,0 +1,49 @@ +// 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.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class OsuArgonSkinTransformer : ISkin + { + public OsuArgonSkinTransformer(ISkin skin) + { + } + + public Drawable? GetDrawableComponent(ISkinComponent component) + { + if (component is OsuSkinComponent osuComponent) + { + switch (osuComponent.Component) + { + case OsuSkinComponents.HitCircle: + case OsuSkinComponents.SliderHeadHitCircle: + return new ArgonMainCirclePiece(); + } + } + + return null; + } + + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + return null; + } + + public ISample? GetSample(ISampleInfo sampleInfo) + { + return null; + } + + public IBindable? GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull + { + return null; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs index b941a86171..c14246e90e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class RingPiece : CircularContainer { - public RingPiece() + public RingPiece(float thickness = 9) { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Origin = Anchor.Centre; Masking = true; - BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other. + BorderThickness = thickness; BorderColour = Color4.White; Child = new Box From 4a86fe1ca90d79fc46db9fa8a23bd77f55ccfb34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 19:25:12 +0900 Subject: [PATCH 242/583] Tweak main circle animations --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 74 +++++++++++++++---- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 7f645f1452..02271b6a7a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); + private readonly FlashPiece flash; [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; @@ -82,6 +84,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Y = -2, Text = @"1", }, + flash = new FlashPiece(), ring = new RingPiece(border_thickness), }; } @@ -104,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); + flash.Colour = colour.NewValue.Multiply(1f); }, true); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); @@ -119,27 +123,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon switch (state) { case ArmedState.Hit: - const double fade_out_time = 600; - const double flash_in = 200; + const double fade_out_time = 800; + const double flash_in = 150; - number.FadeOut(flash_in / 4); - - outerGradient.ResizeTo(Size, fade_out_time / 2, Easing.OutQuint); - outerGradient.FadeOut(flash_in); - - innerGradient.ResizeTo(Size, fade_out_time * 2, Easing.OutQuint); - innerGradient.FadeOut(flash_in); - - innerFill.ResizeTo(Size, fade_out_time * 1.5, Easing.OutQuint); - innerFill.FadeOut(flash_in); + number.FadeOut(flash_in / 2); outerFill.FadeOut(flash_in); - ring.ResizeTo(Size + new Vector2(ring.BorderThickness * 1.5f), fade_out_time / 1.5f, Easing.OutQuint); - ring.FadeOut(fade_out_time); + ring.ResizeTo(Size - new Vector2(ring.BorderThickness * 1.5f), flash_in, Easing.OutQuint); - using (BeginDelayedSequence(flash_in)) - this.FadeOut(fade_out_time, Easing.OutQuint); + ring.TransformTo(nameof + (BorderColour), ColourInfo.GradientVertical( + accentColour.Value.Opacity(0.5f), + accentColour.Value.Opacity(0)), fade_out_time); + + flash.FadeTo(1, flash_in * 2, Easing.OutQuint); + + using (BeginDelayedSequence(flash_in / 8)) + { + outerGradient.ResizeTo(outerGradient.Size * 0.8f, flash_in, Easing.OutQuint); + + using (BeginDelayedSequence(flash_in / 8)) + { + innerGradient.ResizeTo(innerGradient.Size * 0.8f, flash_in, Easing.OutQuint); + innerFill.ResizeTo(innerFill.Size * 0.8f, flash_in, Easing.OutQuint); + } + } + + innerFill.FadeOut(flash_in, Easing.OutQuint); + innerGradient.FadeOut(flash_in, Easing.OutQuint); + + this.FadeOut(fade_out_time, Easing.OutQuad); break; } @@ -153,5 +167,33 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon if (drawableObject.IsNotNull()) drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } + + private class FlashPiece : Circle + { + public FlashPiece() + { + Size = new Vector2(OsuHitObject.OBJECT_RADIUS); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Alpha = 0; + Blending = BlendingParameters.Additive; + + Child.Alpha = 0; + Child.AlwaysPresent = true; + } + + protected override void Update() + { + base.Update(); + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Colour, + Radius = OsuHitObject.OBJECT_RADIUS * 1.4f, + }; + } + } } } From 02fbb04ba86c8af0ad251f3a1eeb1d146ebfdc4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Sep 2022 19:25:04 +0900 Subject: [PATCH 243/583] Add basic argon judgement --- .../Skinning/Argon/ArgonJudgementPiece.cs | 85 +++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 21 +++-- 2 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs new file mode 100644 index 0000000000..315b9ec65e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -0,0 +1,85 @@ +// 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.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + { + protected readonly HitResult Result; + + protected SpriteText JudgementText { get; private set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public ArgonJudgementPiece(HitResult result) + { + Result = result; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + JudgementText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Result.GetDescription().ToUpperInvariant(), + Colour = colours.ForHitResult(Result), + Spacing = new Vector2(5, 0), + Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold), + } + }; + } + + /// + /// Plays the default animation for this judgement piece. + /// + /// + /// The base implementation only handles fade (for all result types) and misses. + /// Individual rulesets are recommended to implement their appropriate hit animations. + /// + public virtual void PlayAnimation() + { + switch (Result) + { + default: + JudgementText + .ScaleTo(Vector2.One) + .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); + break; + + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + this.RotateTo(0); + this.RotateTo(40, 800, Easing.InQuint); + break; + } + + this.FadeOutFromOne(800); + } + + public Drawable? GetAboveHitObjectsProxiedContent() => null; + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 6aced740bc..6523a5cb88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning.Argon @@ -18,14 +19,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon public Drawable? GetDrawableComponent(ISkinComponent component) { - if (component is OsuSkinComponent osuComponent) + switch (component) { - switch (osuComponent.Component) - { - case OsuSkinComponents.HitCircle: - case OsuSkinComponents.SliderHeadHitCircle: - return new ArgonMainCirclePiece(); - } + case GameplaySkinComponent resultComponent: + return new ArgonJudgementPiece(resultComponent.Component); + + case OsuSkinComponent osuComponent: + switch (osuComponent.Component) + { + case OsuSkinComponents.HitCircle: + case OsuSkinComponents.SliderHeadHitCircle: + return new ArgonMainCirclePiece(); + } + + break; } return null; From 403cc592088744ebd65ea222b2b3d1700b32243e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 13:19:56 +0900 Subject: [PATCH 244/583] Further animation tweaks and better documentation --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 02271b6a7a..1271bd8d61 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly Circle innerGradient; private readonly Circle innerFill; - private readonly RingPiece ring; + private readonly RingPiece border; private readonly OsuSpriteText number; private readonly IBindable accentColour = new Bindable(); @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Text = @"1", }, flash = new FlashPiece(), - ring = new RingPiece(border_thickness), + border = new RingPiece(border_thickness), }; } @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); - flash.Colour = colour.NewValue.Multiply(1f); + flash.Colour = colour.NewValue; }, true); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); @@ -123,38 +123,51 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon switch (state) { case ArmedState.Hit: + // Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec. const double fade_out_time = 800; - const double flash_in = 150; - number.FadeOut(flash_in / 2); + const double flash_in_duration = 150; + const double resize_duration = 300; - outerFill.FadeOut(flash_in); + const float shrink_size = 0.8f; - ring.ResizeTo(Size - new Vector2(ring.BorderThickness * 1.5f), flash_in, Easing.OutQuint); + // Animating with the number present is distracting. + // The number disappearing is hidden by the bright flash. + number.FadeOut(flash_in_duration / 2); - ring.TransformTo(nameof + // The fill layers add too much noise during the explosion animation. + // They will be hidden by the additive effects anyway. + outerFill.FadeOut(flash_in_duration, Easing.OutQuint); + innerFill.FadeOut(flash_in_duration, Easing.OutQuint); + + // The inner-most gradient should actually be resizing, but is only visible for + // a few milliseconds before it's hidden by the flash, so it's pointless overhead to bother with it. + innerGradient.FadeOut(flash_in_duration, Easing.OutQuint); + + // The border is always white, but after hit it gets coloured by the skin/beatmap's colouring. + // A gradient is applied to make the border less prominent over the course of the animation. + // Without this, the border dominates the visual presence of the explosion animation in a bad way. + border.TransformTo(nameof (BorderColour), ColourInfo.GradientVertical( accentColour.Value.Opacity(0.5f), accentColour.Value.Opacity(0)), fade_out_time); - flash.FadeTo(1, flash_in * 2, Easing.OutQuint); + // The outer ring shrinks immediately, but accounts for its thickness so it doesn't overlap the inner + // gradient layers. + border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf); - using (BeginDelayedSequence(flash_in / 8)) - { - outerGradient.ResizeTo(outerGradient.Size * 0.8f, flash_in, Easing.OutQuint); + // The outer gradient is resize with a slight delay from the border. + // This is to give it a bomb-like effect, with the border "triggering" its animation when getting close. + using (BeginDelayedSequence(flash_in_duration / 12)) + outerGradient.ResizeTo(outerGradient.Size * shrink_size, resize_duration, Easing.OutElasticHalf); - using (BeginDelayedSequence(flash_in / 8)) - { - innerGradient.ResizeTo(innerGradient.Size * 0.8f, flash_in, Easing.OutQuint); - innerFill.ResizeTo(innerFill.Size * 0.8f, flash_in, Easing.OutQuint); - } - } - - innerFill.FadeOut(flash_in, Easing.OutQuint); - innerGradient.FadeOut(flash_in, Easing.OutQuint); + // The flash layer starts white to give the wanted brightness, but is almost immediately + // recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash) + // but works well enough with the colour fade. + flash.FadeTo(1, flash_in_duration, Easing.OutQuint); + flash.FlashColour(Color4.White, flash_in_duration, Easing.OutQuint); this.FadeOut(fade_out_time, Easing.OutQuad); - break; } } @@ -180,6 +193,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Alpha = 0; Blending = BlendingParameters.Additive; + // The edge effect provides the fill due to not being rendered hollow. Child.Alpha = 0; Child.AlwaysPresent = true; } @@ -187,11 +201,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon protected override void Update() { base.Update(); + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = Colour, - Radius = OsuHitObject.OBJECT_RADIUS * 1.4f, + Radius = OsuHitObject.OBJECT_RADIUS * 1.2f, }; } } From cd84503e62a7fbd6f9603771225b246b1bddd04b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 14:38:21 +0900 Subject: [PATCH 245/583] Add slider body --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 22 +++++------ .../Skinning/Argon/ArgonSliderBody.cs | 38 +++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 3 ++ .../Skinning/Default/PlaySliderBody.cs | 14 ++++--- 4 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 1271bd8d61..e4ed3d8fe8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -23,7 +23,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { public class ArgonMainCirclePiece : CompositeDrawable { - private readonly Circle outerFill; + public const float BORDER_THICKNESS = 7; + + public const float OUTER_GRADIENT_SIZE = OsuHitObject.OBJECT_RADIUS * 2 - BORDER_THICKNESS * 3; + private readonly Circle outerGradient; private readonly Circle innerGradient; private readonly Circle innerFill; @@ -45,33 +48,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre; Origin = Anchor.Centre; - const float border_thickness = 7; const float fill_thickness = 24; InternalChildren = new Drawable[] { - outerFill = new Circle // renders white outer border and dark fill - { - Size = Size, - Alpha = 1, - }, outerGradient = new Circle // renders the outer bright gradient { - Size = outerFill.Size - new Vector2(border_thickness * 3), + Size = new Vector2(OUTER_GRADIENT_SIZE), Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerGradient = new Circle // renders the inner bright gradient { - Size = outerGradient.Size - new Vector2(fill_thickness), + Size = new Vector2(OUTER_GRADIENT_SIZE - fill_thickness), Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerFill = new Circle // renders the inner dark fill { - Size = innerGradient.Size - new Vector2(fill_thickness), + Size = new Vector2(OUTER_GRADIENT_SIZE - 2 * fill_thickness), Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -85,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Text = @"1", }, flash = new FlashPiece(), - border = new RingPiece(border_thickness), + border = new RingPiece(BORDER_THICKNESS), }; } @@ -104,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon accentColour.BindValueChanged(colour => { - outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); + innerFill.Colour = colour.NewValue.Darken(4); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); flash.Colour = colour.NewValue; @@ -137,7 +134,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // The fill layers add too much noise during the explosion animation. // They will be hidden by the additive effects anyway. - outerFill.FadeOut(flash_in_duration, Easing.OutQuint); innerFill.FadeOut(flash_in_duration, Easing.OutQuint); // The inner-most gradient should actually be resizing, but is only visible for diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs new file mode 100644 index 0000000000..115e153f34 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs @@ -0,0 +1,38 @@ +// 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.Extensions.Color4Extensions; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonSliderBody : PlaySliderBody + { + protected override void LoadComplete() + { + base.LoadComplete(); + + AccentColourBindable.BindValueChanged(accent => + { + BorderColour = accent.NewValue; + }, true); + ScaleBindable.BindValueChanged(scale => PathRadius = ArgonMainCirclePiece.OUTER_GRADIENT_SIZE / 2 * scale.NewValue, true); + + BorderSize = ArgonMainCirclePiece.BORDER_THICKNESS / 4; + } + + protected override Default.DrawableSliderPath CreateSliderPath() => new DrawableSliderPath(); + + private class DrawableSliderPath : Default.DrawableSliderPath + { + protected override Color4 ColourAt(float position) + { + if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion) + return BorderColour; + + return AccentColour.Darken(4); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 6523a5cb88..d257271455 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.HitCircle: case OsuSkinComponents.SliderHeadHitCircle: return new ArgonMainCirclePiece(); + + case OsuSkinComponents.SliderBody: + return new ArgonSliderBody(); } break; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index 83f7bb8904..6c422cf127 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -16,9 +16,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public abstract class PlaySliderBody : SnakingSliderBody { - private IBindable scaleBindable; + protected IBindable ScaleBindable { get; private set; } = null!; + + protected IBindable AccentColourBindable { get; private set; } = null!; + private IBindable pathVersion; - private IBindable accentColour; [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } @@ -30,14 +32,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var drawableSlider = (DrawableSlider)drawableObject; - scaleBindable = drawableSlider.ScaleBindable.GetBoundCopy(); - scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true); + ScaleBindable = drawableSlider.ScaleBindable.GetBoundCopy(); + ScaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true); pathVersion = drawableSlider.PathVersion.GetBoundCopy(); pathVersion.BindValueChanged(_ => Refresh()); - accentColour = drawableObject.AccentColour.GetBoundCopy(); - accentColour.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true); + AccentColourBindable = drawableObject.AccentColour.GetBoundCopy(); + AccentColourBindable.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true); config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut); From ef6ea49b188b42aeb1771054356ed5bbbc9f4671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 14:58:27 +0900 Subject: [PATCH 246/583] Add slider ball --- .../Skinning/Argon/ArgonSliderBall.cs | 55 +++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 6 ++ 2 files changed, 61 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs new file mode 100644 index 0000000000..5c31f079d4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs @@ -0,0 +1,55 @@ +// 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.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonSliderBall : CircularContainer + { + private readonly Box fill; + private readonly SpriteIcon icon; + + public ArgonSliderBall() + { + Size = new Vector2(ArgonMainCirclePiece.OUTER_GRADIENT_SIZE); + + Masking = true; + + BorderThickness = ArgonMainCirclePiece.BORDER_THICKNESS * 2; + BorderColour = Color4.White; + + InternalChildren = new Drawable[] + { + fill = new Box + { + Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + icon = new SpriteIcon + { + Size = new Vector2(48), + Scale = new Vector2(0.6f, 0.8f), + Icon = FontAwesome.Solid.AngleRight, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + + protected override void Update() + { + base.Update(); + + fill.Rotation = -Rotation; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index d257271455..de55fd5743 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -33,6 +33,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.SliderBody: return new ArgonSliderBody(); + + case OsuSkinComponents.SliderBall: + return new ArgonSliderBall(); + + case OsuSkinComponents.SliderScorePoint: + return new ArgonSliderScorePoint(); } break; From d4e2f70f877910d48e69349d2f2e7686cd57384b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 14:59:03 +0900 Subject: [PATCH 247/583] Add slider score point --- .../Skinning/Argon/ArgonSliderScorePoint.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs new file mode 100644 index 0000000000..1ab546b613 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs @@ -0,0 +1,40 @@ +// 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; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonSliderScorePoint : CircularContainer + { + private Bindable accentColour = null!; + + private const float size = 12; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject hitObject) + { + Masking = true; + Origin = Anchor.Centre; + Size = new Vector2(size); + BorderThickness = 3; + BorderColour = Color4.White; + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + }; + + accentColour = hitObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(accent => BorderColour = accent.NewValue); + } + } +} From 3c0983b0dbc95ebcc4ea8d7717793cf50a78cbad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 15:39:08 +0900 Subject: [PATCH 248/583] Add slider reverse arrow --- .../Skinning/Argon/ArgonReverseArrow.cs | 54 +++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 3 ++ 2 files changed, 57 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs new file mode 100644 index 0000000000..3f13fe135e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -0,0 +1,54 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonReverseArrow : CompositeDrawable + { + private Bindable accentColour = null!; + + private SpriteIcon icon = null!; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject hitObject) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + + InternalChildren = new Drawable[] + { + new Circle + { + Size = new Vector2(40, 20), + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + icon = new SpriteIcon + { + Icon = FontAwesome.Solid.AngleDoubleRight, + Size = new Vector2(16), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + + accentColour = hitObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index de55fd5743..dcd198c700 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.SliderScorePoint: return new ArgonSliderScorePoint(); + + case OsuSkinComponents.ReverseArrow: + return new ArgonReverseArrow(); } break; From 4f1530c2dbc8abbde9a3ab1813085b69c7aca996 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 16:07:13 +0900 Subject: [PATCH 249/583] Add slider follow circle --- .../Skinning/Argon/ArgonFollowCircle.cs | 71 +++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 3 + 2 files changed, 74 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs new file mode 100644 index 0000000000..83c5f6295a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs @@ -0,0 +1,71 @@ +// 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.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonFollowCircle : FollowCircle + { + public ArgonFollowCircle() + { + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 4, + BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), + Blending = BlendingParameters.Additive, + Child = new Box + { + Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), + RelativeSizeAxes = Axes.Both, + Alpha = 0.3f, + } + }; + } + + protected override void OnSliderPress() + { + const float duration = 300f; + + if (Precision.AlmostEquals(0, Alpha)) + this.ScaleTo(1); + + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) + .FadeIn(duration, Easing.OutQuint); + } + + protected override void OnSliderRelease() + { + const float duration = 150; + + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint) + .FadeTo(0, duration, Easing.OutQuint); + } + + protected override void OnSliderEnd() + { + const float duration = 300; + + this.ScaleTo(1, duration, Easing.OutQuint) + .FadeOut(duration / 2, Easing.OutQuint); + } + + protected override void OnSliderTick() + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + } + + protected override void OnSliderBreak() + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index dcd198c700..6815124056 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -37,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.SliderBall: return new ArgonSliderBall(); + case OsuSkinComponents.SliderFollowCircle: + return new ArgonFollowCircle(); + case OsuSkinComponents.SliderScorePoint: return new ArgonSliderScorePoint(); From edc624c4beb63114772cc9e2de80c85bff91af16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 16:07:16 +0900 Subject: [PATCH 250/583] Tweak slider ball fade to be less present after slider ends --- .../Skinning/Argon/ArgonSliderBall.cs | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs index 5c31f079d4..ae5c75549c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs @@ -1,11 +1,14 @@ // 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.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -16,6 +19,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly Box fill; private readonly SpriteIcon icon; + private readonly Vector2 defaultIconScale = new Vector2(0.6f, 0.8f); + + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public ArgonSliderBall() { Size = new Vector2(ArgonMainCirclePiece.OUTER_GRADIENT_SIZE); @@ -37,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon icon = new SpriteIcon { Size = new Vector2(48), - Scale = new Vector2(0.6f, 0.8f), + Scale = defaultIconScale, Icon = FontAwesome.Solid.AngleRight, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,11 +53,57 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _) + { + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which in this case have no visual impact (due to + // instant fade) but may negatively affect performance + if (drawableObject is not DrawableSlider) + return; + + const float duration = 200; + const float icon_scale = 0.9f; + + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) + { + this.FadeInFromZero(duration, Easing.OutQuint); + icon.ScaleTo(0).Then().ScaleTo(defaultIconScale, duration, Easing.OutElasticHalf); + } + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + this.FadeOut(duration, Easing.OutQuint); + icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint); + } + } + protected override void Update() { base.Update(); - fill.Rotation = -Rotation; + //undo rotation on layers which should not be rotated. + float appliedRotation = Parent.Rotation; + + fill.Rotation = -appliedRotation; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From 4eae5f87331ac25aa6a8e0b3ead84be786051157 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 16:14:35 +0900 Subject: [PATCH 251/583] Add back outer fill for hitcircles (but not slider head circles) --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 11 +++++++++-- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index e4ed3d8fe8..9390c60977 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon public const float OUTER_GRADIENT_SIZE = OsuHitObject.OBJECT_RADIUS * 2 - BORDER_THICKNESS * 3; + private readonly Circle outerFill; private readonly Circle outerGradient; private readonly Circle innerGradient; private readonly Circle innerFill; @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; - public ArgonMainCirclePiece() + public ArgonMainCirclePiece(bool withOuterFill) { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -52,6 +53,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon InternalChildren = new Drawable[] { + outerFill = new Circle // renders white outer border and dark fill + { + Size = Size, + Alpha = withOuterFill ? 1 : 0, + }, outerGradient = new Circle // renders the outer bright gradient { Size = new Vector2(OUTER_GRADIENT_SIZE), @@ -101,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon accentColour.BindValueChanged(colour => { - innerFill.Colour = colour.NewValue.Darken(4); + outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); flash.Colour = colour.NewValue; @@ -134,6 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // The fill layers add too much noise during the explosion animation. // They will be hidden by the additive effects anyway. + outerFill.FadeOut(flash_in_duration, Easing.OutQuint); innerFill.FadeOut(flash_in_duration, Easing.OutQuint); // The inner-most gradient should actually be resizing, but is only visible for diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 6815124056..8a1bf00877 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -28,8 +28,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon switch (osuComponent.Component) { case OsuSkinComponents.HitCircle: + return new ArgonMainCirclePiece(true); + case OsuSkinComponents.SliderHeadHitCircle: - return new ArgonMainCirclePiece(); + return new ArgonMainCirclePiece(false); case OsuSkinComponents.SliderBody: return new ArgonSliderBody(); From c8206b747b1488a65dc8d6b5f457a828648ced82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 17:05:36 +0900 Subject: [PATCH 252/583] Fix border / slider body metrics --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 15 +++++++++------ .../Skinning/Argon/ArgonSliderBall.cs | 2 +- .../Skinning/Argon/ArgonSliderBody.cs | 14 ++++++++------ .../Skinning/Default/DrawableSliderPath.cs | 4 ++-- .../Skinning/Default/RingPiece.cs | 2 -- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 9390c60977..0479b3ff3e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -23,9 +23,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { public class ArgonMainCirclePiece : CompositeDrawable { - public const float BORDER_THICKNESS = 7; + public const float BORDER_THICKNESS = (OsuHitObject.OBJECT_RADIUS * 2) * (2f / 58); - public const float OUTER_GRADIENT_SIZE = OsuHitObject.OBJECT_RADIUS * 2 - BORDER_THICKNESS * 3; + public const float GRADIENT_THICKNESS = BORDER_THICKNESS * 2.5f; + + public const float OUTER_GRADIENT_SIZE = (OsuHitObject.OBJECT_RADIUS * 2) - BORDER_THICKNESS * 4; + + public const float INNER_GRADIENT_SIZE = OUTER_GRADIENT_SIZE - GRADIENT_THICKNESS * 2; + public const float INNER_FILL_SIZE = INNER_GRADIENT_SIZE - GRADIENT_THICKNESS * 2; private readonly Circle outerFill; private readonly Circle outerGradient; @@ -49,8 +54,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre; Origin = Anchor.Centre; - const float fill_thickness = 24; - InternalChildren = new Drawable[] { outerFill = new Circle // renders white outer border and dark fill @@ -67,14 +70,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon }, innerGradient = new Circle // renders the inner bright gradient { - Size = new Vector2(OUTER_GRADIENT_SIZE - fill_thickness), + Size = new Vector2(INNER_GRADIENT_SIZE), Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerFill = new Circle // renders the inner dark fill { - Size = new Vector2(OUTER_GRADIENT_SIZE - 2 * fill_thickness), + Size = new Vector2(INNER_FILL_SIZE), Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs index ae5c75549c..3df9edd225 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Masking = true; - BorderThickness = ArgonMainCirclePiece.BORDER_THICKNESS * 2; + BorderThickness = ArgonMainCirclePiece.GRADIENT_THICKNESS; BorderColour = Color4.White; InternalChildren = new Drawable[] diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs index 115e153f34..e1642d126d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs @@ -11,15 +11,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { protected override void LoadComplete() { + const float path_radius = ArgonMainCirclePiece.OUTER_GRADIENT_SIZE / 2; + base.LoadComplete(); - AccentColourBindable.BindValueChanged(accent => - { - BorderColour = accent.NewValue; - }, true); - ScaleBindable.BindValueChanged(scale => PathRadius = ArgonMainCirclePiece.OUTER_GRADIENT_SIZE / 2 * scale.NewValue, true); + AccentColourBindable.BindValueChanged(accent => BorderColour = accent.NewValue, true); + ScaleBindable.BindValueChanged(scale => PathRadius = path_radius * scale.NewValue, true); - BorderSize = ArgonMainCirclePiece.BORDER_THICKNESS / 4; + // This border size thing is kind of weird, hey. + const float intended_thickness = ArgonMainCirclePiece.GRADIENT_THICKNESS / path_radius; + + BorderSize = intended_thickness / Default.DrawableSliderPath.BORDER_PORTION; } protected override Default.DrawableSliderPath CreateSliderPath() => new DrawableSliderPath(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DrawableSliderPath.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DrawableSliderPath.cs index 94f93807d4..e3a83a9280 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DrawableSliderPath.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DrawableSliderPath.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public abstract class DrawableSliderPath : SmoothPath { - protected const float BORDER_PORTION = 0.128f; - protected const float GRADIENT_PORTION = 1 - BORDER_PORTION; + public const float BORDER_PORTION = 0.128f; + public const float GRADIENT_PORTION = 1 - BORDER_PORTION; private const float border_max_size = 8f; private const float border_min_size = 0f; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs index c14246e90e..e813a7e274 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; From 70d60a7e732d622f5e2068a6f43ce567b7c58a4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 17:18:14 +0900 Subject: [PATCH 253/583] Update import skin test in line with new default --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 3cbc205eaf..5c20f46787 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Skins.IO skinManager.CurrentSkinInfo.Value.PerformRead(s => { Assert.IsFalse(s.Protected); - Assert.AreEqual(typeof(TrianglesSkin), s.CreateInstance(skinManager).GetType()); + Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType()); new LegacySkinExporter(osu.Dependencies.Get()).ExportModelTo(s, exportStream); @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Skins.IO { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); - Assert.AreEqual(typeof(TrianglesSkin), s.CreateInstance(skinManager).GetType()); + Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType()); }); return Task.CompletedTask; From b55aa68177b925b27b2147ace222ec052fc77e1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 22:46:55 +0900 Subject: [PATCH 254/583] Remove xmldoc on private fields --- osu.Game/Skinning/SkinManager.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 059767d8f8..0e66278fc0 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -39,6 +39,11 @@ namespace osu.Game.Skinning [ExcludeFromDynamicCompile] public class SkinManager : ModelManager, ISkinSource, IStorageResourceProvider, IModelImporter { + /// + /// The default "classic" skin. + /// + public Skin DefaultClassicSkin { get; } + private readonly AudioManager audio; private readonly Scheduler scheduler; @@ -55,21 +60,10 @@ namespace osu.Game.Skinning private readonly IResourceStore userFiles; - /// - /// The default "triangles" skin. - /// private Skin argonSkin { get; } - /// - /// The default skin (old). - /// private Skin trianglesSkin { get; } - /// - /// The default "classic" skin. - /// - public Skin DefaultClassicSkin { get; } - public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) : base(storage, realm) { From 03cc6b8af3ef954b46c9ba467bc60e4c8a9c4785 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Sep 2022 23:44:03 +0900 Subject: [PATCH 255/583] Fix parameter name --- osu.Game/Localisation/GeneralSettingsStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 8506971756..3278b20983 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Localisation /// /// "You are running the latest release ({0})" /// - public static LocalisableString RunningLatestRelease(string arg0) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", arg0); + public static LocalisableString RunningLatestRelease(string version) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", version); private static string getKey(string key) => $"{prefix}:{key}"; } From 46db3ad96d0e1cd6ba4176b9b474cb79a338965d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 00:06:02 +0900 Subject: [PATCH 256/583] Move implementation to individual classes --- osu.Game/Screens/Play/Player.cs | 26 ++++++++++++----------- osu.Game/Screens/Play/ReplayPlayer.cs | 10 +++++++++ osu.Game/Screens/Play/SoloPlayer.cs | 10 +++++++++ osu.Game/Screens/Select/PlaySongSelect.cs | 17 ++++++++++----- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e1530d7ea2..e31e18046e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -832,23 +833,24 @@ namespace osu.Game.Screens.Play HUDOverlay.HoldingForHUD.BindValueChanged(_ => updateLeaderboardExpandedState()); LocalUserPlaying.BindValueChanged(_ => updateLeaderboardExpandedState(), true); - LoadComponentAsync(CreateGameplayLeaderboard(), leaderboard => + var gameplayLeaderboard = CreateGameplayLeaderboard(); + + if (gameplayLeaderboard != null) { - if (!LoadedBeatmapSuccessfully) - return; + LoadComponentAsync(gameplayLeaderboard, leaderboard => + { + if (!LoadedBeatmapSuccessfully) + return; - leaderboard.Expanded.BindTo(LeaderboardExpandedState); + leaderboard.Expanded.BindTo(LeaderboardExpandedState); - AddLeaderboardToHUD(leaderboard); - }); + AddLeaderboardToHUD(leaderboard); + }); + } } - public readonly BindableList LeaderboardScores = new BindableList(); - - protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User) - { - Scores = { BindTarget = LeaderboardScores } - }; + [CanBeNull] + protected virtual GameplayLeaderboard CreateGameplayLeaderboard() => null; protected virtual void AddLeaderboardToHUD(GameplayLeaderboard leaderboard) => HUDOverlay.LeaderboardFlow.Add(leaderboard); diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index e82238945b..163b9c593f 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Bindables; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -14,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play @@ -55,6 +57,14 @@ namespace osu.Game.Screens.Play // Don't re-import replay scores as they're already present in the database. protected override Task ImportScore(Score score) => Task.CompletedTask; + public readonly BindableList LeaderboardScores = new BindableList(); + + protected override GameplayLeaderboard CreateGameplayLeaderboard() => + new SoloGameplayLeaderboard(Score.ScoreInfo.User) + { + Scores = { BindTarget = LeaderboardScores } + }; + protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); public bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 565f256277..a36697c96e 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -5,12 +5,14 @@ using System; using System.Diagnostics; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Screens.Play { @@ -40,6 +42,14 @@ namespace osu.Game.Screens.Play return new CreateSoloScoreRequest(Beatmap.Value.BeatmapInfo, rulesetId, Game.VersionHash); } + public readonly BindableList LeaderboardScores = new BindableList(); + + protected override GameplayLeaderboard CreateGameplayLeaderboard() => + new SoloGameplayLeaderboard(Score.ScoreInfo.User) + { + Scores = { BindTarget = LeaderboardScores } + }; + protected override bool HandleTokenRetrievalFailure(Exception exception) => false; protected override APIRequest CreateSubmissionRequest(Score score, long token) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 068f78172b..94e4215175 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Screens; @@ -115,11 +114,19 @@ namespace osu.Game.Screens.Select var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); if (replayGeneratingMod != null) - player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)); + { + player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)) + { + LeaderboardScores = { BindTarget = playBeatmapDetailArea.Leaderboard.Scores } + }; + } else - player = new SoloPlayer(); - - ((IBindableList)player.LeaderboardScores).BindTo(playBeatmapDetailArea.Leaderboard.Scores); + { + player = new SoloPlayer + { + LeaderboardScores = { BindTarget = playBeatmapDetailArea.Leaderboard.Scores } + }; + } return player; } From 1c02fa839965ce673f823c4dc2e3def7caa2d181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 00:08:06 +0900 Subject: [PATCH 257/583] Update comment to not mention horizontal plane --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3e661e76f4..3fbb051c3b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -197,7 +197,7 @@ namespace osu.Game.Screens.Play // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. foreach (var element in mainComponents.Components.Cast()) { - // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. + // for now align some top components with the bottom-edge of the lowest top-anchored hud element. if (element.Anchor.HasFlagFast(Anchor.y0)) { // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. From 18fe37bb222c6a296a866da22ee569be46d36ea4 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Mon, 19 Sep 2022 17:25:35 +0200 Subject: [PATCH 258/583] Reverted changes --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 11fa751daf..956d0e0c14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -108,14 +108,14 @@ namespace osu.Game.Rulesets.Catch.Tests float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; AddStep("catch fruit", () => { - attemptCatch(new Fruit { OriginalX = -halfWidth + 1 }); - attemptCatch(new Fruit { OriginalX = halfWidth - 1 }); + attemptCatch(new Fruit { X = -halfWidth + 1 }); + attemptCatch(new Fruit { X = halfWidth - 1 }); }); checkPlate(2); AddStep("miss fruit", () => { - attemptCatch(new Fruit { OriginalX = -halfWidth - 1 }); - attemptCatch(new Fruit { OriginalX = halfWidth + 1 }); + attemptCatch(new Fruit { X = -halfWidth - 1 }); + attemptCatch(new Fruit { X = halfWidth + 1 }); }); checkPlate(2); } From 4958421303567ec8817cc340ae78102ae2602ec8 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Mon, 19 Sep 2022 17:26:04 +0200 Subject: [PATCH 259/583] Moved X clamping from X to EffectiveX --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index c6d63f6b81..cd2b8348e2 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects [JsonIgnore] public float X { - set => originalX.Value = Math.Clamp(value, 0, CatchPlayfield.WIDTH); + set => originalX.Value = value; } private HitObjectProperty xOffset; @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// This value is the original value plus the offset applied by the beatmap processing. /// Use if a value not affected by the offset is desired. /// - public float EffectiveX => OriginalX + XOffset; + public float EffectiveX => Math.Clamp(OriginalX + XOffset, 0, CatchPlayfield.WIDTH); public double TimePreempt { get; set; } = 1000; From 74056201e7903fd572bd1a4a9f580cd74db2c78a Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 19 Sep 2022 09:15:59 -0700 Subject: [PATCH 260/583] Return smoke key to key overlay --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 14 -------------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 6500ade7ea..9332aa679a 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -57,20 +57,6 @@ namespace osu.Game.Rulesets.Osu return base.HandleMouseTouchStateChange(e); } - public override void Attach(KeyCounterDisplay keyCounter) - { - var receptor = new ActionReceptor(keyCounter); - - KeyBindingContainer.Add(receptor); - - keyCounter.SetReceptor(receptor); - keyCounter.AddRange(new[] - { - new KeyCounterAction(OsuAction.LeftButton), - new KeyCounterAction(OsuAction.RightButton), - }); - } - private class OsuKeyBindingContainer : RulesetKeyBindingContainer { public bool AllowUserPresses = true; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index ce87d8b6c0..1a97153f2f 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public virtual void Attach(KeyCounterDisplay keyCounter) + public void Attach(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); From c3b8e1d718a8e5c3bc259a752d39aa632a2359dc Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 19 Sep 2022 10:16:05 -0700 Subject: [PATCH 261/583] Fix test and spawn smoke immediately --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 1 - osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 14 +++++++++----- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 4 ++++ .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 9332aa679a..dec965e567 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -10,7 +10,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu { diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index bada67816b..7359f6029e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -166,17 +166,21 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); + Anchor = Anchor.TopLeft; + Origin = Anchor.TopLeft; + + SmokeStartTime = Time.Current; + + totalDistance = PointInterval; + if (smokeContainer != null) { smokeContainer.SmokeMoved += onSmokeMoved; smokeContainer.SmokeEnded += onSmokeEnded; IsActive = true; + + onSmokeMoved(smokeContainer.LastMousePosition, Time.Current); } - - Anchor = Anchor.TopLeft; - Origin = Anchor.TopLeft; - - SmokeStartTime = Time.Current; } private Vector2 nextPointDirection() diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 07a073c3e5..2b5bddf959 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.UI public event Action? SmokeMoved; public event Action? SmokeEnded; + public Vector2 LastMousePosition; + private bool isSmoking; public override bool ReceivePositionalInputAt(Vector2 _) => true; @@ -50,6 +52,8 @@ namespace osu.Game.Rulesets.Osu.UI if (isSmoking) SmokeMoved?.Invoke(e.MousePosition, Time.Current); + LastMousePosition = e.MousePosition; + return base.OnMouseMove(e); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 0a32513834..bd55ed8bd6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); addSeekStep(3000); AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15); AddStep("clear results", () => Player.Results.Clear()); addSeekStep(0); AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); From b0a3c9152a413a7467aceb9c0a2634e5dcc7f902 Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Mon, 19 Sep 2022 21:33:38 +0200 Subject: [PATCH 262/583] Enable snapping for slider control points in the editor --- .../Sliders/Components/PathControlPointVisualiser.cs | 9 ++++++++- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 ++- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index c24f78e430..9de62d1a46 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -307,7 +307,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var controlPoint = controlPoints[i]; if (selectedControlPoints.Contains(controlPoint)) - controlPoint.Position = dragStartPositions[i] + (e.MousePosition - e.MouseDownPosition); + { + Vector2 newPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); + var result = snapProvider?.FindSnappedPositionAndTime(newPosition); + + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newPosition) - slider.Position; + + controlPoint.Position = dragStartPositions[i] + movementDelta; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index e2f98c273e..dd5335a743 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -198,7 +198,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Update the cursor position. - cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position); + cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; } else if (cursor != null) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index eb69efd636..5673dab7e8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -163,7 +163,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnDrag(DragEvent e) { if (placementControlPoint != null) - placementControlPoint.Position = e.MousePosition - HitObject.Position; + { + var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition)); + placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? e.MousePosition) - HitObject.Position; + } } protected override void OnMouseUp(MouseUpEvent e) From a9c120c2145ecba824d511dcc94bca3367f05640 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 12:46:35 +0900 Subject: [PATCH 263/583] Reduce volume requirement for showing muted notification to 1% --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6373633b5a..1491567cab 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -502,7 +502,7 @@ namespace osu.Game.Screens.Play private int restartCount; - private const double volume_requirement = 0.05; + private const double volume_requirement = 0.01; private void showMuteWarningIfNeeded() { From b5771ece41ca51db56c6fd57ca63c4d7e2282fa6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 12:52:04 +0900 Subject: [PATCH 264/583] Reduce the amount by which clicking the mute notification incraeses volume --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1d101383cc..6b24ac7384 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -264,13 +264,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationMasterVolume() { - addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault); + addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.Value == 0.5); } [Test] public void TestMutedNotificationTrackVolume() { - addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault); + addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.Value == 0.5); } [Test] diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 1491567cab..e32d3d90be 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -539,10 +539,11 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. + // Note that we only restore halfway to ensure the user isn't suddenly overloaded by unexpectedly high volume. if (audioManager.Volume.Value <= volume_requirement) - audioManager.Volume.SetDefault(); + audioManager.Volume.Value = 0.5f; if (audioManager.VolumeTrack.Value <= volume_requirement) - audioManager.VolumeTrack.SetDefault(); + audioManager.VolumeTrack.Value = 0.5f; return true; }; From d22d009fb33a97df29dc1b9759c3c625a95259c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=BA=E3=82=BF=E3=82=B1?= Date: Tue, 20 Sep 2022 14:02:11 +0900 Subject: [PATCH 265/583] fix review points. items not included in this localization were reverted. --- .../MaintenanceSettingsStrings.cs | 30 ------------------- .../MassDeleteConfirmationDialog.cs | 3 +- .../MassVideoDeleteConfirmationDialog.cs | 3 +- .../StableDirectoryLocationDialog.cs | 9 +++--- .../StableDirectorySelectScreen.cs | 3 +- 5 files changed, 7 insertions(+), 41 deletions(-) diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 4648682e64..8aa0adf7a0 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -134,36 +134,6 @@ namespace osu.Game.Localisation /// public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!"); - /// - /// "Everything?" - /// - public static LocalisableString MassDeleteConfirmation => new TranslatableString(getKey(@"mass_delete_confirmation"), @"Everything?"); - - /// - /// "All beatmap videos? This cannot be undone!" - /// - public static LocalisableString MassVideoDeleteConfirmation => new TranslatableString(getKey(@"mass_video_delete_confirmation"), @"All beatmap videos? This cannot be undone!"); - - /// - /// "Failed to automatically locate an osu!stable installation." - /// - public static LocalisableString StableDirectoryLocationHeader => new TranslatableString(getKey(@"stable_directory_location_header"), @"Failed to automatically locate an osu!stable installation."); - - /// - /// "An existing install could not be located. If you know where it is, you can help locate it." - /// - public static LocalisableString StableDirectoryLocationBody => new TranslatableString(getKey(@"stable_directory_location_body"), @"An existing install could not be located. If you know where it is, you can help locate it."); - - /// - /// "Sure! I know where it is located!" - /// - public static LocalisableString StableDirectoryLocationOk => new TranslatableString(getKey(@"stable_directory_location_ok"), @"Sure! I know where it is located!"); - - /// - /// "Actually I don't have osu!stable installed." - /// - public static LocalisableString StableDirectoryLocationCancel => new TranslatableString(getKey(@"stable_directory_location_cancel"), @"Actually I don't have osu!stable installed."); - /// /// "Please select your osu!stable install location" /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs index bcfccaa5e2..19e6f83dac 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Localisation; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance @@ -11,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public MassDeleteConfirmationDialog(Action deleteAction) { - BodyText = MaintenanceSettingsStrings.MassDeleteConfirmation; + BodyText = "Everything?"; DeleteAction = deleteAction; } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs index a386c64806..fc8c9d497b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -11,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public MassVideoDeleteConfirmationDialog(Action deleteAction) : base(deleteAction) { - BodyText = MaintenanceSettingsStrings.MassVideoDeleteConfirmation; + BodyText = "All beatmap videos? This cannot be undone!"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs index 7b7ea7cee0..8aff4520b5 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectoryLocationDialog.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; -using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Screens; @@ -20,20 +19,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public StableDirectoryLocationDialog(TaskCompletionSource taskCompletionSource) { - HeaderText = MaintenanceSettingsStrings.StableDirectoryLocationHeader; - BodyText = MaintenanceSettingsStrings.StableDirectoryLocationBody; + HeaderText = "Failed to automatically locate an osu!stable installation."; + BodyText = "An existing install could not be located. If you know where it is, you can help locate it."; Icon = FontAwesome.Solid.QuestionCircle; Buttons = new PopupDialogButton[] { new PopupDialogOkButton { - Text = MaintenanceSettingsStrings.StableDirectoryLocationOk, + Text = "Sure! I know where it is located!", Action = () => Schedule(() => performer.PerformFromScreen(screen => screen.Push(new StableDirectorySelectScreen(taskCompletionSource)))) }, new PopupDialogCancelButton { - Text = MaintenanceSettingsStrings.StableDirectoryLocationCancel, + Text = "Actually I don't have osu!stable installed.", Action = () => taskCompletionSource.TrySetCanceled() } }; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 22cf2e7076..047d589689 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Localisation; using osu.Framework.Screens; -using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -20,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override bool IsValidDirectory(DirectoryInfo info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; - public override LocalisableString HeaderText => MaintenanceSettingsStrings.StableDirectorySelectHeader; + public override LocalisableString HeaderText => "Please select your osu!stable install location"; public StableDirectorySelectScreen(TaskCompletionSource taskCompletionSource) { From 700000b583d4beb19b537a5e25d17c38fb54d64f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 15:51:56 +0900 Subject: [PATCH 266/583] Use custom notification with timer --- .../Online/Multiplayer/MultiplayerClient.cs | 7 +-- .../Multiplayer/ServerShutdownNotification.cs | 49 +++++++++++++++++++ osu.Game/Utils/HumanizerUtils.cs | 23 +++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/ServerShutdownNotification.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f9236cbfac..75334952f0 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -589,12 +589,7 @@ namespace osu.Game.Online.Multiplayer if (countdown == null) return; - PostNotification?.Invoke(new SimpleNotification - { - Text = countdown.FinalNotification - ? $"The multiplayer server is restarting in {countdown.TimeRemaining:hh\\:mm\\:ss}. This multiplayer room will be closed shortly." - : $"The multiplayer server is restarting in {countdown.TimeRemaining:hh\\:mm\\:ss}." - }); + PostNotification?.Invoke(new ServerShutdownNotification(countdown.TimeRemaining)); } Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability) diff --git a/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs b/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs new file mode 100644 index 0000000000..dc61fe4ce5 --- /dev/null +++ b/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs @@ -0,0 +1,49 @@ +// 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 Humanizer.Localisation; +using osu.Framework.Allocation; +using osu.Game.Overlays.Notifications; +using osu.Game.Utils; + +namespace osu.Game.Online.Multiplayer +{ + public class ServerShutdownNotification : SimpleNotification + { + private readonly DateTimeOffset endDate; + + public ServerShutdownNotification(TimeSpan duration) + { + endDate = DateTimeOffset.UtcNow + duration; + } + + [BackgroundDependencyLoader] + private void load() + { + updateTime(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Scheduler.Add(updateTimeWithReschedule); + } + + private void updateTimeWithReschedule() + { + updateTime(); + + // The remaining time on a countdown may be at a fractional portion between two seconds. + // We want to align certain audio/visual cues to the point at which integer seconds change. + // To do so, we schedule to the next whole second. Note that scheduler invocation isn't + // guaranteed to be accurate, so this may still occur slightly late, but even in such a case + // the next invocation will be roughly correct. + double timeToNextSecond = endDate.Subtract(DateTimeOffset.UtcNow).TotalMilliseconds % 1000; + + Scheduler.AddDelayed(updateTimeWithReschedule, timeToNextSecond); + } + + private void updateTime() => Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(endDate.Subtract(DateTimeOffset.Now), precision: 2, minUnit: TimeUnit.Second)}."; + } +} diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 5b7c3630d9..0da346ed73 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using Humanizer; +using Humanizer.Localisation; namespace osu.Game.Utils { @@ -26,5 +27,27 @@ namespace osu.Game.Utils return input.Humanize(culture: new CultureInfo("en-US")); } } + + /// + /// Turns the current or provided timespan into a human readable sentence + /// + /// The date to be humanized + /// The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned + /// The maximum unit of time to output. The default value is . The time units and will give approximations for time spans bigger 30 days by calculating with 365.2425 days a year and 30.4369 days a month. + /// The minimum unit of time to output. + /// Uses words instead of numbers if true. E.g. one day. + /// distance of time in words + public static string Humanize(TimeSpan input, int precision = 1, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond, bool toWords = false) + { + // this works around https://github.com/xamarin/xamarin-android/issues/2012 and https://github.com/Humanizr/Humanizer/issues/690#issuecomment-368536282 + try + { + return input.Humanize(precision: precision, maxUnit: maxUnit, minUnit: minUnit); + } + catch (ArgumentException) + { + return input.Humanize(culture: new CultureInfo("en-US"), precision: precision, maxUnit: maxUnit, minUnit: minUnit); + } + } } } From ef29987f362bc51c39b43af4f4d6d4ca7f721652 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 15:52:20 +0900 Subject: [PATCH 267/583] Remove FinalNotification --- osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs b/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs index 4def3acc5e..b0a45dc768 100644 --- a/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs +++ b/osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs @@ -11,10 +11,5 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] public class ServerShuttingDownCountdown : MultiplayerCountdown { - /// - /// If this is the final notification, no more events will be sent after this. - /// - [Key(2)] - public bool FinalNotification { get; set; } } } From b84f716c22f192ae67907232715c7ae3d87ae67b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 16:02:28 +0900 Subject: [PATCH 268/583] Display seconds when hours>0 to be more lively --- osu.Game/Online/Multiplayer/ServerShutdownNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs b/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs index dc61fe4ce5..7241341bc9 100644 --- a/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs +++ b/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs @@ -44,6 +44,6 @@ namespace osu.Game.Online.Multiplayer Scheduler.AddDelayed(updateTimeWithReschedule, timeToNextSecond); } - private void updateTime() => Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(endDate.Subtract(DateTimeOffset.Now), precision: 2, minUnit: TimeUnit.Second)}."; + private void updateTime() => Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(endDate.Subtract(DateTimeOffset.Now), precision: 3, minUnit: TimeUnit.Second)}."; } } From 68c040175a4f026ca450f07fa060f9b86d16c962 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 17:01:44 +0900 Subject: [PATCH 269/583] Ensure `Leaderboard.Scores` is updated immediately via request flow --- osu.Game/Online/Leaderboards/Leaderboard.cs | 22 +++++++++++++------ .../Select/Leaderboards/BeatmapLeaderboard.cs | 13 +++++------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9b6e9fbec7..d5834c4329 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -185,14 +185,22 @@ namespace osu.Game.Online.Leaderboards if (scores != null) this.scores.AddRange(scores); - userScoreContainer.Score.Value = userScore; + // Schedule needs to be non-delayed here for the weird logic in refetchScores to work. + // If it is removed, the placeholder will be incorrectly updated to "no scores" rather than "retrieving". + // This whole flow should be refactored in the future. + Scheduler.Add(applyNewScores, false); - if (userScore == null) - userScoreContainer.Hide(); - else - userScoreContainer.Show(); + void applyNewScores() + { + userScoreContainer.Score.Value = userScore; - Scheduler.Add(updateScoresDrawables, false); + if (userScore == null) + userScoreContainer.Hide(); + else + userScoreContainer.Show(); + + updateScoresDrawables(); + } } /// @@ -212,8 +220,8 @@ namespace osu.Game.Online.Leaderboards Debug.Assert(ThreadSafety.IsUpdateThread); cancelPendingWork(); - SetScores(null); + SetScores(null); setState(LeaderboardState.Retrieving); currentFetchCancellationSource = new CancellationTokenSource(); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index ed4da5e848..798cf29715 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -148,13 +148,10 @@ namespace osu.Game.Screens.Select.Leaderboards var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); - req.Success += r => Schedule(() => - { - SetScores( - scoreManager.OrderByTotalScore(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), - r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) - ); - }); + req.Success += r => SetScores( + scoreManager.OrderByTotalScore(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), + r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) + ); return req; } @@ -209,7 +206,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scoreManager.OrderByTotalScore(scores.Detach()); - Schedule(() => SetScores(scores)); + SetScores(scores); } } From e0f1f1a5e1fe26fa17402a57a8d432b7230e608c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 17:20:47 +0900 Subject: [PATCH 270/583] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 77c29a5d6e..4f69d27df6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 29e690a024..3c24250300 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 83410b08f6..b57256dec9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 90a05f4bed05cb3ed017e9fc3c92d04daddd816b Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 20 Sep 2022 01:40:20 -0700 Subject: [PATCH 271/583] Cap smoke on point count + omit invisible vertices --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 7359f6029e..70913b0851 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private float totalDistance; private Vector2? lastPosition; - private const double max_duration = 60_000; + private const int max_point_count = 72_000; public override float Height { @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Skinning lastPosition = position; - if (time - SmokeStartTime > max_duration) + if (SmokePoints.Count >= max_point_count) onSmokeEnded(time); } @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Osu.Skinning if (Points.Count == 0) return; - QuadBatch ??= renderer.CreateQuadBatch(7200, 10); + QuadBatch ??= renderer.CreateQuadBatch(max_point_count / 10, 10); Texture ??= renderer.WhitePixel; var shader = GetAppropriateShader(renderer); @@ -410,6 +410,9 @@ namespace osu.Game.Rulesets.Osu.Skinning var dir = PointDirection(point); var ortho = dir.PerpendicularLeft; + if (color.A == 0 || scale == 0) + return; + var localTopLeft = point.Position + (Radius * scale * (-ortho - dir)) - PositionOffset; var localTopRight = point.Position + (Radius * scale * (-ortho + dir)) - PositionOffset; var localBotLeft = point.Position + (Radius * scale * (ortho - dir)) - PositionOffset; From fc6ab9c6a96b7a7892e77ae8449eb7edc372375e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Sep 2022 17:42:39 +0900 Subject: [PATCH 272/583] Add test coverage of shutdown notifications --- .../UserInterface/TestSceneNotificationOverlay.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index e978b57ba4..4f980dc74f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -1,6 +1,7 @@ // 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; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Updater; @@ -422,6 +424,14 @@ namespace osu.Game.Tests.Visual.UserInterface AddRepeatStep("send barrage", sendBarrage, 10); } + [Test] + public void TestServerShuttingDownNotification() + { + AddStep("post with 5 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(5)))); + AddStep("post with 30 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(30)))); + AddStep("post with 6 hours", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromHours(6)))); + } + protected override void Update() { base.Update(); From 979f7f88a17d248d4db249e3caf99c3885d23001 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Tue, 20 Sep 2022 12:41:06 +0200 Subject: [PATCH 273/583] Fixed TestCatcherWidth and added TestFruitClampedToCatchableRegion --- .../TestSceneCatcher.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 956d0e0c14..732184a8a8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -106,20 +106,32 @@ namespace osu.Game.Rulesets.Catch.Tests public void TestCatcherCatchWidth() { float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; + const float center_x = CatchPlayfield.CENTER_X; + AddStep("move catcher to center", () => catcher.X = center_x); AddStep("catch fruit", () => { - attemptCatch(new Fruit { X = -halfWidth + 1 }); - attemptCatch(new Fruit { X = halfWidth - 1 }); + attemptCatch(new Fruit { X = center_x + -halfWidth + 1 }); + attemptCatch(new Fruit { X = center_x + halfWidth - 1 }); }); checkPlate(2); AddStep("miss fruit", () => { - attemptCatch(new Fruit { X = -halfWidth - 1 }); - attemptCatch(new Fruit { X = halfWidth + 1 }); + attemptCatch(new Fruit { X = center_x + -halfWidth - 1 }); + attemptCatch(new Fruit { X = center_x + halfWidth + 1 }); }); checkPlate(2); } + [Test] + public void TestFruitClampedToCatchableRegion() + { + AddStep("catch fruit left", () => attemptCatch(new Fruit { X = -CatchPlayfield.WIDTH })); + checkPlate(1); + AddStep("move catcher to right", () => catcher.X = CatchPlayfield.WIDTH); + AddStep("catch fruit right", () => attemptCatch(new Fruit { X = CatchPlayfield.WIDTH * 2 })); + checkPlate(2); + } + [Test] public void TestFruitChangesCatcherState() { From d777afc4542820c6df82e558536a9b8e16d6058b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 19:53:39 +0900 Subject: [PATCH 274/583] Remove countdown at under 5 seconds --- .../Multiplayer/ServerShutdownNotification.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs b/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs index 7241341bc9..c114741be8 100644 --- a/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs +++ b/osu.Game/Online/Multiplayer/ServerShutdownNotification.cs @@ -4,6 +4,7 @@ using System; using Humanizer.Localisation; using osu.Framework.Allocation; +using osu.Framework.Threading; using osu.Game.Overlays.Notifications; using osu.Game.Utils; @@ -12,6 +13,7 @@ namespace osu.Game.Online.Multiplayer public class ServerShutdownNotification : SimpleNotification { private readonly DateTimeOffset endDate; + private ScheduledDelegate? updateDelegate; public ServerShutdownNotification(TimeSpan duration) { @@ -27,7 +29,7 @@ namespace osu.Game.Online.Multiplayer protected override void LoadComplete() { base.LoadComplete(); - Scheduler.Add(updateTimeWithReschedule); + updateDelegate = Scheduler.Add(updateTimeWithReschedule); } private void updateTimeWithReschedule() @@ -41,9 +43,20 @@ namespace osu.Game.Online.Multiplayer // the next invocation will be roughly correct. double timeToNextSecond = endDate.Subtract(DateTimeOffset.UtcNow).TotalMilliseconds % 1000; - Scheduler.AddDelayed(updateTimeWithReschedule, timeToNextSecond); + updateDelegate = Scheduler.AddDelayed(updateTimeWithReschedule, timeToNextSecond); } - private void updateTime() => Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(endDate.Subtract(DateTimeOffset.Now), precision: 3, minUnit: TimeUnit.Second)}."; + private void updateTime() + { + TimeSpan remaining = endDate.Subtract(DateTimeOffset.Now); + + if (remaining.TotalSeconds <= 5) + { + updateDelegate?.Cancel(); + Text = "The multiplayer server will be right back..."; + } + else + Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(remaining, precision: 3, minUnit: TimeUnit.Second)}."; + } } } From 5d73de9021d947346786b356373b6cdd25833556 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:20:32 +0900 Subject: [PATCH 275/583] Perform matrix mults on the GPU --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 70913b0851..58ba8ba0b1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -366,14 +366,16 @@ namespace osu.Game.Rulesets.Osu.Skinning var shader = GetAppropriateShader(renderer); renderer.SetBlend(BlendingParameters.Additive); + renderer.PushLocalMatrix(DrawInfo.Matrix); shader.Bind(); Texture.Bind(); UpdateDrawVariables(renderer); - UpdateVertexBuffer(); + UpdateVertexBuffer(renderer); shader.Unbind(); + renderer.PopLocalMatrix(); } protected Color4 ColorAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour @@ -395,7 +397,7 @@ namespace osu.Game.Rulesets.Osu.Skinning TextureRect = Texture.GetTextureRect(); } - protected virtual void UpdateVertexBuffer() + protected virtual void UpdateVertexBuffer(IRenderer renderer) { foreach (var point in Points) drawPointQuad(point); @@ -420,25 +422,25 @@ namespace osu.Game.Rulesets.Osu.Skinning QuadBatch.Add(new TexturedVertex2D { - Position = Vector2Extensions.Transform(localTopLeft, DrawInfo.Matrix), + Position = localTopLeft, TexturePosition = TextureRect.TopLeft, Colour = Color4Extensions.Multiply(ColorAtPosition(localTopLeft), color), }); QuadBatch.Add(new TexturedVertex2D { - Position = Vector2Extensions.Transform(localTopRight, DrawInfo.Matrix), + Position = localTopRight, TexturePosition = TextureRect.TopRight, Colour = Color4Extensions.Multiply(ColorAtPosition(localTopRight), color), }); QuadBatch.Add(new TexturedVertex2D { - Position = Vector2Extensions.Transform(localBotRight, DrawInfo.Matrix), + Position = localBotRight, TexturePosition = TextureRect.BottomRight, Colour = Color4Extensions.Multiply(ColorAtPosition(localBotRight), color), }); QuadBatch.Add(new TexturedVertex2D { - Position = Vector2Extensions.Transform(localBotLeft, DrawInfo.Matrix), + Position = localBotLeft, TexturePosition = TextureRect.BottomLeft, Colour = Color4Extensions.Multiply(ColorAtPosition(localBotLeft), color), }); From f7962c993d88b9072d749cfad8814e43980b8afa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:36:44 +0900 Subject: [PATCH 276/583] Reduce the number of points --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 58ba8ba0b1..80341c5ef1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private float totalDistance; private Vector2? lastPosition; - private const int max_point_count = 72_000; + private const int max_point_count = 18_000; public override float Height { From 5d3c6efcc52ae54a2755d3bab0d95c948f1e74fa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:38:22 +0900 Subject: [PATCH 277/583] Dispose quad batch --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 80341c5ef1..c07c2f960f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -445,6 +445,12 @@ namespace osu.Game.Rulesets.Osu.Skinning Colour = Color4Extensions.Multiply(ColorAtPosition(localBotLeft), color), }); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + QuadBatch?.Dispose(); + } } } } From 9f23210e7efbea97cda8b56bb73786fb99b8dff3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:39:12 +0900 Subject: [PATCH 278/583] Use British-English --- .../Skinning/Default/DefaultSmoke.cs | 2 +- .../Skinning/Legacy/LegacySmoke.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 17 ++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index 6eebd18305..85d1018b7f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default fadeOutTime = SmokeStartTime + fade_out_speed * (CurrentTime - (SmokeEndTime + fade_out_delay)); } - protected override Color4 PointColor(SmokePoint point) + protected override Color4 PointColour(SmokePoint point) { var color = Color4.White; color.A = alpha; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index d8c3ff3521..d3ce294696 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy finalFadeOutTime = final_fade_out_speed * (CurrentTime - SmokeEndTime) + SmokeEndTime - initialFadeOutDurationTrunc * (1 + 1 / re_fade_in_speed); } - protected override Color4 PointColor(SmokePoint point) + protected override Color4 PointColour(SmokePoint point) { var color = Color4.White; diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index c07c2f960f..63a370981e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -315,7 +315,6 @@ namespace osu.Game.Rulesets.Osu.Skinning protected new Smoke Source => (Smoke)base.Source; protected IVertexBatch? QuadBatch; - protected readonly List Points = new List(); protected float Radius; @@ -378,11 +377,11 @@ namespace osu.Game.Rulesets.Osu.Skinning renderer.PopLocalMatrix(); } - protected Color4 ColorAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour + protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour ? ((SRGBColour)DrawColourInfo.Colour).Linear : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, DrawSize)).Linear; - protected abstract Color4 PointColor(SmokePoint point); + protected abstract Color4 PointColour(SmokePoint point); protected abstract float PointScale(SmokePoint point); @@ -407,12 +406,12 @@ namespace osu.Game.Rulesets.Osu.Skinning { Debug.Assert(QuadBatch != null); - var color = PointColor(point); + var colour = PointColour(point); float scale = PointScale(point); var dir = PointDirection(point); var ortho = dir.PerpendicularLeft; - if (color.A == 0 || scale == 0) + if (colour.A == 0 || scale == 0) return; var localTopLeft = point.Position + (Radius * scale * (-ortho - dir)) - PositionOffset; @@ -424,25 +423,25 @@ namespace osu.Game.Rulesets.Osu.Skinning { Position = localTopLeft, TexturePosition = TextureRect.TopLeft, - Colour = Color4Extensions.Multiply(ColorAtPosition(localTopLeft), color), + Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour), }); QuadBatch.Add(new TexturedVertex2D { Position = localTopRight, TexturePosition = TextureRect.TopRight, - Colour = Color4Extensions.Multiply(ColorAtPosition(localTopRight), color), + Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour), }); QuadBatch.Add(new TexturedVertex2D { Position = localBotRight, TexturePosition = TextureRect.BottomRight, - Colour = Color4Extensions.Multiply(ColorAtPosition(localBotRight), color), + Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour), }); QuadBatch.Add(new TexturedVertex2D { Position = localBotLeft, TexturePosition = TextureRect.BottomLeft, - Colour = Color4Extensions.Multiply(ColorAtPosition(localBotLeft), color), + Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour), }); } From ff6e4e3a9644c03c211a3221cd263ca4edc97732 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:42:12 +0900 Subject: [PATCH 279/583] Privatise setters --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 63a370981e..19891c823d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -314,19 +314,19 @@ namespace osu.Game.Rulesets.Osu.Skinning { protected new Smoke Source => (Smoke)base.Source; - protected IVertexBatch? QuadBatch; + protected IVertexBatch? QuadBatch { get; private set; } protected readonly List Points = new List(); - protected float Radius; - protected Vector2 DrawSize; - protected Vector2 PositionOffset; - protected Texture? Texture; + protected float Radius { get; private set; } + protected Vector2 DrawSize { get; private set; } + protected Vector2 PositionOffset { get; private set; } + protected Texture? Texture { get; private set; } - protected double SmokeStartTime; - protected double SmokeEndTime; - protected double CurrentTime; + protected double SmokeStartTime { get; private set; } + protected double SmokeEndTime { get; private set; } + protected double CurrentTime { get; private set; } - protected RectangleF TextureRect; + protected RectangleF TextureRect { get; private set; } private IFrameBasedClock? clock; From c28ed477e152c4dd1fb082b6aedb16bfffa7b6ec Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:54:49 +0900 Subject: [PATCH 280/583] Expose less stuff, clean up implementation --- .../Skinning/Default/DefaultSmoke.cs | 5 +- .../Skinning/Legacy/LegacySmoke.cs | 6 - osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 148 +++++------------- 3 files changed, 44 insertions(+), 115 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index 85d1018b7f..a3c5733eb4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Graphics.Rendering; using osuTK; using osuTK.Graphics; @@ -34,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { } - protected override void UpdateDrawVariables(IRenderer renderer) + public override void ApplyState() { - base.UpdateDrawVariables(renderer); + base.ApplyState(); fadeOutTime = SmokeStartTime + fade_out_speed * (CurrentTime - (SmokeEndTime + fade_out_delay)); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index d3ce294696..50669cf79a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Graphics.Rendering; using osu.Framework.Utils; using osu.Game.Skinning; using osuTK; @@ -99,11 +98,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); rotationSeed = Source.RotationSeed; - } - - protected override void UpdateDrawVariables(IRenderer renderer) - { - base.UpdateDrawVariables(renderer); rotationRNG = new Random(rotationSeed); initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime); diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 19891c823d..410ee7b4f0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; -using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.UI; using osuTK; @@ -41,57 +40,11 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - private Texture? texture; + protected Texture? Texture { get; set; } - protected Texture? Texture - { - get => texture; - set - { - texture = value; - Invalidate(Invalidation.DrawNode); - } - } + protected double SmokeStartTime { get; private set; } = double.MinValue; - private double smokeTimeStart = double.MinValue; - - protected double SmokeStartTime - { - get => smokeTimeStart; - private set - { - if (smokeTimeStart == value) - return; - - smokeTimeStart = value; - Invalidate(Invalidation.DrawNode); - } - } - - private double smokeTimeEnd = double.MaxValue; - - protected double SmokeEndTime - { - get => smokeTimeEnd; - private set - { - if (smokeTimeEnd == value) - return; - - smokeTimeEnd = value; - Invalidate(Invalidation.DrawNode); - } - } - - public override IFrameBasedClock Clock - { - get => base.Clock; - set - { - base.Clock = value; - Invalidate(Invalidation.DrawNode); - } - } + protected double SmokeEndTime { get; private set; } = double.MaxValue; private Vector2 topLeft; @@ -104,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return; topLeft = value; - Invalidate(); + Invalidate(Invalidation.Layout); } } @@ -277,6 +230,8 @@ namespace osu.Game.Rulesets.Osu.Skinning base.Update(); Position = TopLeft; + + Invalidate(Invalidation.DrawNode); } protected override void Dispose(bool isDisposing) @@ -314,21 +269,16 @@ namespace osu.Game.Rulesets.Osu.Skinning { protected new Smoke Source => (Smoke)base.Source; - protected IVertexBatch? QuadBatch { get; private set; } - protected readonly List Points = new List(); - - protected float Radius { get; private set; } - protected Vector2 DrawSize { get; private set; } - protected Vector2 PositionOffset { get; private set; } - protected Texture? Texture { get; private set; } - protected double SmokeStartTime { get; private set; } protected double SmokeEndTime { get; private set; } protected double CurrentTime { get; private set; } - protected RectangleF TextureRect { get; private set; } - - private IFrameBasedClock? clock; + private readonly List points = new List(); + private IVertexBatch? quadBatch; + private float radius; + private Vector2 drawSize; + private Vector2 positionOffset; + private Texture? texture; protected SmokeDrawNode(ITexturedShaderDrawable source) : base(source) @@ -339,28 +289,29 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.ApplyState(); - Points.Clear(); - Points.AddRange(Source.SmokePoints); + points.Clear(); + points.AddRange(Source.SmokePoints); - Radius = Source.Radius; - DrawSize = Source.DrawSize; - PositionOffset = Source.TopLeft; - Texture = Source.Texture; - clock = Source.Clock; + radius = Source.Radius; + drawSize = Source.DrawSize; + positionOffset = Source.TopLeft; + texture = Source.Texture; SmokeStartTime = Source.SmokeStartTime; SmokeEndTime = Source.SmokeEndTime; + CurrentTime = Source.Clock.CurrentTime; } public sealed override void Draw(IRenderer renderer) { base.Draw(renderer); - if (Points.Count == 0) + if (points.Count == 0) return; - QuadBatch ??= renderer.CreateQuadBatch(max_point_count / 10, 10); - Texture ??= renderer.WhitePixel; + quadBatch ??= renderer.CreateQuadBatch(max_point_count / 10, 10); + texture ??= renderer.WhitePixel; + RectangleF textureRect = texture.GetTextureRect(); var shader = GetAppropriateShader(renderer); @@ -368,10 +319,10 @@ namespace osu.Game.Rulesets.Osu.Skinning renderer.PushLocalMatrix(DrawInfo.Matrix); shader.Bind(); - Texture.Bind(); + texture.Bind(); - UpdateDrawVariables(renderer); - UpdateVertexBuffer(renderer); + foreach (var point in points) + drawPointQuad(point, textureRect); shader.Unbind(); renderer.PopLocalMatrix(); @@ -379,7 +330,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour ? ((SRGBColour)DrawColourInfo.Colour).Linear - : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, DrawSize)).Linear; + : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear; protected abstract Color4 PointColour(SmokePoint point); @@ -387,24 +338,9 @@ namespace osu.Game.Rulesets.Osu.Skinning protected abstract Vector2 PointDirection(SmokePoint point); - protected virtual void UpdateDrawVariables(IRenderer renderer) + private void drawPointQuad(SmokePoint point, RectangleF textureRect) { - Debug.Assert(clock != null); - Debug.Assert(Texture != null); - - CurrentTime = clock.CurrentTime; - TextureRect = Texture.GetTextureRect(); - } - - protected virtual void UpdateVertexBuffer(IRenderer renderer) - { - foreach (var point in Points) - drawPointQuad(point); - } - - private void drawPointQuad(SmokePoint point) - { - Debug.Assert(QuadBatch != null); + Debug.Assert(quadBatch != null); var colour = PointColour(point); float scale = PointScale(point); @@ -414,33 +350,33 @@ namespace osu.Game.Rulesets.Osu.Skinning if (colour.A == 0 || scale == 0) return; - var localTopLeft = point.Position + (Radius * scale * (-ortho - dir)) - PositionOffset; - var localTopRight = point.Position + (Radius * scale * (-ortho + dir)) - PositionOffset; - var localBotLeft = point.Position + (Radius * scale * (ortho - dir)) - PositionOffset; - var localBotRight = point.Position + (Radius * scale * (ortho + dir)) - PositionOffset; + var localTopLeft = point.Position + (radius * scale * (-ortho - dir)) - positionOffset; + var localTopRight = point.Position + (radius * scale * (-ortho + dir)) - positionOffset; + var localBotLeft = point.Position + (radius * scale * (ortho - dir)) - positionOffset; + var localBotRight = point.Position + (radius * scale * (ortho + dir)) - positionOffset; - QuadBatch.Add(new TexturedVertex2D + quadBatch.Add(new TexturedVertex2D { Position = localTopLeft, - TexturePosition = TextureRect.TopLeft, + TexturePosition = textureRect.TopLeft, Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour), }); - QuadBatch.Add(new TexturedVertex2D + quadBatch.Add(new TexturedVertex2D { Position = localTopRight, - TexturePosition = TextureRect.TopRight, + TexturePosition = textureRect.TopRight, Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour), }); - QuadBatch.Add(new TexturedVertex2D + quadBatch.Add(new TexturedVertex2D { Position = localBotRight, - TexturePosition = TextureRect.BottomRight, + TexturePosition = textureRect.BottomRight, Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour), }); - QuadBatch.Add(new TexturedVertex2D + quadBatch.Add(new TexturedVertex2D { Position = localBotLeft, - TexturePosition = TextureRect.BottomLeft, + TexturePosition = textureRect.BottomLeft, Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour), }); } @@ -448,7 +384,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - QuadBatch?.Dispose(); + quadBatch?.Dispose(); } } } From 102c1409674733535121b8957d8194c6a2940515 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 20:59:58 +0900 Subject: [PATCH 281/583] Remove another invalidate --- .../Skinning/Legacy/LegacySmoke.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index 50669cf79a..556996895c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -35,20 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float max_rotation = 0.25f; - private int rotationSeed = RNG.Next(); - - protected int RotationSeed - { - get => rotationSeed; - set - { - if (rotationSeed == value) - return; - - rotationSeed = value; - Invalidate(Invalidation.DrawNode); - } - } + protected int RotationSeed { get; set; } = RNG.Next(); protected override double LifetimeAfterSmokeEnd { From 3ec16063bdb017b7c07f11ed764023a47c1a4efb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Sep 2022 21:01:18 +0900 Subject: [PATCH 282/583] And another invalidate --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 410ee7b4f0..3afcc031a5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -30,14 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected float Radius { get => radius ?? Texture?.DisplayWidth * 0.165f ?? 3; - set - { - if (radius == value) - return; - - radius = value; - Invalidate(Invalidation.DrawNode); - } + set => radius = value; } protected Texture? Texture { get; set; } From ff568211528b63442f858dc459497cb4142c87c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 00:24:29 +0900 Subject: [PATCH 283/583] Change `SkinnableHUDComponentTestScene` to show new HUD twice Co-authored-by: Salman Ahmed --- .../Visual/Gameplay/SkinnableHUDComponentTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs index 1bf1bd4c81..ea4aa98f86 100644 --- a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(skin => { - var implementation = skin is not TrianglesSkin + var implementation = skin is LegacySkin ? CreateLegacyImplementation() : CreateDefaultImplementation(); From cb8ed1f1449fca16321da357d37505a9de073150 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 01:29:12 +0900 Subject: [PATCH 284/583] Adjust variables slightly for readability --- .../TestSceneCatcher.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 732184a8a8..182cc51572 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -106,18 +106,23 @@ namespace osu.Game.Rulesets.Catch.Tests public void TestCatcherCatchWidth() { float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; - const float center_x = CatchPlayfield.CENTER_X; - AddStep("move catcher to center", () => catcher.X = center_x); + + AddStep("move catcher to center", () => catcher.X = CatchPlayfield.CENTER_X); + + float leftPlateBounds = CatchPlayfield.CENTER_X - halfWidth; + float rightPlateBounds = CatchPlayfield.CENTER_X + halfWidth; + AddStep("catch fruit", () => { - attemptCatch(new Fruit { X = center_x + -halfWidth + 1 }); - attemptCatch(new Fruit { X = center_x + halfWidth - 1 }); + attemptCatch(new Fruit { X = leftPlateBounds + 1 }); + attemptCatch(new Fruit { X = rightPlateBounds - 1 }); }); checkPlate(2); + AddStep("miss fruit", () => { - attemptCatch(new Fruit { X = center_x + -halfWidth - 1 }); - attemptCatch(new Fruit { X = center_x + halfWidth + 1 }); + attemptCatch(new Fruit { X = leftPlateBounds - 1 }); + attemptCatch(new Fruit { X = rightPlateBounds + 1 }); }); checkPlate(2); } From ddbd69dc678fa10fe237186fc2a6aeaa352a80b7 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 20 Sep 2022 09:44:01 -0700 Subject: [PATCH 285/583] Replace LifetimeAfterSmokeEnd with abstract LifetimeEnd --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs | 4 ++-- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index a3c5733eb4..65ae490882 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private const double fade_out_duration = 50; private const float alpha = 0.5f; - protected override double LifetimeAfterSmokeEnd => fade_out_delay + fade_out_duration + (SmokeEndTime - SmokeStartTime) / fade_out_speed; + public override double LifetimeEnd => SmokeEndTime + fade_out_delay + fade_out_duration + (SmokeEndTime - SmokeStartTime) / fade_out_speed; public DefaultSmoke() { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index 556996895c..f02c20fefb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -37,12 +37,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected int RotationSeed { get; set; } = RNG.Next(); - protected override double LifetimeAfterSmokeEnd + public override double LifetimeEnd { get { double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); - return final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; + return SmokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 3afcc031a5..c381b543b3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -69,7 +69,6 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - protected abstract double LifetimeAfterSmokeEnd { get; } protected virtual float PointInterval => Radius * 7f / 8; protected bool IsActive { get; private set; } @@ -206,6 +205,8 @@ namespace osu.Game.Rulesets.Osu.Skinning BottomRight = new Vector2(BottomRight.X, position.Y); } + public abstract override double LifetimeEnd { get; } + private void onSmokeEnded(double time) { if (!IsActive) @@ -213,7 +214,6 @@ namespace osu.Game.Rulesets.Osu.Skinning IsActive = false; SmokeEndTime = time; - LifetimeEnd = time + LifetimeAfterSmokeEnd + 100; } protected abstract override DrawNode CreateDrawNode(); From 6b1cd1bce353e8766711a12bb36d8bbad958841a Mon Sep 17 00:00:00 2001 From: Drison64 Date: Tue, 20 Sep 2022 19:13:40 +0200 Subject: [PATCH 286/583] Clamped JuiceStream to Playfield, but broke few tests --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 311e15116e..0f2ce2e5e3 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -84,8 +85,8 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, - X = OriginalX + Path.PositionAt( - lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X, + X = EffectiveX + ClampToPlayField(Path.PositionAt( + lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X), }); } } @@ -102,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = dropletSamples, StartTime = e.Time, - X = OriginalX + Path.PositionAt(e.PathProgress).X, + X = EffectiveX + ClampToPlayField(Path.PositionAt(e.PathProgress).X), }); break; @@ -113,14 +114,16 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, - X = OriginalX + Path.PositionAt(e.PathProgress).X, + X = EffectiveX + ClampToPlayField(Path.PositionAt(e.PathProgress).X), }); break; } } } - public float EndX => OriginalX + this.CurvePositionAt(1).X; + public float EndX => EffectiveX + this.CurvePositionAt(1).X; + + public float ClampToPlayField(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH); [JsonIgnore] public double Duration From a4fae370135c046c1d847fec39e10a75b97421a1 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Tue, 20 Sep 2022 19:45:39 +0200 Subject: [PATCH 287/583] Fixed tests failing --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 0f2ce2e5e3..a0b6c7e724 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, - X = EffectiveX + ClampToPlayField(Path.PositionAt( + X = ClampToPlayField(EffectiveX + Path.PositionAt( lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X), }); } @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = dropletSamples, StartTime = e.Time, - X = EffectiveX + ClampToPlayField(Path.PositionAt(e.PathProgress).X), + X = ClampToPlayField(EffectiveX + Path.PositionAt(e.PathProgress).X), }); break; @@ -114,14 +114,14 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, - X = EffectiveX + ClampToPlayField(Path.PositionAt(e.PathProgress).X), + X = ClampToPlayField(EffectiveX + Path.PositionAt(e.PathProgress).X), }); break; } } } - public float EndX => EffectiveX + this.CurvePositionAt(1).X; + public float EndX => ClampToPlayField(EffectiveX + this.CurvePositionAt(1).X); public float ClampToPlayField(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH); From 092e6cfa1da050a5d16d0ac86fc95547f14eabbc Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 20 Sep 2022 12:03:07 -0700 Subject: [PATCH 288/583] Lock smoke bounds to playfield --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 86 ++----------------------- 1 file changed, 5 insertions(+), 81 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index c381b543b3..4d2a193a8a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -39,36 +39,6 @@ namespace osu.Game.Rulesets.Osu.Skinning protected double SmokeEndTime { get; private set; } = double.MaxValue; - private Vector2 topLeft; - - protected Vector2 TopLeft - { - get => topLeft; - set - { - if (topLeft == value) - return; - - topLeft = value; - Invalidate(Invalidation.Layout); - } - } - - private Vector2 bottomRight; - - protected Vector2 BottomRight - { - get => bottomRight; - set - { - if (bottomRight == value) - return; - - bottomRight = value; - Invalidate(Invalidation.Layout); - } - } - protected virtual float PointInterval => Radius * 7f / 8; protected bool IsActive { get; private set; } @@ -79,24 +49,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private const int max_point_count = 18_000; - public override float Height - { - get => base.Height = BottomRight.Y - TopLeft.Y; - set => throw new InvalidOperationException($"Cannot manually set {nameof(Height)} of {nameof(Smoke)}."); - } - - public override float Width - { - get => base.Width = BottomRight.X - TopLeft.X; - set => throw new InvalidOperationException($"Cannot manually set {nameof(Width)} of {nameof(Smoke)}."); - } - - public override Vector2 Size - { - get => base.Size = BottomRight - TopLeft; - set => throw new InvalidOperationException($"Cannot manually set {nameof(Size)} of {nameof(Smoke)}."); - } - [Resolved(CanBeNull = true)] private SmokeContainer? smokeContainer { get; set; } @@ -111,8 +63,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - Anchor = Anchor.TopLeft; - Origin = Anchor.TopLeft; + RelativeSizeAxes = Axes.Both; SmokeStartTime = Time.Current; @@ -157,7 +108,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer()); SmokePoints.RemoveRange(index, SmokePoints.Count - index); - recalculateBounds(); } totalDistance %= PointInterval; @@ -175,7 +125,6 @@ namespace osu.Game.Rulesets.Osu.Skinning } Invalidate(Invalidation.DrawNode); - adaptBounds(position); } lastPosition = position; @@ -184,27 +133,6 @@ namespace osu.Game.Rulesets.Osu.Skinning onSmokeEnded(time); } - private void recalculateBounds() - { - TopLeft = BottomRight = Vector2.Zero; - - foreach (var point in SmokePoints) - adaptBounds(point.Position); - } - - private void adaptBounds(Vector2 position) - { - if (position.X < TopLeft.X) - TopLeft = new Vector2(position.X, TopLeft.Y); - else if (position.X > BottomRight.X) - BottomRight = new Vector2(position.X, BottomRight.Y); - - if (position.Y < TopLeft.Y) - TopLeft = new Vector2(TopLeft.X, position.Y); - else if (position.Y > BottomRight.Y) - BottomRight = new Vector2(BottomRight.X, position.Y); - } - public abstract override double LifetimeEnd { get; } private void onSmokeEnded(double time) @@ -222,8 +150,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.Update(); - Position = TopLeft; - Invalidate(Invalidation.DrawNode); } @@ -270,7 +196,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private IVertexBatch? quadBatch; private float radius; private Vector2 drawSize; - private Vector2 positionOffset; private Texture? texture; protected SmokeDrawNode(ITexturedShaderDrawable source) @@ -287,7 +212,6 @@ namespace osu.Game.Rulesets.Osu.Skinning radius = Source.Radius; drawSize = Source.DrawSize; - positionOffset = Source.TopLeft; texture = Source.Texture; SmokeStartTime = Source.SmokeStartTime; @@ -343,10 +267,10 @@ namespace osu.Game.Rulesets.Osu.Skinning if (colour.A == 0 || scale == 0) return; - var localTopLeft = point.Position + (radius * scale * (-ortho - dir)) - positionOffset; - var localTopRight = point.Position + (radius * scale * (-ortho + dir)) - positionOffset; - var localBotLeft = point.Position + (radius * scale * (ortho - dir)) - positionOffset; - var localBotRight = point.Position + (radius * scale * (ortho + dir)) - positionOffset; + var localTopLeft = point.Position + (radius * scale * (-ortho - dir)); + var localTopRight = point.Position + (radius * scale * (-ortho + dir)); + var localBotLeft = point.Position + (radius * scale * (ortho - dir)); + var localBotRight = point.Position + (radius * scale * (ortho + dir)); quadBatch.Add(new TexturedVertex2D { From b8f2e13503c8899880c065ac0f2bdba808674b23 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Sep 2022 22:27:27 +0300 Subject: [PATCH 289/583] Avoid catching all exceptions raising from skin instance creation --- osu.Game/Skinning/SkinInfo.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 34728245c8..04d1216ed6 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; using Newtonsoft.Json; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.IO; @@ -45,20 +44,18 @@ namespace osu.Game.Skinning var type = string.IsNullOrEmpty(InstantiationInfo) // handle the case of skins imported before InstantiationInfo was added. ? typeof(LegacySkin) - : Type.GetType(InstantiationInfo).AsNonNull(); + : Type.GetType(InstantiationInfo); - try + if (type == null) { - return (Skin)Activator.CreateInstance(type, this, resources); - } - catch - { - // Since the class was renamed from "DefaultSkin" to "TrianglesSkin", the instantiation would fail + // Since the class was renamed from "DefaultSkin" to "TrianglesSkin", the type retrieval would fail // for user modified skins. This aims to amicably handle that. // If we ever add more default skins in the future this will need some kind of proper migration rather than - // a single catch. + // a single fallback. return new TrianglesSkin(this, resources); } + + return (Skin)Activator.CreateInstance(type, this, resources); } public IList Files { get; } = null!; From b7c1e8cc5ace501a15384c45ec49b14d03d17573 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 20 Sep 2022 23:11:38 +0200 Subject: [PATCH 290/583] Remove setting `AngleVariety` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index c829d1f696..e1ebecc6f2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -38,16 +38,6 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - [SettingSource("Angle variety", "The amount of variety in how sharp angles are", SettingControlType = typeof(SettingsSlider))] - public BindableFloat AngleVariety { get; } = new BindableFloat - { - Default = 3, - Value = 3, - MinValue = 0, - MaxValue = 10, - Precision = 0.1f - }; - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; private Random random = null!; @@ -111,7 +101,9 @@ namespace osu.Game.Rulesets.Osu.Mods private float getRandomOffset(float stdDev) { - float customMultiplier = AngleVariety.Value / AngleVariety.Default; + // Range: [0.5;2] + float customMultiplier = (1.5f * AngleSharpness.MaxValue - AngleSharpness.Value) / (1.5f * AngleSharpness.MaxValue - AngleSharpness.Default); + return OsuHitObjectGenerationUtils.RandomGaussian(random, 0, stdDev * customMultiplier); } From b1ecac514ad7fd0580878f02942d237e7c9763b0 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 20 Sep 2022 23:13:38 +0200 Subject: [PATCH 291/583] Correct comments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e1ebecc6f2..78c7aa53f4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -101,7 +101,8 @@ namespace osu.Game.Rulesets.Osu.Mods private float getRandomOffset(float stdDev) { - // Range: [0.5;2] + // Range: [0.5, 2] + // Higher angle sharpness -> lower multiplier float customMultiplier = (1.5f * AngleSharpness.MaxValue - AngleSharpness.Value) / (1.5f * AngleSharpness.MaxValue - AngleSharpness.Default); return OsuHitObjectGenerationUtils.RandomGaussian(random, 0, stdDev * customMultiplier); @@ -112,14 +113,14 @@ namespace osu.Game.Rulesets.Osu.Mods /// Whether the relative angle should be positive or negative. private float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - // Range [0.1;1] + // Range: [0.1, 1] float angleSharpness = AngleSharpness.Value / AngleSharpness.MaxValue; - // Range [0;0.9] + // Range: [0, 0.9] float angleWideness = 1 - angleSharpness; - // Range: [-60;30] + // Range: [-60, 30] float customOffsetX = angleSharpness * 100 - 70; - // Range: [-0.075;0.15] + // Range: [-0.075, 0.15] float customOffsetY = angleWideness * 0.25f - 0.075f; targetDistance += customOffsetX; From 8b918d29fc2e2387430eea1010b5c0ff01be93de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Sep 2022 00:15:49 +0300 Subject: [PATCH 292/583] Add failing test case --- .../TestSceneNotificationOverlay.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index c8126d883c..19da31a91b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -230,6 +230,31 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent); } + [Test] + public void TestProgressClick() + { + ProgressNotification notification = null!; + + AddStep("add progress notification", () => + { + notification = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + notificationOverlay.Post(notification); + progressingNotifications.Add(notification); + }); + + AddStep("hover over notification", () => InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single())); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("not cancelled", () => notification.State == ProgressNotificationState.Active); + + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("cancelled", () => notification.State == ProgressNotificationState.Cancelled); + } + [Test] public void TestCompleteProgress() { From dcfb6a2f060a1de3e86128080bae42d6d81935b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Sep 2022 00:15:45 +0300 Subject: [PATCH 293/583] Fix progress notifications no longer blocking close on click --- osu.Game/Overlays/Notifications/Notification.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 4e7cebf0ae..ea654e1272 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -229,8 +229,8 @@ namespace osu.Game.Overlays.Notifications protected override bool OnClick(ClickEvent e) { // Clicking with anything but left button should dismiss but not perform the activation action. - if (e.Button == MouseButton.Left) - Activated?.Invoke(); + if (e.Button == MouseButton.Left && Activated?.Invoke() == false) + return true; Close(false); return true; From fe9e8635fc3424f635f0a6db04441abee170bb1a Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Wed, 21 Sep 2022 01:04:39 +0200 Subject: [PATCH 294/583] Fix snapping of already existing slider control points in the editor --- .../Sliders/Components/PathControlPointVisualiser.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 9de62d1a46..0dd522fd47 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -303,16 +303,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } else { + var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition)); + + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position; + for (int i = 0; i < controlPoints.Count; ++i) { var controlPoint = controlPoints[i]; if (selectedControlPoints.Contains(controlPoint)) { - Vector2 newPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); - var result = snapProvider?.FindSnappedPositionAndTime(newPosition); - - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newPosition) - slider.Position; - controlPoint.Position = dragStartPositions[i] + movementDelta; } } From 368229f324bca76a9ea409f39f49c8a926e168d4 Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Wed, 21 Sep 2022 01:25:01 +0200 Subject: [PATCH 295/583] Remove unnecessary brackets --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 0dd522fd47..628329ba64 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -311,9 +311,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var controlPoint = controlPoints[i]; if (selectedControlPoints.Contains(controlPoint)) - { controlPoint.Position = dragStartPositions[i] + movementDelta; - } } } From 29fa868fde5ab7162c742515150d7569977414b0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Sep 2022 03:03:34 +0300 Subject: [PATCH 296/583] Fix test scene not resetting mouse position Also fixes until step flipped. --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 19da31a91b..b314d95597 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void SetUp() => Schedule(() => { + InputManager.MoveMouseTo(Vector2.Zero); + TimeToCompleteProgress = 2000; progressingNotifications.Clear(); @@ -326,7 +328,7 @@ namespace osu.Game.Tests.Visual.UserInterface { SimpleNotification notification = null!; AddStep(@"post", () => notificationOverlay.Post(notification = new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" })); - AddUntilStep("check is toast", () => !notification.IsInToastTray); + AddUntilStep("check is toast", () => notification.IsInToastTray); AddAssert("light is not visible", () => notification.ChildrenOfType().Single().Alpha == 0); AddUntilStep("wait for forward to overlay", () => !notification.IsInToastTray); From a8338c4efd5cf9dbb89b772850c30b557cee3125 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 13:38:40 +0900 Subject: [PATCH 297/583] Add local score after existing scores to avoid initial re-sort --- .../Play/HUD/SoloGameplayLeaderboard.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 00efcb1ea9..eff7870d89 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -45,15 +45,6 @@ namespace osu.Game.Screens.Play.HUD if (!Scores.Any()) return; - ILeaderboardScore local = Add(trackingUser, true); - - local.TotalScore.BindTarget = scoreProcessor.TotalScore; - local.Accuracy.BindTarget = scoreProcessor.Accuracy; - local.Combo.BindTarget = scoreProcessor.Combo; - - // Local score should always show lower than any existing scores in cases of ties. - local.DisplayOrder.Value = long.MaxValue; - foreach (var s in Scores) { var score = Add(s.User, false); @@ -68,6 +59,15 @@ namespace osu.Game.Screens.Play.HUD score.Combo.Value = s.MaxCombo; score.DisplayOrder.Value = s.OnlineID > 0 ? s.OnlineID : s.Date.ToUnixTimeSeconds(); } + + ILeaderboardScore local = Add(trackingUser, true); + + local.TotalScore.BindTarget = scoreProcessor.TotalScore; + local.Accuracy.BindTarget = scoreProcessor.Accuracy; + local.Combo.BindTarget = scoreProcessor.Combo; + + // Local score should always show lower than any existing scores in cases of ties. + local.DisplayOrder.Value = long.MaxValue; } } } From 4385001d282c83eadb462d74fcab789af9d68538 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 13:59:11 +0900 Subject: [PATCH 298/583] Fix solo leaderboard seeing imported score via realm subscription flow --- osu.Game/Screens/Play/SoloPlayer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index a36697c96e..d7730737d6 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; +using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -52,6 +53,16 @@ namespace osu.Game.Screens.Play protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + protected override Task ImportScore(Score score) + { + // Before importing a score, stop binding the leaderboard with its score source. + // This avoids a case where the imported score may cause a leaderboard refresh + // (if the leaderboard's source is local). + LeaderboardScores.UnbindBindings(); + + return base.ImportScore(score); + } + protected override APIRequest CreateSubmissionRequest(Score score, long token) { IBeatmapInfo beatmap = score.ScoreInfo.BeatmapInfo; From 5cc2721e9afaaf5476df4024cbd9b1e069449062 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 14:42:02 +0900 Subject: [PATCH 299/583] Add failing test showing layout failure in gameplay leaderboard --- .../Gameplay/TestSceneGameplayLeaderboard.cs | 74 +++++++++++++++---- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index 663e398c01..72656c29b1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -6,7 +6,9 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; +using osu.Framework.Extensions.PolygonExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; @@ -18,37 +20,52 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneGameplayLeaderboard : OsuTestScene { - private readonly TestGameplayLeaderboard leaderboard; + private TestGameplayLeaderboard leaderboard; private readonly BindableDouble playerScore = new BindableDouble(); public TestSceneGameplayLeaderboard() { - Add(leaderboard = new TestGameplayLeaderboard + AddStep("toggle expanded", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(2), + if (leaderboard != null) + leaderboard.Expanded.Value = !leaderboard.Expanded.Value; }); + + AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v); } - [SetUpSteps] - public void SetUpSteps() + [Test] + public void TestLayoutWithManyScores() { - AddStep("reset leaderboard", () => + createLeaderboard(); + + AddStep("add many scores in one go", () => { - leaderboard.Clear(); - playerScore.Value = 1222333; + for (int i = 0; i < 32; i++) + createRandomScore(new APIUser { Username = $"Player {i + 1}" }); + + // Add player at end to force an animation down the whole list. + playerScore.Value = 0; + createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true); }); - AddStep("add local player", () => createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true)); - AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value); - AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v); + // Gameplay leaderboard has custom scroll logic, which when coupled with LayoutDuration + // has caused layout to not work in the past. + + AddUntilStep("wait for fill flow layout", + () => leaderboard.ChildrenOfType>().First().ScreenSpaceDrawQuad.Intersects(leaderboard.ScreenSpaceDrawQuad)); + + AddUntilStep("wait for some scores not masked away", + () => leaderboard.ChildrenOfType().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre))); } [Test] public void TestPlayerScore() { + createLeaderboard(); + addLocalPlayer(); + var player2Score = new BindableDouble(1234567); var player3Score = new BindableDouble(1111111); @@ -73,6 +90,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRandomScores() { + createLeaderboard(); + addLocalPlayer(); + int playerNumber = 1; AddRepeatStep("add player with random score", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 10); } @@ -80,6 +100,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExistingUsers() { + createLeaderboard(); + addLocalPlayer(); + AddStep("add peppy", () => createRandomScore(new APIUser { Username = "peppy", Id = 2 })); AddStep("add smoogipoo", () => createRandomScore(new APIUser { Username = "smoogipoo", Id = 1040328 })); AddStep("add flyte", () => createRandomScore(new APIUser { Username = "flyte", Id = 3103765 })); @@ -89,6 +112,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMaxHeight() { + createLeaderboard(); + addLocalPlayer(); + int playerNumber = 1; AddRepeatStep("add 3 other players", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 3); checkHeight(4); @@ -103,6 +129,28 @@ namespace osu.Game.Tests.Visual.Gameplay => AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount); } + private void addLocalPlayer() + { + AddStep("add local player", () => + { + playerScore.Value = 1222333; + createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true); + }); + } + + private void createLeaderboard() + { + AddStep("create leaderboard", () => + { + Child = leaderboard = new TestGameplayLeaderboard + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2), + }; + }); + } + private void createRandomScore(APIUser user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user); private void createLeaderboardScore(BindableDouble score, APIUser user, bool isTracked = false) From 087ca59ebb0362a19202704cd4e94141839d476d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 14:48:53 +0900 Subject: [PATCH 300/583] Add extra margin space to flow equal to height of leaderboard This ensures the content is always on screen, but also accounts for the fact that scroll operations without animation were actually forcing the local score to a location it can't usually reside at. Basically, the local score was in the scroll extension region (due to always trying to scroll the local player to the middle of the display, but there being no other content below the local player to scroll up by). --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index f21ce5e36a..62c8788396 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -96,6 +96,7 @@ namespace osu.Game.Screens.Play.HUD int displayCount = Math.Min(Flow.Count, maxPanels); Height = displayCount * (GameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y); + Flow.Margin = new MarginPadding { Bottom = Height }; requiresScroll = displayCount != Flow.Count; return drawable; From b04871f40a0060ac0474f5fc5cbcd416d5127ed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 14:50:23 +0900 Subject: [PATCH 301/583] Animate scroll for a better visual experience --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 62c8788396..c4d81e7e2a 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play.HUD if (requiresScroll && trackedScore != null) { float scrollTarget = scroll.GetChildPosInContent(trackedScore) + trackedScore.DrawHeight / 2 - scroll.DrawHeight / 2; - scroll.ScrollTo(scrollTarget, false); + scroll.ScrollTo(scrollTarget); } const float panel_height = GameplayLeaderboardScore.PANEL_HEIGHT; From 09960512716ebb755c2ed6d91cd8e9637203a3b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 15:03:06 +0900 Subject: [PATCH 302/583] Order test scores more correctly --- .../Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index 2b8eb3a80c..ac73e88468 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) }, new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) }, new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) }, - }.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 500000 + i * 10000 })).ToList(); + }.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList(); } } } From 5b73f24864058b3c6ac85e12a9fec5d87ece2e0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 15:21:32 +0900 Subject: [PATCH 303/583] Fix outdated scores being held by `BeatmapLeaderboard` after external beatmap switch --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 798cf29715..0f6ac2b455 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -41,6 +41,11 @@ namespace osu.Game.Screens.Select.Leaderboards return; beatmapInfo = value; + + // Refetch is scheduled, which can cause scores to be outdated if the leaderboard is not currently updating. + // As scores are potentially used by other components, clear them eagerly to ensure a more correct state. + SetScores(null); + RefetchScores(); } } From 992441b9de388e8b463862ec8960ded13085a385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 16:04:32 +0900 Subject: [PATCH 304/583] Disable alpha component parsing in beatmap / skin colour sections --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 6 +++--- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 2 +- osu.Game/Skinning/LegacySkinDecoder.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9fc1eb7650..fdd0167ed3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -306,7 +306,7 @@ namespace osu.Game.Tests.Beatmaps.Formats new Color4(128, 255, 128, 255), new Color4(255, 187, 255, 255), new Color4(255, 177, 140, 255), - new Color4(100, 100, 100, 100), + new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored. }; Assert.AreEqual(expectedColors.Length, comboColors.Count); for (int i = 0; i < expectedColors.Length; i++) diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 419eb87b1a..6756f27ecd 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Skins new Color4(142, 199, 255, 255), new Color4(255, 128, 128, 255), new Color4(128, 255, 255, 255), - new Color4(100, 100, 100, 100), + new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored. }; Assert.AreEqual(expectedColors.Count, comboColors.Count); diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 3d65ab8e0f..9c066ada08 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps.Formats switch (section) { case Section.Colours: - HandleColours(output, line); + HandleColours(output, line, false); return; } } @@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps.Formats return line; } - protected void HandleColours(TModel output, string line) + protected void HandleColours(TModel output, string line, bool allowAlpha) { var pair = SplitKeyVal(line); @@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps.Formats try { - byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255; + byte alpha = allowAlpha && split.Length == 4 ? byte.Parse(split[3]) : (byte)255; colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha); } catch diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 49914c53aa..0aafdd4db0 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -121,7 +121,7 @@ namespace osu.Game.Skinning break; case string when pair.Key.StartsWith("Colour", StringComparison.Ordinal): - HandleColours(currentConfig, line); + HandleColours(currentConfig, line, true); break; // Custom sprite paths diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index e5f87b3230..11c21d432f 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -48,7 +48,7 @@ namespace osu.Game.Skinning // osu!catch section only has colour settings // so no harm in handling the entire section case Section.CatchTheBeat: - HandleColours(skin, line); + HandleColours(skin, line, true); return; } From 2b8b138079aefad5586c70cf5cf84f1ab9f1dfe2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 21 Sep 2022 18:44:01 +0900 Subject: [PATCH 305/583] Add "keybindings" search term to settings --- osu.Game/Overlays/Settings/Sections/InputSection.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 4d75537f6b..a8fe3d04be 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -3,6 +3,8 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -20,6 +22,8 @@ namespace osu.Game.Overlays.Settings.Sections public override LocalisableString Header => InputSettingsStrings.InputSectionHeader; + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" }); + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.Keyboard From 1ef09f2ae1e3cfb9c0c7080af6fc4b003300da67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 23:26:25 +0900 Subject: [PATCH 306/583] Fix regression in `TestSceneSongProgress` --- .../Visual/Gameplay/TestSceneSongProgress.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 3487f4dbff..6127aa304c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -1,8 +1,8 @@ // 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.Extensions.IEnumerableExtensions; @@ -51,13 +51,14 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestToggleSeeking() { - DefaultSongProgress getDefaultProgress() => this.ChildrenOfType().Single(); + void applyToDefaultProgress(Action action) => + this.ChildrenOfType().ForEach(action); - AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true); - AddStep("hide graph", () => getDefaultProgress().ShowGraph.Value = false); - AddStep("disallow seeking", () => getDefaultProgress().AllowSeeking.Value = false); - AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true); - AddStep("show graph", () => getDefaultProgress().ShowGraph.Value = true); + AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true)); + AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false)); + AddStep("disallow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = false)); + AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true)); + AddStep("show graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = true)); } private void setHitObjects() From 86a09ad2cfee383ef397aeacbcdb2152c40e4f12 Mon Sep 17 00:00:00 2001 From: Drison64 Date: Wed, 21 Sep 2022 17:14:02 +0200 Subject: [PATCH 307/583] Rename ClampToPlayField --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index a0b6c7e724..015457e84f 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, - X = ClampToPlayField(EffectiveX + Path.PositionAt( + X = ClampToPlayfield(EffectiveX + Path.PositionAt( lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X), }); } @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = dropletSamples, StartTime = e.Time, - X = ClampToPlayField(EffectiveX + Path.PositionAt(e.PathProgress).X), + X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X), }); break; @@ -114,16 +114,16 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, - X = ClampToPlayField(EffectiveX + Path.PositionAt(e.PathProgress).X), + X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X), }); break; } } } - public float EndX => ClampToPlayField(EffectiveX + this.CurvePositionAt(1).X); + public float EndX => ClampToPlayfield(EffectiveX + this.CurvePositionAt(1).X); - public float ClampToPlayField(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH); + public float ClampToPlayfield(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH); [JsonIgnore] public double Duration From 5d543545884a3543644f816cce00b08ab055850d Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Wed, 21 Sep 2022 18:53:25 +0200 Subject: [PATCH 308/583] Add missing ToScreenSpace() call --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 5673dab7e8..7c289b5b05 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (placementControlPoint != null) { var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition)); - placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? e.MousePosition) - HitObject.Position; + placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - HitObject.Position; } } From 112bbe2296b3ea9e068e14a8cdaee03031ee2c40 Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Wed, 21 Sep 2022 18:56:22 +0200 Subject: [PATCH 309/583] Always use Parent.ToScreenSpace() in PathControlPointVisualiser --- .../Sliders/Components/PathControlPointVisualiser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 628329ba64..94655f3cf7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -303,9 +303,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } else { - var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition)); + var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition)); - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position; + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position; for (int i = 0; i < controlPoints.Count; ++i) { From db21601632d36e48399b95439f571ee75e1fb115 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 12:52:24 +0900 Subject: [PATCH 310/583] Add warning message in `files` folder to avoid accidental deletion This is a pretty standard practice for applications that have data stored in folders where a user may accidentally determine that the content is unnecessary. Aims to address cases like https://github.com/ppy/osu/discussions/20394#discussioncomment-3705694. It's not the first time this has come up, and definitely won't be the last. --- osu.Game/OsuGameBase.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8b016e8eb0..2d32fe398a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Threading; @@ -255,6 +256,8 @@ namespace osu.Game InitialiseFonts(); + addFilesWarning(); + Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler)); @@ -373,6 +376,29 @@ namespace osu.Game Beatmap.BindValueChanged(onBeatmapChanged); } + private void addFilesWarning() + { + var realmStore = new RealmFileStore(realm, Storage); + + const string filename = "IMPORTANT READ ME.txt"; + + if (!realmStore.Storage.Exists(filename)) + { + using (var stream = realmStore.Storage.CreateFileSafely(filename)) + using (var textWriter = new StreamWriter(stream)) + { + textWriter.WriteLine(@"This folder contains all your user files (beatmaps, skins, replays etc.)"); + textWriter.WriteLine(@"Please do not touch or delete this folder!!"); + textWriter.WriteLine(); + textWriter.WriteLine(@"If you are really looking to completely delete user data, please delete"); + textWriter.WriteLine(@"the parent folder including all other files and directories"); + textWriter.WriteLine(); + textWriter.WriteLine(@"For more information on how these files are organised,"); + textWriter.WriteLine(@"see https://github.com/ppy/osu/wiki/User-file-storage"); + } + } + } + private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) { // FramedBeatmapClock uses a decoupled clock internally which will mutate the source if it is an `IAdjustableClock`. From 2bdc6417e9f23105d6df4c3e4c49c445added1c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Sep 2022 23:38:02 +0900 Subject: [PATCH 311/583] Mark `DrawableOsuHitObject` `abstract` for clarity --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 6e525071ca..c0acae378c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableOsuHitObject : DrawableHitObject + public abstract class DrawableOsuHitObject : DrawableHitObject { public readonly IBindable PositionBindable = new Bindable(); public readonly IBindable StackHeightBindable = new Bindable(); From 78625fda7d82de8a62bd3000b4db29e4b3f3fa49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 14:42:19 +0900 Subject: [PATCH 312/583] Improve exception when attempting to call `DrawableHitObject.ClearInternal` --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 39ccaa2e5d..e218da918c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); - // Explicit non-virtual function call. + // Explicit non-virtual function call in case a DrawableHitObject overrides AddInternal. base.AddInternal(Samples = new PausableSkinnableSound()); CurrentSkin = skinSource; @@ -405,7 +405,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public event Action ApplyCustomUpdateState; - protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); + protected override void ClearInternal(bool disposeChildren = true) => + // See sample addition in load method. + throw new InvalidOperationException( + $"Should never clear a {nameof(DrawableHitObject)} as the base implementation adds components. If attempting to use {nameof(InternalChild)} or {nameof(InternalChildren)}, using {nameof(AddInternal)} or {nameof(AddRangeInternal)} instead."); private void updateState(ArmedState newState, bool force = false) { From b844d76311e535f908e312be03f6f2c9fc314f2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 14:30:01 +0900 Subject: [PATCH 313/583] Move `Shake` logic local to hitcircle/slider --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 35 +++++++++++++------ .../Objects/Drawables/DrawableOsuHitObject.cs | 22 +++--------- .../Objects/Drawables/DrawableSlider.cs | 22 +++++++++--- .../Objects/Drawables/DrawableSliderHead.cs | 9 ++--- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 9d06ff5801..e5b7208da6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods if (!objects.Any()) return false; - return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType().First().Children.OfType().Single().Scale.X, target)); + return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType().First().Scale.X, target)); } private bool checkSomeHit() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 23e5cb0ad7..09a460569a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; @@ -47,6 +48,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } + private ShakeContainer shakeContainer; + [BackgroundDependencyLoader] private void load() { @@ -72,18 +75,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()) + shakeContainer = new ShakeContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + ShakeDuration = 30, RelativeSizeAxes = Axes.Both, - Alpha = 0, - Scale = new Vector2(4), + Children = new Drawable[] + { + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Scale = new Vector2(4), + } + } } } }, @@ -123,6 +134,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void Shake() => shakeContainer.Shake(); + protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); @@ -139,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) { - Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss)); + Shake(); return; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index c0acae378c..6f4ca30bd0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -6,12 +6,11 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -34,8 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public Func CheckHittable; - private ShakeContainer shakeContainer; - protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) { @@ -45,12 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void load() { Alpha = 0; - - base.AddInternal(shakeContainer = new ShakeContainer - { - ShakeDuration = 30, - RelativeSizeAxes = Axes.Both - }); } protected override void OnApply() @@ -73,18 +64,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.UnbindFrom(HitObject.ScaleBindable); } - // Forward all internal management to shakeContainer. - // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) - protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable); - protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); - protected override bool RemoveInternal(Drawable drawable, bool disposeImmediately) => shakeContainer.Remove(drawable, disposeImmediately); - protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager; - public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); + /// + /// Shake the hit object in case it was clicked far too early or late (aka "note lock"). + /// + public virtual void Shake() { } /// /// Causes this to get missed, disregarding all conditions in implementations of . diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index d83f5df7a3..3393d26e01 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning; @@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SkinnableDrawable Body { get; private set; } + private ShakeContainer shakeContainer; + /// /// A target container which can be used to add top level elements to the slider's display. /// Intended to be used for proxy purposes only. @@ -76,10 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { InternalChildren = new Drawable[] { - Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), - tailContainer = new Container { RelativeSizeAxes = Axes.Both }, - tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, + shakeContainer = new ShakeContainer + { + ShakeDuration = 30, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + tickContainer = new Container { RelativeSizeAxes = Axes.Both }, + repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, + } + }, + // slider head is not included in shake as it handles hit detection, and handles its own shaking. headContainer = new Container { RelativeSizeAxes = Axes.Both }, OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, }, Ball, @@ -109,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables PathVersion.BindTo(HitObject.Path.Version); } + public override void Shake() => shakeContainer.Shake(); + protected override void OnFree() { base.OnFree(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 70b1bd225f..80b9544e5b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -63,7 +63,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables pathVersion.BindTo(DrawableSlider.PathVersion); - OnShake = DrawableSlider.Shake; CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true; } @@ -96,9 +95,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } - public Action OnShake; - - public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); + public override void Shake() + { + base.Shake(); + DrawableSlider.Shake(); + } private void updatePosition() { From 749053481a2d0fa210754f9d53840a6852b5ad99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 14:46:37 +0900 Subject: [PATCH 314/583] Update osu! hitobject implementation to avoid triggering `ClearInternal` --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- .../Objects/Drawables/DrawableSliderRepeat.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 09a460569a..c5992b359d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Origin = Anchor.Centre; - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { scaleContainer = new Container { @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } }, - }; + }); Size = HitArea.DrawSize; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3393d26e01..d58a435728 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { shakeContainer = new ShakeContainer { @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, }, Ball, slidingSample = new PausableSkinnableSound { Looping = true } - }; + }); PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 1bddc603ac..7b9c0c7e40 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - InternalChild = scaleContainer = new Container + AddInternal(scaleContainer = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, Arrow = new ReverseArrowPiece(), } - }; + }); ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index df3a12fe33..063d297f5a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { scaleContainer = new Container { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) } }, - }; + }); ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 3ffbe68b98..4bd98fc8b2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Origin = Anchor.Centre; - InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer + AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer { Masking = true, Origin = Anchor.Centre, @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }; + }); ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } From e48fe3a9e23d5f40f7703812244727406e09db67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:17:37 +0900 Subject: [PATCH 315/583] Add nullability to test scene --- .../Gameplay/TestSceneColourHitErrorMeter.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs index 5fdd00a7f6..edb95ecf70 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -1,11 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using System; using System.Diagnostics; using NUnit.Framework; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; @@ -21,22 +21,27 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneColourHitErrorMeter : OsuTestScene { - private DependencyProvidingContainer dependencyContainer; + private DependencyProvidingContainer dependencyContainer = null!; private readonly Bindable lastJudgementResult = new Bindable(); - private ScoreProcessor scoreProcessor; + private ScoreProcessor scoreProcessor = null!; + private int iteration; - private ColourHitErrorMeter colourHitErrorMeter; + + private ColourHitErrorMeter colourHitErrorMeter = null!; public TestSceneColourHitErrorMeter() { AddSliderStep("Manual Opacity test", 0.01f, 1, 1, alpha => { - if (colourHitErrorMeter != null) colourHitErrorMeter.HitShapeOpacity.Value = alpha; + if (colourHitErrorMeter.IsNotNull()) + colourHitErrorMeter.HitShapeOpacity.Value = alpha; }); + AddSliderStep("Manual spacing test", 0, 10, 2, spacing => { - if (colourHitErrorMeter != null) colourHitErrorMeter.HitShapeSpacing.Value = spacing; + if (colourHitErrorMeter.IsNotNull()) + colourHitErrorMeter.HitShapeSpacing.Value = spacing; }); } From f3898da37ade7ba9ee848499034eecd13e31817e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:18:20 +0900 Subject: [PATCH 316/583] Rename judgement count to not mention "shape" --- .../Visual/Gameplay/TestSceneColourHitErrorMeter.cs | 2 +- .../Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs index edb95ecf70..23c7410231 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestJudgementAmountChange() { AddRepeatStep("Add judgement", applyOneJudgement, 10); - AddStep("Judgement count change to 4", () => colourHitErrorMeter.HitShapeCount.Value = 4); + AddStep("Judgement count change to 4", () => colourHitErrorMeter.JudgementCount.Value = 4); AddRepeatStep("Add judgement", applyOneJudgement, 8); } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 8991d97bd4..08df9833d0 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const int drawable_judgement_size = 8; [SettingSource("Judgement count", "Number of displayed judgements")] - public BindableNumber HitShapeCount { get; } = new BindableNumber(20) + public BindableNumber JudgementCount { get; } = new BindableNumber(20) { MinValue = 1, MaxValue = 30, @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) return; - judgementsFlow.Push(GetColourForHitResult(judgement.Type), HitShapeCount.Value); + judgementsFlow.Push(GetColourForHitResult(judgement.Type), JudgementCount.Value); } protected override void LoadComplete() @@ -69,14 +69,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters HitShapeOpacity.BindValueChanged(_ => judgementsFlow.Alpha = HitShapeOpacity.Value, true); HitShapeSpacing.BindValueChanged(_ => { - judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; + judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; judgementsFlow.Spacing = new Vector2(0, HitShapeSpacing.Value); }, true); - HitShapeCount.BindValueChanged(_ => + JudgementCount.BindValueChanged(_ => { //Used to clear out the overflowing judgement children when the value is lowered judgementsFlow.RemoveAll(_ => true, true); - judgementsFlow.Height = HitShapeCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; + judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; }, true); HitShape.BindValueChanged(_ => judgementsFlow.Shape.Value = HitShape.Value, true); } From 19db7c5977dec67acc528047b80f0e9920f0cb9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:19:00 +0900 Subject: [PATCH 317/583] Remove "Opacity" setting This should be implemented at a higher level --- .../Gameplay/TestSceneColourHitErrorMeter.cs | 14 -------------- .../Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 9 --------- 2 files changed, 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs index 23c7410231..84be2c8eb9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -32,12 +32,6 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneColourHitErrorMeter() { - AddSliderStep("Manual Opacity test", 0.01f, 1, 1, alpha => - { - if (colourHitErrorMeter.IsNotNull()) - colourHitErrorMeter.HitShapeOpacity.Value = alpha; - }); - AddSliderStep("Manual spacing test", 0, 10, 2, spacing => { if (colourHitErrorMeter.IsNotNull()) @@ -75,14 +69,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Ruleset CreateRuleset() => new OsuRuleset(); - [Test] - public void TestOpacityChange() - { - AddRepeatStep("Add judgement", applyOneJudgement, 5); - AddStep("Change opacity to 30%", () => colourHitErrorMeter.HitShapeOpacity.Value = 0.3f); - AddRepeatStep("Add judgement", applyOneJudgement, 5); - } - [Test] public void TestSpacingChange() { diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 08df9833d0..bed815b98e 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -28,14 +28,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Precision = 1 }; - [SettingSource("Opacity", "Visibility of the displayed judgements")] - public BindableNumber HitShapeOpacity { get; } = new BindableNumber(1) - { - MinValue = 0.01f, - MaxValue = 1, - Precision = 0.01f, - }; - [SettingSource("Spacing", "Space between each displayed judgement")] public BindableNumber HitShapeSpacing { get; } = new BindableNumber(2) { @@ -66,7 +58,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void LoadComplete() { base.LoadComplete(); - HitShapeOpacity.BindValueChanged(_ => judgementsFlow.Alpha = HitShapeOpacity.Value, true); HitShapeSpacing.BindValueChanged(_ => { judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; From ae70b2c32fcf5805aafae8da1d81290b0e1bd7e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:21:27 +0900 Subject: [PATCH 318/583] Fix other weirdness in variable and description naming --- .../Gameplay/TestSceneColourHitErrorMeter.cs | 6 ++--- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 26 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs index 84be2c8eb9..fccbf092fc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("Manual spacing test", 0, 10, 2, spacing => { if (colourHitErrorMeter.IsNotNull()) - colourHitErrorMeter.HitShapeSpacing.Value = spacing; + colourHitErrorMeter.JudgementSpacing.Value = spacing; }); } @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestSpacingChange() { AddRepeatStep("Add judgement", applyOneJudgement, 5); - AddStep("Change spacing", () => colourHitErrorMeter.HitShapeSpacing.Value = 10); + AddStep("Change spacing", () => colourHitErrorMeter.JudgementSpacing.Value = 10); AddRepeatStep("Add judgement", applyOneJudgement, 5); } @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestHitErrorShapeChange() { AddRepeatStep("Add judgement", applyOneJudgement, 8); - AddStep("Change shape square", () => colourHitErrorMeter.HitShape.Value = ColourHitErrorMeter.ShapeStyle.Square); + AddStep("Change shape square", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Square); AddRepeatStep("Add judgement", applyOneJudgement, 10); } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index bed815b98e..48b6676020 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const int animation_duration = 200; private const int drawable_judgement_size = 8; - [SettingSource("Judgement count", "Number of displayed judgements")] + [SettingSource("Judgement count", "The number of displayed judgements")] public BindableNumber JudgementCount { get; } = new BindableNumber(20) { MinValue = 1, @@ -28,23 +28,26 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Precision = 1 }; - [SettingSource("Spacing", "Space between each displayed judgement")] - public BindableNumber HitShapeSpacing { get; } = new BindableNumber(2) + [SettingSource("Judgement spacing", "The space between each displayed judgement")] + public BindableNumber JudgementSpacing { get; } = new BindableNumber(2) { MinValue = 0, MaxValue = 10, Precision = 0.1f }; - [SettingSource("Shape", "The shape of each displayed judgement")] - public Bindable HitShape { get; } = new Bindable(); + [SettingSource("Judgement shape", "The shape of each displayed judgement")] + public Bindable JudgementShape { get; } = new Bindable(); private readonly JudgementFlow judgementsFlow; public ColourHitErrorMeter() { AutoSizeAxes = Axes.Both; - InternalChild = judgementsFlow = new JudgementFlow(); + InternalChild = judgementsFlow = new JudgementFlow + { + Shape = { BindTarget = JudgementShape } + }; } protected override void OnNewJudgement(JudgementResult judgement) @@ -58,18 +61,19 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void LoadComplete() { base.LoadComplete(); - HitShapeSpacing.BindValueChanged(_ => + + JudgementSpacing.BindValueChanged(_ => { - judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; - judgementsFlow.Spacing = new Vector2(0, HitShapeSpacing.Value); + judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + JudgementSpacing.Value) - JudgementSpacing.Value; + judgementsFlow.Spacing = new Vector2(0, JudgementSpacing.Value); }, true); + JudgementCount.BindValueChanged(_ => { //Used to clear out the overflowing judgement children when the value is lowered judgementsFlow.RemoveAll(_ => true, true); - judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + HitShapeSpacing.Value) - HitShapeSpacing.Value; + judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + JudgementSpacing.Value) - JudgementSpacing.Value; }, true); - HitShape.BindValueChanged(_ => judgementsFlow.Shape.Value = HitShape.Value, true); } public override void Clear() => judgementsFlow.Clear(); From 0f663deda068ec41c2f5a5ef49e35d80d4c262e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:24:57 +0900 Subject: [PATCH 319/583] Fix changing shape causing alpha to be permanently reset to zero --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 48b6676020..83830a73eb 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -112,6 +112,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private readonly Color4 colour; + private Container content = null!; + public HitErrorShape(Color4 colour, int size) { this.colour = colour; @@ -122,33 +124,29 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { base.LoadComplete(); + Child = content = new Container + { + RelativeSizeAxes = Axes.Both, + Colour = colour + }; + Shape.BindValueChanged(shape => { switch (shape.NewValue) { case ShapeStyle.Circle: - Child = new Circle - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = colour - }; + content.Child = new Circle { RelativeSizeAxes = Axes.Both }; break; case ShapeStyle.Square: - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = colour - }; + content.Child = new Box { RelativeSizeAxes = Axes.Both }; break; } }, true); - Child.FadeInFromZero(animation_duration, Easing.OutQuint); - Child.MoveToY(-DrawSize.Y); - Child.MoveToY(0, animation_duration, Easing.OutQuint); + content.FadeInFromZero(animation_duration, Easing.OutQuint); + content.MoveToY(-DrawSize.Y); + content.MoveToY(0, animation_duration, Easing.OutQuint); } public void Remove() From 26d98323ff88b318de252e7e72539169f01f9d26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:45:23 +0900 Subject: [PATCH 320/583] Fix bindable flow, code quality, everything --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 83830a73eb..530743137d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -24,8 +24,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public BindableNumber JudgementCount { get; } = new BindableNumber(20) { MinValue = 1, - MaxValue = 30, - Precision = 1 + MaxValue = 50, }; [SettingSource("Judgement spacing", "The space between each displayed judgement")] @@ -46,7 +45,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters AutoSizeAxes = Axes.Both; InternalChild = judgementsFlow = new JudgementFlow { - Shape = { BindTarget = JudgementShape } + JudgementShape = { BindTarget = JudgementShape }, + JudgementSpacing = { BindTarget = JudgementSpacing }, + JudgementCount = { BindTarget = JudgementCount } }; } @@ -55,25 +56,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) return; - judgementsFlow.Push(GetColourForHitResult(judgement.Type), JudgementCount.Value); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - JudgementSpacing.BindValueChanged(_ => - { - judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + JudgementSpacing.Value) - JudgementSpacing.Value; - judgementsFlow.Spacing = new Vector2(0, JudgementSpacing.Value); - }, true); - - JudgementCount.BindValueChanged(_ => - { - //Used to clear out the overflowing judgement children when the value is lowered - judgementsFlow.RemoveAll(_ => true, true); - judgementsFlow.Height = JudgementCount.Value * (drawable_judgement_size + JudgementSpacing.Value) - JudgementSpacing.Value; - }, true); + judgementsFlow.Push(GetColourForHitResult(judgement.Type)); } public override void Clear() => judgementsFlow.Clear(); @@ -82,7 +65,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); - public readonly Bindable Shape = new Bindable(); + public readonly Bindable JudgementShape = new Bindable(); + + public readonly Bindable JudgementSpacing = new Bindable(); + + public readonly Bindable JudgementCount = new Bindable(); public JudgementFlow() { @@ -92,15 +79,41 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters LayoutEasing = Easing.OutQuint; } - public void Push(Color4 colour, int maxErrorShapeCount) + protected override void LoadComplete() + { + base.LoadComplete(); + + JudgementCount.BindValueChanged(count => + { + removeExtraJudgements(); + updateMetrics(); + }); + + JudgementSpacing.BindValueChanged(_ => updateMetrics(), true); + } + + public void Push(Color4 colour) { Add(new HitErrorShape(colour, drawable_judgement_size) { - Shape = { BindTarget = Shape }, + Shape = { BindTarget = JudgementShape }, }); - if (Children.Count > maxErrorShapeCount) - Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); + removeExtraJudgements(); + } + + private void removeExtraJudgements() + { + var remainingChildren = Children.Where(c => !c.IsRemoved); + + while (remainingChildren.Count() > JudgementCount.Value) + remainingChildren.First().Remove(); + } + + private void updateMetrics() + { + Height = JudgementCount.Value * (drawable_judgement_size + JudgementSpacing.Value) - JudgementSpacing.Value; + Spacing = new Vector2(0, JudgementSpacing.Value); } } From 1ed4b9c11c1ce300022e505ec91c9258fa16b9f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:45:30 +0900 Subject: [PATCH 321/583] Add more flexibility to test --- .../Visual/Gameplay/TestSceneColourHitErrorMeter.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs index fccbf092fc..a953db4f19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs @@ -32,11 +32,17 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneColourHitErrorMeter() { - AddSliderStep("Manual spacing test", 0, 10, 2, spacing => + AddSliderStep("Judgement spacing", 0, 10, 2, spacing => { if (colourHitErrorMeter.IsNotNull()) colourHitErrorMeter.JudgementSpacing.Value = spacing; }); + + AddSliderStep("Judgement count", 1, 50, 5, spacing => + { + if (colourHitErrorMeter.IsNotNull()) + colourHitErrorMeter.JudgementCount.Value = spacing; + }); } [SetUpSteps] @@ -91,6 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("Add judgement", applyOneJudgement, 8); AddStep("Change shape square", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Square); AddRepeatStep("Add judgement", applyOneJudgement, 10); + AddStep("Change shape circle", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Circle); } private void applyOneJudgement() From f0dcda9accbf4e67060407ec3c49e4a758953069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 15:49:02 +0900 Subject: [PATCH 322/583] Decrease precision of spacing setting --- osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 530743137d..dadec7c06b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -32,7 +32,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { MinValue = 0, MaxValue = 10, - Precision = 0.1f }; [SettingSource("Judgement shape", "The shape of each displayed judgement")] From 0458305a23f801928f85f1a587961732adb68d96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 17:40:19 +0900 Subject: [PATCH 323/583] Add argon judgement bubbles --- .../Skinning/Argon/ArgonJudgementPiece.cs | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index 315b9ec65e..b08b7b4e85 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -1,14 +1,17 @@ // 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 osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; using osuTK; @@ -20,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon protected SpriteText JudgementText { get; private set; } = null!; + private RingExplosion? ringExplosion; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -42,10 +47,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Origin = Anchor.Centre, Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), + Blending = BlendingParameters.Additive, Spacing = new Vector2(5, 0), Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold), - } + }, }; + + if (Result.IsHit()) + { + AddInternal(ringExplosion = new RingExplosion(Result) + { + Colour = colours.ForHitResult(Result), + }); + } } /// @@ -78,8 +92,80 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } this.FadeOutFromOne(800); + + ringExplosion?.PlayAnimation(); } public Drawable? GetAboveHitObjectsProxiedContent() => null; + + private class RingExplosion : CompositeDrawable + { + private readonly float travel = 52; + + public RingExplosion(HitResult result) + { + const float thickness = 4; + + const float small_size = 9; + const float large_size = 14; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Blending = BlendingParameters.Additive; + + int countSmall = 0; + int countLarge = 0; + + switch (result) + { + case HitResult.Meh: + countSmall = 3; + travel *= 0.3f; + break; + + case HitResult.Ok: + case HitResult.Good: + countSmall = 4; + travel *= 0.6f; + break; + + case HitResult.Great: + case HitResult.Perfect: + countSmall = 4; + countLarge = 4; + break; + } + + for (int i = 0; i < countSmall; i++) + AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) }); + + for (int i = 0; i < countLarge; i++) + AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) }); + } + + public void PlayAnimation() + { + foreach (var c in InternalChildren) + { + const float start_position_ratio = 0.3f; + + float direction = RNG.NextSingle(0, 360); + float distance = RNG.NextSingle(travel / 2, travel); + + c.MoveTo(new Vector2( + MathF.Cos(direction) * distance * start_position_ratio, + MathF.Sin(direction) * distance * start_position_ratio + )); + + c.MoveTo(new Vector2( + MathF.Cos(direction) * distance, + MathF.Sin(direction) * distance + ), 600, Easing.OutQuint); + } + + this.FadeOutFromOne(1000, Easing.OutQuint); + } + } } } From b321afc3a67da245eb23e7052b02e07defb5a433 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 18:00:02 +0900 Subject: [PATCH 324/583] Update framework --- osu.Android.props | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 77c29a5d6e..dd263d6aaa 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9a63ccbf48..7e6bd7c7be 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1041,7 +1041,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || !entry.LoggerName.Equals(ITabletHandler.LOGGER_NAME, StringComparison.OrdinalIgnoreCase)) + if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(@"[Tablet]", StringComparison.Ordinal)) return; if (entry.Level == LogLevel.Error) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 29e690a024..c4e2a5168e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 83410b08f6..622efcd63d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -82,7 +82,7 @@ - + From 68015ef10c14ec9f21dfc3fa3718a71a1ec22aa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 18:12:28 +0900 Subject: [PATCH 325/583] Fix argon reverse arrow becoming white after switching skins --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index 3f13fe135e..9d44db3614 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon }; accentColour = hitObject.AccentColour.GetBoundCopy(); - accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4)); + accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true); } } } From fa3f53f39f59439a011f19a1886105ad91152810 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Sep 2022 12:37:21 +0300 Subject: [PATCH 326/583] Remove tablet prefix from error notification --- osu.Game/OsuGame.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7e6bd7c7be..1f0c27b286 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1037,18 +1037,21 @@ namespace osu.Game private void forwardTabletLogsToNotifications() { + const string tablet_prefix = @"[Tablet] "; bool notifyOnWarning = true; Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(@"[Tablet]", StringComparison.Ordinal)) + if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(tablet_prefix, StringComparison.Ordinal)) return; + string message = entry.Message.Replace(tablet_prefix, string.Empty); + if (entry.Level == LogLevel.Error) { Schedule(() => Notifications.Post(new SimpleNotification { - Text = $"Encountered tablet error: \"{entry.Message}\"", + Text = $"Encountered tablet error: \"{message}\"", Icon = FontAwesome.Solid.PenSquare, IconColour = Colours.RedDark, })); From ea7c5458fe091c61ca854be4640b48b4d3e5fd78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 18:43:41 +0900 Subject: [PATCH 327/583] Fix sprite dropdown not showing resources from non-legacy skins during gameplay --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 2 +- osu.Game/Skinning/ISkinTransformer.cs | 17 +++++++++++++++++ osu.Game/Skinning/LegacySkinTransformer.cs | 5 +---- osu.Game/Skinning/SkinnableSprite.cs | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Skinning/ISkinTransformer.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index d5cc469ca9..22944becf3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null); - if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin)) + if (topProvider is ISkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin)) { AddInternal(ApproachCircle = new Sprite { diff --git a/osu.Game/Skinning/ISkinTransformer.cs b/osu.Game/Skinning/ISkinTransformer.cs new file mode 100644 index 0000000000..f985b8afcd --- /dev/null +++ b/osu.Game/Skinning/ISkinTransformer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Skinning +{ + /// + /// A skin transformer takes in an and applies transformations to it. + /// The most common use case is allowing individual rulesets to add skinnable components without directly coupling to underlying skins. + /// + public interface ISkinTransformer : ISkin + { + /// + /// The original skin that is being transformed. + /// + ISkin Skin { get; } + } +} diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 8f2526db37..353ee067d1 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -18,11 +18,8 @@ namespace osu.Game.Skinning /// /// Transformer used to handle support of legacy features for individual rulesets. /// - public abstract class LegacySkinTransformer : ISkin + public abstract class LegacySkinTransformer : ISkinTransformer { - /// - /// The which is being transformed. - /// [NotNull] public ISkin Skin { get; } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index f8a9aaa6fb..5a39121b16 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -100,7 +100,7 @@ namespace osu.Game.Skinning { foreach (var skin in skins) { - if (skin is LegacySkinTransformer transformer && isUserSkin(transformer.Skin)) + if (skin is ISkinTransformer transformer && isUserSkin(transformer.Skin)) return transformer.Skin; if (isUserSkin(skin)) From f8e9a960ba1bc1756185eabee09c1b2201f4da99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 18:44:04 +0900 Subject: [PATCH 328/583] Fix argon skin not forwarding resource lookups to user backing --- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 8a1bf00877..1a569edc3e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -11,10 +11,13 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public class OsuArgonSkinTransformer : ISkin + public class OsuArgonSkinTransformer : ISkinTransformer { + public ISkin Skin { get; } + public OsuArgonSkinTransformer(ISkin skin) { + Skin = skin; } public Drawable? GetDrawableComponent(ISkinComponent component) @@ -55,15 +58,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon return null; } - public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - { - return null; - } + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT); - public ISample? GetSample(ISampleInfo sampleInfo) - { - return null; - } + public ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo); public IBindable? GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull { From 74aefdc5bd690574d3d9ca369cbde4355595f28c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 18:50:19 +0900 Subject: [PATCH 329/583] Move basic transformer behaviour to base `abstract` class --- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 23 +++---------- osu.Game/Skinning/LegacySkinTransformer.cs | 29 +++-------------- osu.Game/Skinning/SkinTransformer.cs | 32 +++++++++++++++++++ 3 files changed, 41 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Skinning/SkinTransformer.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 1a569edc3e..3e5bfd25b5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -1,26 +1,20 @@ // 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.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public class OsuArgonSkinTransformer : ISkinTransformer + public class OsuArgonSkinTransformer : SkinTransformer { - public ISkin Skin { get; } - public OsuArgonSkinTransformer(ISkin skin) + : base(skin) { - Skin = skin; } - public Drawable? GetDrawableComponent(ISkinComponent component) + public override Drawable? GetDrawableComponent(ISkinComponent component) { switch (component) { @@ -55,16 +49,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon break; } - return null; - } - - public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT); - - public ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo); - - public IBindable? GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull - { - return null; + return base.GetDrawableComponent(component); } } } diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 353ee067d1..2de1564a5c 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -1,14 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; -using JetBrains.Annotations; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; using static osu.Game.Skinning.SkinConfiguration; @@ -18,24 +11,14 @@ namespace osu.Game.Skinning /// /// Transformer used to handle support of legacy features for individual rulesets. /// - public abstract class LegacySkinTransformer : ISkinTransformer + public abstract class LegacySkinTransformer : SkinTransformer { - [NotNull] - public ISkin Skin { get; } - - protected LegacySkinTransformer([NotNull] ISkin skin) + protected LegacySkinTransformer(ISkin skin) + : base(skin) { - Skin = skin ?? throw new ArgumentNullException(nameof(skin)); } - public virtual Drawable GetDrawableComponent(ISkinComponent component) => Skin.GetDrawableComponent(component); - - public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); - - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => Skin.GetTexture(componentName, wrapModeS, wrapModeT); - - public virtual ISample GetSample(ISampleInfo sampleInfo) + public override ISample? GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) return Skin.GetSample(sampleInfo); @@ -44,9 +27,7 @@ namespace osu.Game.Skinning if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) return new SampleVirtual(); - return Skin.GetSample(sampleInfo); + return base.GetSample(sampleInfo); } - - public virtual IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); } } diff --git a/osu.Game/Skinning/SkinTransformer.cs b/osu.Game/Skinning/SkinTransformer.cs new file mode 100644 index 0000000000..4da60f1e43 --- /dev/null +++ b/osu.Game/Skinning/SkinTransformer.cs @@ -0,0 +1,32 @@ +// 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 osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; + +namespace osu.Game.Skinning +{ + public abstract class SkinTransformer : ISkinTransformer + { + public ISkin Skin { get; } + + protected SkinTransformer(ISkin skin) + { + Skin = skin ?? throw new ArgumentNullException(nameof(skin)); + } + + public virtual Drawable? GetDrawableComponent(ISkinComponent component) => Skin.GetDrawableComponent(component); + + public virtual Texture? GetTexture(string componentName) => GetTexture(componentName, default, default); + + public virtual Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT); + + public virtual ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo); + + public virtual IBindable? GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull => Skin.GetConfig(lookup); + } +} From 794a1319546163d2aa91b08a90d2a800f82393c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Sep 2022 13:17:01 +0300 Subject: [PATCH 330/583] Update method name and conditions --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1f0c27b286..ff974820a7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -188,7 +188,7 @@ namespace osu.Game { this.args = args; - forwardRuntimeLogsToNotifications(); + forwardGeneralLogsToNotifications(); forwardTabletLogsToNotifications(); SentryLogger = new SentryLogger(this); @@ -994,7 +994,7 @@ namespace osu.Game overlay.Depth = (float)-Clock.CurrentTime; } - private void forwardRuntimeLogsToNotifications() + private void forwardGeneralLogsToNotifications() { int recentLogCount = 0; @@ -1002,7 +1002,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target == null) return; + if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return; const int short_term_display_limit = 3; From fd92bcdff255bfcf69d1dab1f54fdc4fc9b4fec2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Sep 2022 13:21:27 +0300 Subject: [PATCH 331/583] Ignore case sensitivity during message comparison Co-authored-by: Dan Balasescu --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff974820a7..474c855c3d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1042,7 +1042,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(tablet_prefix, StringComparison.Ordinal)) + if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(tablet_prefix, StringComparison.OrdinalIgnoreCase)) return; string message = entry.Message.Replace(tablet_prefix, string.Empty); From 4528c1037b4fb5541364b54cf1a20c699e19fed3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 22 Sep 2022 19:26:12 +0900 Subject: [PATCH 332/583] Resolve CI inspections --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index e5b7208da6..88b6b9dd56 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; From 5243ff3c2252b8a3bf37580018c8b7d753a1a6b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 19:16:49 +0900 Subject: [PATCH 333/583] Add argon combo colours I'm 99% sure these are just test colours flyte was using, but they look good so let's go with them. I've added two new colours to increase the default combo colour rotation to 6. The initial ordering still matches, for whatever that's worth. --- osu.Game/Skinning/ArgonSkin.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 2c17b28b01..010e2175e1 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -44,6 +44,28 @@ namespace osu.Game.Skinning : base(skin, resources) { this.resources = resources; + + Configuration.CustomComboColours = new List + { + // Standard combo progression order is green - blue - red - yellow. + // But for whatever reason, this starts from index 1, not 0. + // + // We've added two new combo colours in argon, so to ensure the initial rotation matches, + // this same progression is in slots 1 - 4. + + // Orange + new Color4(241, 116, 0, 255), + // Green + new Color4(0, 241, 53, 255), + // Blue + new Color4(0, 82, 241, 255), + // Red + new Color4(241, 0, 0, 255), + // Yellow + new Color4(232, 235, 0, 255), + // Purple + new Color4(92, 0, 241, 255), + }; } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT); From 8f7a306d81a721878c7be093e17445e57f4ed3de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 19:53:16 +0900 Subject: [PATCH 334/583] Inline comment regarding margin necessity --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index c4d81e7e2a..05a04c3bae 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -96,6 +96,13 @@ namespace osu.Game.Screens.Play.HUD int displayCount = Math.Min(Flow.Count, maxPanels); Height = displayCount * (GameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y); + // Add extra margin space to flow equal to height of leaderboard. + // This ensures the content is always on screen, but also accounts for the fact that scroll operations + // without animation were actually forcing the local score to a location it can't usually reside at. + // + // Basically, the local score was in the scroll extension region (due to always trying to scroll the + // local player to the middle of the display, but there being no other content below the local player + // to scroll up by). Flow.Margin = new MarginPadding { Bottom = Height }; requiresScroll = displayCount != Flow.Count; From 7508592789b08cb27097449c36b0f78d32061dfe Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 22 Sep 2022 19:56:24 +0900 Subject: [PATCH 335/583] Clean up some checks --- osu.Game/Online/Leaderboards/Leaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d5834c4329..569015dcd3 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -169,7 +169,7 @@ namespace osu.Game.Online.Leaderboards throw new InvalidOperationException($"State {state} cannot be set by a leaderboard implementation."); } - Debug.Assert(scores.Any() != true); + Debug.Assert(scores.Any()); setState(state); } @@ -258,7 +258,7 @@ namespace osu.Game.Online.Leaderboards .Expire(); scoreFlowContainer = null; - if (scores.Any() != true) + if (scores.Any()) { setState(LeaderboardState.NoScores); return; From 8875f7ee4334fba722fd5999083ee6e485c99f2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 20:06:24 +0900 Subject: [PATCH 336/583] Add very basic follow point implementation for argon skin This is just something to look better than the glowing mess which triangles fallback was providing. This element hasn't been designed yet, so I'm just filling in with something amicable for the time being. --- .../Skinning/Argon/ArgonFollowPoint.cs | 39 +++++++++++++++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 3 ++ 2 files changed, 42 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs new file mode 100644 index 0000000000..47dae3c30a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs @@ -0,0 +1,39 @@ +// 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.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonFollowPoint : CompositeDrawable + { + public ArgonFollowPoint() + { + Blending = BlendingParameters.Additive; + + Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")); + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(8), + Colour = OsuColour.Gray(0.2f), + }, + new SpriteIcon + { + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(8), + X = 4, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 3e5bfd25b5..9ce4323c35 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.ReverseArrow: return new ArgonReverseArrow(); + + case OsuSkinComponents.FollowPoint: + return new ArgonFollowPoint(); } break; From 37e25792443331c9dc8cbcdd0217273c11c36b83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 20:35:26 +0900 Subject: [PATCH 337/583] Fix reversed checks --- osu.Game/Online/Leaderboards/Leaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 569015dcd3..206c15afde 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -169,7 +169,7 @@ namespace osu.Game.Online.Leaderboards throw new InvalidOperationException($"State {state} cannot be set by a leaderboard implementation."); } - Debug.Assert(scores.Any()); + Debug.Assert(!scores.Any()); setState(state); } @@ -258,7 +258,7 @@ namespace osu.Game.Online.Leaderboards .Expire(); scoreFlowContainer = null; - if (scores.Any()) + if (!scores.Any()) { setState(LeaderboardState.NoScores); return; From a2ff31f37d73f55e449f17cf33f144de624923cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 20:43:38 +0900 Subject: [PATCH 338/583] Fix failing test --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 182cc51572..cca87d42a1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -250,11 +250,9 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestHitLightingColour() { - var fruitColour = SkinConfiguration.DefaultComboColours[1]; AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true)); AddStep("catch fruit", () => attemptCatch(new Fruit())); - AddAssert("correct hit lighting colour", () => - catcher.ChildrenOfType().First()?.Entry?.ObjectColour == fruitColour); + AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType().First()?.Entry?.ObjectColour == this.ChildrenOfType().First().AccentColour.Value); } [Test] From 7527d815e83cbc3745c876d8115154df28f3aea4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 22 Sep 2022 20:58:23 +0900 Subject: [PATCH 339/583] Remove unused using --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index cca87d42a1..2dc99077d3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK; From 6a0047b7a224dee2b012ffc922c8d73a2c2a55f6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 22 Sep 2022 21:01:23 +0900 Subject: [PATCH 340/583] Update location of FullscreenCapability bindable --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 4 ++-- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 28642f12a1..c64a3101b7 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -73,8 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModes.BindTo(host.Window.SupportedWindowModes); } - if (host.Window is WindowsWindow windowsWindow) - fullscreenCapability.BindTo(windowsWindow.FullscreenCapability); + if (host.Renderer is IWindowsRenderer windowsRenderer) + fullscreenCapability.BindTo(windowsRenderer.FullscreenCapability); Children = new Drawable[] { diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index c9d4dc7811..bacaccd68e 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -261,8 +261,8 @@ namespace osu.Game.Screens.Utility string exclusive = "unknown"; - if (host.Window is WindowsWindow windowsWindow) - exclusive = windowsWindow.FullscreenCapability.ToString(); + if (host.Renderer is IWindowsRenderer windowsRenderer) + exclusive = windowsRenderer.FullscreenCapability.ToString(); statusText.Clear(); From e95758cfcd630170fb24f860800b9a13f9983661 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Sep 2022 20:39:46 +0900 Subject: [PATCH 341/583] Add basic "argon" cursor --- .../Skinning/Argon/ArgonCursor.cs | 80 +++++++++++++++++++ .../Skinning/Argon/ArgonCursorTrail.cs | 29 +++++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 6 ++ 3 files changed, 115 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs new file mode 100644 index 0000000000..446f3c83ae --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs @@ -0,0 +1,80 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonCursor : OsuCursorSprite + { + public ArgonCursor() + { + RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new[] + { + ExpandTarget = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 6, + BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.4f, + Colour = Colour4.FromHex("FC618F").Darken(0.6f), + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White.Opacity(0.8f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + }, + }, + new Circle + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.2f), + Colour = new Color4(255, 255, 255, 255), + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 20, + Colour = new Color4(171, 255, 255, 100), + }, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs new file mode 100644 index 0000000000..9bb3122a3b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs @@ -0,0 +1,29 @@ +// 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.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonCursorTrail : CursorTrail + { + protected override float IntervalMultiplier => 0.4f; + + protected override float FadeExponent => 4; + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Cursor/cursortrail"); + Scale = new Vector2(0.8f / Texture.ScaleAdjust); + + Blending = BlendingParameters.Additive; + + Alpha = 0.8f; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 9ce4323c35..ead49a8af2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -47,6 +47,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.FollowPoint: return new ArgonFollowPoint(); + + case OsuSkinComponents.Cursor: + return new ArgonCursor(); + + case OsuSkinComponents.CursorTrail: + return new ArgonCursorTrail(); } break; From 3811ea113d993b9e2705cc3ed93a22b6d2d5cd01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Sep 2022 00:53:52 +0900 Subject: [PATCH 342/583] Adjust argon animations a bit --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 0479b3ff3e..ffdcba3cdb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon const double fade_out_time = 800; const double flash_in_duration = 150; - const double resize_duration = 300; + const double resize_duration = 400; const float shrink_size = 0.8f; @@ -165,13 +165,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // The outer gradient is resize with a slight delay from the border. // This is to give it a bomb-like effect, with the border "triggering" its animation when getting close. using (BeginDelayedSequence(flash_in_duration / 12)) + { outerGradient.ResizeTo(outerGradient.Size * shrink_size, resize_duration, Easing.OutElasticHalf); + outerGradient + .FadeColour(Color4.White, 80) + .Then() + .FadeOut(flash_in_duration); + } // The flash layer starts white to give the wanted brightness, but is almost immediately // recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash) // but works well enough with the colour fade. flash.FadeTo(1, flash_in_duration, Easing.OutQuint); - flash.FlashColour(Color4.White, flash_in_duration, Easing.OutQuint); + flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint); this.FadeOut(fade_out_time, Easing.OutQuad); break; From e9c3478f69359f861ecadc1524461b8ffb735826 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Sep 2022 20:07:40 +0300 Subject: [PATCH 343/583] Add very basic "argon" spinner --- .../Skinning/Argon/ArgonSpinner.cs | 146 +++++++++++ .../Skinning/Argon/ArgonSpinnerDisc.cs | 247 ++++++++++++++++++ .../Skinning/Argon/ArgonSpinnerTicks.cs | 61 +++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 3 + 4 files changed, 457 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs new file mode 100644 index 0000000000..f9b2f84984 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -0,0 +1,146 @@ +// 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.Globalization; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonSpinner : CompositeDrawable + { + private DrawableSpinner drawableSpinner = null!; + + private OsuSpriteText bonusCounter = null!; + + private Container spmContainer = null!; + private OsuSpriteText spmCounter = null!; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableHitObject) + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + drawableSpinner = (DrawableSpinner)drawableHitObject; + + InternalChildren = new Drawable[] + { + bonusCounter = new OsuSpriteText + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 24), + Y = -120, + }, + new ArgonSpinnerDisc + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + bonusCounter = new OsuSpriteText + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 28, weight: FontWeight.Bold), + Y = -120, + }, + spmContainer = new Container + { + Alpha = 0f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 100, + Children = new[] + { + spmCounter = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"0", + Font = OsuFont.Default.With(size: 28, weight: FontWeight.SemiBold) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"SPINS PER MINUTE", + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + Y = 30 + } + } + } + }; + } + + private IBindable gainedBonus = null!; + private IBindable spinsPerMinute = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy(); + gainedBonus.BindValueChanged(bonus => + { + bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo); + bonusCounter.FadeOutFromOne(1500); + bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); + }); + + spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy(); + spinsPerMinute.BindValueChanged(spm => + { + spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); + }, true); + + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); + } + + protected override void Update() + { + base.Update(); + + if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null) + fadeCounterOnTimeStart(); + } + + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) + { + if (!(drawableHitObject is DrawableSpinner)) + return; + + fadeCounterOnTimeStart(); + } + + private void fadeCounterOnTimeStart() + { + if (drawableSpinner.Result?.TimeStarted is double startTime) + { + using (BeginAbsoluteSequence(startTime)) + spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableSpinner.IsNotNull()) + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs new file mode 100644 index 0000000000..a6211ecb01 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs @@ -0,0 +1,247 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonSpinnerDisc : CompositeDrawable + { + private const float initial_scale = 1.3f; + private const float idle_alpha = 0.2f; + private const float tracking_alpha = 0.4f; + + private const float idle_centre_size = 80f; + private const float tracking_centre_size = 40f; + + private DrawableSpinner drawableSpinner = null!; + + private readonly BindableBool complete = new BindableBool(); + + private int wholeRotationCount; + + private bool checkNewRotationCount + { + get + { + int rotations = (int)(drawableSpinner.Result.RateAdjustedRotation / 360); + + if (wholeRotationCount == rotations) return false; + + wholeRotationCount = rotations; + return true; + } + } + + private Container disc = null!; + private Container centre = null!; + private CircularContainer fill = null!; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableHitObject) + { + drawableSpinner = (DrawableSpinner)drawableHitObject; + + // we are slightly bigger than our parent, to clip the top and bottom of the circle + // this should probably be revisited when scaled spinners are a thing. + Scale = new Vector2(initial_scale); + + InternalChildren = new Drawable[] + { + disc = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + fill = new CircularContainer + { + Name = @"Fill", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Colour4.FromHex("FC618F").Opacity(1f), + Radius = 40, + }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0f, + AlwaysPresent = true, + } + }, + new CircularContainer + { + Name = @"Ring", + Masking = true, + BorderColour = Color4.White, + BorderThickness = 5, + RelativeSizeAxes = Axes.Both, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + } + }, + new ArgonSpinnerTicks(), + } + }, + centre = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(idle_centre_size), + Children = new[] + { + new RingPiece(10) + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + }, + new RingPiece(3) + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1f), + } + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); + } + + protected override void Update() + { + base.Update(); + + complete.Value = Time.Current >= drawableSpinner.Result.TimeCompleted; + + if (complete.Value) + { + if (checkNewRotationCount) + { + fill.FinishTransforms(false, nameof(Alpha)); + fill + .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) + .Then() + .FadeTo(tracking_alpha, 250, Easing.OutQuint); + } + } + else + { + fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime)); + } + + if (centre.Width == idle_centre_size && drawableSpinner.Result?.TimeStarted != null) + updateCentrePieceSize(); + + const float initial_fill_scale = 0.1f; + float targetScale = initial_fill_scale + (0.98f - initial_fill_scale) * drawableSpinner.Progress; + + fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); + disc.Rotation = drawableSpinner.RotationTracker.Rotation; + } + + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) + { + if (!(drawableHitObject is DrawableSpinner)) + return; + + Spinner spinner = drawableSpinner.HitObject; + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) + { + this.ScaleTo(initial_scale); + this.RotateTo(0); + + using (BeginDelayedSequence(spinner.TimePreempt / 2)) + { + // constant ambient rotation to give the spinner "spinning" character. + this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); + } + + using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset)) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out); + this.RotateTo(Rotation + 180, 320); + break; + + case ArmedState.Miss: + this.ScaleTo(initial_scale * 0.8f, 320, Easing.In); + break; + } + } + } + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) + { + centre.ScaleTo(0); + disc.ScaleTo(0); + + using (BeginDelayedSequence(spinner.TimePreempt / 2)) + { + centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); + disc.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + + using (BeginDelayedSequence(spinner.TimePreempt / 2)) + { + centre.ScaleTo(0.8f, spinner.TimePreempt / 2, Easing.OutQuint); + disc.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + } + } + } + + if (drawableSpinner.Result?.TimeStarted != null) + updateCentrePieceSize(); + } + + private void updateCentrePieceSize() + { + Debug.Assert(drawableSpinner.Result?.TimeStarted != null); + + Spinner spinner = drawableSpinner.HitObject; + + using (BeginAbsoluteSequence(drawableSpinner.Result.TimeStarted.Value)) + centre.ResizeTo(new Vector2(tracking_centre_size), spinner.TimePreempt / 2, Easing.OutQuint); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableSpinner.IsNotNull()) + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs new file mode 100644 index 0000000000..0203432088 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs @@ -0,0 +1,61 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public class ArgonSpinnerTicks : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load() + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + + const float count = 25; + + for (float i = 0; i < count; i++) + { + AddInternal(new CircularContainer + { + RelativePositionAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + BorderColour = Color4.White, + BorderThickness = 2f, + Size = new Vector2(30, 5), + Origin = Anchor.Centre, + Position = new Vector2( + 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.75f, + 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.75f + ), + Rotation = -i / count * 360 - 120, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Colour4.White.Opacity(0.2f), + Radius = 30, + }, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + } + } + }); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 3e5bfd25b5..ba79d94937 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -42,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.SliderScorePoint: return new ArgonSliderScorePoint(); + case OsuSkinComponents.SpinnerBody: + return new ArgonSpinner(); + case OsuSkinComponents.ReverseArrow: return new ArgonReverseArrow(); } From 49af80e17b099e02cb9a99b5f0b11dee7f1fba57 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Sep 2022 20:39:13 +0300 Subject: [PATCH 344/583] Remove 1.3x scale and adjust metrics with it --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs | 4 ++-- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs index f9b2f84984..95438e9588 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -55,14 +55,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Default.With(size: 28, weight: FontWeight.Bold), - Y = -120, + Y = -100, }, spmContainer = new Container { Alpha = 0f, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Y = 100, + Y = 60, Children = new[] { spmCounter = new OsuSpriteText diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs index a6211ecb01..4669b5b913 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { public class ArgonSpinnerDisc : CompositeDrawable { - private const float initial_scale = 1.3f; + private const float initial_scale = 1f; private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; From a771d4031f5f7cf579167512ed35f3d99be800a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Sep 2022 21:27:06 +0300 Subject: [PATCH 345/583] Separate triangles-specific test steps to own scene --- .../TestSceneSpinnerRotation.cs | 13 -- .../TestSceneTrianglesSpinnerRotation.cs | 149 ++++++++++++++++++ 2 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index c01b2576e8..5fa4e24f5e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -43,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Tests => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); private DrawableSpinner drawableSpinner = null!; - private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single(); [SetUpSteps] public override void SetUpSteps() @@ -77,7 +75,6 @@ namespace osu.Game.Rulesets.Osu.Tests { double finalCumulativeTrackerRotation = 0; double finalTrackerRotation = 0, trackerRotationTolerance = 0; - double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0; addSeekStep(spinner_start_time + 5000); AddStep("retrieve disc rotation", () => @@ -85,11 +82,6 @@ namespace osu.Game.Rulesets.Osu.Tests finalTrackerRotation = drawableSpinner.RotationTracker.Rotation; trackerRotationTolerance = Math.Abs(finalTrackerRotation * 0.05f); }); - AddStep("retrieve spinner symbol rotation", () => - { - finalSpinnerSymbolRotation = spinnerSymbol.Rotation; - spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f); - }); AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation); addSeekStep(spinner_start_time + 2500); @@ -98,8 +90,6 @@ namespace osu.Game.Rulesets.Osu.Tests // due to the exponential damping applied we're allowing a larger margin of error of about 10% // (5% relative to the final rotation value, but we're half-way through the spin). () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance)); - AddAssert("symbol rotation rewound", - () => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation rewound", // cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error. () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100)); @@ -107,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(spinner_start_time + 5000); AddAssert("is disc rotation almost same", () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance)); - AddAssert("is symbol rotation almost same", - () => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation almost same", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100)); } @@ -122,7 +110,6 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(5000); AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.RotationTracker.Rotation > 0 : drawableSpinner.RotationTracker.Rotation < 0); - AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0); } private Replay flip(Replay scoreReplay) => new Replay diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs new file mode 100644 index 0000000000..80e3af6cc0 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs @@ -0,0 +1,149 @@ +// 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.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Replays; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneTrianglesSpinnerRotation : TestSceneOsuPlayer + { + private const double spinner_start_time = 100; + private const double spinner_duration = 6000; + + [Resolved] + private SkinManager skinManager { get; set; } = null!; + + [Resolved] + private AudioManager audioManager { get; set; } = null!; + + protected override bool Autoplay => true; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + + private DrawableSpinner drawableSpinner = null!; + private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single(); + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("set triangles skin", () => skinManager.CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged()); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First()); + } + + [Test] + public void TestSymbolMiddleRewindingRotation() + { + double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0; + + addSeekStep(spinner_start_time + 5000); + AddStep("retrieve spinner symbol rotation", () => + { + finalSpinnerSymbolRotation = spinnerSymbol.Rotation; + spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f); + }); + + addSeekStep(spinner_start_time + 2500); + AddAssert("symbol rotation rewound", + () => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance)); + + addSeekStep(spinner_start_time + 5000); + AddAssert("is symbol rotation almost same", + () => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance)); + } + + [Test] + public void TestSymbolRotationDirection([Values(true, false)] bool clockwise) + { + if (clockwise) + transformReplay(flip); + + addSeekStep(5000); + AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0); + } + + private Replay flip(Replay scoreReplay) => new Replay + { + Frames = scoreReplay + .Frames + .Cast() + .Select(replayFrame => + { + var flippedPosition = new Vector2(OsuPlayfield.BASE_SIZE.X - replayFrame.Position.X, replayFrame.Position.Y); + return new OsuReplayFrame(replayFrame.Time, flippedPosition, replayFrame.Actions.ToArray()); + }) + .Cast() + .ToList() + }; + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); + AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(100)); + } + + private void transformReplay(Func replayTransformation) => AddStep("set replay", () => + { + var drawableRuleset = this.ChildrenOfType().Single(); + var score = drawableRuleset.ReplayScore; + var transformedScore = new Score + { + ScoreInfo = score.ScoreInfo, + Replay = replayTransformation.Invoke(score.Replay) + }; + drawableRuleset.SetReplayScore(transformedScore); + }); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = new List + { + new Spinner + { + Position = new Vector2(256, 192), + StartTime = spinner_start_time, + Duration = spinner_duration + }, + } + }; + + private class ScoreExposedPlayer : TestPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public ScoreExposedPlayer() + : base(false, false) + { + } + } + } +} From d538295ad5712af0d7124ac4131e383219845459 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Thu, 22 Sep 2022 23:04:43 +0200 Subject: [PATCH 346/583] Add `TestSceneOsuModRandom` --- .../Mods/TestSceneOsuModRandom.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs new file mode 100644 index 0000000000..c24ba6d530 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModRandom : OsuModTestScene + { + [TestCase(1)] + [TestCase(7)] + [TestCase(10)] + public void TestDefaultBeatmap(float angleSharpness) => CreateModTest(new ModTestData + { + Mod = new OsuModRandom + { + AngleSharpness = { Value = angleSharpness } + }, + Autoplay = true, + PassCondition = () => true + }); + + [TestCase(1)] + [TestCase(7)] + [TestCase(10)] + public void TestJumpBeatmap(float angleSharpness) => CreateModTest(new ModTestData + { + Mod = new OsuModRandom + { + AngleSharpness = { Value = angleSharpness } + }, + Beatmap = jumpBeatmap, + Autoplay = true, + PassCondition = () => true + }); + + [TestCase(1)] + [TestCase(7)] + [TestCase(10)] + public void TestStreamBeatmap(float angleSharpness) => CreateModTest(new ModTestData + { + Mod = new OsuModRandom + { + AngleSharpness = { Value = angleSharpness } + }, + Beatmap = streamBeatmap, + Autoplay = true, + PassCondition = () => true + }); + + private OsuBeatmap jumpBeatmap => + createHitCircleBeatmap(new[] { 100, 200, 300, 400 }, 8, 300, 2 * 300); + + private OsuBeatmap streamBeatmap => + createHitCircleBeatmap(new[] { 10, 20, 30, 40, 50, 60, 70, 80 }, 16, 150, 4 * 150); + + private OsuBeatmap createHitCircleBeatmap(IEnumerable spacings, int objectsPerSpacing, int interval, int beatLength) + { + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new TimingControlPoint + { + Time = 0, + BeatLength = beatLength + }); + + var beatmap = new OsuBeatmap + { + BeatmapInfo = new BeatmapInfo + { + StackLeniency = 0, + Difficulty = new BeatmapDifficulty + { + ApproachRate = 8.5f + } + }, + ControlPointInfo = controlPointInfo + }; + + foreach (int spacing in spacings) + { + for (int i = 0; i < objectsPerSpacing; i++) + { + beatmap.HitObjects.Add(new HitCircle + { + StartTime = interval * beatmap.HitObjects.Count, + Position = beatmap.HitObjects.Count % 2 == 0 ? Vector2.Zero : new Vector2(spacing, 0), + NewCombo = i == 0 + }); + } + } + + return beatmap; + } + } +} From a65a76f7de5de1f3f298359cdcbd99b3d2c20069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Sep 2022 20:05:12 +0900 Subject: [PATCH 347/583] Fix off-thread drawable mutation in multiplayer settings overlay when an error occurs Closes #20413. --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 75bd6eb04d..15138b18b0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -424,7 +424,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void hideError() => ErrorText.FadeOut(50); - private void onSuccess(Room room) + private void onSuccess(Room room) => Schedule(() => { Debug.Assert(applyingSettingsOperation != null); @@ -432,9 +432,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match applyingSettingsOperation.Dispose(); applyingSettingsOperation = null; - } + }); - private void onError(string text) + private void onError(string text) => Schedule(() => { Debug.Assert(applyingSettingsOperation != null); @@ -455,7 +455,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match applyingSettingsOperation.Dispose(); applyingSettingsOperation = null; - } + }); } public class CreateOrUpdateButton : TriangleButton From 4e9053b0996fb2714a041196e58fa300d9a4df21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Sep 2022 20:10:57 +0900 Subject: [PATCH 348/583] Fix crash when storyboard attempts to play at main menu --- .../Drawables/DrawableStoryboardAnimation.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 369a3ee7ba..d1dc2b5d83 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; -using osu.Game.Screens.Play; +using osu.Game.Beatmaps; using osu.Game.Skinning; using osuTK; @@ -91,6 +91,9 @@ namespace osu.Game.Storyboards.Drawables [Resolved] private ISkinSource skin { get; set; } + [Resolved] + private IBeatSyncProvider beatSyncProvider { get; set; } + [BackgroundDependencyLoader] private void load(TextureStore textureStore, Storyboard storyboard) { @@ -116,9 +119,6 @@ namespace osu.Game.Storyboards.Drawables Animation.ApplyTransforms(this); } - [Resolved] - private IGameplayClock gameplayClock { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -128,7 +128,7 @@ namespace osu.Game.Storyboards.Drawables // // In the case of storyboard animations, we want to synchronise with game time perfectly // so let's get a correct time based on gameplay clock and earliest transform. - PlaybackPosition = gameplayClock.CurrentTime - Animation.EarliestTransformTime; + PlaybackPosition = (beatSyncProvider?.Clock?.CurrentTime ?? Clock.CurrentTime) - Animation.EarliestTransformTime; } private void skinSourceChanged() From 0fa5e1b28e0e8d9b00ebe6d85fd6517b958a5656 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Sep 2022 20:18:44 +0900 Subject: [PATCH 349/583] Fix intermittent leaderboard crash due to request finishing after drawable is disposed --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0f6ac2b455..ffb13dc458 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -153,10 +153,11 @@ namespace osu.Game.Screens.Select.Leaderboards var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); - req.Success += r => SetScores( + // Schedule is required to avoid potential object disposed exception when LoadComponentAsync is eventually called. + req.Success += r => Schedule(() => SetScores( scoreManager.OrderByTotalScore(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) - ); + )); return req; } From 965d0603ce4346f6be847568336eff24c4b469e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Sep 2022 21:11:25 +0900 Subject: [PATCH 350/583] Fix slider tick colour not being applied properly --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs index 1ab546b613..4c6b9a2f17 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon }; accentColour = hitObject.AccentColour.GetBoundCopy(); - accentColour.BindValueChanged(accent => BorderColour = accent.NewValue); + accentColour.BindValueChanged(accent => BorderColour = accent.NewValue, true); } } } From 0e38ff07c74de55cfc640361f51b8ea5658171d9 Mon Sep 17 00:00:00 2001 From: NullifiedJosh <86538544+NullifiedJosh@users.noreply.github.com> Date: Fri, 23 Sep 2022 20:19:56 +0800 Subject: [PATCH 351/583] Check if relax is one of the mods, if so hide. --- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ef2936ac94..07968e4bee 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; @@ -26,9 +27,15 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; + private bool showMobileMapper = true; + public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { + // Check if mods have RelaxMod instance + if (mods.OfType().Any()) + showMobileMapper = false; + Direction.Value = ScrollingDirection.Down; TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450); } @@ -36,7 +43,8 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - KeyBindingInputManager.Add(new CatchTouchInputMapper()); + if (showMobileMapper) + KeyBindingInputManager.Add(new CatchTouchInputMapper()); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); From 4bd92b89bcee77c01a7050648020518018e3f8e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Sep 2022 16:29:19 +0300 Subject: [PATCH 352/583] Change test scene to ensure progress notification is closed on completion --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index b314d95597..47958f4a41 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -465,7 +465,7 @@ namespace osu.Game.Tests.Visual.UserInterface { base.Update(); - progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); + progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed && n.WasClosed); if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) { From cdc3afac88b3ac203ab6999ce2c8dbdc37906909 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Sep 2022 16:30:07 +0300 Subject: [PATCH 353/583] Fix update progress notification not closing on completion --- osu.Game/Updater/UpdateManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 100464029b..7b540cb564 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -156,6 +156,7 @@ namespace osu.Game.Updater switch (State) { case ProgressNotificationState.Cancelled: + case ProgressNotificationState.Completed: base.Close(runFlingAnimation); break; } From 835cae30872e3ffa5f196dd3ffcd9ec7347609b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Sep 2022 17:06:55 +0300 Subject: [PATCH 354/583] Remove null conditional --- osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index d1dc2b5d83..07e1e86617 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -128,7 +128,7 @@ namespace osu.Game.Storyboards.Drawables // // In the case of storyboard animations, we want to synchronise with game time perfectly // so let's get a correct time based on gameplay clock and earliest transform. - PlaybackPosition = (beatSyncProvider?.Clock?.CurrentTime ?? Clock.CurrentTime) - Animation.EarliestTransformTime; + PlaybackPosition = (beatSyncProvider.Clock?.CurrentTime ?? Clock.CurrentTime) - Animation.EarliestTransformTime; } private void skinSourceChanged() From 6c8e587344581907013d223200c9b4dd3b10e96e Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 24 Sep 2022 21:52:16 +0300 Subject: [PATCH 355/583] Move margin value to constant --- osu.Game/Overlays/NowPlayingOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 6eddc7da83..687731d068 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -38,6 +38,7 @@ namespace osu.Game.Overlays private const float transition_length = 800; private const float progress_height = 10; private const float bottom_black_area_height = 55; + private const float margin = 10; private Drawable background; private ProgressBar progressBar; @@ -69,7 +70,7 @@ namespace osu.Game.Overlays public NowPlayingOverlay() { Width = 400; - Margin = new MarginPadding(10); + Margin = new MarginPadding(margin); } [BackgroundDependencyLoader] @@ -194,7 +195,7 @@ namespace osu.Game.Overlays LoadComponentAsync(playlist = new PlaylistOverlay { RelativeSizeAxes = Axes.X, - Y = player_height + 10, + Y = player_height + margin, }, _ => { dragContainer.Add(playlist); From 5d0b2d34c9289eb79a26aa58e238ee24c56e14f0 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 24 Sep 2022 23:35:06 +0300 Subject: [PATCH 356/583] Make playlist to always be in screen bounds --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 5 ++-- osu.Game/Overlays/NowPlayingOverlay.cs | 27 +++++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 9fe2fd5279..63f1aa248c 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -24,7 +25,7 @@ namespace osu.Game.Overlays.Music public class PlaylistOverlay : VisibilityContainer { private const float transition_duration = 600; - private const float playlist_height = 510; + public const float PLAYLIST_HEIGHT = 510; private readonly BindableList> beatmapSets = new BindableList>(); @@ -130,7 +131,7 @@ namespace osu.Game.Overlays.Music filter.Search.HoldFocus = true; Schedule(() => filter.Search.TakeFocus()); - this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); + this.ResizeTo(new Vector2(1, RelativeSizeAxes.HasFlagFast(Axes.Y) ? 1f : PLAYLIST_HEIGHT), transition_duration, Easing.OutQuint); this.FadeIn(transition_duration, Easing.OutQuint); } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 687731d068..900b4bebf0 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -54,6 +54,7 @@ namespace osu.Game.Overlays private Container dragContainer; private Container playerContainer; + private Container playlistContainer; protected override string PopInSampleName => "UI/now-playing-pop-in"; protected override string PopOutSampleName => "UI/now-playing-pop-out"; @@ -83,7 +84,6 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Children = new Drawable[] { playerContainer = new Container @@ -183,8 +183,13 @@ namespace osu.Game.Overlays } }, }, + playlistContainer = new Container + { + RelativeSizeAxes = Axes.X, + Y = player_height + margin, + } } - } + }, }; } @@ -194,11 +199,10 @@ namespace osu.Game.Overlays { LoadComponentAsync(playlist = new PlaylistOverlay { - RelativeSizeAxes = Axes.X, - Y = player_height + margin, + RelativeSizeAxes = Axes.Both, }, _ => { - dragContainer.Add(playlist); + playlistContainer.Add(playlist); playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); @@ -243,7 +247,18 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); - Height = dragContainer.Height; + playlistContainer.Height = MathF.Min(Parent.DrawHeight - margin * 3 - player_height, PlaylistOverlay.PLAYLIST_HEIGHT); + + float height = player_height; + + if (playlist != null) + { + height += playlist.DrawHeight; + if (playlist.State.Value == Visibility.Visible) + height += margin; + } + + Height = dragContainer.Height = height; } protected override void Update() From 38f0524e88168b5ef0d175207fd938eb2251ea4b Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 25 Sep 2022 18:00:28 +0200 Subject: [PATCH 357/583] build: harden ci.yml permissions Signed-off-by: Alex --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 082e0d247c..33ec3d6602 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read # to fetch code (actions/checkout) + jobs: inspect-code: name: Code Quality From 90dd3c3020c5df90fb48980a4fb9de03dd3b9d52 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 25 Sep 2022 18:03:13 +0200 Subject: [PATCH 358/583] build: harden report-nunit.yml permissions Signed-off-by: Alex --- .github/workflows/report-nunit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index 358cbda17a..bfc9620174 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -8,8 +8,12 @@ on: workflows: ["Continuous Integration"] types: - completed +permissions: {} jobs: annotate: + permissions: + checks: write # to create checks (dorny/test-reporter) + name: Annotate CI run with test results runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} From 68f62ca9cd653f0e9175be716ebd2ac492c3b6f7 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 25 Sep 2022 18:04:27 +0200 Subject: [PATCH 359/583] build: harden sentry-release.yml permissions Signed-off-by: Alex --- .github/workflows/sentry-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index 442b97c473..cce3f23e5f 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -5,6 +5,9 @@ on: tags: - '*' +permissions: + contents: read # to fetch code (actions/checkout) + jobs: sentry_release: runs-on: ubuntu-latest From 537796c3a17fb049a4b417c1f6835e4ffd35d208 Mon Sep 17 00:00:00 2001 From: o-dasher <88356162+o-dasher@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:49:22 -0400 Subject: [PATCH 360/583] Cleanup mod bindings --- .../Mods/CatchModFlashlight.cs | 8 +------ .../Mods/CatchModNoScope.cs | 4 +--- .../Mods/ManiaModFlashlight.cs | 10 ++------- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 4 +--- .../Mods/OsuModFlashlight.cs | 10 ++------- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 4 +--- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 4 +--- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 +--- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 6 +---- .../Mods/TaikoModFlashlight.cs | 10 ++------- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 14 +++--------- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 4 +--- osu.Game/Rulesets/Mods/ModHalfTime.cs | 4 +--- osu.Game/Rulesets/Mods/ModMuted.cs | 22 ++++--------------- osu.Game/Rulesets/Mods/ModRandom.cs | 6 +---- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +--- osu.Game/Rulesets/Mods/ModWindDown.cs | 14 +++--------- osu.Game/Rulesets/Mods/ModWindUp.cs | 14 +++--------- 18 files changed, 30 insertions(+), 116 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index abe391ba4e..e8b126cfed 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -21,17 +21,11 @@ namespace osu.Game.Rulesets.Catch.Mods { MinValue = 0.5f, MaxValue = 1.5f, - Default = 1f, - Value = 1f, Precision = 0.1f }; [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] - public override BindableBool ComboBasedSize { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override float DefaultFlashlightSize => 350; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 9038153e20..1b781b032c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -22,10 +22,8 @@ namespace osu.Game.Rulesets.Catch.Mods "The combo count at which the catcher becomes completely hidden", SettingControlType = typeof(SettingsSlider) )] - public override BindableInt HiddenComboCount { get; } = new BindableInt + public override BindableInt HiddenComboCount { get; } = new BindableInt(10) { - Default = 10, - Value = 10, MinValue = 0, MaxValue = 50, }; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 8ef5bfd94c..72ea1a2431 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -18,21 +18,15 @@ namespace osu.Game.Rulesets.Mania.Mods public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableFloat SizeMultiplier { get; } = new BindableFloat + public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, MaxValue = 3f, - Default = 1f, - Value = 1f, Precision = 0.1f }; [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] - public override BindableBool ComboBasedSize { get; } = new BindableBool - { - Default = false, - Value = false - }; + public override BindableBool ComboBasedSize { get; } = new BindableBool(); public override float DefaultFlashlightSize => 50; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index e624660410..b2980f5311 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -19,12 +19,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Hit them at the right size!"; [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] - public override BindableNumber StartScale { get; } = new BindableFloat + public override BindableNumber StartScale { get; } = new BindableFloat(2) { MinValue = 1f, MaxValue = 25f, - Default = 2f, - Value = 2f, Precision = 0.1f, }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index e5a458488e..7b34fe05a9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -33,21 +33,15 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableFloat SizeMultiplier { get; } = new BindableFloat + public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, MaxValue = 2f, - Default = 1f, - Value = 1f, Precision = 0.1f }; [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] - public override BindableBool ComboBasedSize { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override float DefaultFlashlightSize => 180; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index b77c887cd3..b49257e289 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -19,12 +19,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Hit them at the right size!"; [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] - public override BindableNumber StartScale { get; } = new BindableFloat + public override BindableNumber StartScale { get; } = new BindableFloat(0.5f) { MinValue = 0f, MaxValue = 0.99f, - Default = 0.5f, - Value = 0.5f, Precision = 0.01f, }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 817f7b599c..84538cec30 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -27,10 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods "The combo count at which the cursor becomes completely hidden", SettingControlType = typeof(SettingsSlider) )] - public override BindableInt HiddenComboCount { get; } = new BindableInt + public override BindableInt HiddenComboCount { get; } = new BindableInt(10) { - Default = 10, - Value = 10, MinValue = 0, MaxValue = 50, }; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 78c7aa53f4..618fcfe05d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -29,10 +29,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); [SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider))] - public BindableFloat AngleSharpness { get; } = new BindableFloat + public BindableFloat AngleSharpness { get; } = new BindableFloat(7) { - Default = 7, - Value = 7, MinValue = 1, MaxValue = 10, Precision = 0.1f diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 861ad80b7f..406968ba08 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -53,11 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods }).ToArray(); [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] - public Bindable Seed { get; } = new Bindable - { - Default = null, - Value = null - }; + public Bindable Seed { get; } = new Bindable(); [SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")] public Bindable Metronome { get; } = new BindableBool(true); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 66616486df..05baf88a33 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -18,21 +18,15 @@ namespace osu.Game.Rulesets.Taiko.Mods public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableFloat SizeMultiplier { get; } = new BindableFloat + public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, MaxValue = 1.5f, - Default = 1f, - Value = 1f, Precision = 0.1f }; [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] - public override BindableBool ComboBasedSize { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override float DefaultFlashlightSize => 250; diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 697b303689..e7127abcf0 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -36,32 +36,24 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp), typeof(ModAutoplay) }; [SettingSource("Initial rate", "The starting speed of the track")] - public BindableNumber InitialRate { get; } = new BindableDouble + public BindableNumber InitialRate { get; } = new BindableDouble(1) { MinValue = 0.5, MaxValue = 2, - Default = 1, - Value = 1, Precision = 0.01 }; [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] - public BindableBool AdjustPitch { get; } = new BindableBool - { - Default = true, - Value = true - }; + public BindableBool AdjustPitch { get; } = new BindableBool(true); /// /// The instantaneous rate of the track. /// Every frame this mod will attempt to smoothly adjust this to meet . /// - public BindableNumber SpeedChange { get; } = new BindableDouble + public BindableNumber SpeedChange { get; } = new BindableDouble(1) { MinValue = min_allowable_rate, MaxValue = max_allowable_rate, - Default = 1, - Value = 1 }; // The two constants below denote the maximum allowable range of rates that `SpeedChange` can take. diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index d8a41ae658..9e4469bf25 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -18,12 +18,10 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Zoooooooooom..."; [SettingSource("Speed increase", "The actual increase to apply")] - public override BindableNumber SpeedChange { get; } = new BindableDouble + public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) { MinValue = 1.01, MaxValue = 2, - Default = 1.5, - Value = 1.5, Precision = 0.01, }; } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 8d8b97e79e..7d858dca6f 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -18,12 +18,10 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Less zoom..."; [SettingSource("Speed decrease", "The actual decrease to apply")] - public override BindableNumber SpeedChange { get; } = new BindableDouble + public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) { MinValue = 0.5, MaxValue = 0.99, - Default = 0.75, - Value = 0.75, Precision = 0.01, }; } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 9735d6b536..05ecd37000 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -36,34 +36,20 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber currentCombo = new BindableInt(); [SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")] - public BindableBool EnableMetronome { get; } = new BindableBool - { - Default = true, - Value = true - }; + public BindableBool EnableMetronome { get; } = new BindableBool(true); [SettingSource("Final volume at combo", "The combo count at which point the track reaches its final volume.", SettingControlType = typeof(SettingsSlider))] - public BindableInt MuteComboCount { get; } = new BindableInt + public BindableInt MuteComboCount { get; } = new BindableInt(100) { - Default = 100, - Value = 100, MinValue = 0, MaxValue = 500, }; [SettingSource("Start muted", "Increase volume as combo builds.")] - public BindableBool InverseMuting { get; } = new BindableBool - { - Default = false, - Value = false - }; + public BindableBool InverseMuting { get; } = new BindableBool(); [SettingSource("Mute hit sounds", "Hit sounds are also muted alongside the track.")] - public BindableBool AffectsHitSounds { get; } = new BindableBool - { - Default = true, - Value = true - }; + public BindableBool AffectsHitSounds { get; } = new BindableBool(true); protected ModMuted() { diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 1f7742b075..178b9fb619 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -18,10 +18,6 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] - public Bindable Seed { get; } = new Bindable - { - Default = null, - Value = null - }; + public Bindable Seed { get; } = new Bindable(); } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 72a7f4b9a3..c4cb41fb6a 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -39,10 +39,8 @@ namespace osu.Game.Rulesets.Mods private double finalRateTime; private double beginRampTime; - public BindableNumber SpeedChange { get; } = new BindableDouble + public BindableNumber SpeedChange { get; } = new BindableDouble(1) { - Default = 1, - Value = 1, Precision = 0.01, }; diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 22ed7c2efd..15ee37d481 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -18,31 +18,23 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown; [SettingSource("Initial rate", "The starting speed of the track")] - public override BindableNumber InitialRate { get; } = new BindableDouble + public override BindableNumber InitialRate { get; } = new BindableDouble(1) { MinValue = 0.51, MaxValue = 2, - Default = 1, - Value = 1, Precision = 0.01, }; [SettingSource("Final rate", "The speed increase to ramp towards")] - public override BindableNumber FinalRate { get; } = new BindableDouble + public override BindableNumber FinalRate { get; } = new BindableDouble(0.75) { MinValue = 0.5, MaxValue = 1.99, - Default = 0.75, - Value = 0.75, Precision = 0.01, }; [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] - public override BindableBool AdjustPitch { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool AdjustPitch { get; } = new BindableBool(true); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 13ece6d9a3..6d8838c5a5 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -18,31 +18,23 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp; [SettingSource("Initial rate", "The starting speed of the track")] - public override BindableNumber InitialRate { get; } = new BindableDouble + public override BindableNumber InitialRate { get; } = new BindableDouble(1) { MinValue = 0.5, MaxValue = 1.99, - Default = 1, - Value = 1, Precision = 0.01, }; [SettingSource("Final rate", "The speed increase to ramp towards")] - public override BindableNumber FinalRate { get; } = new BindableDouble + public override BindableNumber FinalRate { get; } = new BindableDouble(1.5) { MinValue = 0.51, MaxValue = 2, - Default = 1.5, - Value = 1.5, Precision = 0.01, }; [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] - public override BindableBool AdjustPitch { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool AdjustPitch { get; } = new BindableBool(true); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); From feadac1f790fec56a1954cdab86f4deccfb9d68d Mon Sep 17 00:00:00 2001 From: o-dasher <88356162+o-dasher@users.noreply.github.com> Date: Sun, 25 Sep 2022 16:02:40 -0400 Subject: [PATCH 361/583] Cleanup all other leftover binding instantiation --- .../Online/TestAPIModJsonSerialization.cs | 14 +++----------- .../Online/TestAPIModMessagePackSerialization.cs | 14 +++----------- .../Visual/Settings/TestSceneSettingsItem.cs | 12 ++---------- .../Visual/Settings/TestSceneSettingsSource.cs | 6 +----- .../UserInterface/TestSceneExpandingContainer.cs | 8 ++------ .../ControlPoints/DifficultyControlPoint.cs | 1 - .../Beatmaps/ControlPoints/EffectControlPoint.cs | 1 - .../Beatmaps/ControlPoints/SampleControlPoint.cs | 1 - .../Beatmaps/ControlPoints/TimingControlPoint.cs | 1 - .../UI/Scrolling/DrawableScrollingRuleset.cs | 1 - .../Screens/Play/MasterGameplayClockContainer.cs | 1 - .../Play/PlayerSettings/BeatmapOffsetControl.cs | 2 -- .../Play/PlayerSettings/PlaybackSettings.cs | 1 - 13 files changed, 11 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 17709fb10f..da250c1e05 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -204,31 +204,23 @@ namespace osu.Game.Tests.Online public override double ScoreMultiplier => 1; [SettingSource("Initial rate", "The starting speed of the track")] - public override BindableNumber InitialRate { get; } = new BindableDouble + public override BindableNumber InitialRate { get; } = new BindableDouble(1.5) { MinValue = 1, MaxValue = 2, - Default = 1.5, - Value = 1.5, Precision = 0.01, }; [SettingSource("Final rate", "The speed increase to ramp towards")] - public override BindableNumber FinalRate { get; } = new BindableDouble + public override BindableNumber FinalRate { get; } = new BindableDouble(0.5) { MinValue = 0, MaxValue = 1, - Default = 0.5, - Value = 0.5, Precision = 0.01, }; [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] - public override BindableBool AdjustPitch { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool AdjustPitch { get; } = new BindableBool(true); } private class TestModDifficultyAdjust : ModDifficultyAdjust diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs index b17414e026..1d8cbffcdb 100644 --- a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs @@ -124,31 +124,23 @@ namespace osu.Game.Tests.Online public override double ScoreMultiplier => 1; [SettingSource("Initial rate", "The starting speed of the track")] - public override BindableNumber InitialRate { get; } = new BindableDouble + public override BindableNumber InitialRate { get; } = new BindableDouble(1.5) { MinValue = 1, MaxValue = 2, - Default = 1.5, - Value = 1.5, Precision = 0.01, }; [SettingSource("Final rate", "The speed increase to ramp towards")] - public override BindableNumber FinalRate { get; } = new BindableDouble + public override BindableNumber FinalRate { get; } = new BindableDouble(0.5) { MinValue = 0, MaxValue = 1, - Default = 0.5, - Value = 0.5, Precision = 0.01, }; [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] - public override BindableBool AdjustPitch { get; } = new BindableBool - { - Default = true, - Value = true - }; + public override BindableBool AdjustPitch { get; } = new BindableBool(true); } private class TestModEnum : Mod diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index b3d1966511..6ff53663ba 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -29,11 +29,7 @@ namespace osu.Game.Tests.Visual.Settings { Child = textBox = new SettingsTextBox { - Current = new Bindable - { - Default = "test", - Value = "test" - } + Current = new Bindable("test") }; }); AddUntilStep("wait for loaded", () => textBox.IsLoaded); @@ -59,11 +55,7 @@ namespace osu.Game.Tests.Visual.Settings { Child = textBox = new SettingsTextBox { - Current = new Bindable - { - Default = "test", - Value = "test" - } + Current = new Bindable("test") }; }); AddUntilStep("wait for loaded", () => textBox.IsLoaded); diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs index dc2a687bd5..3cf6f7febf 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -67,11 +67,7 @@ namespace osu.Game.Tests.Visual.Settings }; [SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))] - public Bindable IntTextBoxBindable { get; } = new Bindable - { - Default = null, - Value = null - }; + public Bindable IntTextBoxBindable { get; } = new Bindable(); } private enum TestEnum diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index 704185b117..20f173ab04 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -38,20 +38,16 @@ namespace osu.Game.Tests.Visual.UserInterface { slider1 = new ExpandableSlider> { - Current = new BindableFloat + Current = new BindableFloat(1.0f) { - Default = 1.0f, - MinValue = 1.0f, MaxValue = 10.0f, Precision = 0.01f, }, }, slider2 = new ExpandableSlider { - Current = new BindableDouble + Current = new BindableDouble(1.0) { - Default = 1.0, - MinValue = 1.0, MaxValue = 10.0, Precision = 0.01, }, diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index c199d1da59..e6f1609d7f 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -24,7 +24,6 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1) { Precision = 0.01, - Default = 1, MinValue = 0.1, MaxValue = 10 }; diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index ead07b4eaa..7c4313a015 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -28,7 +28,6 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1) { Precision = 0.01, - Default = 1, MinValue = 0.01, MaxValue = 10 }; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 78dec67937..c454439c5c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -45,7 +45,6 @@ namespace osu.Game.Beatmaps.ControlPoints { MinValue = 0, MaxValue = 100, - Default = 100 }; /// diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 23d4d10fd8..61cc060594 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -49,7 +49,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { - Default = DEFAULT_BEAT_LENGTH, MinValue = 6, MaxValue = 60000 }; diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index d8a8a6ccd8..825aba5bc2 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -60,7 +60,6 @@ namespace osu.Game.Rulesets.UI.Scrolling /// protected readonly BindableDouble TimeRange = new BindableDouble(time_span_default) { - Default = time_span_default, MinValue = time_span_min, MaxValue = time_span_max }; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 047f25a111..c3c351ac36 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Play public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { - Default = 1, MinValue = 0.5, MaxValue = 2, Precision = 0.1, diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 75da8e7b9d..537f4d811a 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Play.PlayerSettings public BindableDouble Current { get; } = new BindableDouble { - Default = 0, - Value = 0, MinValue = -50, MaxValue = 50, Precision = 0.1, diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 12646d656a..14e3123028 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -17,7 +17,6 @@ namespace osu.Game.Screens.Play.PlayerSettings public readonly Bindable UserPlaybackRate = new BindableDouble(1) { - Default = 1, MinValue = 0.5, MaxValue = 2, Precision = 0.1, From 578f1e1e6c16fbf1001e4b5a8155ff35250fa695 Mon Sep 17 00:00:00 2001 From: o-dasher <88356162+o-dasher@users.noreply.github.com> Date: Sun, 25 Sep 2022 16:28:03 -0400 Subject: [PATCH 362/583] Fixes SizeMultiplier binding --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e8b126cfed..41d4590aa8 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableFloat SizeMultiplier { get; } = new BindableFloat + public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, MaxValue = 1.5f, From a9318e5c0418eba7867b43732005160e3ab58162 Mon Sep 17 00:00:00 2001 From: o-dasher <88356162+o-dasher@users.noreply.github.com> Date: Sun, 25 Sep 2022 16:31:04 -0400 Subject: [PATCH 363/583] Fixes small oversight was applying a value when it wasn't supposed to be applied. --- .../Visual/UserInterface/TestSceneExpandingContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index 20f173ab04..04ca9accac 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -38,16 +38,20 @@ namespace osu.Game.Tests.Visual.UserInterface { slider1 = new ExpandableSlider> { - Current = new BindableFloat(1.0f) + Current = new BindableFloat { + Default = 1.0f, + MinValue = 1.0f, MaxValue = 10.0f, Precision = 0.01f, }, }, slider2 = new ExpandableSlider { - Current = new BindableDouble(1.0) + Current = new BindableDouble { + Default = 1.0, + MinValue = 1.0 MaxValue = 10.0, Precision = 0.01, }, From ced72126601f19287be2bbc8bfc52f59be8bd4ea Mon Sep 17 00:00:00 2001 From: o-dasher <88356162+o-dasher@users.noreply.github.com> Date: Sun, 25 Sep 2022 16:33:39 -0400 Subject: [PATCH 364/583] Fixes syntax error --- .../Visual/UserInterface/TestSceneExpandingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index 04ca9accac..704185b117 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface Current = new BindableDouble { Default = 1.0, - MinValue = 1.0 + MinValue = 1.0, MaxValue = 10.0, Precision = 0.01, }, From a7449380cda7920f23cbb6d2f034ec634dcdba16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 14:11:59 +0900 Subject: [PATCH 365/583] Fix osu! slider ticks appearing too late --- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 569e9b7c1c..676ff62455 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects // This is so on repeats ticks don't appear too late to be visually processed by the player. offset = 200; else - offset = TimeFadeIn * 0.66f; + offset = TimePreempt * 0.66f; TimePreempt = (StartTime - SpanStartTime) / 2 + offset; } From 82d4689716e81d09c3a58596de54c288fbb45e02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 14:30:31 +0900 Subject: [PATCH 366/583] Add failing test showing progress notifications can be flung and cancelled --- .../TestSceneNotificationOverlay.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index b314d95597..895e62d41c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -107,9 +107,9 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("start drag", () => { - InputManager.MoveMouseTo(notification.ChildrenOfType().Single()); + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single()); InputManager.PressButton(MouseButton.Left); - InputManager.MoveMouseTo(notification.ChildrenOfType().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0)); + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0)); }); AddStep("fling away", () => @@ -123,6 +123,45 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0); } + [Test] + public void TestProgressNotificationCantBeFlung() + { + bool activated = false; + ProgressNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + Activated = () => activated = true, + }); + + progressingNotifications.Add(notification); + }); + + AddStep("start drag", () => + { + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single()); + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0)); + }); + + AddStep("attempt fling", () => + { + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddUntilStep("was not closed", () => !notification.WasClosed); + AddUntilStep("was not cancelled", () => notification.State == ProgressNotificationState.Active); + AddAssert("was not activated", () => !activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + + AddUntilStep("was completed", () => notification.State == ProgressNotificationState.Completed); + } + [Test] public void TestDismissWithoutActivationCloseButton() { From b6dd8168d29313dafdfaeccc90bb4420896df7a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 14:30:40 +0900 Subject: [PATCH 367/583] Fix progress notifications being able to be flung --- osu.Game/Overlays/Notifications/Notification.cs | 4 +++- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index ea654e1272..8be9d2072b 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -68,6 +68,8 @@ namespace osu.Game.Overlays.Notifications public virtual bool Read { get; set; } + protected virtual bool AllowFlingDismiss => true; + public new bool IsDragged => dragContainer.IsDragged; protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check; @@ -315,7 +317,7 @@ namespace osu.Game.Overlays.Notifications protected override void OnDragEnd(DragEndEvent e) { - if (Rotation < -10 || velocity.X < -0.3f) + if (notification.AllowFlingDismiss && (Rotation < -10 || velocity.X < -0.3f)) notification.Close(true); else if (X > 30 || velocity.X > 0.3f) notification.ForwardToOverlay?.Invoke(); diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 61bb22041e..4cf47013bd 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -25,6 +25,8 @@ namespace osu.Game.Overlays.Notifications public Func? CancelRequested { get; set; } + protected override bool AllowFlingDismiss => false; + /// /// The function to post completion notifications back to. /// From 4bd96108c01dc2453f7570475f2a04ac6ac463fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 14:45:17 +0900 Subject: [PATCH 368/583] Fix crash on game exit due to incorrect scheduling of realm change handler --- osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index 82f49e0aef..4963de7251 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -123,7 +123,7 @@ namespace osu.Game.Overlays.FirstRunSetup beatmapSubscription?.Dispose(); } - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes, Exception error) => Schedule(() => { currentlyLoadedBeatmaps.Text = FirstRunSetupBeatmapScreenStrings.CurrentlyLoadedBeatmaps(sender.Count); @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.FirstRunSetup currentlyLoadedBeatmaps.ScaleTo(1.1f) .ScaleTo(1, 1500, Easing.OutQuint); } - } + }); private void downloadTutorial() { From c4887269f7b9096e038bd30bd50e2dd6b489e1d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 14:55:23 +0900 Subject: [PATCH 369/583] Avoid performing a realm write if there are no changes pending in `RulesetConfigManager` --- osu.Game/Rulesets/Configuration/RulesetConfigManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 5a03d66b84..4ff4f66665 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -58,6 +58,9 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } + if (!changed.Any()) + return true; + realm?.Write(r => { foreach (var c in changed) From 43c16cb613cafcb35c1d862214322aff3e17b875 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 15:10:02 +0900 Subject: [PATCH 370/583] Fix potential crash from incorrect drawable mutation in tablet settings display --- .../Settings/Sections/Input/TabletSettings.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 271438ed14..544259ddf1 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -215,21 +215,21 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(tabletHandler.Rotation); areaOffset.BindTo(tabletHandler.AreaOffset); - areaOffset.BindValueChanged(val => + areaOffset.BindValueChanged(val => Schedule(() => { offsetX.Value = val.NewValue.X; offsetY.Value = val.NewValue.Y; - }, true); + }), true); offsetX.BindValueChanged(val => areaOffset.Value = new Vector2(val.NewValue, areaOffset.Value.Y)); offsetY.BindValueChanged(val => areaOffset.Value = new Vector2(areaOffset.Value.X, val.NewValue)); areaSize.BindTo(tabletHandler.AreaSize); - areaSize.BindValueChanged(val => + areaSize.BindValueChanged(val => Schedule(() => { sizeX.Value = val.NewValue.X; sizeY.Value = val.NewValue.Y; - }, true); + }), true); sizeX.BindValueChanged(val => { @@ -255,7 +255,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }); tablet.BindTo(tabletHandler.Tablet); - tablet.BindValueChanged(val => + tablet.BindValueChanged(val => Schedule(() => { Scheduler.AddOnce(updateVisibility); @@ -274,7 +274,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input sizeY.Default = sizeY.MaxValue = tab.Size.Y; areaSize.Default = new Vector2(sizeX.Default, sizeY.Default); - }, true); + }), true); } private void updateVisibility() From 4e9b25d8b0828039524974123f48d0215ef12ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 15:42:37 +0900 Subject: [PATCH 371/583] Move `MusicController` binds to `LoadComplete` for added thread safety --- osu.Game/Overlays/MusicController.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index da87336039..793b7e294f 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -58,12 +58,11 @@ namespace osu.Game.Overlays [Resolved] private RealmAccess realm { get; set; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - // Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. - // They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load(). - beatmap.BindValueChanged(beatmapChanged, true); + base.LoadComplete(); + + beatmap.BindValueChanged(b => changeBeatmap(b.NewValue), true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } @@ -263,8 +262,6 @@ namespace osu.Game.Overlays private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); - private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue); - private void changeBeatmap(WorkingBeatmap newWorking) { // This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order From 32d56fe3a959a40082094b3d9399e178039f8766 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 16:02:33 +0900 Subject: [PATCH 372/583] Use request cancellation and `IsDisposed` guard instead of more `Schedule` --- osu.Game/Online/Leaderboards/Leaderboard.cs | 13 +++++++++---- .../Select/Leaderboards/BeatmapLeaderboard.cs | 17 ++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 206c15afde..0f444a368a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -185,10 +185,15 @@ namespace osu.Game.Online.Leaderboards if (scores != null) this.scores.AddRange(scores); - // Schedule needs to be non-delayed here for the weird logic in refetchScores to work. - // If it is removed, the placeholder will be incorrectly updated to "no scores" rather than "retrieving". - // This whole flow should be refactored in the future. - Scheduler.Add(applyNewScores, false); + // Non-delayed schedule may potentially run inline (due to IsMainThread check passing) after leaderboard is disposed. + // This is guarded against in BeatmapLeaderboard via web request cancellation, but let's be extra safe. + if (!IsDisposed) + { + // Schedule needs to be non-delayed here for the weird logic in refetchScores to work. + // If it is removed, the placeholder will be incorrectly updated to "no scores" rather than "retrieving". + // This whole flow should be refactored in the future. + Scheduler.Add(applyNewScores, false); + } void applyNewScores() { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index ffb13dc458..50204db2bf 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -89,6 +89,8 @@ namespace osu.Game.Screens.Select.Leaderboards private IDisposable scoreSubscription; + private GetScoresRequest scoreRetrievalRequest; + [BackgroundDependencyLoader] private void load() { @@ -151,15 +153,14 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); + scoreRetrievalRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); - // Schedule is required to avoid potential object disposed exception when LoadComponentAsync is eventually called. - req.Success += r => Schedule(() => SetScores( - scoreManager.OrderByTotalScore(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), - r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) - )); + scoreRetrievalRequest.Success += response => SetScores( + scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), + response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) + ); - return req; + return scoreRetrievalRequest; } protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope) @@ -219,7 +220,9 @@ namespace osu.Game.Screens.Select.Leaderboards protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + scoreSubscription?.Dispose(); + scoreRetrievalRequest?.Cancel(); } } } From a1297af4410beba1dc8d1c5a24e9165eeea01b06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 16:12:47 +0900 Subject: [PATCH 373/583] Apply NRT to base `Leaderboard` classes --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 13 ++++------ osu.Game/Online/Leaderboards/Leaderboard.cs | 24 ++++++++----------- .../Leaderboards/UserTopScoreContainer.cs | 10 ++++---- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 07da1790c8..1839821bb5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -36,10 +34,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Cached(typeof(IDialogOverlay))] private readonly DialogOverlay dialogOverlay; - private ScoreManager scoreManager; - - private RulesetStore rulesetStore; - private BeatmapManager beatmapManager; + private ScoreManager scoreManager = null!; + private RulesetStore rulesetStore = null!; + private BeatmapManager beatmapManager = null!; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -74,7 +71,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestLocalScoresDisplay() { - BeatmapInfo beatmapInfo = null; + BeatmapInfo beatmapInfo = null!; AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local); @@ -387,7 +384,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state); - public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); + public new void SetScores(IEnumerable? scores, ScoreInfo? userScore = null) => base.SetScores(scores, userScore); } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0f444a368a..69b4e5b209 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; @@ -54,23 +51,23 @@ namespace osu.Game.Online.Leaderboards private readonly Container placeholderContainer; private readonly UserTopScoreContainer userScoreContainer; - private FillFlowContainer scoreFlowContainer; + private FillFlowContainer? scoreFlowContainer; private readonly LoadingSpinner loading; - private CancellationTokenSource currentFetchCancellationSource; - private CancellationTokenSource currentScoresAsyncLoadCancellationSource; + private CancellationTokenSource? currentFetchCancellationSource; + private CancellationTokenSource? currentScoresAsyncLoadCancellationSource; - private APIRequest fetchScoresRequest; + private APIRequest? fetchScoresRequest; private LeaderboardState state; [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } + private IAPIProvider? api { get; set; } private readonly IBindable apiState = new Bindable(); - private TScope scope; + private TScope scope = default!; public TScope Scope { @@ -179,7 +176,7 @@ namespace osu.Game.Online.Leaderboards /// /// The scores to display. /// The user top score, if any. - protected void SetScores(IEnumerable scores, TScoreInfo userScore = default) + protected void SetScores(IEnumerable? scores, TScoreInfo? userScore = default) { this.scores.Clear(); if (scores != null) @@ -213,8 +210,7 @@ namespace osu.Game.Online.Leaderboards /// /// /// An responsible for the fetch operation. This will be queued and performed automatically. - [CanBeNull] - protected abstract APIRequest FetchScores(CancellationToken cancellationToken); + protected abstract APIRequest? FetchScores(CancellationToken cancellationToken); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -298,7 +294,7 @@ namespace osu.Game.Online.Leaderboards #region Placeholder handling - private Placeholder placeholder; + private Placeholder? placeholder; private void setState(LeaderboardState state) { @@ -325,7 +321,7 @@ namespace osu.Game.Online.Leaderboards placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); } - private Placeholder getPlaceholderFor(LeaderboardState state) + private Placeholder? getPlaceholderFor(LeaderboardState state) { switch (state) { diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index 2d2d82821c..391e8804f0 100644 --- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Threading; using osu.Framework.Bindables; @@ -18,13 +16,15 @@ namespace osu.Game.Online.Leaderboards { private const int duration = 500; - public Bindable Score = new Bindable(); + public Bindable Score = new Bindable(); private readonly Container scoreContainer; private readonly Func createScoreDelegate; protected override bool StartHidden => true; + private CancellationTokenSource? loadScoreCancellation; + public UserTopScoreContainer(Func createScoreDelegate) { this.createScoreDelegate = createScoreDelegate; @@ -65,9 +65,7 @@ namespace osu.Game.Online.Leaderboards Score.BindValueChanged(onScoreChanged); } - private CancellationTokenSource loadScoreCancellation; - - private void onScoreChanged(ValueChangedEvent score) + private void onScoreChanged(ValueChangedEvent score) { var newScore = score.NewValue; From 5fc836d1f09cebf983313c9b91a5c252890c607a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 16:15:18 +0900 Subject: [PATCH 374/583] Apply NRT to `BeatmapLeaderboard` / `MatchLeaderboard` --- .../Match/Components/MatchLeaderboard.cs | 6 ++-- .../Select/Leaderboards/BeatmapLeaderboard.cs | 35 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 64644be965..ee5ee576d8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -16,7 +14,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public class MatchLeaderboard : Leaderboard { [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -33,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores(CancellationToken cancellationToken) + protected override APIRequest? FetchScores(CancellationToken cancellationToken) { if (roomId.Value == null) return null; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 50204db2bf..161d4847bf 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -25,11 +23,11 @@ namespace osu.Game.Screens.Select.Leaderboards { public class BeatmapLeaderboard : Leaderboard { - public Action ScoreSelected; + public Action? ScoreSelected; - private BeatmapInfo beatmapInfo; + private BeatmapInfo? beatmapInfo; - public BeatmapInfo BeatmapInfo + public BeatmapInfo? BeatmapInfo { get => beatmapInfo; set @@ -70,26 +68,26 @@ namespace osu.Game.Screens.Select.Leaderboards } [Resolved] - private ScoreManager scoreManager { get; set; } + private ScoreManager scoreManager { get; set; } = null!; [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; [Resolved] - private IBindable> mods { get; set; } + private IBindable> mods { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; - private IDisposable scoreSubscription; + private IDisposable? scoreSubscription; - private GetScoresRequest scoreRetrievalRequest; + private GetScoresRequest? scoreRetrievalRequest; [BackgroundDependencyLoader] private void load() @@ -104,10 +102,9 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; - protected override APIRequest FetchScores(CancellationToken cancellationToken) + protected override APIRequest? FetchScores(CancellationToken cancellationToken) { var fetchBeatmapInfo = BeatmapInfo; - var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; if (fetchBeatmapInfo == null) { @@ -115,13 +112,15 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } + var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; + if (Scope == BeatmapLeaderboardScope.Local) { subscribeToLocalScores(fetchBeatmapInfo, cancellationToken); return null; } - if (api?.IsLoggedIn != true) + if (!api.IsLoggedIn) { SetErrorState(LeaderboardState.NotLoggedIn); return null; @@ -145,7 +144,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - IReadOnlyList requestMods = null; + IReadOnlyList? requestMods = null; if (filterMods && !mods.Value.Any()) // add nomod for the request @@ -186,7 +185,7 @@ namespace osu.Game.Screens.Select.Leaderboards + $" AND {nameof(ScoreInfo.DeletePending)} == false" , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); - void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) + void localScoresChanged(IRealmCollection sender, ChangeSet? changes, Exception exception) { if (cancellationToken.IsCancellationRequested) return; From 20da1051d9ab2e217f8895d4020683445618aa27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 16:21:23 +0900 Subject: [PATCH 375/583] Apply NRT to `GameplayLeaderboard` hierarchy Just some cleanup work that I wanted to do as part of my last changes. --- .../Spectate/MultiSpectatorLeaderboard.cs | 2 -- .../HUD/MultiplayerGameplayLeaderboard.cs | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index c7af87a91d..4e9ab07e4c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Timing; using osu.Game.Online.Multiplayer; diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 56756249b3..4201b3f4c9 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -12,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; @@ -34,19 +33,20 @@ namespace osu.Game.Screens.Play.HUD public readonly SortedDictionary TeamScores = new SortedDictionary(); [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private SpectatorClient spectatorClient { get; set; } + private SpectatorClient spectatorClient { get; set; } = null!; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private UserLookupCache userLookupCache { get; set; } + private UserLookupCache userLookupCache { get; set; } = null!; + + private Bindable scoringMode = null!; private readonly MultiplayerRoomUser[] playingUsers; - private Bindable scoringMode; private readonly IBindableList playingUserIds = new BindableList(); @@ -126,14 +126,17 @@ namespace osu.Game.Screens.Play.HUD playingUserIds.BindCollectionChanged(playingUsersChanged); } - protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(IUser user, bool isTracked) + protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(IUser? user, bool isTracked) { var leaderboardScore = base.CreateLeaderboardScoreDrawable(user, isTracked); - if (UserScores[user.OnlineID].Team is int team) + if (user != null) { - leaderboardScore.BackgroundColour = getTeamColour(team).Lighten(1.2f); - leaderboardScore.TextColour = Color4.White; + if (UserScores[user.OnlineID].Team is int team) + { + leaderboardScore.BackgroundColour = getTeamColour(team).Lighten(1.2f); + leaderboardScore.TextColour = Color4.White; + } } return leaderboardScore; @@ -189,7 +192,7 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - if (spectatorClient != null) + if (spectatorClient.IsNotNull()) { foreach (var user in playingUsers) spectatorClient.StopWatchingUser(user.UserID); From 0f18bef3f366eff9de289c0eb5bad5a4657867d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Sep 2022 17:59:39 +0900 Subject: [PATCH 376/583] Fix incorrect test assumptions --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index db380cfdb7..c1a9768cf0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.PressButton(MouseButton.Left); }); - AddUntilStep("wait for fetch", () => leaderboard.Scores != null); + AddUntilStep("wait for fetch", () => leaderboard.Scores.Any()); AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID)); // "Clean up" @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestDeleteViaDatabase() { AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); - AddUntilStep("wait for fetch", () => leaderboard.Scores != null); + AddUntilStep("wait for fetch", () => leaderboard.Scores.Any()); AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID)); } } From 6e1edc4d8d81ada51e958d5920846790b97cedf3 Mon Sep 17 00:00:00 2001 From: abstrakt Date: Mon, 26 Sep 2022 14:06:35 +0200 Subject: [PATCH 377/583] Use the `StackedEndPosition` to determine the jump distance in the `FlashlightEvaluator`. Signed-off-by: abstrakt --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 2ba856d014..dabbfcd2fb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (!(currentObj.BaseObject is Spinner)) { - double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; + double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; cumulativeStrainTime += lastObj.StrainTime; From f81107eb5ac7dacb58c2b0955a975e0113eaf5e2 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 20:07:43 +0800 Subject: [PATCH 378/583] add gameplay leaderboard config --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 54c545e367..8d1de971b3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -131,6 +131,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.KeyOverlay, false); + SetDefault(OsuSetting.GameplayLeaderboard, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); SetDefault(OsuSetting.FloatingComments, false); @@ -294,6 +295,7 @@ namespace osu.Game.Configuration LightenDuringBreaks, ShowStoryboard, KeyOverlay, + GameplayLeaderboard, PositionalHitsounds, PositionalHitsoundsLevel, AlwaysPlayFirstComboBreak, From bed25ff60e92b87d212a15281d65556e555e587c Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 20:08:26 +0800 Subject: [PATCH 379/583] add gameplay leaderboard config string --- osu.Game/Localisation/GameplaySettingsStrings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 13cfcc3a19..40f39d927d 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -79,6 +79,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay"); + /// + /// "Always show gameplay leaderboard" + /// + public static LocalisableString AlwaysShowGameplayLeaderboard => new TranslatableString(getKey(@"gameplay_leaderboard"), @"Always show gameplay leaderboard"); + /// /// "Always play first combo break sound" /// From dc051a8b7992ebf6f7a7b8f08da6b16ea8b97ee8 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 20:08:58 +0800 Subject: [PATCH 380/583] add gameplay leaderboard config --- osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index 0893af7d3e..12534c51a8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -38,6 +38,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Current = config.GetBindable(OsuSetting.KeyOverlay), Keywords = new[] { "counter" }, }, + new SettingsCheckbox + { + LabelText = GameplaySettingsStrings.AlwaysShowGameplayLeaderboard, + Current = config.GetBindable(OsuSetting.GameplayLeaderboard), + Keywords = new[] { "leaderboard", "score" }, + }, }; } } From ee4d1b2d58cbb2d3c2fa51f65d88eea438f7e070 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 20:13:07 +0800 Subject: [PATCH 381/583] add config visibility --- .../Screens/Play/HUD/GameplayLeaderboard.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 2d816fbd55..b618ce907d 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -3,12 +3,14 @@ using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Users; using osuTK; @@ -18,6 +20,10 @@ namespace osu.Game.Screens.Play.HUD { public abstract class GameplayLeaderboard : CompositeDrawable { + + private const int duration = 100; + + private readonly Bindable configVisibility = new Bindable(); private readonly Cached sorting = new Cached(); public Bindable Expanded = new Bindable(); @@ -57,11 +63,19 @@ namespace osu.Game.Screens.Play.HUD }; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); + } + protected override void LoadComplete() { base.LoadComplete(); Scheduler.AddDelayed(sort, 1000, true); + + configVisibility.BindValueChanged(_ => updateVisibility(), true); } /// @@ -193,5 +207,8 @@ namespace osu.Game.Screens.Play.HUD public override bool HandlePositionalInput => false; public override bool HandleNonPositionalInput => false; } + + private void updateVisibility() => + Flow.FadeTo(configVisibility.Value ? 1 : 0, duration); } } From 5c132aadeb4fb60bd4dc0a50e3a9d5872643b0f1 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 20:15:17 +0800 Subject: [PATCH 382/583] add visual test --- .../Gameplay/TestSceneGameplayLeaderboard.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index 72656c29b1..eb5846669f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -5,12 +5,14 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.PolygonExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Play.HUD; using osuTK; @@ -24,6 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly BindableDouble playerScore = new BindableDouble(); + private Bindable configVisibility = new Bindable(); + public TestSceneGameplayLeaderboard() { AddStep("toggle expanded", () => @@ -35,6 +39,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v); } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); + } + [Test] public void TestLayoutWithManyScores() { @@ -129,6 +139,21 @@ namespace osu.Game.Tests.Visual.Gameplay => AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount); } + [Test] + public void TestVisibility() + { + createLeaderboard(); + addLocalPlayer(); + + AddStep("set visible true", () => configVisibility.Value = true); + AddWaitStep("wait", 1); + AddAssert("is leaderboard fully visible", () => leaderboard.FlowAlpha == 1); + + AddStep("set visible false", () => configVisibility.Value = false); + AddWaitStep("wait", 1); + AddAssert("is leaderboard fully invisible", () => leaderboard.FlowAlpha == 0); + } + private void addLocalPlayer() { AddStep("add local player", () => @@ -163,6 +188,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public float Spacing => Flow.Spacing.Y; + public float FlowAlpha => Flow.Alpha; + public bool CheckPositionByUsername(string username, int? expectedPosition) { var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username); From 4295d9c169b5b1453162131909c6f58c95ff7cdb Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 21:11:14 +0800 Subject: [PATCH 383/583] revert --- .../Gameplay/TestSceneGameplayLeaderboard.cs | 27 ------------------- .../Screens/Play/HUD/GameplayLeaderboard.cs | 17 ------------ 2 files changed, 44 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index eb5846669f..72656c29b1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -5,14 +5,12 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.PolygonExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Play.HUD; using osuTK; @@ -26,8 +24,6 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly BindableDouble playerScore = new BindableDouble(); - private Bindable configVisibility = new Bindable(); - public TestSceneGameplayLeaderboard() { AddStep("toggle expanded", () => @@ -39,12 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); - } - [Test] public void TestLayoutWithManyScores() { @@ -139,21 +129,6 @@ namespace osu.Game.Tests.Visual.Gameplay => AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount); } - [Test] - public void TestVisibility() - { - createLeaderboard(); - addLocalPlayer(); - - AddStep("set visible true", () => configVisibility.Value = true); - AddWaitStep("wait", 1); - AddAssert("is leaderboard fully visible", () => leaderboard.FlowAlpha == 1); - - AddStep("set visible false", () => configVisibility.Value = false); - AddWaitStep("wait", 1); - AddAssert("is leaderboard fully invisible", () => leaderboard.FlowAlpha == 0); - } - private void addLocalPlayer() { AddStep("add local player", () => @@ -188,8 +163,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public float Spacing => Flow.Spacing.Y; - public float FlowAlpha => Flow.Alpha; - public bool CheckPositionByUsername(string username, int? expectedPosition) { var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username); diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index b618ce907d..2d816fbd55 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -3,14 +3,12 @@ using System; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Users; using osuTK; @@ -20,10 +18,6 @@ namespace osu.Game.Screens.Play.HUD { public abstract class GameplayLeaderboard : CompositeDrawable { - - private const int duration = 100; - - private readonly Bindable configVisibility = new Bindable(); private readonly Cached sorting = new Cached(); public Bindable Expanded = new Bindable(); @@ -63,19 +57,11 @@ namespace osu.Game.Screens.Play.HUD }; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); - } - protected override void LoadComplete() { base.LoadComplete(); Scheduler.AddDelayed(sort, 1000, true); - - configVisibility.BindValueChanged(_ => updateVisibility(), true); } /// @@ -207,8 +193,5 @@ namespace osu.Game.Screens.Play.HUD public override bool HandlePositionalInput => false; public override bool HandleNonPositionalInput => false; } - - private void updateVisibility() => - Flow.FadeTo(configVisibility.Value ? 1 : 0, duration); } } From 5d18001d7542159c2959ea75499b4ca5b6da8621 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 21:11:38 +0800 Subject: [PATCH 384/583] move config --- .../Screens/Play/HUD/SoloGameplayLeaderboard.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index eff7870d89..fb7e2ff00f 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; @@ -13,6 +15,9 @@ namespace osu.Game.Screens.Play.HUD { public class SoloGameplayLeaderboard : GameplayLeaderboard { + private const int duration = 100; + + private readonly Bindable configVisibility = new Bindable(); private readonly IUser trackingUser; public readonly IBindableList Scores = new BindableList(); @@ -31,10 +36,18 @@ namespace osu.Game.Screens.Play.HUD this.trackingUser = trackingUser; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); + } + protected override void LoadComplete() { base.LoadComplete(); Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); + + configVisibility.BindValueChanged(_ => updateVisibility(), true); } private void showScores() @@ -69,5 +82,8 @@ namespace osu.Game.Screens.Play.HUD // Local score should always show lower than any existing scores in cases of ties. local.DisplayOrder.Value = long.MaxValue; } + + private void updateVisibility() => + Flow.FadeTo(configVisibility.Value ? 1 : 0, duration); } } From 1fab1db145843644699095a4ab319d832c84e5ae Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 21:11:48 +0800 Subject: [PATCH 385/583] move test --- .../TestSceneSoloGameplayLeaderboard.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index ac73e88468..cb48275e21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -9,11 +9,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Gameplay { @@ -24,6 +26,16 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly BindableList scores = new BindableList(); + private Bindable configVisibility = new Bindable(); + + private TestSoloGameplayLeaderboard? testSoloGameplayLeaderboard; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); + } + [SetUpSteps] public void SetUpSteps() { @@ -37,7 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay Id = 2, }; - Child = new SoloGameplayLeaderboard(trackingUser) + Child = testSoloGameplayLeaderboard = new TestSoloGameplayLeaderboard(trackingUser) { Scores = { BindTarget = scores }, Anchor = Anchor.Centre, @@ -57,6 +69,18 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v); } + [Test] + public void TestVisibility() + { + AddStep("set visible true", () => configVisibility.Value = true); + AddWaitStep("wait", 1); + AddAssert("is leaderboard fully visible", () => testSoloGameplayLeaderboard?.FlowAlpha == 1); + + AddStep("set visible false", () => configVisibility.Value = false); + AddWaitStep("wait", 1); + AddAssert("is leaderboard fully invisible", () => testSoloGameplayLeaderboard?.FlowAlpha == 0); + } + private static List createSampleScores() { return new[] @@ -68,5 +92,13 @@ namespace osu.Game.Tests.Visual.Gameplay new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) }, }.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList(); } + + private class TestSoloGameplayLeaderboard : SoloGameplayLeaderboard + { + public float FlowAlpha => Flow.Alpha; + public TestSoloGameplayLeaderboard(IUser trackingUser) + : base(trackingUser) + { } + } } } From 214a1c2d7fede26719017d7e8dcf73838661d02a Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 26 Sep 2022 21:26:13 +0800 Subject: [PATCH 386/583] code factor --- .../Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index cb48275e21..128e8b6c8a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly BindableList scores = new BindableList(); - private Bindable configVisibility = new Bindable(); + private readonly Bindable configVisibility = new Bindable(); private TestSoloGameplayLeaderboard? testSoloGameplayLeaderboard; @@ -96,9 +96,11 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestSoloGameplayLeaderboard : SoloGameplayLeaderboard { public float FlowAlpha => Flow.Alpha; + public TestSoloGameplayLeaderboard(IUser trackingUser) - : base(trackingUser) - { } + : base(trackingUser) + { + } } } } From 8aff856c793d8078d0217a6a0bf0cab837f37728 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Sep 2022 05:18:13 +0300 Subject: [PATCH 387/583] Fix `SkinnableTestScene` sizing logic potentially failing to work --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a8193701ef..f8f15e2729 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -140,6 +140,7 @@ namespace osu.Game.Tests.Visual { c.RelativeSizeAxes = Axes.None; c.AutoSizeAxes = Axes.None; + c.Size = Vector2.Zero; c.RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None; c.AutoSizeAxes = autoSize ? Axes.Both : Axes.None; From de6709d12c5cd5f2d7d522d1f074095fe223e6f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 12:46:15 +0900 Subject: [PATCH 388/583] Fix `rank_history` serialisation order dependence ```csharp [network] 2022-09-26 18:18:39 [verbose]: Processing response from https://dev.ppy.sh/api/v2/me/ failed with Newtonsoft.Json.JsonSerializationException: Error setting value to 'rankHistory' on 'osu.Game.Online.API.Requests.Responses.APIUser'. [network] 2022-09-26 18:18:39 [verbose]: ---> System.NullReferenceException: Object reference not set to an instance of an object. [network] 2022-09-26 18:18:39 [verbose]: at osu.Game.Online.API.Requests.Responses.APIUser.set_rankHistory(APIRankHistory value) in /tmp/osu/osu.Game/Online/API/Requests/Responses/APIUser.cs:line 231 ``` --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 5f843e9a7b..d3ddcffaf5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -228,7 +228,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"rank_history")] private APIRankHistory rankHistory { - set => statistics.RankHistory = value; + set => Statistics.RankHistory = value; } [JsonProperty("badges")] From bac3108aea03fb72b9ccc146b6cfb7353cf8f63a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 14:29:59 +0900 Subject: [PATCH 389/583] Remove unnecessary keywords --- osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index 12534c51a8..88a27840d8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -42,7 +42,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = GameplaySettingsStrings.AlwaysShowGameplayLeaderboard, Current = config.GetBindable(OsuSetting.GameplayLeaderboard), - Keywords = new[] { "leaderboard", "score" }, }, }; } From 320f134634fa3095c8522abc0b228c22f2671d92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 14:43:08 +0900 Subject: [PATCH 390/583] Use same logic as `KeyCounterDisplay` --- osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs | 9 ++++++++- osu.Game/Screens/Play/ReplayPlayer.cs | 1 + osu.Game/Screens/Play/SoloPlayer.cs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index fb7e2ff00f..471aaf9a6d 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -31,6 +31,12 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private ScoreManager scoreManager { get; set; } = null!; + /// + /// Whether the leaderboard should be visible regardless of the configuration value. + /// This is true by default, but can be changed. + /// + public readonly Bindable AlwaysVisible = new Bindable(true); + public SoloGameplayLeaderboard(IUser trackingUser) { this.trackingUser = trackingUser; @@ -47,6 +53,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); + AlwaysVisible.BindValueChanged(_ => updateVisibility()); configVisibility.BindValueChanged(_ => updateVisibility(), true); } @@ -84,6 +91,6 @@ namespace osu.Game.Screens.Play.HUD } private void updateVisibility() => - Flow.FadeTo(configVisibility.Value ? 1 : 0, duration); + this.FadeTo(AlwaysVisible.Value || configVisibility.Value ? 1 : 0, duration); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 163b9c593f..5382e283e0 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -62,6 +62,7 @@ namespace osu.Game.Screens.Play protected override GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User) { + AlwaysVisible = { Value = true }, Scores = { BindTarget = LeaderboardScores } }; diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index d7730737d6..ee19391b89 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -48,6 +48,7 @@ namespace osu.Game.Screens.Play protected override GameplayLeaderboard CreateGameplayLeaderboard() => new SoloGameplayLeaderboard(Score.ScoreInfo.User) { + AlwaysVisible = { Value = false }, Scores = { BindTarget = LeaderboardScores } }; From 4260ace1a6d24ff2a5282a89d93646b5a75a508a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 14:46:35 +0900 Subject: [PATCH 391/583] Fix test coverage --- .../TestSceneSoloGameplayLeaderboard.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index 128e8b6c8a..8e438e98b6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Bindable configVisibility = new Bindable(); - private TestSoloGameplayLeaderboard? testSoloGameplayLeaderboard; + private SoloGameplayLeaderboard leaderboard = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -49,11 +49,12 @@ namespace osu.Game.Tests.Visual.Gameplay Id = 2, }; - Child = testSoloGameplayLeaderboard = new TestSoloGameplayLeaderboard(trackingUser) + Child = leaderboard = new SoloGameplayLeaderboard(trackingUser) { Scores = { BindTarget = scores }, Anchor = Anchor.Centre, Origin = Anchor.Centre, + AlwaysVisible = { Value = false }, Expanded = { Value = true }, }; }); @@ -72,13 +73,17 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestVisibility() { - AddStep("set visible true", () => configVisibility.Value = true); - AddWaitStep("wait", 1); - AddAssert("is leaderboard fully visible", () => testSoloGameplayLeaderboard?.FlowAlpha == 1); + AddStep("set config visible true", () => configVisibility.Value = true); + AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1); - AddStep("set visible false", () => configVisibility.Value = false); - AddWaitStep("wait", 1); - AddAssert("is leaderboard fully invisible", () => testSoloGameplayLeaderboard?.FlowAlpha == 0); + AddStep("set config visible false", () => configVisibility.Value = false); + AddUntilStep("leaderboard not visible", () => leaderboard.Alpha == 0); + + AddStep("set always visible", () => leaderboard.AlwaysVisible.Value = true); + AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1); + + AddStep("set config visible true", () => configVisibility.Value = true); + AddAssert("leaderboard still visible", () => leaderboard.Alpha == 1); } private static List createSampleScores() @@ -92,15 +97,5 @@ namespace osu.Game.Tests.Visual.Gameplay new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) }, }.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList(); } - - private class TestSoloGameplayLeaderboard : SoloGameplayLeaderboard - { - public float FlowAlpha => Flow.Alpha; - - public TestSoloGameplayLeaderboard(IUser trackingUser) - : base(trackingUser) - { - } - } } } From 87a1e05641d596025c73f3a572560990d391eb69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 15:06:05 +0900 Subject: [PATCH 392/583] Remove unused using statement --- .../Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index 8e438e98b6..6b52e934b7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Gameplay { From 9dc0eb7fd02a81aebabb36a75c4573be545bf666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:04:56 +0900 Subject: [PATCH 393/583] Remove unused static --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 076ce224f0..fce73d908f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -34,8 +33,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private OsuColour colours { get; set; } - private static readonly int highest_divisor = BindableBeatDivisor.PREDEFINED_DIVISORS.Last(); - public TimelineTickDisplay() { RelativeSizeAxes = Axes.Both; From ccae721af2577dca7141b534204b3c552ec46cc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:05:10 +0900 Subject: [PATCH 394/583] Ensure `ZoomableScrollContainer` content isn't shown until zoom is set --- .../Timeline/ZoomableScrollContainer.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 0fb59a8a1f..839b2b5bad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -56,7 +56,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected ZoomableScrollContainer() : base(Direction.Horizontal) { - base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); + base.Content.Add(zoomedContent = new Container + { + RelativeSizeAxes = Axes.Y, + // We must hide content until SetupZoom is called. + // If not, a child component that relies on its DrawWidth (via RelativeSizeAxes) may see a very incorrect value + // momentarily, as noticed in the TimelineTickDisplay, which would render thousands of ticks incorrectly. + Alpha = 0, + }); AddLayout(zoomedContentWidthCache); } @@ -94,6 +101,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline maxZoom = maximum; CurrentZoom = zoomTarget = initial; isZoomSetUp = true; + + zoomedContent.Show(); } /// @@ -118,9 +127,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline CurrentZoom = zoomTarget = newZoom; } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); if (!zoomedContentWidthCache.IsValid) updateZoomedContentWidth(); From e227519b093b4b2fb48dab6f5eabdf5364e8fa0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:18:45 +0900 Subject: [PATCH 395/583] Don't attempt to render timeline ticks until `DrawWidth` is above zero --- .../Timeline/TimelineTickDisplay.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index fce73d908f..9018c95a47 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -77,20 +77,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.Update(); - if (timeline != null) + if (timeline == null || !(DrawWidth > 0)) return; + + (float, float) newRange = ( + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X); + + if (visibleRange != newRange) { - var newRange = ( - (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, - (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X); + visibleRange = newRange; - if (visibleRange != newRange) - { - visibleRange = newRange; - - // actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries. - if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick)) - tickCache.Invalidate(); - } + // actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries. + if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick)) + tickCache.Invalidate(); } if (!tickCache.IsValid) From a9088d9231042d4dccee5a95a96551b66242224c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:11:36 +0900 Subject: [PATCH 396/583] Add assert/log output when too many ticks are being displayed --- .../Components/Timeline/TimelineTickDisplay.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 9018c95a47..251306ee25 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -4,10 +4,12 @@ #nullable disable using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -147,6 +149,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + if (Children.Count > 512) + { + // There should always be a sanely small number of ticks rendered. + // If this assertion triggers, either the zoom logic is broken or a beatmap is + // probably doing weird things... + // + // Let's hope the latter never happens. + // If it does, we can choose to either fix it or ignore it as an outlier. + string message = $"Timeline is rendering many ticks ({Children.Count})"; + + Logger.Log(message); + Debug.Fail(message); + } + int usedDrawables = drawableIndex; // save a few drawables beyond the currently used for edge cases. From 0296685c74bbc47cebdc9d7c09b91bc10e10ff6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:25:24 +0900 Subject: [PATCH 397/583] Start key counter / gameplay leaderboard hidden to avoid initial fade out --- osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs | 3 +++ osu.Game/Screens/Play/KeyCounterDisplay.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 471aaf9a6d..53981b9edc 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -53,6 +53,9 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); + // Alpha will be updated via `updateVisibility` below. + Alpha = 0; + AlwaysVisible.BindValueChanged(_ => updateVisibility()); configVisibility.BindValueChanged(_ => updateVisibility(), true); } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index b6094726c0..1b726b0f7b 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Play { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, + Alpha = 0, }; } From c61f5403ab03378cce3013fa40f90714bd26083e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:32:30 +0900 Subject: [PATCH 398/583] Increase delay back slightly on notifications posting after disabled period --- osu.Game/Overlays/NotificationOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index fad8afd371..8e70f539a5 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -113,7 +113,7 @@ namespace osu.Game.Overlays if (enabled) // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. - notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 100); + notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 250); else processingPosts = false; } From 461ba64bfafa7c8a629f54e1c0fb4199a2a1790c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:32:53 +0900 Subject: [PATCH 399/583] Flush toast tray on entering a no-notification section --- osu.Game/Overlays/NotificationOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 8e70f539a5..36548c893c 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -115,7 +115,10 @@ namespace osu.Game.Overlays // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 250); else + { processingPosts = false; + toastTray.FlushAllToasts(); + } } protected override void LoadComplete() From 8b722f88172c20ff806d00a645048b7e9babd25d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:41:55 +0900 Subject: [PATCH 400/583] Change default skin for new installations to "Argon" Closes #20459. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 54c545e367..c1d87915ce 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Configuration { // UI/selection defaults SetDefault(OsuSetting.Ruleset, string.Empty); - SetDefault(OsuSetting.Skin, SkinInfo.TRIANGLES_SKIN.ToString()); + SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString()); SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); SetDefault(OsuSetting.BeatmapDetailModsFilter, false); From 3ece7205eda7ea1207375f2df4b38bb58918abcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 17:52:29 +0900 Subject: [PATCH 401/583] Fix potential crash when losing network connectivity at online play screen Addresses #20448. --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 61ea7d68ee..7e5d90bd4f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -105,7 +105,8 @@ namespace osu.Game.Screens.OnlinePlay while (this.IsCurrentScreen()) this.Exit(); } - else + // Also handle the case where a child screen is current (ie. gameplay). + else if (this.GetChildScreen() != null) { this.MakeCurrent(); Schedule(forcefullyExit); From 58217b68396d00d16b50d6e454320c8795af25ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 20:09:21 +0900 Subject: [PATCH 402/583] Fix weird conditional --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 251306ee25..c1c9b2493b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.Update(); - if (timeline == null || !(DrawWidth > 0)) return; + if (timeline == null || DrawWidth <= 0) return; (float, float) newRange = ( (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, From da8d94c4b46cf891eef362cb0085e8480998b629 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 27 Sep 2022 20:24:44 +0900 Subject: [PATCH 403/583] Fix test failures due to scheduled operations --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 15138b18b0..bbdfed0a00 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -54,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { private const float disabled_alpha = 0.2f; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + public Action? SettingsApplied; public OsuTextBox NameField = null!; From 1811647e34c7c663b1d94722a02a26cae38a06d5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 27 Sep 2022 20:30:41 +0900 Subject: [PATCH 404/583] Make room requests handler handle GetBeatmapRequest --- .../Online/API/Requests/GetBeatmapRequest.cs | 22 +++++---- .../OnlinePlay/TestRoomRequestsHandler.cs | 45 ++++++++++++------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index efecc0fc25..3383d21dfc 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -9,27 +9,25 @@ namespace osu.Game.Online.API.Requests { public class GetBeatmapRequest : APIRequest { - private readonly IBeatmapInfo beatmapInfo; - - private readonly string filename; + public readonly IBeatmapInfo BeatmapInfo; + public readonly string Filename; public GetBeatmapRequest(IBeatmapInfo beatmapInfo) { - this.beatmapInfo = beatmapInfo; - - filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty; + BeatmapInfo = beatmapInfo; + Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty; } protected override WebRequest CreateWebRequest() { var request = base.CreateWebRequest(); - if (beatmapInfo.OnlineID > 0) - request.AddParameter(@"id", beatmapInfo.OnlineID.ToString()); - if (!string.IsNullOrEmpty(beatmapInfo.MD5Hash)) - request.AddParameter(@"checksum", beatmapInfo.MD5Hash); - if (!string.IsNullOrEmpty(filename)) - request.AddParameter(@"filename", filename); + if (BeatmapInfo.OnlineID > 0) + request.AddParameter(@"id", BeatmapInfo.OnlineID.ToString()); + if (!string.IsNullOrEmpty(BeatmapInfo.MD5Hash)) + request.AddParameter(@"checksum", BeatmapInfo.MD5Hash); + if (!string.IsNullOrEmpty(Filename)) + request.AddParameter(@"filename", Filename); return request; } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index fa7ade2c07..ef4539ba56 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -135,25 +135,15 @@ namespace osu.Game.Tests.Visual.OnlinePlay }); return true; + case GetBeatmapRequest getBeatmapRequest: + { + getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.BeatmapInfo.OnlineID).Single()); + return true; + } + case GetBeatmapsRequest getBeatmapsRequest: { - var result = new List(); - - foreach (int id in getBeatmapsRequest.BeatmapIds) - { - var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); - - if (baseBeatmap == null) - { - baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo; - baseBeatmap.OnlineID = id; - baseBeatmap.BeatmapSet!.OnlineID = id; - } - - result.Add(OsuTestScene.CreateAPIBeatmap(baseBeatmap)); - } - - getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = result }); + getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = createResponseBeatmaps(getBeatmapsRequest.BeatmapIds.ToArray()) }); return true; } @@ -175,6 +165,27 @@ namespace osu.Game.Tests.Visual.OnlinePlay } } + List createResponseBeatmaps(params int[] beatmapIds) + { + var result = new List(); + + foreach (int id in beatmapIds) + { + var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); + + if (baseBeatmap == null) + { + baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo; + baseBeatmap.OnlineID = id; + baseBeatmap.BeatmapSet!.OnlineID = id; + } + + result.Add(OsuTestScene.CreateAPIBeatmap(baseBeatmap)); + } + + return result; + } + return false; } From ed0752a5f12d29de2ec0e57e7cd9ae25ee0ef729 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 21:55:03 +0900 Subject: [PATCH 405/583] Update test assumptions --- osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs index 3757c49f18..010ed23c9b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestEditDefaultSkin() { - AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.TRIANGLES_SKIN); + AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.ARGON_SKIN); AddStep("open settings", () => { Game.Settings.Show(); }); @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("open skin editor", () => skinEditor.Show()); // Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part). - AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.TRIANGLES_SKIN); + AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.ARGON_SKIN); AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected)); AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType().SingleOrDefault()?.Enabled.Value == true); From f3e25eacadc046c96669dab7e5f430b068d851ce Mon Sep 17 00:00:00 2001 From: o-dasher <88356162+o-dasher@users.noreply.github.com> Date: Tue, 27 Sep 2022 09:25:58 -0400 Subject: [PATCH 406/583] Removes unnecessary setting sources specifications --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 3 --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 7 ------- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 3 --- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 7 ------- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 ++ osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 3 --- osu.Game/Rulesets/Mods/ModNoScope.cs | 7 +++++++ osu.Game/Rulesets/Mods/ModWindDown.cs | 4 ---- osu.Game/Rulesets/Mods/ModWindUp.cs | 4 ---- 12 files changed, 9 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 41d4590aa8..1adc969f8f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; @@ -16,7 +15,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; - [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, @@ -24,7 +22,6 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override float DefaultFlashlightSize => 350; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 1b781b032c..19b4a39f97 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -6,8 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; -using osu.Game.Configuration; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.UI; @@ -17,11 +15,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public override LocalisableString Description => "Where's the catcher?"; - [SettingSource( - "Hidden at combo", - "The combo count at which the catcher becomes completely hidden", - SettingControlType = typeof(SettingsSlider) - )] public override BindableInt HiddenComboCount { get; } = new BindableInt(10) { MinValue = 0, diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 72ea1a2431..6eaede2112 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; -using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osuTK; @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(); public override float DefaultFlashlightSize => 50; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index b2980f5311..f6622c268d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -18,7 +17,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Hit them at the right size!"; - [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public override BindableNumber StartScale { get; } = new BindableFloat(2) { MinValue = 1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 7b34fe05a9..79f5eed139 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -32,7 +32,6 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = default_follow_delay, }; - [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, @@ -40,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override float DefaultFlashlightSize => 180; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index b49257e289..3d066d3ada 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -18,7 +17,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Hit them at the right size!"; - [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public override BindableNumber StartScale { get; } = new BindableFloat(0.5f) { MinValue = 0f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 84538cec30..2f84c30581 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -7,8 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; @@ -22,11 +20,6 @@ namespace osu.Game.Rulesets.Osu.Mods private PeriodTracker spinnerPeriods = null!; - [SettingSource( - "Hidden at combo", - "The combo count at which the cursor becomes completely hidden", - SettingControlType = typeof(SettingsSlider) - )] public override BindableInt HiddenComboCount { get; } = new BindableInt(10) { MinValue = 0, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 59984f9a7b..6f1206382a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; + [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public abstract BindableNumber StartScale { get; } protected virtual float EndScale => 1; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 05baf88a33..1caacdd1d7 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; - [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1) { MinValue = 0.5f, @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override float DefaultFlashlightSize => 250; diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 1b9ce833ad..36fbb88943 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -6,7 +6,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -34,6 +36,11 @@ namespace osu.Game.Rulesets.Mods protected float ComboBasedAlpha; + [SettingSource( + "Hidden at combo", + "The combo count at which the cursor becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] public abstract BindableInt HiddenComboCount { get; } public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 15ee37d481..35a673093b 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Sloooow doooown..."; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown; - [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble(1) { MinValue = 0.51, @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble(0.75) { MinValue = 0.5, @@ -33,7 +30,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public override BindableBool AdjustPitch { get; } = new BindableBool(true); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 6d8838c5a5..bbc8382055 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Can you keep up?"; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp; - [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble(1) { MinValue = 0.5, @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble(1.5) { MinValue = 0.51, @@ -33,7 +30,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public override BindableBool AdjustPitch { get; } = new BindableBool(true); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); From 0cffbb756286971645105c3794afd03bca3e4122 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Tue, 27 Sep 2022 16:54:24 +0200 Subject: [PATCH 407/583] Clamp `LifetimeStart` of `HitObject`'s to their judgement windows --- .../Objects/Drawables/DrawableHoldNoteTail.cs | 2 +- .../Objects/Drawables/DrawableSpinnerTick.cs | 2 +- .../Objects/Drawables/DrawableDrumRollTick.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 6 +++++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index a7bdcd047e..3084f71be2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); - protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; + public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 6a15463a32..4975ca1248 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; } - protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; + public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; /// /// Apply a judgement result. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 705a0a8047..451c5a793b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Filled = HitObject.FirstTick }); - protected override double MaximumJudgementOffset => HitObject.HitWindow; + public override double MaximumJudgementOffset => HitObject.HitWindow; protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e218da918c..dec68a6c22 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -651,7 +651,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This does not affect the time offset provided to invocations of . /// - protected virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; + public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; /// /// Applies the of this , notifying responders such as diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 0ed3ca1e63..d0aca4e7fc 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; @@ -214,7 +215,10 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); + double computedStartTime = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); + + // always load the hitobject before its first judgement offset + return Math.Min(hitObject.HitObject.StartTime - hitObject.MaximumJudgementOffset, computedStartTime); } private void updateLayoutRecursive(DrawableHitObject hitObject) From eae8916e76ac0b0795565fd1d93cdb10a5136e2a Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Tue, 27 Sep 2022 19:52:55 +0200 Subject: [PATCH 408/583] Add test --- .../Judgements/TestSceneHitJudgements.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs index a405f0e8ba..d2d5cdb6ac 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; @@ -129,5 +130,32 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements AssertResult(0, HitResult.Miss); AssertResult(0, HitResult.IgnoreMiss); } + + [Test] + public void TestHighVelocityHit() + { + const double hit_time = 1000; + + var beatmap = CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time, + }); + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 6 }); + beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 10 }); + + var hitWindows = new HitWindows(); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time - hitWindows.WindowFor(HitResult.Great), TaikoAction.LeftCentre), + }, beatmap); + + AssertJudgementCount(1); + AssertResult(0, HitResult.Ok); + } } } From 8edb1cb98a45127c2c484001269f87ae2435b6f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Sep 2022 15:57:30 +0900 Subject: [PATCH 409/583] Add test coverage of current score staying on screen --- .../Visual/Gameplay/TestSceneGameplayLeaderboard.cs | 10 ++++++++++ osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index 72656c29b1..171ae829a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -58,6 +58,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for some scores not masked away", () => leaderboard.ChildrenOfType().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre))); + + AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad)); + + AddStep("change score to middle", () => playerScore.Value = 1000000); + AddWaitStep("wait for movement", 5); + AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad)); + + AddStep("change score to first", () => playerScore.Value = 5000000); + AddWaitStep("wait for movement", 5); + AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad)); } [Test] diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 2d816fbd55..e550c00390 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.HUD private bool requiresScroll; private readonly OsuScrollContainer scroll; - private GameplayLeaderboardScore? trackedScore; + public GameplayLeaderboardScore? TrackedScore { get; private set; } private const int max_panels = 8; @@ -78,10 +78,10 @@ namespace osu.Game.Screens.Play.HUD if (isTracked) { - if (trackedScore != null) + if (TrackedScore != null) throw new InvalidOperationException("Cannot track more than one score."); - trackedScore = drawable; + TrackedScore = drawable; } drawable.Expanded.BindTo(Expanded); @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD public void Clear() { Flow.Clear(); - trackedScore = null; + TrackedScore = null; scroll.ScrollToStart(false); } @@ -119,9 +119,10 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - if (requiresScroll && trackedScore != null) + if (requiresScroll && TrackedScore != null) { - float scrollTarget = scroll.GetChildPosInContent(trackedScore) + trackedScore.DrawHeight / 2 - scroll.DrawHeight / 2; + float scrollTarget = scroll.GetChildPosInContent(TrackedScore) + TrackedScore.DrawHeight / 2 - scroll.DrawHeight / 2; + scroll.ScrollTo(scrollTarget); } From c8643ed2655afe7d4f7018bc767cc9bd385af5a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Sep 2022 15:34:35 +0900 Subject: [PATCH 410/583] Set `ClampExtension` to zero to stop jittering --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index e550c00390..fde895a1ca 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Play.HUD { scroll = new InputDisabledScrollContainer { + ClampExtension = 0, RelativeSizeAxes = Axes.Both, Child = Flow = new FillFlowContainer { @@ -92,14 +93,6 @@ namespace osu.Game.Screens.Play.HUD int displayCount = Math.Min(Flow.Count, max_panels); Height = displayCount * (GameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y); - // Add extra margin space to flow equal to height of leaderboard. - // This ensures the content is always on screen, but also accounts for the fact that scroll operations - // without animation were actually forcing the local score to a location it can't usually reside at. - // - // Basically, the local score was in the scroll extension region (due to always trying to scroll the - // local player to the middle of the display, but there being no other content below the local player - // to scroll up by). - Flow.Margin = new MarginPadding { Bottom = Height }; requiresScroll = displayCount != Flow.Count; return drawable; From cdcc8494c9cc9e967e89b5fa06eef21bfaa97092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Sep 2022 16:10:19 +0900 Subject: [PATCH 411/583] Fix fade being applied for too long when leaderboard scrolls to start --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 2d816fbd55..226cff9da1 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Play.HUD float fadeBottom = scroll.Current + scroll.DrawHeight; float fadeTop = scroll.Current + panel_height; - if (scroll.Current <= 0) fadeTop -= panel_height; + if (scroll.IsScrolledToStart()) fadeTop -= panel_height; if (!scroll.IsScrolledToEnd()) fadeBottom -= panel_height; // logic is mostly shared with Leaderboard, copied here for simplicity. From 4ee435507d3f078a5507faacd002162db922cebe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Sep 2022 17:58:47 +0900 Subject: [PATCH 412/583] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index dd263d6aaa..c33937ad95 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c4e2a5168e..31b17fb8e1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 622efcd63d..a9a83e2802 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -82,7 +82,7 @@ - + From 2493468800a3bd2371d70fd8ad1aa03ec8903ebb Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Sun, 2 Oct 2022 18:40:31 +0800 Subject: [PATCH 413/583] display highest combo instead of current combo --- osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 53981b9edc..ab3cf2950c 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Play.HUD local.TotalScore.BindTarget = scoreProcessor.TotalScore; local.Accuracy.BindTarget = scoreProcessor.Accuracy; - local.Combo.BindTarget = scoreProcessor.Combo; + local.Combo.BindTarget = scoreProcessor.HighestCombo; // Local score should always show lower than any existing scores in cases of ties. local.DisplayOrder.Value = long.MaxValue; From b6701dd57892f4f585adb1494388768538fb69cc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 Oct 2022 15:29:53 +0300 Subject: [PATCH 414/583] Add failing test case --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index e2c825df0b..a26a7e97be 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected new OutroPlayer Player => (OutroPlayer)base.Player; + private double currentBeatmapDuration; private double currentStoryboardDuration; private bool showResults = true; @@ -45,7 +46,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0)); AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false); - AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000); + AddStep("set beatmap duration to 0s", () => currentBeatmapDuration = 0); + AddStep("set storyboard duration to 8s", () => currentStoryboardDuration = 8000); AddStep("set ShowResults = true", () => showResults = true); } @@ -151,6 +153,24 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("player exited", () => Stack.CurrentScreen == null); } + [Test] + public void TestPerformExitAfterOutro() + { + CreateTest(() => + { + AddStep("set beatmap duration to 4s", () => currentBeatmapDuration = 4000); + AddStep("set storyboard duration to 1s", () => currentStoryboardDuration = 1000); + }); + + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); + AddStep("exit via pause", () => Player.ExitViaPause()); + AddAssert("player paused", () => !Player.IsResuming); + + AddStep("resume player", () => Player.Resume()); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddUntilStep("wait for score shown", () => Player.IsScoreShown); + } + protected override bool AllowFail => true; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); @@ -160,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap(); - beatmap.HitObjects.Add(new HitCircle()); + beatmap.HitObjects.Add(new HitCircle { StartTime = currentBeatmapDuration }); return beatmap; } @@ -189,7 +209,7 @@ namespace osu.Game.Tests.Visual.Gameplay private event Func failConditions; public OutroPlayer(Func failConditions, bool showResults = true) - : base(false, showResults) + : base(showResults: showResults) { this.failConditions = failConditions; } From 59728b0ccb78fc7394cc15016cfe1c55fa45884b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 Oct 2022 15:30:06 +0300 Subject: [PATCH 415/583] Fix results display delegate potentially cancelled while not exiting --- osu.Game/Screens/Play/Player.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e31e18046e..9bc784411a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -566,9 +566,6 @@ namespace osu.Game.Screens.Play /// protected void PerformExit(bool showDialogFirst) { - // if an exit has been requested, cancel any pending completion (the user has shown intention to exit). - resultsDisplayDelegate?.Cancel(); - // there is a chance that an exit request occurs after the transition to results has already started. // even in such a case, the user has shown intent, so forcefully return to this screen (to proceed with the upwards exit process). if (!this.IsCurrentScreen()) @@ -603,6 +600,9 @@ namespace osu.Game.Screens.Play } } + // if an exit has been requested, cancel any pending completion (the user has shown intention to exit). + resultsDisplayDelegate?.Cancel(); + // The actual exit is performed if // - the pause / fail dialog was not requested // - the pause / fail dialog was requested but is already displayed (user showing intention to exit). From a810afafb39752affe1e8f59deef4afc36ed095e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 Oct 2022 15:37:56 +0300 Subject: [PATCH 416/583] Reschedule results display delegate to avoid potential softlocks in the future --- osu.Game/Screens/Play/Player.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9bc784411a..7721d5b912 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -780,19 +780,11 @@ namespace osu.Game.Screens.Play /// /// /// A final display will only occur once all work is completed in . This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . - /// - /// Calling this method multiple times will have no effect. /// /// Whether a minimum delay () should be added before the screen is displayed. private void progressToResults(bool withDelay) { - if (resultsDisplayDelegate != null) - // Note that if progressToResults is called one withDelay=true and then withDelay=false, this no-delay timing will not be - // accounted for. shouldn't be a huge concern (a user pressing the skip button after a results progression has already been queued - // may take x00 more milliseconds than expected in the very rare edge case). - // - // If required we can handle this more correctly by rescheduling here. - return; + resultsDisplayDelegate?.Cancel(); double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0; From 492f26c8328a4562b249911cd2810be2d3f99f28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 16:56:32 +0900 Subject: [PATCH 417/583] Update test step in line with new combo source --- .../Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index 6b52e934b7..c852685b74 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v); AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v); - AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v); + AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.HighestCombo.Value = v); } [Test] From c4dd23ed15c2818f9aab44361e0f1981e57ad901 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 17:22:30 +0900 Subject: [PATCH 418/583] Log token retrieval failures even when gameplay is allowed to continue --- osu.Game/Screens/Play/SubmittingPlayer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index be77304076..85d1ecea6b 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -76,6 +76,7 @@ namespace osu.Game.Screens.Play req.Success += r => { + Logger.Log($"Score submission token retrieved ({r.ID})"); token = r.ID; tcs.SetResult(true); }; @@ -104,6 +105,12 @@ namespace osu.Game.Screens.Play this.Exit(); }); } + else + { + // Gameplay is allowed to continue, but we still should keep track of the error. + // In the future, this should be visible to the user in some way. + Logger.Log($"Score submission token retrieval failed ({exception.Message})"); + } tcs.SetResult(false); } From 65369e96ebf1ee90e5a74dba974a79824b8e54f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 17:29:46 +0900 Subject: [PATCH 419/583] Ensure token retrieval failure logic only runs once --- osu.Game/Screens/Play/SubmittingPlayer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 85d1ecea6b..d56b9c23c8 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -92,6 +92,11 @@ namespace osu.Game.Screens.Play void handleTokenFailure(Exception exception) { + // This method may be invoked multiple times due to the Task.Wait call above. + // We only really care about the first error. + if (!tcs.TrySetResult(false)) + return; + if (HandleTokenRetrievalFailure(exception)) { if (string.IsNullOrEmpty(exception.Message)) @@ -111,8 +116,6 @@ namespace osu.Game.Screens.Play // In the future, this should be visible to the user in some way. Logger.Log($"Score submission token retrieval failed ({exception.Message})"); } - - tcs.SetResult(false); } } From 42aac16b377b7b8ab52fba8107a84a5d13fa87c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 18:12:36 +0900 Subject: [PATCH 420/583] Adjust leaderboard score panels sizing based on accuracy/combo width --- .../TestSceneSoloGameplayLeaderboard.cs | 3 +- .../Play/HUD/GameplayLeaderboardScore.cs | 44 +++++++++++++++---- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs index c852685b74..60ed0012ae 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs @@ -66,7 +66,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v); AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v); - AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.HighestCombo.Value = v); + AddSliderStep("combo", 0, 10000, 0, v => scoreProcessor.HighestCombo.Value = v); + AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value); } [Test] diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 29354e610d..548dd2a5bb 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -39,8 +40,6 @@ namespace osu.Game.Screens.Play.HUD private const float rank_text_width = 35f; - private const float score_components_width = 85f; - private const float avatar_size = 25f; private const double panel_transition_duration = 500; @@ -161,7 +160,7 @@ namespace osu.Game.Screens.Play.HUD { new Dimension(GridSizeMode.Absolute, rank_text_width), new Dimension(), - new Dimension(GridSizeMode.AutoSize, maxSize: score_components_width), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { @@ -286,8 +285,19 @@ namespace osu.Game.Screens.Play.HUD LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add); TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); - Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); - Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); + + Accuracy.BindValueChanged(v => + { + accuracyText.Text = v.NewValue.FormatAccuracy(); + updateDetailsWidth(); + }, true); + + Combo.BindValueChanged(v => + { + comboText.Text = $"{v.NewValue}x"; + updateDetailsWidth(); + }, true); + HasQuit.BindValueChanged(_ => updateState()); } @@ -303,13 +313,10 @@ namespace osu.Game.Screens.Play.HUD private void changeExpandedState(ValueChangedEvent expanded) { - scoreComponents.ClearTransforms(); - if (expanded.NewValue) { gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint); - scoreComponents.ResizeWidthTo(score_components_width, panel_transition_duration, Easing.OutQuint); scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint); usernameText.FadeIn(panel_transition_duration, Easing.OutQuint); @@ -318,13 +325,32 @@ namespace osu.Game.Screens.Play.HUD { gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint); - scoreComponents.ResizeWidthTo(0, panel_transition_duration, Easing.OutQuint); scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint); usernameText.FadeOut(text_transition_duration, Easing.OutQuint); } + + updateDetailsWidth(); } + private float? scoreComponentsTargetWidth; + + // Schedule required to get correct DrawWidth from text after updates. + private void updateDetailsWidth() => SchedulerAfterChildren.AddOnce(() => + { + const float score_components_min_width = 88f; + + float newWidth = Expanded.Value + ? Math.Max(score_components_min_width, comboText.DrawWidth + accuracyText.DrawWidth + 25) + : 0; + + if (scoreComponentsTargetWidth == newWidth) + return; + + scoreComponentsTargetWidth = newWidth; + scoreComponents.ResizeWidthTo(newWidth, panel_transition_duration, Easing.OutQuint); + }); + private void updateState() { bool widthExtension = false; From d9f678d94220e8a3adefc9e058a521263d0fca9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 18:16:54 +0900 Subject: [PATCH 421/583] Change song select random key binding to not handle key repeat --- osu.Game/Screens/Select/FooterButtonRandom.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 1f56915f62..aad7fdff39 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -138,7 +138,8 @@ namespace osu.Game.Screens.Select return false; } - TriggerClick(); + if (!e.Repeat) + TriggerClick(); return true; } From 0df217d85caf46e9dfaf1d9d9126b541a8a95034 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 3 Oct 2022 18:24:56 +0900 Subject: [PATCH 422/583] Add ability to adjust flashlight smoothness --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 6d7706cde2..7ed73b3163 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -201,6 +201,20 @@ namespace osu.Game.Rulesets.Mods } } + private float flashlightSmoothness = 1.1f; + + public float FlashlightSmoothness + { + get => flashlightSmoothness; + set + { + if (flashlightSmoothness == value) return; + + flashlightSmoothness = value; + Invalidate(Invalidation.DrawNode); + } + } + private class FlashlightDrawNode : DrawNode { protected new Flashlight Source => (Flashlight)base.Source; @@ -210,6 +224,7 @@ namespace osu.Game.Rulesets.Mods private Vector2 flashlightPosition; private Vector2 flashlightSize; private float flashlightDim; + private float flashlightSmoothness; private IVertexBatch? quadBatch; private Action? addAction; @@ -228,6 +243,7 @@ namespace osu.Game.Rulesets.Mods flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; flashlightDim = Source.FlashlightDim; + flashlightSmoothness = Source.flashlightSmoothness; } public override void Draw(IRenderer renderer) @@ -249,6 +265,7 @@ namespace osu.Game.Rulesets.Mods shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); + shader.GetUniform("flashlightSmoothness").UpdateValue(ref flashlightSmoothness); renderer.DrawQuad(renderer.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: addAction); From 8598eb29f8393106b1dbea30c9b1b8fbdddd3418 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 3 Oct 2022 18:26:35 +0900 Subject: [PATCH 423/583] Adjust flashlight to closely match classic --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 79f5eed139..66f367c79b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override BindableBool ComboBasedSize { get; } = new BindableBool(true); - public override float DefaultFlashlightSize => 180; + public override float DefaultFlashlightSize => 200; private OsuFlashlight flashlight = null!; @@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Mods followDelay = modFlashlight.FollowDelay.Value; FlashlightSize = new Vector2(0, GetSizeFor(0)); + FlashlightSmoothness = 1.4f; } public void OnSliderTrackingChange(ValueChangedEvent e) From deae7cff60efdf4e58bbdcae9c7121565d575d2a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 3 Oct 2022 18:38:29 +0900 Subject: [PATCH 424/583] Adjust flashliht scaling to match classic --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 7ed73b3163..a594363d4c 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -151,9 +151,9 @@ namespace osu.Game.Rulesets.Mods if (comboBasedSize) { if (combo >= 200) - size *= 0.8f; + size *= 0.625f; else if (combo >= 100) - size *= 0.9f; + size *= 0.8125f; } return size; From 13ee5c179ea2dd338b3f9ef0001b4bb8a8941b90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 18:42:40 +0900 Subject: [PATCH 425/583] Add missing parenthesis in log message --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 59c0af1533..8a42fef43d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -563,7 +563,7 @@ namespace osu.Game PerformFromScreen(screen => { - Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score"); + Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score)"); Ruleset.Value = databasedScore.ScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); From 7fbbe88c8ea945dfd8aa4b51091c354074e2a2d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 20:24:09 +0900 Subject: [PATCH 426/583] Add test coverage of song select score presentation failures --- .../Navigation/TestScenePresentScore.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 5e76fe1519..003cec0d07 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -9,8 +9,10 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; @@ -92,6 +94,31 @@ namespace osu.Game.Tests.Visual.Navigation returnToMenu(); } + [Test] + 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); + + AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq"); + AddUntilStep("wait for no results", () => Beatmap.IsDefault); + + var firstImport = importScore(1, new CatchRuleset().RulesetInfo); + presentAndConfirm(firstImport, type); + } + + [Test] + 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); + + AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); + + var firstImport = importScore(1, new CatchRuleset().RulesetInfo); + presentAndConfirm(firstImport, type); + } + [Test] public void TestFromSongSelect([Values] ScorePresentType type) { From 332d63b53bee3d5ea18a4e7853aee834674d5039 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 20:04:37 +0900 Subject: [PATCH 427/583] Always return to main menu before attempting to present a score from import --- osu.Game/OsuGame.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8a42fef43d..6359ed3e27 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -561,6 +561,8 @@ namespace osu.Game return; } + // This should be able to be performed from song select, but that is disabled for now + // due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios). PerformFromScreen(screen => { Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score)"); @@ -578,7 +580,7 @@ namespace osu.Game screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false)); break; } - }, validScreens: new[] { typeof(PlaySongSelect) }); + }); } public override Task Import(params ImportTask[] imports) From dfb143ec0bb1483c61b8a7d72812b535ba241466 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Oct 2022 21:14:57 +0900 Subject: [PATCH 428/583] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index dd263d6aaa..102d368095 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c4e2a5168e..9cc4f3fbab 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 622efcd63d..cc922d304f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 929eb8559e9101e1ce1ba6a2249d15c9531d17d0 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 3 Oct 2022 16:02:33 -0700 Subject: [PATCH 429/583] Fix `LegacySmoke` alpha calculations --- .../Skinning/Legacy/LegacySmoke.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index f02c20fefb..b8d70c1c6d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -67,6 +67,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected new LegacySmoke Source => (LegacySmoke)base.Source; private double initialFadeOutDurationTrunc; + private double firstVisiblePointTime; + private double initialFadeOutTime; private double reFadeInTime; private double finalFadeOutTime; @@ -83,20 +85,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.ApplyState(); - initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); rotationSeed = Source.RotationSeed; - rotationRNG = new Random(rotationSeed); - initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime); - reFadeInTime = re_fade_in_speed * (CurrentTime - SmokeEndTime) + SmokeEndTime - initialFadeOutDurationTrunc; - finalFadeOutTime = final_fade_out_speed * (CurrentTime - SmokeEndTime) + SmokeEndTime - initialFadeOutDurationTrunc * (1 + 1 / re_fade_in_speed); + + initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc; + + initialFadeOutTime = CurrentTime; + reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed); + finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed); } protected override Color4 PointColour(SmokePoint point) { var color = Color4.White; - double timeDoingInitialFadeOut = initialFadeOutTime - point.Time; + double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time; if (timeDoingInitialFadeOut > 0) { @@ -106,8 +110,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (color.A > 0) { - double timeDoingReFadeIn = reFadeInTime - point.Time; - double timeDoingFinalFadeOut = finalFadeOutTime - point.Time; + double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed; + double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed; if (timeDoingFinalFadeOut > 0) { From c1da3bc9cf831b35f368bb54c64e84bdede47339 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 3 Oct 2022 16:03:37 -0700 Subject: [PATCH 430/583] Remove skinnable parents at the same time as their smoke children --- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 2b5bddf959..f8551e46e9 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -44,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.UI { isSmoking = false; SmokeEnded?.Invoke(Time.Current); + + foreach (SkinnableDrawable skinnable in Children) + skinnable.LifetimeEnd = skinnable.Drawable.LifetimeEnd; } } From 343bdaa98efbc457ba5383fe4c488c0ba1297f7a Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 3 Oct 2022 16:07:39 -0700 Subject: [PATCH 431/583] Remove unnecessary `IsActive` variable --- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index 4d2a193a8a..fae875eedb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Skinning protected double SmokeEndTime { get; private set; } = double.MaxValue; protected virtual float PointInterval => Radius * 7f / 8; - protected bool IsActive { get; private set; } protected readonly List SmokePoints = new List(); @@ -73,7 +72,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { smokeContainer.SmokeMoved += onSmokeMoved; smokeContainer.SmokeEnded += onSmokeEnded; - IsActive = true; onSmokeMoved(smokeContainer.LastMousePosition, Time.Current); } @@ -87,9 +85,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private void onSmokeMoved(Vector2 position, double time) { - if (!IsActive) - return; - lastPosition ??= position; float delta = (position - (Vector2)lastPosition).LengthFast; @@ -137,10 +132,12 @@ namespace osu.Game.Rulesets.Osu.Skinning private void onSmokeEnded(double time) { - if (!IsActive) - return; + if (smokeContainer != null) + { + smokeContainer.SmokeMoved -= onSmokeMoved; + smokeContainer.SmokeEnded -= onSmokeEnded; + } - IsActive = false; SmokeEndTime = time; } From ccef189b81569aa25b0d2ce26c03039be9a7aeea Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 3 Oct 2022 18:19:05 -0700 Subject: [PATCH 432/583] Add barebones test for smoke --- .../metrics-skin/cursor-smoke@2x.png | Bin 0 -> 251 bytes .../Resources/old-skin/cursor-smoke.png | Bin 0 -> 2249 bytes osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs | 97 ++++++++++++++++++ osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 4 +- 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/cursor-smoke@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/cursor-smoke@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/cursor-smoke@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b1380a47a47f4e1b293237f24f54606600a72964 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85p>QL70(Y)*K0--~>+>$B>G+x3d-b7z}wDZvFm0znsl}FGJhS zNhUL2p0i9pQWN3LxM3Pof-dWUsCWMx?mqphHvMN5)mND^EPTSXYMH5sl2F_0v2 ik^M!U07BoXGyGz#Jf&%6Kd%7Y#^CAd=d#Wzp$PzN-cP*% literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png new file mode 100644 index 0000000000000000000000000000000000000000..5f7beae4e9dddd9493c3353ae209ea8c5679ca9c GIT binary patch literal 2249 zcmaJ@dstFw8b=WE5?-g&a_UIUrk21_P#{rJyk$6wkeA0z2~L6v2f2unsYJGBUdqyL zZaHPsWz*WG)y%Z1p3K=*9L+4Z)~skMt(nrwQmZ|fHS@>1=Xt*Ko%efw@9+J+m+yJb zw%C{my!AqB6bglpiez(<9WX6R3*@cTYF{CnnUWh3f@(ClJw*)jbQXt&LjAqcW?Fzn zY%6(Wk`jfoy=q!!*Wu$L6bcg{;U%gPIcpd~SV|O#;PntOS1L!)C{#dDu3R9@gjB$K zC|x3B5`JsFL;xfrCLw{!A#>!RkXRC_RY39D7@km@DWr=CL4kPdfLsQ`Aca%{AXl0t zQ!;Xygc)K6@@+aM5r7$pDw9cInIZy-oLC?fRzLuiNc9$yeL%pUP6VkGItZ==e8`|L z2_%zz=-wd6p!hRDKVbGDAmIQYKq1<|;IhMK!yy)vAXcg53=&DB(GWGhL|Bnd0_k)* ziR?r2@$p6w-pV|gN|5Uu9Cnqz(gxp535y70;2d+DpL6uZkckn zUP$FgxdJ%}B$7?3eFkzk{~s!qenBf$T!CjX)7p9X5?6qzW*3Lt$M zXOs2iNB~|kI_SoTe&JDmzk1_NDjuE@XWxeb? zFd!}ejosVpZ!wOG%i;a@)pZ^2;KFNGm#rMCzmqhM-f0`vPNH*CS0uId48}Jk^EV#4 zbEL&{QRVf?1qnmH5V7M&dc}E<)IRO@DYib83svpK=Z4ao-VOPU9)K;Ink#6Qzr1!H zlU;NeF|@(IKJVIaRng^#jlPW6wQo18a?{@8Mx5h$d~?dxt~&WN6?;~G%8GrNj_tfe zzwLB)zDuhk0R7rHd~~=bO?ZZ?11P)r#_qGa@+RzKv~VM4S)r{x&7ow#<*+}l(4xeB zEaFA8=4n*x_YEg?F7!gXNQwT^<~vhUu-Q>so9AuEC-)qlJ-fXy7M{{(t`DiZrPDlb zFhi%R@4BQ|(l!O0U*%E73*|=RS+U=hHVwYW6FZ^XY_r6AUo+gn*V|V2A8w}KT@v8p zkurWi9|#-BKJQuKj4r6%ExDGkg6t7qy{_!hWJ^(f-x13tmiC8N(+gJy?{JNGvcqZ2 zOLv{d-&jpSw<*ErWSHM;J=*r;iMo!{W-ZK*Uy&UOU1RlL6Xu&)dy~;VhfZ>RU-}As z(>BB|69p~uAhtB`EBAPzts@3n_m>2ECE||hQ#W(F8BbaL>q-umKZx#@)W)NQHl68a zxy`}7aX(J4((BE(1=$A9Ta2&3s=@4>i$VTs%+s+hdTNK^C#%QHZkCoZU7y*o?>lPi zA5PWP=NJp-wc~5iiwJ9|p5%$9q91BTT>3hriu=npiH+f^t`5IpsAfC%lf?w;xd4yM9-hOI$o_@jD9zP8yOxP78RZ8ykxTuA6Z`3 zpZKu=fQZDnb|)82D`BnS&*RSfDf(5YomKO0Fs%G00r|oqOZGIUsPl&I%F-1Nddy|9 zna9WSfp7Z5iumR4H~y*Bny=y=ACC#ylAnJU_ZN(zYw+vii;Ny6!C&9nqYu>;>{;+S zKe~(>J^U|@PF8KPNa5vmK!-1Q66<6iL_3gqlc3$^o$oB7Q_Z5BoxuMy2~ zJTJ;Jz8^)oPHd@NtEFky8}B4b>K3O@Sd`-3i+=*qk>i7kb=Ihsz0*@?-6IF4&}`m? z50l-&o)!5_=`^p<59f;AM&q7L%^u5c!L^NTBzY%+kAB{o9QT%5|FJvxoZ4T$W~2wx ze1EwPf3xz5@sO|5E4gCR%Y|ci%f`7y@eQnh3|Bey9@Aa3?snU=TSKiw(QigL^bADr zqh|zXbCb32x%+A9epyueL{wzw4>e`=yMmhCup`U&jwTGfKJmsGvuA#KUz%M@!prGX zr58_m#tu~PxaYiCgGps`OV{qtFJS&{JH{*>#`YZ!Jf)%9YjUj29{|g{XbHV@s literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs new file mode 100644 index 0000000000..ce1cab3b9b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs @@ -0,0 +1,97 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Framework.Testing.Input; +using osu.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneSmoke : OsuSkinnableTestScene + { + [Test] + public void TestSmoking() + { + AddStep("Create smoke", () => + { + SetContents(_ => + { + return new SmokingInputManager + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.95f), + Child = new TestSmokeContainer { RelativeSizeAxes = Axes.Both }, + }; + }); + }); + } + + private const double spin_duration = 5_000; + private const float spin_angle = 4 * MathF.PI; + + private class SmokingInputManager : ManualInputManager + { + private double? startTime; + + public SmokingInputManager() + { + UseParentInput = false; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + MoveMouseTo(ToScreenSpace(DrawSize / 2)); + } + + protected override void Update() + { + base.Update(); + + startTime ??= Time.Current; + + float fraction = (float)((Time.Current - startTime) / spin_duration); + + float angle = fraction * spin_angle; + float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2; + + Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2; + MoveMouseTo(ToScreenSpace(pos)); + } + } + + private class TestSmokeContainer : SmokeContainer + { + private double? startTime; + private bool isPressing; + private bool isFinished; + + protected override void Update() + { + base.Update(); + + startTime ??= Time.Current; + + if (!isPressing && !isFinished && Time.Current > startTime + 0.1) + { + OnPressed(new KeyBindingPressEvent(new InputState(), OsuAction.Smoke)); + isPressing = true; + isFinished = false; + } + + if (isPressing && Time.Current > startTime + spin_duration) + { + OnReleased(new KeyBindingReleaseEvent(new InputState(), OsuAction.Smoke)); + isPressing = false; + isFinished = true; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index f8551e46e9..449dd920d6 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Osu.Skinning; +using osu.Framework.Logging; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.UI { if (e.Action == OsuAction.Smoke) { + Logger.Log("holy moly"); + isSmoking = true; AddInternal(new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Smoke), _ => new DefaultSmoke())); From eaab0deef32f037a2de634c8eb0406d95cc601c8 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Mon, 3 Oct 2022 19:20:51 -0700 Subject: [PATCH 433/583] Fix InspectCode issues --- osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs | 11 ++++------- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 6 +++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs index ce1cab3b9b..82417d09a7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs @@ -19,14 +19,11 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("Create smoke", () => { - SetContents(_ => + SetContents(_ => new SmokingInputManager { - return new SmokingInputManager - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.95f), - Child = new TestSmokeContainer { RelativeSizeAxes = Axes.Both }, - }; + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.95f), + Child = new TestSmokeContainer { RelativeSizeAxes = Axes.Both }, }); }); } diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 449dd920d6..e139e16ff2 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -48,8 +49,11 @@ namespace osu.Game.Rulesets.Osu.UI isSmoking = false; SmokeEnded?.Invoke(Time.Current); - foreach (SkinnableDrawable skinnable in Children) + foreach (Drawable child in Children) + { + var skinnable = (SkinnableDrawable)child; skinnable.LifetimeEnd = skinnable.Drawable.LifetimeEnd; + } } } From 97207c11f55f5a0f464a67c7543c312e2b81ee7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 18:52:21 +0900 Subject: [PATCH 434/583] Add base transformer for osu!mania argon skin --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 +++ .../Argon/ManiaArgonSkinTransformer.cs | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 061dedb07a..c6b20b1baf 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Mania.Edit.Setup; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mania.Skinning.Argon; using osu.Game.Rulesets.Mania.Skinning.Legacy; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -68,6 +69,9 @@ namespace osu.Game.Rulesets.Mania { case LegacySkin: return new ManiaLegacySkinTransformer(skin, beatmap); + + case ArgonSkin: + return new ManiaArgonSkinTransformer(skin); } return null; diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs new file mode 100644 index 0000000000..67dd484923 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -0,0 +1,35 @@ +// 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.Graphics; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ManiaArgonSkinTransformer : SkinTransformer + { + public ManiaArgonSkinTransformer(ISkin skin) + : base(skin) + { + } + + public override Drawable? GetDrawableComponent(ISkinComponent component) + { + switch (component) + { + case ManiaSkinComponent maniaComponent: + switch (maniaComponent.Component) + { + case ManiaSkinComponents.KeyArea: + return new ArgonKeyArea(); + + // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. + } + + break; + } + + return base.GetDrawableComponent(component); + } + } +} From 326a3e65834a3950ccd49c361e17cac6a88f5ed5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 18:52:35 +0900 Subject: [PATCH 435/583] Add TODO in osu! argon transformer regarding missing components --- osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 7bc6723afb..3794350f6a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.CursorTrail: return new ArgonCursorTrail(); + + // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. } break; From 83e7cc1e09b45adcd1454f63d74b0dd9c5e5c6d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 18:52:43 +0900 Subject: [PATCH 436/583] Add argon key area --- .../Skinning/Argon/ArgonKeyArea.cs | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs new file mode 100644 index 0000000000..055c21390c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -0,0 +1,167 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ArgonKeyArea : CompositeDrawable, IKeyBindingHandler + { + private const float key_icon_size = 10; + + private readonly IBindable direction = new Bindable(); + + private Container directionContainer = null!; + private Container keyIcon = null!; + private Drawable gradient = null!; + + [Resolved] + private Column column { get; set; } = null!; + + public ArgonKeyArea() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + const float icon_circle_size = 8; + const float icon_spacing = 8; + const float icon_vertical_offset = 20; + + InternalChild = directionContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = Stage.HIT_TARGET_POSITION, + Children = new[] + { + gradient = new Box + { + Name = "Key gradient", + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f + }, + keyIcon = new Container + { + Name = "Icons", + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Children = new[] + { + new Circle + { + Y = icon_vertical_offset, + Size = new Vector2(icon_circle_size), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Blending = BlendingParameters.Additive, + Colour = column.AccentColour, + Masking = true, + }, + new Circle + { + X = -icon_spacing, + Y = icon_vertical_offset + icon_spacing * 1.2f, + Size = new Vector2(icon_circle_size), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Blending = BlendingParameters.Additive, + Colour = column.AccentColour, + Masking = true, + }, + new Circle + { + X = icon_spacing, + Y = icon_vertical_offset + icon_spacing * 1.2f, + Size = new Vector2(icon_circle_size), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Blending = BlendingParameters.Additive, + Colour = column.AccentColour, + Masking = true, + } + } + }, + } + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + switch (direction.NewValue) + { + case ScrollingDirection.Up: + directionContainer.Scale = new Vector2(1, -1); + directionContainer.Anchor = Anchor.TopLeft; + directionContainer.Origin = Anchor.BottomLeft; + gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)); + break; + + case ScrollingDirection.Down: + directionContainer.Scale = new Vector2(1, 1); + directionContainer.Anchor = Anchor.BottomLeft; + directionContainer.Origin = Anchor.BottomLeft; + gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black); + break; + } + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == column.Action.Value) + { + foreach (var circle in keyIcon.Children) + { + circle.ScaleTo(1.1f, 50, Easing.OutQuint); + + circle.FadeColour(Color4.White, 50, Easing.OutQuint); + circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Color4.White.Opacity(0.05f), + Radius = 10, + }, 50, Easing.OutQuint); + } + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == column.Action.Value) + { + foreach (var circle in keyIcon.Children) + { + circle.ScaleTo(1f, 125, Easing.OutQuint); + + circle.FadeColour(column.AccentColour, 200, Easing.OutQuint); + circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Color4.White.Opacity(0), + Radius = 10, + }, 200, Easing.OutQuint); + } + } + } + } +} From 4718f4ac249017a695c5421fbc9e3c3ac1897228 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 19:13:37 +0900 Subject: [PATCH 437/583] Add second icon and improve glow effect --- .../Skinning/Argon/ArgonKeyArea.cs | 66 ++++++++++++------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 055c21390c..1bda832b76 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -1,11 +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.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.Effects; using osu.Framework.Graphics.Shapes; @@ -20,13 +20,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public class ArgonKeyArea : CompositeDrawable, IKeyBindingHandler { - private const float key_icon_size = 10; - private readonly IBindable direction = new Bindable(); private Container directionContainer = null!; - private Container keyIcon = null!; - private Drawable gradient = null!; + private Container keyIcon = null!; + private Drawable background = null!; [Resolved] private Column column { get; set; } = null!; @@ -40,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private void load(IScrollingInfo scrollingInfo) { const float icon_circle_size = 8; - const float icon_spacing = 8; - const float icon_vertical_offset = 20; + const float icon_spacing = 7; + const float icon_vertical_offset = -30; InternalChild = directionContainer = new Container { @@ -49,13 +47,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Height = Stage.HIT_TARGET_POSITION, Children = new[] { - gradient = new Box + background = new Box { Name = "Key gradient", RelativeSizeAxes = Axes.Both, - Alpha = 0.5f + Colour = column.AccentColour.Darken(0.6f), }, - keyIcon = new Container + keyIcon = new Container { Name = "Icons", RelativeSizeAxes = Axes.Both, @@ -67,8 +65,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { Y = icon_vertical_offset, Size = new Vector2(icon_circle_size), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, Blending = BlendingParameters.Additive, Colour = column.AccentColour, Masking = true, @@ -78,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon X = -icon_spacing, Y = icon_vertical_offset + icon_spacing * 1.2f, Size = new Vector2(icon_circle_size), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, Blending = BlendingParameters.Additive, Colour = column.AccentColour, Masking = true, @@ -89,11 +87,30 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon X = icon_spacing, Y = icon_vertical_offset + icon_spacing * 1.2f, Size = new Vector2(icon_circle_size), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, Blending = BlendingParameters.Additive, Colour = column.AccentColour, Masking = true, + }, + new CircularContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Y = -icon_vertical_offset, + Size = new Vector2(22, 14), + Masking = true, + BorderThickness = 4, + BorderColour = Color4.White, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, } } }, @@ -112,14 +129,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon directionContainer.Scale = new Vector2(1, -1); directionContainer.Anchor = Anchor.TopLeft; directionContainer.Origin = Anchor.BottomLeft; - gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)); break; case ScrollingDirection.Down: directionContainer.Scale = new Vector2(1, 1); directionContainer.Anchor = Anchor.BottomLeft; directionContainer.Origin = Anchor.BottomLeft; - gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black); break; } } @@ -128,7 +143,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (e.Action == column.Action.Value) { - foreach (var circle in keyIcon.Children) + foreach (var circle in keyIcon.Children.OfType()) { circle.ScaleTo(1.1f, 50, Easing.OutQuint); @@ -136,8 +151,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(0.05f), - Radius = 10, + Colour = Color4.White.Opacity(circle is Circle ? 0.05f : 0.2f), + Radius = 40, }, 50, Easing.OutQuint); } } @@ -149,16 +164,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (e.Action == column.Action.Value) { - foreach (var circle in keyIcon.Children) + foreach (var circle in keyIcon.Children.OfType()) { circle.ScaleTo(1f, 125, Easing.OutQuint); - circle.FadeColour(column.AccentColour, 200, Easing.OutQuint); + // TODO: temp lol + if (circle is Circle) + circle.FadeColour(column.AccentColour, 200, Easing.OutQuint); + circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = Color4.White.Opacity(0), - Radius = 10, + Radius = 30, }, 200, Easing.OutQuint); } } From d32eb6456112ef071fc5ccfc98ea74ba2f4e8007 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 19:32:58 +0900 Subject: [PATCH 438/583] Adjust colour application to stay around a bit longer --- .../Skinning/Argon/ArgonKeyArea.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 1bda832b76..63e00041b8 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -143,9 +143,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (e.Action == column.Action.Value) { + background + .FadeColour(column.AccentColour.Lighten(0.3f), 50, Easing.OutQuint).Then() + .FadeColour(column.AccentColour, 100, Easing.OutQuint); + foreach (var circle in keyIcon.Children.OfType()) { - circle.ScaleTo(1.1f, 50, Easing.OutQuint); + circle.ScaleTo(0.9f, 50, Easing.OutQuint); circle.FadeColour(Color4.White, 50, Easing.OutQuint); circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters @@ -164,20 +168,22 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (e.Action == column.Action.Value) { + background.FadeColour(column.AccentColour.Darken(0.6f), 800, Easing.OutQuint); + foreach (var circle in keyIcon.Children.OfType()) { - circle.ScaleTo(1f, 125, Easing.OutQuint); + circle.ScaleTo(1f, 200, Easing.OutQuint); // TODO: temp lol if (circle is Circle) - circle.FadeColour(column.AccentColour, 200, Easing.OutQuint); + circle.FadeColour(column.AccentColour, 800, Easing.OutQuint); circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = Color4.White.Opacity(0), Radius = 30, - }, 200, Easing.OutQuint); + }, 800, Easing.OutQuint); } } } From 36e2f5c512783d9f542f23fb4e7a4b69efceaa59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Sep 2022 19:48:27 +0900 Subject: [PATCH 439/583] Add argon hit target pieces --- .../Skinning/Argon/ArgonHitTarget.cs | 33 +++++++++++++++++++ .../Skinning/Argon/ArgonKeyArea.cs | 27 +++++++++++++-- .../Argon/ManiaArgonSkinTransformer.cs | 3 ++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs new file mode 100644 index 0000000000..6518607c9d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs @@ -0,0 +1,33 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Skinning.Default; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ArgonHitTarget : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = DefaultNotePiece.NOTE_HEIGHT; + + InternalChildren = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.3f, + Blending = BlendingParameters.Additive, + Colour = Color4.White + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 63e00041b8..f2acc61416 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private Container keyIcon = null!; private Drawable background = null!; + private Circle hitTargetLine = null!; + [Resolved] private Column column { get; set; } = null!; @@ -61,6 +64,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Origin = Anchor.TopCentre, Children = new[] { + hitTargetLine = new Circle() + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = OsuColour.Gray(196 / 255f), + Height = 4, + Masking = true, + }, new Circle { Y = icon_vertical_offset, @@ -149,13 +161,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon foreach (var circle in keyIcon.Children.OfType()) { - circle.ScaleTo(0.9f, 50, Easing.OutQuint); + if (circle != hitTargetLine) + circle.ScaleTo(0.9f, 50, Easing.OutQuint); circle.FadeColour(Color4.White, 50, Easing.OutQuint); + + // TODO: VERY TMPOERAOIRY. + float f = circle == hitTargetLine ? 0.2f : (circle is Circle ? 0.05f : 0.2f); + circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(circle is Circle ? 0.05f : 0.2f), + Colour = Color4.White.Opacity(f), Radius = 40, }, 50, Easing.OutQuint); } @@ -175,7 +192,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon circle.ScaleTo(1f, 200, Easing.OutQuint); // TODO: temp lol - if (circle is Circle) + if (circle == hitTargetLine) + { + circle.FadeColour(OsuColour.Gray(196 / 255f), 800, Easing.OutQuint); + } + else if (circle is Circle) circle.FadeColour(column.AccentColour, 800, Easing.OutQuint); circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 67dd484923..dab71a65c3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case ManiaSkinComponent maniaComponent: switch (maniaComponent.Component) { + case ManiaSkinComponents.HitTarget: + return new ArgonHitTarget(); + case ManiaSkinComponents.KeyArea: return new ArgonKeyArea(); From 21620bee1adce55fb12a6b9b65f7f0ad4ed0e675 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 4 Oct 2022 14:58:00 +0900 Subject: [PATCH 440/583] Attempt to fix deadlock in test --- ...stSceneOnlinePlayBeatmapAvailabilityTracker.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 3f20f843a7..e7590df3e0 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; @@ -78,7 +77,7 @@ namespace osu.Game.Tests.Online } }; - beatmaps.AllowImport = new TaskCompletionSource(); + beatmaps.AllowImport.Reset(); testBeatmapFile = TestResources.GetQuickTestBeatmapForImport(); @@ -132,7 +131,7 @@ namespace osu.Game.Tests.Online AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); - AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); + AddStep("allow importing", () => beatmaps.AllowImport.Set()); AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable); @@ -141,7 +140,7 @@ namespace osu.Game.Tests.Online [Test] public void TestTrackerRespectsSoftDeleting() { - AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); + AddStep("allow importing", () => beatmaps.AllowImport.Set()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); @@ -155,7 +154,7 @@ namespace osu.Game.Tests.Online [Test] public void TestTrackerRespectsChecksum() { - AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); + AddStep("allow importing", () => beatmaps.AllowImport.Set()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); @@ -202,7 +201,7 @@ namespace osu.Game.Tests.Online private class TestBeatmapManager : BeatmapManager { - public TaskCompletionSource AllowImport = new TaskCompletionSource(); + public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim(); public Live CurrentImport { get; private set; } @@ -229,7 +228,9 @@ namespace osu.Game.Tests.Online public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) { - testBeatmapManager.AllowImport.Task.WaitSafely(); + if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken)) + throw new TimeoutException("Timeout waiting for import to be allowed."); + return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken)); } } From 5d80950eaf51f01f37471bf59f46f27f67d4f809 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 Oct 2022 14:01:36 +0900 Subject: [PATCH 441/583] Compute lifetime from entry in scrolling container --- .../Scrolling/ScrollingHitObjectContainer.cs | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index d0aca4e7fc..b012abb6b5 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Layout; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -127,6 +127,16 @@ namespace osu.Game.Rulesets.UI.Scrolling private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight; + public override void Add(HitObjectLifetimeEntry entry) + { + // Scroll info is not available until loaded. + // The lifetime of all entries will be updated in the first Update. + if (IsLoaded) + setComputedLifetimeStart(entry); + + base.Add(entry); + } + protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { base.AddDrawable(entry, drawable); @@ -145,7 +155,6 @@ namespace osu.Game.Rulesets.UI.Scrolling private void invalidateHitObject(DrawableHitObject hitObject) { - hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); layoutComputed.Remove(hitObject); } @@ -157,10 +166,8 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Clear(); - // Reset lifetime to the conservative estimation. - // If a drawable becomes alive by this lifetime, its lifetime will be updated to a more precise lifetime in the next update. foreach (var entry in Entries) - entry.SetInitialLifetime(); + setComputedLifetimeStart(entry); scrollingInfo.Algorithm.Reset(); @@ -187,38 +194,46 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) + /// + /// Get a conservative maximum bounding box of a corresponding to . + /// It is used to calculate when the hit object appears. + /// + protected virtual RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100); + + private double computeDisplayStartTime(HitObjectLifetimeEntry entry) { - // Origin position may be relative to the parent size - Debug.Assert(hitObject.Parent != null); + RectangleF boundingBox = GetConservativeBoundingBox(entry); + float startOffset = 0; - float originAdjustment = 0.0f; - - // calculate the dimension of the part of the hitobject that should already be visible - // when the hitobject origin first appears inside the scrolling container switch (direction.Value) { - case ScrollingDirection.Up: - originAdjustment = hitObject.OriginPosition.Y; + case ScrollingDirection.Right: + startOffset = boundingBox.Right; break; case ScrollingDirection.Down: - originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y; + startOffset = boundingBox.Bottom; break; case ScrollingDirection.Left: - originAdjustment = hitObject.OriginPosition.X; + startOffset = -boundingBox.Left; break; - case ScrollingDirection.Right: - originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X; + case ScrollingDirection.Up: + startOffset = -boundingBox.Top; break; } - double computedStartTime = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); + return scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); + } + + private void setComputedLifetimeStart(HitObjectLifetimeEntry entry) + { + double computedStartTime = computeDisplayStartTime(entry); // always load the hitobject before its first judgement offset - return Math.Min(hitObject.HitObject.StartTime - hitObject.MaximumJudgementOffset, computedStartTime); + double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0; + entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime); } private void updateLayoutRecursive(DrawableHitObject hitObject) From 781f5420b0d1aa0a1f359c0f74e59a2787851ce8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 Oct 2022 15:17:11 +0900 Subject: [PATCH 442/583] Add test for scrolling hit object lifetime --- .../Gameplay/TestSceneScrollingHitObjects.cs | 70 +++++++++++++++---- .../UI/Scrolling/ScrollingPlayfield.cs | 4 +- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 8a4818d2f8..98afbe92b1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -11,8 +11,10 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); } - private void addHitObject(double time) + [Test] + public void TestVeryFlowScroll() + { + const double long_time_range = 100000; + var manualClock = new ManualClock(); + + AddStep("set manual clock", () => + { + manualClock.CurrentTime = 0; + scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock)); + + setScrollAlgorithm(ScrollVisualisationMethod.Constant); + scrollContainers.ForEach(c => c.TimeRange = long_time_range); + }); + + AddStep("add hit objects", () => + { + addHitObject(long_time_range); + addHitObject(long_time_range + 100, 250); + }); + + AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2)); + } + + private void addHitObject(double time, float size = 75) { playfields.ForEach(p => { - var hitObject = new TestDrawableHitObject(time); - setAnchor(hitObject, p); + var hitObject = new TestHitObject(size) { StartTime = time }; + var drawable = new TestDrawableHitObject(hitObject); - p.Add(hitObject); + setAnchor(drawable, p); + p.Add(drawable); }); } @@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay } }; } + + protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer(); } private class TestDrawableControlPoint : DrawableHitObject @@ -281,22 +310,39 @@ namespace osu.Game.Tests.Visual.Gameplay } } - private class TestDrawableHitObject : DrawableHitObject + private class TestHitObject : HitObject { - public TestDrawableHitObject(double time) - : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty }) - { - Origin = Anchor.Custom; - OriginPosition = new Vector2(75 / 4.0f); + public readonly float Size; - AutoSizeAxes = Axes.Both; + public TestHitObject(float size) + { + Size = size; + } + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject(TestHitObject hitObject) + : base(hitObject) + { + Origin = Anchor.Centre; + Size = new Vector2(hitObject.Size); AddInternal(new Box { - Size = new Vector2(75), + RelativeSizeAxes = Axes.Both, Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1) }); } } + + private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer + { + protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry) + { + var hitObject = (TestHitObject)entry.HitObject; + return new RectangleF().Inflate(hitObject.Size / 2); + } + } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 078f06b745..34e5b7f9de 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time); - protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(); + protected sealed override HitObjectContainer CreateHitObjectContainer() => CreateScrollingHitObjectContainer(); + + protected virtual ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new ScrollingHitObjectContainer(); } } From 1cccd03480dcf9c54031ac564a0e947ecb76d56e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 Oct 2022 15:03:04 +0900 Subject: [PATCH 443/583] Fix scrolling nested hit object lifetime not set --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index b012abb6b5..37da157cc1 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -251,8 +251,9 @@ namespace osu.Game.Rulesets.UI.Scrolling { updateLayoutRecursive(obj); - // Nested hitobjects don't need to scroll, but they do need accurate positions + // Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime updatePosition(obj, hitObject.HitObject.StartTime); + setComputedLifetimeStart(obj.Entry); } } From 2aa4d21c757c84dd4ddefffd3620b326f7b6b0d8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 Oct 2022 15:08:53 +0900 Subject: [PATCH 444/583] Remove code that is not needed anymore --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs | 4 ---- .../Objects/Drawables/DrawableManiaHitObject.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 6020348938..a607ed572d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } - protected override void UpdateInitialTransforms() - { - } - protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150); } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index bcc10ab7bc..6cd39d835d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected readonly IBindable Direction = new Bindable(); - // Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms. - // Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1. - protected override double InitialLifetimeOffset => 30000; - [Resolved(canBeNull: true)] private ManiaPlayfield playfield { get; set; } From 1ffa0afafc2663b780f93a48cca3d048d601daf0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 16:05:36 +0900 Subject: [PATCH 445/583] Tweak visuals and fix up code quality --- .../Skinning/Argon/ArgonKeyArea.cs | 189 ++++++++++-------- 1 file changed, 107 insertions(+), 82 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index f2acc61416..0153cdbd68 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -24,11 +23,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private readonly IBindable direction = new Bindable(); private Container directionContainer = null!; - private Container keyIcon = null!; private Drawable background = null!; private Circle hitTargetLine = null!; + private Container bottomIcon = null!; + private CircularContainer topIcon = null!; + [Resolved] private Column column { get; set; } = null!; @@ -56,56 +57,58 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon RelativeSizeAxes = Axes.Both, Colour = column.AccentColour.Darken(0.6f), }, - keyIcon = new Container + hitTargetLine = new Circle + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = OsuColour.Gray(196 / 255f), + Height = 4, + Masking = true, + }, + new Container { Name = "Icons", RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Children = new[] + Children = new Drawable[] { - hitTargetLine = new Circle() + bottomIcon = new Container { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, - Colour = OsuColour.Gray(196 / 255f), - Height = 4, - Masking = true, - }, - new Circle - { + Blending = BlendingParameters.Additive, + Colour = column.AccentColour, Y = icon_vertical_offset, - Size = new Vector2(icon_circle_size), - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Blending = BlendingParameters.Additive, - Colour = column.AccentColour, - Masking = true, + Children = new[] + { + new Circle + { + Size = new Vector2(icon_circle_size), + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + }, + new Circle + { + X = -icon_spacing, + Y = icon_spacing * 1.2f, + Size = new Vector2(icon_circle_size), + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + }, + new Circle + { + X = icon_spacing, + Y = icon_spacing * 1.2f, + Size = new Vector2(icon_circle_size), + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + }, + } }, - new Circle - { - X = -icon_spacing, - Y = icon_vertical_offset + icon_spacing * 1.2f, - Size = new Vector2(icon_circle_size), - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Blending = BlendingParameters.Additive, - Colour = column.AccentColour, - Masking = true, - }, - new Circle - { - X = icon_spacing, - Y = icon_vertical_offset + icon_spacing * 1.2f, - Size = new Vector2(icon_circle_size), - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Blending = BlendingParameters.Additive, - Colour = column.AccentColour, - Masking = true, - }, - new CircularContainer + topIcon = new CircularContainer { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, @@ -153,29 +156,41 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == column.Action.Value) + if (e.Action != column.Action.Value) return false; + + const double lighting_fade_in_duration = 50; + Color4 lightingColour = column.AccentColour.Lighten(0.9f); + + background + .FadeColour(column.AccentColour.Lighten(0.4f), 40).Then() + .FadeColour(column.AccentColour, 150, Easing.OutQuint); + + hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); + hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { - background - .FadeColour(column.AccentColour.Lighten(0.3f), 50, Easing.OutQuint).Then() - .FadeColour(column.AccentColour, 100, Easing.OutQuint); + Type = EdgeEffectType.Glow, + Colour = lightingColour.Opacity(0.7f), + Radius = 20, + }, lighting_fade_in_duration, Easing.OutQuint); - foreach (var circle in keyIcon.Children.OfType()) + topIcon.ScaleTo(0.9f, lighting_fade_in_duration, Easing.OutQuint); + topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = lightingColour.Opacity(0.1f), + Radius = 20, + }, lighting_fade_in_duration, Easing.OutQuint); + + bottomIcon.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); + + foreach (var circle in bottomIcon) + { + circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { - if (circle != hitTargetLine) - circle.ScaleTo(0.9f, 50, Easing.OutQuint); - - circle.FadeColour(Color4.White, 50, Easing.OutQuint); - - // TODO: VERY TMPOERAOIRY. - float f = circle == hitTargetLine ? 0.2f : (circle is Circle ? 0.05f : 0.2f); - - circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(f), - Radius = 40, - }, 50, Easing.OutQuint); - } + Type = EdgeEffectType.Glow, + Colour = lightingColour.Opacity(0.3f), + Radius = 60, + }, lighting_fade_in_duration, Easing.OutQuint); } return false; @@ -183,29 +198,39 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public void OnReleased(KeyBindingReleaseEvent e) { - if (e.Action == column.Action.Value) + if (e.Action != column.Action.Value) return; + + const double lighting_fade_out_duration = 300; + Color4 lightingColour = column.AccentColour.Lighten(0.9f).Opacity(0); + + background.FadeColour(column.AccentColour.Darken(0.6f), lighting_fade_out_duration, Easing.OutQuint); + + topIcon.ScaleTo(1f, 200, Easing.OutQuint); + topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { - background.FadeColour(column.AccentColour.Darken(0.6f), 800, Easing.OutQuint); + Type = EdgeEffectType.Glow, + Colour = lightingColour, + Radius = 20, + }, lighting_fade_out_duration, Easing.OutQuint); - foreach (var circle in keyIcon.Children.OfType()) + hitTargetLine.FadeColour(OsuColour.Gray(196 / 255f), lighting_fade_out_duration, Easing.OutQuint); + hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = lightingColour, + Radius = 30, + }, lighting_fade_out_duration, Easing.OutQuint); + + bottomIcon.FadeColour(column.AccentColour, lighting_fade_out_duration, Easing.OutQuint); + + foreach (var circle in bottomIcon) + { + circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { - circle.ScaleTo(1f, 200, Easing.OutQuint); - - // TODO: temp lol - if (circle == hitTargetLine) - { - circle.FadeColour(OsuColour.Gray(196 / 255f), 800, Easing.OutQuint); - } - else if (circle is Circle) - circle.FadeColour(column.AccentColour, 800, Easing.OutQuint); - - circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(0), - Radius = 30, - }, 800, Easing.OutQuint); - } + Type = EdgeEffectType.Glow, + Colour = lightingColour, + Radius = 30, + }, lighting_fade_out_duration, Easing.OutQuint); } } } From 15d159a97e05b80445bc2e50161b448d827f96d4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 Oct 2022 16:10:18 +0900 Subject: [PATCH 446/583] Fix tests --- .../Objects/Drawables/DrawableHoldNoteHead.cs | 8 -------- .../Visual/Gameplay/TestSceneScrollingHitObjects.cs | 6 ++++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index d374e935ec..66cc93b033 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -30,14 +30,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public bool UpdateResult() => base.UpdateResult(true); - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - // This hitobject should never expire, so this is just a safe maximum. - LifetimeEnd = LifetimeStart + 30000; - } - protected override void UpdateHitStateTransforms(ArmedState state) { // suppress the base call explicitly. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 98afbe92b1..156a1ee34a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -340,8 +340,10 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry) { - var hitObject = (TestHitObject)entry.HitObject; - return new RectangleF().Inflate(hitObject.Size / 2); + if (entry.HitObject is TestHitObject testObject) + return new RectangleF().Inflate(testObject.Size / 2); + + return base.GetConservativeBoundingBox(entry); } } } From 345430ab39d45f7d47231b080472bb660c364e0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 16:15:24 +0900 Subject: [PATCH 447/583] Fix argon hit target area not being aligned correctly --- .../Skinning/Argon/ArgonHitTarget.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs index 6518607c9d..4750118583 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs @@ -2,18 +2,22 @@ // 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; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Mania.Skinning.Default; +using osu.Game.Rulesets.UI.Scrolling; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { public class ArgonHitTarget : CompositeDrawable { + private readonly IBindable direction = new Bindable(); + [BackgroundDependencyLoader] - private void load() + private void load(IScrollingInfo scrollingInfo) { RelativeSizeAxes = Axes.X; Height = DefaultNotePiece.NOTE_HEIGHT; @@ -28,6 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Colour = Color4.White }, }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + Anchor = Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } } } From b0a948df459440dc7aefcf661e446a3eefe732bd Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 4 Oct 2022 00:17:00 -0700 Subject: [PATCH 448/583] Move `LegacySmoke` animation implementation to `Smoke` --- WeatherConfig.dat | 3 + .../Skinning/Default/DefaultSmoke.cs | 52 ------- .../Skinning/Legacy/LegacySmoke.cs | 144 +----------------- .../Legacy/OsuLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Smoke.cs | 127 +++++++++++++-- 5 files changed, 121 insertions(+), 207 deletions(-) create mode 100644 WeatherConfig.dat diff --git a/WeatherConfig.dat b/WeatherConfig.dat new file mode 100644 index 0000000000..88a312bf91 --- /dev/null +++ b/WeatherConfig.dat @@ -0,0 +1,3 @@ +5363943 +La Jolla,US +F diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index 65ae490882..c3bc04a858 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -1,65 +1,13 @@ // 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 osu.Framework.Graphics; -using osuTK; -using osuTK.Graphics; - namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultSmoke : Smoke { - private const double fade_out_delay = 8000; - private const double fade_out_speed = 3; - private const double fade_out_duration = 50; - private const float alpha = 0.5f; - - public override double LifetimeEnd => SmokeEndTime + fade_out_delay + fade_out_duration + (SmokeEndTime - SmokeStartTime) / fade_out_speed; - public DefaultSmoke() { Radius = 2; } - - protected override DrawNode CreateDrawNode() => new DefaultSmokeDrawNode(this); - - private class DefaultSmokeDrawNode : SmokeDrawNode - { - private double fadeOutTime; - - public DefaultSmokeDrawNode(ITexturedShaderDrawable source) - : base(source) - { - } - - public override void ApplyState() - { - base.ApplyState(); - - fadeOutTime = SmokeStartTime + fade_out_speed * (CurrentTime - (SmokeEndTime + fade_out_delay)); - } - - protected override Color4 PointColour(SmokePoint point) - { - var color = Color4.White; - color.A = alpha; - - double timeDoingFadeOut = fadeOutTime - point.Time; - - if (timeDoingFadeOut > 0) - { - float fraction = Math.Clamp((float)(1 - (timeDoingFadeOut / fade_out_duration)), 0, 1); - fraction = MathF.Pow(fraction, 5); - color.A *= fraction; - } - - return color; - } - - protected override float PointScale(SmokePoint point) => 1f; - - protected override Vector2 PointDirection(SmokePoint point) => point.Direction; - } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs index b8d70c1c6d..89e90cd4c8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs @@ -1,157 +1,19 @@ // 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 osu.Framework.Graphics; -using osu.Framework.Utils; +using osu.Framework.Allocation; using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacySmoke : Smoke { - // fade values - private const double initial_fade_out_duration = 4000; - - private const double re_fade_in_speed = 3; - private const double re_fade_in_duration = 50; - - private const double final_fade_out_speed = 2; - private const double final_fade_out_duration = 8000; - - private const float initial_alpha = 0.6f; - private const float re_fade_in_alpha = 1f; - - // scale values - private const double scale_duration = 1200; - - private const float initial_scale = 0.65f; - private const float final_scale = 1f; - - // rotation values - private const double rotation_duration = 500; - - private const float max_rotation = 0.25f; - - protected int RotationSeed { get; set; } = RNG.Next(); - - public override double LifetimeEnd - { - get - { - double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); - return SmokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; - } - } - - private readonly ISkin skin; - - public LegacySmoke(ISkin skin) - { - this.skin = skin; - } - - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(ISkinSource skin) { base.LoadComplete(); Texture = skin.GetTexture("cursor-smoke"); } - - protected override DrawNode CreateDrawNode() => new LegacySmokeDrawNode(this); - - protected class LegacySmokeDrawNode : SmokeDrawNode - { - protected new LegacySmoke Source => (LegacySmoke)base.Source; - - private double initialFadeOutDurationTrunc; - private double firstVisiblePointTime; - - private double initialFadeOutTime; - private double reFadeInTime; - private double finalFadeOutTime; - - private int rotationSeed; - private Random rotationRNG = new Random(); - - public LegacySmokeDrawNode(ITexturedShaderDrawable source) - : base(source) - { - } - - public override void ApplyState() - { - base.ApplyState(); - - rotationSeed = Source.RotationSeed; - rotationRNG = new Random(rotationSeed); - - initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); - firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc; - - initialFadeOutTime = CurrentTime; - reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed); - finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed); - } - - protected override Color4 PointColour(SmokePoint point) - { - var color = Color4.White; - - double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time; - - if (timeDoingInitialFadeOut > 0) - { - float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1); - color.A = (1 - fraction) * initial_alpha; - } - - if (color.A > 0) - { - double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed; - double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed; - - if (timeDoingFinalFadeOut > 0) - { - float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1); - fraction = MathF.Pow(fraction, 5); - color.A = (1 - fraction) * re_fade_in_alpha; - } - else if (timeDoingReFadeIn > 0) - { - float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1); - fraction = 1 - MathF.Pow(1 - fraction, 5); - color.A = fraction * (re_fade_in_alpha - color.A) + color.A; - } - } - - return color; - } - - protected override float PointScale(SmokePoint point) - { - double timeDoingScale = CurrentTime - point.Time; - float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1); - fraction = 1 - MathF.Pow(1 - fraction, 5); - return fraction * (final_scale - initial_scale) + initial_scale; - } - - protected override Vector2 PointDirection(SmokePoint point) - { - float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X); - float finalAngle = initialAngle + nextRotation(); - - double timeDoingRotation = CurrentTime - point.Time; - float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1); - fraction = 1 - MathF.Pow(1 - fraction, 5); - float angle = fraction * (finalAngle - initialAngle) + initialAngle; - - return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); - } - - private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1); - } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index a9186f821e..d44d2b031a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case OsuSkinComponents.Smoke: if (GetTexture("cursor-smoke") != null) - return new LegacySmoke(this); + return new LegacySmoke(); return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs index fae875eedb..5d2aa2b1f6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Smoke.cs @@ -25,6 +25,15 @@ namespace osu.Game.Rulesets.Osu.Skinning public IShader? TextureShader { get; private set; } public IShader? RoundedTextureShader { get; private set; } + public override double LifetimeEnd + { + get + { + double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + return SmokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; + } + } + private float? radius; protected float Radius @@ -39,21 +48,45 @@ namespace osu.Game.Rulesets.Osu.Skinning protected double SmokeEndTime { get; private set; } = double.MaxValue; - protected virtual float PointInterval => Radius * 7f / 8; - protected readonly List SmokePoints = new List(); + protected virtual float PointInterval => Radius * 7f / 8; + private float totalDistance; private Vector2? lastPosition; + private SmokeContainer? smokeContainer; private const int max_point_count = 18_000; - [Resolved(CanBeNull = true)] - private SmokeContainer? smokeContainer { get; set; } + // fade anim values + private const double initial_fade_out_duration = 4000; + + private const double re_fade_in_speed = 3; + private const double re_fade_in_duration = 50; + + private const double final_fade_out_speed = 2; + private const double final_fade_out_duration = 8000; + + private const float initial_alpha = 0.6f; + private const float re_fade_in_alpha = 1f; + + private readonly int rotationSeed = RNG.Next(); + + // scale anim values + private const double scale_duration = 1200; + + private const float initial_scale = 0.65f; + private const float final_scale = 1f; + + // rotation anim values + private const double rotation_duration = 500; + + private const float max_rotation = 0.25f; [BackgroundDependencyLoader] - private void load(ShaderManager shaders) + private void load(SmokeContainer container, ShaderManager shaders) { + smokeContainer = container; RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); } @@ -128,8 +161,6 @@ namespace osu.Game.Rulesets.Osu.Skinning onSmokeEnded(time); } - public abstract override double LifetimeEnd { get; } - private void onSmokeEnded(double time) { if (smokeContainer != null) @@ -141,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokeEndTime = time; } - protected abstract override DrawNode CreateDrawNode(); + protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this); protected override void Update() { @@ -181,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - protected abstract class SmokeDrawNode : TexturedShaderDrawNode + protected class SmokeDrawNode : TexturedShaderDrawNode { protected new Smoke Source => (Smoke)base.Source; @@ -195,7 +226,17 @@ namespace osu.Game.Rulesets.Osu.Skinning private Vector2 drawSize; private Texture? texture; - protected SmokeDrawNode(ITexturedShaderDrawable source) + // anim calculation vars (color, scale, direction) + private double initialFadeOutDurationTrunc; + private double firstVisiblePointTime; + + private double initialFadeOutTime; + private double reFadeInTime; + private double finalFadeOutTime; + + private Random rotationRNG = new Random(); + + public SmokeDrawNode(ITexturedShaderDrawable source) : base(source) { } @@ -214,6 +255,15 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokeStartTime = Source.SmokeStartTime; SmokeEndTime = Source.SmokeEndTime; CurrentTime = Source.Clock.CurrentTime; + + rotationRNG = new Random(Source.rotationSeed); + + initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc; + + initialFadeOutTime = CurrentTime; + reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed); + finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed); } public sealed override void Draw(IRenderer renderer) @@ -246,11 +296,62 @@ namespace osu.Game.Rulesets.Osu.Skinning ? ((SRGBColour)DrawColourInfo.Colour).Linear : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear; - protected abstract Color4 PointColour(SmokePoint point); + protected virtual Color4 PointColour(SmokePoint point) + { + var color = Color4.White; - protected abstract float PointScale(SmokePoint point); + double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time; - protected abstract Vector2 PointDirection(SmokePoint point); + if (timeDoingInitialFadeOut > 0) + { + float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1); + color.A = (1 - fraction) * initial_alpha; + } + + if (color.A > 0) + { + double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed; + double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed; + + if (timeDoingFinalFadeOut > 0) + { + float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1); + fraction = MathF.Pow(fraction, 5); + color.A = (1 - fraction) * re_fade_in_alpha; + } + else if (timeDoingReFadeIn > 0) + { + float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1); + fraction = 1 - MathF.Pow(1 - fraction, 5); + color.A = fraction * (re_fade_in_alpha - color.A) + color.A; + } + } + + return color; + } + + protected virtual float PointScale(SmokePoint point) + { + double timeDoingScale = CurrentTime - point.Time; + float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1); + fraction = 1 - MathF.Pow(1 - fraction, 5); + return fraction * (final_scale - initial_scale) + initial_scale; + } + + protected virtual Vector2 PointDirection(SmokePoint point) + { + float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X); + float finalAngle = initialAngle + nextRotation(); + + double timeDoingRotation = CurrentTime - point.Time; + float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1); + fraction = 1 - MathF.Pow(1 - fraction, 5); + float angle = fraction * (finalAngle - initialAngle) + initialAngle; + + return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); + } + + private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1); private void drawPointQuad(SmokePoint point, RectangleF textureRect) { From 395ab5889220b3d705441871904cbbfe5b7ca907 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 16:17:38 +0900 Subject: [PATCH 449/583] Attempt to fix code style infractions --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 3 +-- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index dab71a65c3..ec6aaf2ef7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (component) { case ManiaSkinComponent maniaComponent: + // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. switch (maniaComponent.Component) { case ManiaSkinComponents.HitTarget: @@ -25,8 +26,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case ManiaSkinComponents.KeyArea: return new ArgonKeyArea(); - - // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. } break; diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 3794350f6a..bf507db50c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon return new ArgonJudgementPiece(resultComponent.Component); case OsuSkinComponent osuComponent: + // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. switch (osuComponent.Component) { case OsuSkinComponents.HitCircle: @@ -56,8 +57,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case OsuSkinComponents.CursorTrail: return new ArgonCursorTrail(); - - // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. } break; From 33f33a684571833115a3e0bd581bbc34f1e253d4 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 4 Oct 2022 00:53:03 -0700 Subject: [PATCH 450/583] Remove things that should've been removed before push --- WeatherConfig.dat | 3 --- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 WeatherConfig.dat diff --git a/WeatherConfig.dat b/WeatherConfig.dat deleted file mode 100644 index 88a312bf91..0000000000 --- a/WeatherConfig.dat +++ /dev/null @@ -1,3 +0,0 @@ -5363943 -La Jolla,US -F diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index e139e16ff2..4984dc1ad1 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -31,8 +30,6 @@ namespace osu.Game.Rulesets.Osu.UI { if (e.Action == OsuAction.Smoke) { - Logger.Log("holy moly"); - isSmoking = true; AddInternal(new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Smoke), _ => new DefaultSmoke())); From c2956c6e1e9e64a1154040550e36ef5042f7adbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 17:28:15 +0900 Subject: [PATCH 451/583] Add osu! hit object dim Stable would dim objects when they can't be hit (ie. the "miss" window is not active yet). This was never implemented in lazer, and causes quite large visual differences. No one has mentioned this yet, but it will definitely be one of those missing pieces which makes lazer feel different to stable. --- .../Objects/Drawables/DrawableOsuHitObject.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 6f4ca30bd0..26518e0a27 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -11,7 +11,9 @@ using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -64,6 +66,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.UnbindFrom(HitObject.ScaleBindable); } + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + double missWindow = HitObject.HitWindows.WindowFor(HitResult.Miss); + + // Of note, no one noticed this was missing for years, but it definitely feels like it should still exist. + // For now this is applied across all skins, and matches stable. + // For simplicity, dim colour is applied to the DrawableHitObject itself. + // We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod). + this.FadeColour(new Color4(195, 195, 195, 255)); + using (BeginDelayedSequence(InitialLifetimeOffset - missWindow)) + this.FadeColour(Color4.White, 100); + } + protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; private OsuInputManager osuActionInputManager; From 41082ab9286921be507a3b4a82d0cc95e69b9438 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 4 Oct 2022 18:21:29 +0900 Subject: [PATCH 452/583] Fix misplaced parenthesis --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6359ed3e27..939d3a63ed 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -565,7 +565,7 @@ namespace osu.Game // due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios). PerformFromScreen(screen => { - Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score)"); + Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score"); Ruleset.Value = databasedScore.ScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); From 7293ad751ee92a3d8d3d4f5dd95d6f2260794c69 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 4 Oct 2022 18:59:33 +0900 Subject: [PATCH 453/583] Update packages --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 77c29a5d6e..c33937ad95 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 29e690a024..31b17fb8e1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 83410b08f6..a9a83e2802 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -82,7 +82,7 @@ - + From 44b99444a7720533a3240dc174a3d4165ff893b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 13:42:04 +0900 Subject: [PATCH 454/583] Hide approach circles immediate on successful hit --- .../Objects/Drawables/DrawableHitCircle.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index c5992b359d..23db29b9a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -204,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // todo: temporary / arbitrary, used for lifetime optimisation. this.Delay(800).FadeOut(); - // in the case of an early state change, the fade should be expedited to the current point in time. - if (HitStateUpdateTime < HitObject.StartTime) - ApproachCircle.FadeOut(50); - switch (state) { + default: + ApproachCircle.FadeOut(); + break; + case ArmedState.Idle: HitArea.HitAction = null; break; From e06ece7531c11c4f9a8717b1bb5f70338a27fbc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 14:15:54 +0900 Subject: [PATCH 455/583] Update framework --- osu.Android.props | 2 +- osu.Desktop/OsuGameDesktop.cs | 7 ++++--- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c33937ad95..6ad810483b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index d9ad95f96a..3ee1b3da30 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -137,12 +137,13 @@ namespace osu.Desktop { base.SetHost(host); - var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); - var desktopWindow = (SDL2DesktopWindow)host.Window; + var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); + if (iconStream != null) + desktopWindow.SetIconFromStream(iconStream); + desktopWindow.CursorState |= CursorState.Hidden; - desktopWindow.SetIconFromStream(iconStream); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 31b17fb8e1..df7bfab17a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index a9a83e2802..9e2568bf7e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -82,7 +82,7 @@ - + From 276021dd85f2abe7774f5a91e474e0f7307b55fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 14:20:01 +0900 Subject: [PATCH 456/583] Remove now unnecessary `ScheduleAfterChildren` --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 548dd2a5bb..2eec8253b3 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -335,8 +335,7 @@ namespace osu.Game.Screens.Play.HUD private float? scoreComponentsTargetWidth; - // Schedule required to get correct DrawWidth from text after updates. - private void updateDetailsWidth() => SchedulerAfterChildren.AddOnce(() => + private void updateDetailsWidth() { const float score_components_min_width = 88f; @@ -349,7 +348,7 @@ namespace osu.Game.Screens.Play.HUD scoreComponentsTargetWidth = newWidth; scoreComponents.ResizeWidthTo(newWidth, panel_transition_duration, Easing.OutQuint); - }); + } private void updateState() { From 56d424003d240863894829e9241ad1b9b18a290a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 14:25:04 +0900 Subject: [PATCH 457/583] Fix sliders not dimming correctly due to modified miss window --- .../Objects/Drawables/DrawableOsuHitObject.cs | 6 ++---- osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs | 9 ++++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 26518e0a27..2935791cde 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -70,14 +70,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - double missWindow = HitObject.HitWindows.WindowFor(HitResult.Miss); - // Of note, no one noticed this was missing for years, but it definitely feels like it should still exist. // For now this is applied across all skins, and matches stable. // For simplicity, dim colour is applied to the DrawableHitObject itself. // We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod). this.FadeColour(new Color4(195, 195, 195, 255)); - using (BeginDelayedSequence(InitialLifetimeOffset - missWindow)) + using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) this.FadeColour(Color4.White, 100); } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index 05fbac625e..6f55e1790f 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -1,20 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuHitWindows : HitWindows { + /// + /// osu! ruleset has a fixed miss window regardless of difficulty settings. + /// + public const double MISS_WINDOW = 400; + private static readonly DifficultyRange[] osu_ranges = { new DifficultyRange(HitResult.Great, 80, 50, 20), new DifficultyRange(HitResult.Ok, 140, 100, 60), new DifficultyRange(HitResult.Meh, 200, 150, 100), - new DifficultyRange(HitResult.Miss, 400, 400, 400), + new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW), }; public override bool IsHitResultAllowed(HitResult result) From 74db42394a47e11c258abc8417d6076c2f850a66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 14:31:12 +0900 Subject: [PATCH 458/583] Silence unobserved exceptions in `BeginPlayingInternal` Closes #20526. --- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index a012bf49b6..48d5c0bea9 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -1,9 +1,9 @@ // 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.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -58,7 +58,7 @@ namespace osu.Game.Online.Spectator { await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); } - catch (HubException exception) + catch (Exception exception) { if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) { From 52002d91dd578efcd216930adab12546ab0c13d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 17:48:56 +0900 Subject: [PATCH 459/583] Only apply dim at top level objects --- .../Objects/Drawables/DrawableOsuHitObject.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 2935791cde..d9d0d28477 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -70,13 +70,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - // Of note, no one noticed this was missing for years, but it definitely feels like it should still exist. - // For now this is applied across all skins, and matches stable. - // For simplicity, dim colour is applied to the DrawableHitObject itself. - // We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod). - this.FadeColour(new Color4(195, 195, 195, 255)); - using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) - this.FadeColour(Color4.White, 100); + // Dim should only be applied at a top level, as it will be implicitly applied to nested objects. + if (ParentHitObject == null) + { + // Of note, no one noticed this was missing for years, but it definitely feels like it should still exist. + // For now this is applied across all skins, and matches stable. + // For simplicity, dim colour is applied to the DrawableHitObject itself. + // We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod). + this.FadeColour(new Color4(195, 195, 195, 255)); + using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) + this.FadeColour(Color4.White, 100); + } } protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; From 04abb2ce8f1723fbf342d7cb215eea0d20d221bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:26:17 +0900 Subject: [PATCH 460/583] Update default cursor smoke implementation to use a texture --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs index c3bc04a858..1b7a39aa30 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs @@ -1,13 +1,19 @@ // 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.Graphics.Textures; + namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultSmoke : Smoke { - public DefaultSmoke() + [BackgroundDependencyLoader] + private void load(TextureStore textures) { - Radius = 2; + // ISkinSource doesn't currently fallback to global textures. + // We might want to change this in the future if the intention is to allow the user to skin this as per legacy skins. + Texture = textures.Get("Gameplay/osu/cursor-smoke"); } } } From 1e5ff2679b3ba6b00b6e858561f5707e85cdd56d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:27:51 +0900 Subject: [PATCH 461/583] Rename classes to better emphasise that `Smoke` is a single trail segment --- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 2 +- .../Default/{DefaultSmoke.cs => DefaultSmokeSegment.cs} | 2 +- .../Legacy/{LegacySmoke.cs => LegacySmokeSegment.cs} | 2 +- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 4 ++-- osu.Game.Rulesets.Osu/Skinning/{Smoke.cs => SmokeSegment.cs} | 4 ++-- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 5 ++++- 6 files changed, 11 insertions(+), 8 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/Default/{DefaultSmoke.cs => DefaultSmokeSegment.cs} (92%) rename osu.Game.Rulesets.Osu/Skinning/Legacy/{LegacySmoke.cs => LegacySmokeSegment.cs} (90%) rename osu.Game.Rulesets.Osu/Skinning/{Smoke.cs => SmokeSegment.cs} (99%) diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 11daa26072..3ee30ff7dc 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, - Smoke, + SmokeTrail, ApproachCircle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs similarity index 92% rename from osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs rename to osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs index 1b7a39aa30..27a2dc3960 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class DefaultSmoke : Smoke + public class DefaultSmokeSegment : SmokeSegment { [BackgroundDependencyLoader] private void load(TextureStore textures) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs similarity index 90% rename from osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs rename to osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs index 89e90cd4c8..c9c7e86e86 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs @@ -6,7 +6,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacySmoke : Smoke + public class LegacySmokeSegment : SmokeSegment { [BackgroundDependencyLoader] private void load(ISkinSource skin) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d44d2b031a..4d12b85770 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -106,9 +106,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; - case OsuSkinComponents.Smoke: + case OsuSkinComponents.SmokeTrail: if (GetTexture("cursor-smoke") != null) - return new LegacySmoke(); + return new LegacySmokeSegment(); return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Skinning/Smoke.cs rename to osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 5d2aa2b1f6..948ec972a3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Smoke.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -20,7 +20,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { - public abstract class Smoke : Drawable, ITexturedShaderDrawable + public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable { public IShader? TextureShader { get; private set; } public IShader? RoundedTextureShader { get; private set; } @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected class SmokeDrawNode : TexturedShaderDrawNode { - protected new Smoke Source => (Smoke)base.Source; + protected new SmokeSegment Source => (SmokeSegment)base.Source; protected double SmokeStartTime { get; private set; } protected double SmokeEndTime { get; private set; } diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 4984dc1ad1..d26d45c3d7 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -14,6 +14,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.UI { + /// + /// Manages smoke trails generated from user input. + /// [Cached] public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { @@ -31,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.UI if (e.Action == OsuAction.Smoke) { isSmoking = true; - AddInternal(new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Smoke), _ => new DefaultSmoke())); + AddInternal(new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SmokeTrail), _ => new DefaultSmokeSegment())); return true; } From 6628ab5190409e93dd4fb46f57ac0877c1d27905 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:37:09 +0900 Subject: [PATCH 462/583] Refactor to avoid DI / event flow There's always one active smoke segment and it's the direct child of `SmokeContainer`. This can be simplified as such. --- .../Skinning/SmokeSegment.cs | 36 +++---------------- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 25 ++++++------- 2 files changed, 14 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 948ec972a3..245026dc0b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; -using osu.Game.Rulesets.Osu.UI; using osuTK; using osuTK.Graphics; @@ -54,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private float totalDistance; private Vector2? lastPosition; - private SmokeContainer? smokeContainer; private const int max_point_count = 18_000; @@ -84,9 +82,8 @@ namespace osu.Game.Rulesets.Osu.Skinning private const float max_rotation = 0.25f; [BackgroundDependencyLoader] - private void load(SmokeContainer container, ShaderManager shaders) + private void load(ShaderManager shaders) { - smokeContainer = container; RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); } @@ -100,14 +97,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokeStartTime = Time.Current; totalDistance = PointInterval; - - if (smokeContainer != null) - { - smokeContainer.SmokeMoved += onSmokeMoved; - smokeContainer.SmokeEnded += onSmokeEnded; - - onSmokeMoved(smokeContainer.LastMousePosition, Time.Current); - } } private Vector2 nextPointDirection() @@ -116,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); } - private void onSmokeMoved(Vector2 position, double time) + public void AddPosition(Vector2 position, double time) { lastPosition ??= position; @@ -158,17 +147,11 @@ namespace osu.Game.Rulesets.Osu.Skinning lastPosition = position; if (SmokePoints.Count >= max_point_count) - onSmokeEnded(time); + FinishDrawing(time); } - private void onSmokeEnded(double time) + public void FinishDrawing(double time) { - if (smokeContainer != null) - { - smokeContainer.SmokeMoved -= onSmokeMoved; - smokeContainer.SmokeEnded -= onSmokeEnded; - } - SmokeEndTime = time; } @@ -181,17 +164,6 @@ namespace osu.Game.Rulesets.Osu.Skinning Invalidate(Invalidation.DrawNode); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (smokeContainer != null) - { - smokeContainer.SmokeMoved -= onSmokeMoved; - smokeContainer.SmokeEnded -= onSmokeEnded; - } - } - protected struct SmokePoint { public Vector2 Position; diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index d26d45c3d7..c00493e087 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -1,13 +1,12 @@ // 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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -17,15 +16,11 @@ namespace osu.Game.Rulesets.Osu.UI /// /// Manages smoke trails generated from user input. /// - [Cached] public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { - public event Action? SmokeMoved; - public event Action? SmokeEnded; - public Vector2 LastMousePosition; - private bool isSmoking; + private SkinnableDrawable? currentSegment; public override bool ReceivePositionalInputAt(Vector2 _) => true; @@ -33,21 +28,22 @@ namespace osu.Game.Rulesets.Osu.UI { if (e.Action == OsuAction.Smoke) { - isSmoking = true; - AddInternal(new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SmokeTrail), _ => new DefaultSmokeSegment())); - + AddInternal(currentSegment = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SmokeTrail), _ => new DefaultSmokeSegment())); + addPosition(LastMousePosition, Time.Current); return true; } return false; } + private void addPosition(Vector2 position, double timeCurrent) => (currentSegment?.Drawable as SmokeSegment)?.AddPosition(position, timeCurrent); + public void OnReleased(KeyBindingReleaseEvent e) { if (e.Action == OsuAction.Smoke) { - isSmoking = false; - SmokeEnded?.Invoke(Time.Current); + (currentSegment?.Drawable as SmokeSegment)?.FinishDrawing(Time.Current); + currentSegment = null; foreach (Drawable child in Children) { @@ -59,11 +55,10 @@ namespace osu.Game.Rulesets.Osu.UI protected override bool OnMouseMove(MouseMoveEvent e) { - if (isSmoking) - SmokeMoved?.Invoke(e.MousePosition, Time.Current); + if (currentSegment != null) + addPosition(e.MousePosition, Time.Current); LastMousePosition = e.MousePosition; - return base.OnMouseMove(e); } } From 71edd314b1785106be7b55c3ed638c6571d70521 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:51:02 +0900 Subject: [PATCH 463/583] Simplify `SmokeContainer` lifetime logic --- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 31 +++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index c00493e087..5999abc1bf 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -18,9 +17,9 @@ namespace osu.Game.Rulesets.Osu.UI /// public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { - public Vector2 LastMousePosition; + private SkinnableDrawable? currentSegmentSkinnable; - private SkinnableDrawable? currentSegment; + private Vector2 lastMousePosition; public override bool ReceivePositionalInputAt(Vector2 _) => true; @@ -28,38 +27,38 @@ namespace osu.Game.Rulesets.Osu.UI { if (e.Action == OsuAction.Smoke) { - AddInternal(currentSegment = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SmokeTrail), _ => new DefaultSmokeSegment())); - addPosition(LastMousePosition, Time.Current); + AddInternal(currentSegmentSkinnable = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SmokeTrail), _ => new DefaultSmokeSegment())); + + // Add initial position immediately. + addPosition(); return true; } return false; } - private void addPosition(Vector2 position, double timeCurrent) => (currentSegment?.Drawable as SmokeSegment)?.AddPosition(position, timeCurrent); - public void OnReleased(KeyBindingReleaseEvent e) { if (e.Action == OsuAction.Smoke) { - (currentSegment?.Drawable as SmokeSegment)?.FinishDrawing(Time.Current); - currentSegment = null; - - foreach (Drawable child in Children) + if (currentSegmentSkinnable?.Drawable is SmokeSegment segment) { - var skinnable = (SkinnableDrawable)child; - skinnable.LifetimeEnd = skinnable.Drawable.LifetimeEnd; + segment.FinishDrawing(Time.Current); + + currentSegmentSkinnable.LifetimeEnd = segment.LifetimeEnd; + currentSegmentSkinnable = null; } } } protected override bool OnMouseMove(MouseMoveEvent e) { - if (currentSegment != null) - addPosition(e.MousePosition, Time.Current); + lastMousePosition = e.MousePosition; + addPosition(); - LastMousePosition = e.MousePosition; return base.OnMouseMove(e); } + + private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current); } } From 91d877e8931da7e4d738a57189dbb5a1e4b60902 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:52:01 +0900 Subject: [PATCH 464/583] Set `LifetimeEnd` once rather than computing on every access --- osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 245026dc0b..18e0bc5baa 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -24,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Skinning public IShader? TextureShader { get; private set; } public IShader? RoundedTextureShader { get; private set; } - public override double LifetimeEnd - { - get - { - double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); - return SmokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; - } - } - private float? radius; protected float Radius @@ -153,6 +144,9 @@ namespace osu.Game.Rulesets.Osu.Skinning public void FinishDrawing(double time) { SmokeEndTime = time; + + double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); + LifetimeEnd = SmokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; } protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this); From bd82dfc333cce808d06df5001c9e28ba387c589d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:53:07 +0900 Subject: [PATCH 465/583] Remove custom radius implementation --- osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 18e0bc5baa..09cbc1af1d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -24,13 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public IShader? TextureShader { get; private set; } public IShader? RoundedTextureShader { get; private set; } - private float? radius; - - protected float Radius - { - get => radius ?? Texture?.DisplayWidth * 0.165f ?? 3; - set => radius = value; - } + protected float Radius => Texture?.DisplayWidth * 0.165f ?? 3; protected Texture? Texture { get; set; } From 8f0ef99e10670d92855848f0318ca1b98eee8db1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:54:14 +0900 Subject: [PATCH 466/583] Privatise some fields --- .../Skinning/SmokeSegment.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 09cbc1af1d..99196f6967 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -21,24 +21,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable { - public IShader? TextureShader { get; private set; } - public IShader? RoundedTextureShader { get; private set; } - - protected float Radius => Texture?.DisplayWidth * 0.165f ?? 3; - - protected Texture? Texture { get; set; } - - protected double SmokeStartTime { get; private set; } = double.MinValue; - - protected double SmokeEndTime { get; private set; } = double.MaxValue; - - protected readonly List SmokePoints = new List(); - - protected virtual float PointInterval => Radius * 7f / 8; - - private float totalDistance; - private Vector2? lastPosition; - private const int max_point_count = 18_000; // fade anim values @@ -66,6 +48,24 @@ namespace osu.Game.Rulesets.Osu.Skinning private const float max_rotation = 0.25f; + public IShader? TextureShader { get; private set; } + public IShader? RoundedTextureShader { get; private set; } + + protected Texture? Texture { get; set; } + + private float radius => Texture?.DisplayWidth * 0.165f ?? 3; + + protected readonly List SmokePoints = new List(); + + private float pointInterval => radius * 7f / 8; + + private double smokeStartTime { get; set; } = double.MinValue; + + private double smokeEndTime { get; set; } = double.MaxValue; + + private float totalDistance; + private Vector2? lastPosition; + [BackgroundDependencyLoader] private void load(ShaderManager shaders) { @@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both; - SmokeStartTime = Time.Current; + smokeStartTime = Time.Current; - totalDistance = PointInterval; + totalDistance = pointInterval; } private Vector2 nextPointDirection() @@ -96,15 +96,15 @@ namespace osu.Game.Rulesets.Osu.Skinning float delta = (position - (Vector2)lastPosition).LengthFast; totalDistance += delta; - int count = (int)(totalDistance / PointInterval); + int count = (int)(totalDistance / pointInterval); if (count > 0) { Vector2 increment = position - (Vector2)lastPosition; increment.NormalizeFast(); - Vector2 pointPos = (PointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition; - increment *= PointInterval; + Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition; + increment *= pointInterval; if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time) { @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokePoints.RemoveRange(index, SmokePoints.Count - index); } - totalDistance %= PointInterval; + totalDistance %= pointInterval; for (int i = 0; i < count; i++) { @@ -137,10 +137,10 @@ namespace osu.Game.Rulesets.Osu.Skinning public void FinishDrawing(double time) { - SmokeEndTime = time; + smokeEndTime = time; - double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); - LifetimeEnd = SmokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; + double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, smokeEndTime - smokeStartTime); + LifetimeEnd = smokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed; } protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this); @@ -208,12 +208,12 @@ namespace osu.Game.Rulesets.Osu.Skinning points.Clear(); points.AddRange(Source.SmokePoints); - radius = Source.Radius; + radius = Source.radius; drawSize = Source.DrawSize; texture = Source.Texture; - SmokeStartTime = Source.SmokeStartTime; - SmokeEndTime = Source.SmokeEndTime; + SmokeStartTime = Source.smokeStartTime; + SmokeEndTime = Source.smokeEndTime; CurrentTime = Source.Clock.CurrentTime; rotationRNG = new Random(Source.rotationSeed); From 64858cfb8e2dabab010f9fab666cf1cf5b0ed9f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 18:55:49 +0900 Subject: [PATCH 467/583] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index fd57862c4d..8f83c9730b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9cc4f3fbab..434db87a80 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index cc922d304f..f395eab23c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 234c6ac7998fbc6742503e1a589536255554e56a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 5 Oct 2022 20:21:15 +0900 Subject: [PATCH 468/583] Pin taiko PP calculator accuracy to osu-stable values --- .../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 95a1e8bc66..dc7bad2f75 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countOk; private int countMeh; private int countMiss; + private double accuracy; private double effectiveMissCount; @@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + accuracy = customAccuracy; // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) @@ -87,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - return difficultyValue * Math.Pow(score.Accuracy, 2.0); + return difficultyValue * Math.Pow(accuracy, 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) @@ -95,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0; + double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); accuracyValue *= lengthBonus; @@ -110,5 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; + + private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0; } } From 8d29e9e76bc781d8814e70a7a1f0317cfe7a2920 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 20:23:59 +0900 Subject: [PATCH 469/583] Move selection logic from DragBox to BlueprintContainer --- .../Compose/Components/BlueprintContainer.cs | 49 +++++++------------ .../Edit/Compose/Components/DragBox.cs | 46 +++-------------- .../Timeline/TimelineBlueprintContainer.cs | 12 +++-- .../Components/Timeline/TimelineDragBox.cs | 16 +----- 4 files changed, 34 insertions(+), 89 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8b38d9c612..787959d214 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; @@ -13,11 +12,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -79,7 +76,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddRangeInternal(new[] { - DragBox = CreateDragBox(selectBlueprintsFromDragRectangle), + DragBox = CreateDragBox(), SelectionHandler, SelectionBlueprints = CreateSelectionBlueprintContainer(), SelectionHandler.CreateProxy(), @@ -101,7 +98,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [CanBeNull] protected virtual SelectionBlueprint CreateBlueprintFor(T item) => null; - protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); + protected virtual DragBox CreateDragBox() => new DragBox(); /// /// Whether this component is in a state where items outside a drag selection should be deselected. If false, selection will only be added to. @@ -183,13 +180,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - if (DragBox.HandleDrag(e)) - { - DragBox.Show(); - return true; - } - - return false; + DragBox.HandleDrag(e); + DragBox.Show(); + return true; } protected override void OnDrag(DragEvent e) @@ -198,7 +191,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return; if (DragBox.State == Visibility.Visible) + { DragBox.HandleDrag(e); + UpdateSelectionFromDragBox(); + } moveCurrentSelection(e); } @@ -214,8 +210,7 @@ namespace osu.Game.Screens.Edit.Compose.Components changeHandler?.EndChange(); } - if (DragBox.State == Visibility.Visible) - DragBox.Hide(); + DragBox.Hide(); } /// @@ -380,28 +375,20 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Select all masks in a given rectangle selection area. + /// Select all blueprints in a selection area specified by . /// - /// The rectangle to perform a selection on in screen-space coordinates. - private void selectBlueprintsFromDragRectangle(RectangleF rect) + protected virtual void UpdateSelectionFromDragBox() { + var quad = DragBox.Box.ScreenSpaceDrawQuad; + foreach (var blueprint in SelectionBlueprints) { - // only run when utmost necessary to avoid unnecessary rect computations. - bool isValidForSelection() => blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint); + if (blueprint.IsSelected && !AllowDeselectionDuringDrag) + continue; - switch (blueprint.State) - { - case SelectionState.NotSelected: - if (isValidForSelection()) - blueprint.Select(); - break; - - case SelectionState.Selected: - if (AllowDeselectionDuringDrag && !isValidForSelection()) - blueprint.Deselect(); - break; - } + bool shouldBeSelected = blueprint.IsAlive && blueprint.IsPresent && quad.Contains(blueprint.ScreenSpaceSelectionPoint); + if (blueprint.IsSelected != shouldBeSelected) + blueprint.ToggleSelection(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 838562719d..905d47533a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -8,7 +8,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Layout; @@ -21,18 +20,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class DragBox : CompositeDrawable, IStateful { - protected readonly Action PerformSelection; - - protected Drawable Box; + public Drawable Box { get; private set; } /// /// Creates a new . /// - /// A delegate that performs drag selection. - public DragBox(Action performSelection) + public DragBox() { - PerformSelection = performSelection; - RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; @@ -46,30 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual Drawable CreateBox() => new BoxWithBorders(); - private RectangleF? dragRectangle; - /// /// Handle a forwarded mouse event. /// /// The mouse event. - /// Whether the event should be handled and blocking. - public virtual bool HandleDrag(MouseButtonEvent e) + public virtual void HandleDrag(MouseButtonEvent e) { - var dragPosition = e.ScreenSpaceMousePosition; - var dragStartPosition = e.ScreenSpaceMouseDownPosition; - - var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); - - // We use AABBFloat instead of RectangleF since it handles negative sizes for us - var rec = dragQuad.AABBFloat; - dragRectangle = rec; - - var topLeft = ToLocalSpace(rec.TopLeft); - var bottomRight = ToLocalSpace(rec.BottomRight); - - Box.Position = topLeft; - Box.Size = bottomRight - topLeft; - return true; + Box.Position = Vector2.ComponentMin(e.MouseDownPosition, e.MousePosition); + Box.Size = Vector2.ComponentMax(e.MouseDownPosition, e.MousePosition) - Box.Position; } private Visibility state; @@ -87,19 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - protected override void Update() - { - base.Update(); - - if (dragRectangle != null) - PerformSelection?.Invoke(dragRectangle.Value); - } - - public override void Hide() - { - State = Visibility.Hidden; - dragRectangle = null; - } + public override void Hide() => State = Visibility.Hidden; public override void Show() => State = Visibility.Visible; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 590f92d281..da80ed5ad6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -65,7 +64,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - DragBox.Alpha = 0; placement = Beatmap.PlacementObject.GetBoundCopy(); placement.ValueChanged += placementChanged; @@ -93,6 +91,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override bool OnDragStart(DragStartEvent e) + { + if (!base.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition)) + return false; + + return base.OnDragStart(e); + } + protected override void OnDrag(DragEvent e) { handleScrollViaDrag(e); @@ -169,7 +175,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; } - protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); + protected override DragBox CreateDragBox() => new TimelineDragBox(); private void handleScrollViaDrag(DragEvent e) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs index c026c169d6..8b901c8958 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs @@ -6,7 +6,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -24,24 +23,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Timeline timeline { get; set; } - public TimelineDragBox(Action performSelect) - : base(performSelect) - { - } - protected override Drawable CreateBox() => new Box { RelativeSizeAxes = Axes.Y, Alpha = 0.3f }; - public override bool HandleDrag(MouseButtonEvent e) + public override void HandleDrag(MouseButtonEvent e) { - // The dragbox should only be active if the mouseDownPosition.Y is within this drawable's bounds. - float localY = ToLocalSpace(e.ScreenSpaceMouseDownPosition).Y; - if (DrawRectangle.Top > localY || DrawRectangle.Bottom < localY) - return false; - selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; // only calculate end when a transition is not in progress to avoid bouncing. @@ -49,7 +38,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline selectionEnd = e.MousePosition.X / timeline.CurrentZoom; updateDragBoxPosition(); - return true; } private void updateDragBoxPosition() @@ -68,8 +56,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // we don't care about where the hitobjects are vertically. in cases like stacking display, they may be outside the box without this adjustment. boxScreenRect.Y -= boxScreenRect.Height; boxScreenRect.Height *= 2; - - PerformSelection?.Invoke(boxScreenRect); } public override void Hide() From 0ffde02f79e8d67538e941e312abfa1611e5e712 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 20:43:02 +0900 Subject: [PATCH 470/583] Use hit object time for timeline selection --- .../Compose/Components/Timeline/Timeline.cs | 18 ++++++-- .../Timeline/TimelineBlueprintContainer.cs | 24 ++++++++++- .../Components/Timeline/TimelineDragBox.cs | 42 ++++++------------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 721f0c4e3b..a73ada76f5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -304,10 +304,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public double VisibleRange => editorClock.TrackLength / Zoom; - public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) => - new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); + public double TimeAtPosition(float x) + { + return x / Content.DrawWidth * editorClock.TrackLength; + } - private double getTimeFromPosition(Vector2 localPosition) => - (localPosition.X / Content.DrawWidth) * editorClock.TrackLength; + public float PositionAtTime(double time) + { + return (float)(time / editorClock.TrackLength * Content.DrawWidth); + } + + public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) + { + double time = TimeAtPosition(Content.ToLocalSpace(screenSpacePosition).X); + return new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(time)); + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index da80ed5ad6..05897e6d97 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -175,7 +175,29 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; } - protected override DragBox CreateDragBox() => new TimelineDragBox(); + protected sealed override DragBox CreateDragBox() => new TimelineDragBox(); + + protected override void UpdateSelectionFromDragBox() + { + var dragBox = (TimelineDragBox)DragBox; + double minTime = dragBox.MinTime; + double maxTime = dragBox.MaxTime; + Console.WriteLine($"{minTime}, {maxTime}"); + + // TODO: performance + foreach (var hitObject in Beatmap.HitObjects) + { + bool shouldBeSelected = minTime <= hitObject.StartTime && hitObject.StartTime <= maxTime; + bool isSelected = SelectedItems.Contains(hitObject); + if (isSelected != shouldBeSelected) + { + if (!isSelected) + SelectedItems.Add(hitObject); + else + SelectedItems.Remove(hitObject); + } + } + } private void handleScrollViaDrag(DragEvent e) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs index 8b901c8958..4b16848c58 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs @@ -14,11 +14,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineDragBox : DragBox { - // the following values hold the start and end X positions of the drag box in the timeline's local space, - // but with zoom unapplied in order to be able to compensate for positional changes - // while the timeline is being zoomed in/out. - private float? selectionStart; - private float selectionEnd; + public double MinTime => Math.Min(startTime.Value, endTime); + + public double MaxTime => Math.Max(startTime.Value, endTime); + + private double? startTime; + + private double endTime; [Resolved] private Timeline timeline { get; set; } @@ -31,37 +33,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override void HandleDrag(MouseButtonEvent e) { - selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; + startTime ??= timeline.TimeAtPosition(e.MouseDownPosition.X); + endTime = timeline.TimeAtPosition(e.MousePosition.X); - // only calculate end when a transition is not in progress to avoid bouncing. - if (Precision.AlmostEquals(timeline.CurrentZoom, timeline.Zoom)) - selectionEnd = e.MousePosition.X / timeline.CurrentZoom; - - updateDragBoxPosition(); - } - - private void updateDragBoxPosition() - { - if (selectionStart == null) - return; - - float rescaledStart = selectionStart.Value * timeline.CurrentZoom; - float rescaledEnd = selectionEnd * timeline.CurrentZoom; - - Box.X = Math.Min(rescaledStart, rescaledEnd); - Box.Width = Math.Abs(rescaledStart - rescaledEnd); - - var boxScreenRect = Box.ScreenSpaceDrawQuad.AABBFloat; - - // we don't care about where the hitobjects are vertically. in cases like stacking display, they may be outside the box without this adjustment. - boxScreenRect.Y -= boxScreenRect.Height; - boxScreenRect.Height *= 2; + Box.X = timeline.PositionAtTime(MinTime); + Box.Width = timeline.PositionAtTime(MaxTime) - Box.X; } public override void Hide() { base.Hide(); - selectionStart = null; + startTime = null; } } } From 0613388aaa8bb5ae656d1237816e898c5f59b855 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 20:48:35 +0900 Subject: [PATCH 471/583] Make sure all selected items get deleted --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8419d3b380..269c19f846 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -305,7 +305,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void DeleteSelected() { - DeleteItems(selectedBlueprints.Select(b => b.Item)); + DeleteItems(SelectedItems.ToArray()); } #endregion diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 16c0064e80..839535b99f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -352,6 +352,8 @@ namespace osu.Game.Screens.Edit var updates = batchPendingUpdates.ToArray(); batchPendingUpdates.Clear(); + foreach (var h in deletes) SelectedHitObjects.Remove(h); + foreach (var h in deletes) HitObjectRemoved?.Invoke(h); foreach (var h in inserts) HitObjectAdded?.Invoke(h); foreach (var h in updates) HitObjectUpdated?.Invoke(h); From 00b3d97f69abaaed8a29cca1770aca91966e6fc6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 21:26:00 +0900 Subject: [PATCH 472/583] Improve timeline selection performance But selecting a large number of hit objects is still very slow because all DHOs must be added and also `AddBlueprintFor` has quadratic behaviors --- .../Compose/Components/BlueprintContainer.cs | 10 ++++++++-- .../Timeline/TimelineBlueprintContainer.cs | 18 ++++-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 787959d214..da7a8e662b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -58,13 +58,19 @@ namespace osu.Game.Screens.Edit.Compose.Components { case NotifyCollectionChangedAction.Add: foreach (object o in args.NewItems) - SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select(); + { + if (blueprintMap.TryGetValue((T)o, out var blueprint)) + blueprint.Select(); + } break; case NotifyCollectionChangedAction.Remove: foreach (object o in args.OldItems) - SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect(); + { + if (blueprintMap.TryGetValue((T)o, out var blueprint)) + blueprint.Deselect(); + } break; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 05897e6d97..2d6dc797ca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -182,21 +182,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var dragBox = (TimelineDragBox)DragBox; double minTime = dragBox.MinTime; double maxTime = dragBox.MaxTime; - Console.WriteLine($"{minTime}, {maxTime}"); - // TODO: performance - foreach (var hitObject in Beatmap.HitObjects) - { - bool shouldBeSelected = minTime <= hitObject.StartTime && hitObject.StartTime <= maxTime; - bool isSelected = SelectedItems.Contains(hitObject); - if (isSelected != shouldBeSelected) - { - if (!isSelected) - SelectedItems.Add(hitObject); - else - SelectedItems.Remove(hitObject); - } - } + SelectedItems.RemoveAll(hitObject => !shouldBeSelected(hitObject)); + SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).Where(hitObject => shouldBeSelected(hitObject))); + + bool shouldBeSelected(HitObject hitObject) => minTime <= hitObject.StartTime && hitObject.StartTime <= maxTime; } private void handleScrollViaDrag(DragEvent e) From 3108c42ecea1e08ac5a66e98ab7f798c42757fd6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 22:04:43 +0900 Subject: [PATCH 473/583] Fix inspect issues --- .../Timeline/TimelineBlueprintContainer.cs | 3 +-- .../Compose/Components/Timeline/TimelineDragBox.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 2d6dc797ca..04575e55b1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -184,7 +183,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double maxTime = dragBox.MaxTime; SelectedItems.RemoveAll(hitObject => !shouldBeSelected(hitObject)); - SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).Where(hitObject => shouldBeSelected(hitObject))); + SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).Where(shouldBeSelected)); bool shouldBeSelected(HitObject hitObject) => minTime <= hitObject.StartTime && hitObject.StartTime <= maxTime; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs index 4b16848c58..65d9293b7e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs @@ -8,20 +8,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Utils; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineDragBox : DragBox { - public double MinTime => Math.Min(startTime.Value, endTime); + public double MinTime { get; private set; } - public double MaxTime => Math.Max(startTime.Value, endTime); + public double MaxTime { get; private set; } private double? startTime; - private double endTime; - [Resolved] private Timeline timeline { get; set; } @@ -34,7 +31,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override void HandleDrag(MouseButtonEvent e) { startTime ??= timeline.TimeAtPosition(e.MouseDownPosition.X); - endTime = timeline.TimeAtPosition(e.MousePosition.X); + double endTime = timeline.TimeAtPosition(e.MousePosition.X); + + MinTime = Math.Min(startTime.Value, endTime); + MaxTime = Math.Max(startTime.Value, endTime); Box.X = timeline.PositionAtTime(MinTime); Box.Width = timeline.PositionAtTime(MaxTime) - Box.X; From 6753f6b01ab353baaa6575400d074292e2831705 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 22:14:11 +0900 Subject: [PATCH 474/583] Move `AllowDeselectionDuringDrag` down Because it is now ignored in the timeline implementation anyways --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 ++ .../Screens/Edit/Compose/Components/EditorBlueprintContainer.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 4c37d200bc..43ead88d54 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -83,6 +83,8 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + protected override bool AllowDeselectionDuringDrag => !EditorClock.IsRunning; + protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) { base.TransferBlueprintFor(hitObject, drawableObject); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 6a4fe27f04..879ac58887 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -66,8 +66,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints.OrderBy(b => b.Item.StartTime); - protected override bool AllowDeselectionDuringDrag => !EditorClock.IsRunning; - protected override bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) { if (!base.ApplySnapResult(blueprints, result)) From b0213c29e98b3b59efdb16c3ff6457c592f5385b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 22:19:22 +0900 Subject: [PATCH 475/583] Use mid time instead of start time It is closer to the old blueprint-based behavior --- .../Components/Timeline/TimelineBlueprintContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 04575e55b1..08682ae05f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -185,7 +185,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline SelectedItems.RemoveAll(hitObject => !shouldBeSelected(hitObject)); SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).Where(shouldBeSelected)); - bool shouldBeSelected(HitObject hitObject) => minTime <= hitObject.StartTime && hitObject.StartTime <= maxTime; + bool shouldBeSelected(HitObject hitObject) + { + double midTime = (hitObject.StartTime + hitObject.GetEndTime()) / 2; + return minTime <= midTime && midTime <= maxTime; + } } private void handleScrollViaDrag(DragEvent e) From 2a7476cc4a820015898ac9104bacde3e0dc8e10d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 5 Oct 2022 23:29:45 +0900 Subject: [PATCH 476/583] Add test for timeline drag selection --- .../Editing/TestSceneTimelineSelection.cs | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 7e0981ce69..a4f4c375bf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -29,16 +29,18 @@ namespace osu.Game.Tests.Visual.Editing private TimelineBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); + private Vector2 getPosition(HitObject hitObject) => + blueprintContainer.SelectionBlueprints.First(s => s.Item == hitObject).ScreenSpaceDrawQuad.Centre; + + private Vector2 getMiddlePosition(HitObject hitObject1, HitObject hitObject2) => + (getPosition(hitObject1) + getPosition(hitObject2)) / 2; + private void moveMouseToObject(Func targetFunc) { AddStep("move mouse to object", () => { - var pos = blueprintContainer.SelectionBlueprints - .First(s => s.Item == targetFunc()) - .ChildrenOfType() - .First().ScreenSpaceDrawQuad.Centre; - - InputManager.MoveMouseTo(pos); + var hitObject = targetFunc(); + InputManager.MoveMouseTo(getPosition(hitObject)); }); } @@ -262,6 +264,55 @@ namespace osu.Game.Tests.Visual.Editing AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } + [Test] + public void TestBasicDragSelection() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 500, Position = new Vector2(100) }, + new HitCircle { StartTime = 1000, Position = new Vector2(200) }, + new HitCircle { StartTime = 1500, Position = new Vector2(300) }, + }; + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1]))); + AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left)); + + AddStep("drag to select", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[2], addedObjects[3]))); + assertSelectionIs(new[] { addedObjects[1], addedObjects[2] }); + + AddStep("drag to deselect", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[1], addedObjects[2]))); + assertSelectionIs(new[] { addedObjects[1] }); + + AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left)); + assertSelectionIs(new[] { addedObjects[1] }); + } + + [Test] + public void TestFastDragSelection() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 20000, Position = new Vector2(100) }, + new HitCircle { StartTime = 31000, Position = new Vector2(200) }, + new HitCircle { StartTime = 60000, Position = new Vector2(300) }, + }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1]))); + AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left)); + AddStep("start drag", () => InputManager.MoveMouseTo(getPosition(addedObjects[1]))); + + AddStep("jump editor clock", () => EditorClock.Seek(30000)); + AddStep("jump editor clock", () => EditorClock.Seek(60000)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + assertSelectionIs(addedObjects.Skip(1)); + } + private void assertSelectionIs(IEnumerable hitObjects) => AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects)); } From 2c0cd9ea526137ac675457936abe8224f3bcc8fc Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Wed, 5 Oct 2022 19:09:34 -0700 Subject: [PATCH 477/583] Add more smoke tests --- osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs index 82417d09a7..1cb64b71fc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Input.States; +using osu.Framework.Logging; using osu.Framework.Testing.Input; using osu.Game.Rulesets.Osu.UI; using osuTK; @@ -17,22 +19,57 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestSmoking() { - AddStep("Create smoke", () => + addStep("Create short smoke", 2_000); + addStep("Create medium smoke", 5_000); + addStep("Create long smoke", 10_000); + } + + private void addStep(string stepName, double duration) + { + var smokeContainers = new List(); + + AddStep(stepName, () => { - SetContents(_ => new SmokingInputManager + smokeContainers.Clear(); + SetContents(_ => { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.95f), - Child = new TestSmokeContainer { RelativeSizeAxes = Axes.Both }, + smokeContainers.Add(new TestSmokeContainer + { + Duration = duration, + RelativeSizeAxes = Axes.Both + }); + + return new SmokingInputManager + { + Duration = duration, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.95f), + Child = smokeContainers[^1], + }; }); }); + + AddUntilStep("Until skinnable expires", () => + { + if (smokeContainers.Count == 0) + return false; + + Logger.Log("How many: " + smokeContainers.Count); + + foreach (var smokeContainer in smokeContainers) + { + if (smokeContainer.Children.Count != 0) + return false; + } + + return true; + }); } - private const double spin_duration = 5_000; - private const float spin_angle = 4 * MathF.PI; - private class SmokingInputManager : ManualInputManager { + public double Duration { get; init; } + private double? startTime; public SmokingInputManager() @@ -51,9 +88,11 @@ namespace osu.Game.Rulesets.Osu.Tests { base.Update(); + const float spin_angle = 4 * MathF.PI; + startTime ??= Time.Current; - float fraction = (float)((Time.Current - startTime) / spin_duration); + float fraction = (float)((Time.Current - startTime) / Duration); float angle = fraction * spin_angle; float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2; @@ -65,24 +104,27 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestSmokeContainer : SmokeContainer { - private double? startTime; + public double Duration { get; init; } + private bool isPressing; private bool isFinished; + private double? startTime; + protected override void Update() { base.Update(); - startTime ??= Time.Current; + startTime ??= Time.Current + 0.1; - if (!isPressing && !isFinished && Time.Current > startTime + 0.1) + if (!isPressing && !isFinished && Time.Current > startTime) { OnPressed(new KeyBindingPressEvent(new InputState(), OsuAction.Smoke)); isPressing = true; isFinished = false; } - if (isPressing && Time.Current > startTime + spin_duration) + if (isPressing && Time.Current > startTime + Duration) { OnReleased(new KeyBindingReleaseEvent(new InputState(), OsuAction.Smoke)); isPressing = false; From 49e023f8613d44ab03e40fda8236e4bbb0d5e774 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Wed, 5 Oct 2022 19:11:38 -0700 Subject: [PATCH 478/583] Rename `OsuSkinComponents.SmokeTrail` to `CursorSmoke` --- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 2 +- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 3ee30ff7dc..4248cce55a 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, - SmokeTrail, + CursorSmoke, ApproachCircle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 4d12b85770..b778bc21d1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; - case OsuSkinComponents.SmokeTrail: + case OsuSkinComponents.CursorSmoke: if (GetTexture("cursor-smoke") != null) return new LegacySmokeSegment(); diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 5999abc1bf..84596c6d72 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.UI { if (e.Action == OsuAction.Smoke) { - AddInternal(currentSegmentSkinnable = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SmokeTrail), _ => new DefaultSmokeSegment())); + AddInternal(currentSegmentSkinnable = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment())); // Add initial position immediately. addPosition(); From 9d54467145af496dab6d9f6a38517b4e1903ceaa Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Wed, 5 Oct 2022 19:13:06 -0700 Subject: [PATCH 479/583] Make smoke skinnable lifetime more robust --- .../Skinning/SmokeSegment.cs | 2 +- osu.Game.Rulesets.Osu/UI/SmokeContainer.cs | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 99196f6967..6c998e244c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both; - smokeStartTime = Time.Current; + LifetimeStart = smokeStartTime = Time.Current; totalDistance = pointInterval; } diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs index 84596c6d72..beba834e88 100644 --- a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs @@ -1,6 +1,8 @@ // 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -17,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI /// public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { - private SkinnableDrawable? currentSegmentSkinnable; + private SmokeSkinnableDrawable? currentSegmentSkinnable; private Vector2 lastMousePosition; @@ -27,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI { if (e.Action == OsuAction.Smoke) { - AddInternal(currentSegmentSkinnable = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment())); + AddInternal(currentSegmentSkinnable = new SmokeSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment())); // Add initial position immediately. addPosition(); @@ -44,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.UI if (currentSegmentSkinnable?.Drawable is SmokeSegment segment) { segment.FinishDrawing(Time.Current); - - currentSegmentSkinnable.LifetimeEnd = segment.LifetimeEnd; currentSegmentSkinnable = null; } } @@ -60,5 +60,18 @@ namespace osu.Game.Rulesets.Osu.UI } private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current); + + private class SmokeSkinnableDrawable : SkinnableDrawable + { + public override bool RemoveWhenNotAlive => true; + + public override double LifetimeStart => Drawable.LifetimeStart; + public override double LifetimeEnd => Drawable.LifetimeEnd; + + public SmokeSkinnableDrawable(ISkinComponent component, Func? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling) + : base(component, defaultImplementation, confineMode) + { + } + } } } From 0d448e6cc898e181ab65f31280e3be15ee761f42 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 Oct 2022 13:50:56 +0900 Subject: [PATCH 480/583] Fix items without blueprints are not deselected --- .../Compose/Components/BlueprintContainer.cs | 18 +++++++----------- .../Components/EditorBlueprintContainer.cs | 4 ++-- .../Skinning/Editor/SkinBlueprintContainer.cs | 6 ++++++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index da7a8e662b..8aecc75824 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; SelectionHandler = CreateSelectionHandler(); - SelectionHandler.DeselectAll = deselectAll; + SelectionHandler.DeselectAll = DeselectAll; SelectionHandler.SelectedItems.BindTo(SelectedItems); AddRangeInternal(new[] @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (endClickSelection(e) || ClickedBlueprint != null) return true; - deselectAll(); + DeselectAll(); return true; } @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!SelectionHandler.SelectedBlueprints.Any()) return false; - deselectAll(); + DeselectAll(); return true; } @@ -399,18 +399,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Selects all s. + /// Select all currently-present items. /// - protected virtual void SelectAll() - { - // Scheduled to allow the change in lifetime to take place. - Schedule(() => SelectionBlueprints.ToList().ForEach(m => m.Select())); - } + protected abstract void SelectAll(); /// - /// Deselects all selected s. + /// Deselect all selected items. /// - private void deselectAll() => SelectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); + protected void DeselectAll() => SelectedItems.Clear(); protected virtual void OnBlueprintSelected(SelectionBlueprint blueprint) { diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 879ac58887..6682748253 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -131,8 +131,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void SelectAll() { Composer.Playfield.KeepAllAlive(); - - base.SelectAll(); + SelectedItems.Clear(); + SelectedItems.AddRange(Beatmap.HitObjects); } protected override void OnBlueprintSelected(SelectionBlueprint blueprint) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 5a1ef34151..97522ddff8 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -117,6 +117,12 @@ namespace osu.Game.Skinning.Editor return false; } + protected override void SelectAll() + { + SelectedItems.Clear(); + SelectedItems.AddRange(targetComponents.SelectMany(list => list)); + } + /// /// Move the current selection spatially by the specified delta, in screen coordinates (ie. the same coordinates as the blueprints). /// From 29cc55463268c40b688d1125ea7b69886c046135 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 Oct 2022 13:59:54 +0900 Subject: [PATCH 481/583] Ensure blueprint is added for selected hit object --- .../Components/Timeline/TimelineBlueprintContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 08682ae05f..31990bfd35 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -183,7 +183,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double maxTime = dragBox.MaxTime; SelectedItems.RemoveAll(hitObject => !shouldBeSelected(hitObject)); - SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).Where(shouldBeSelected)); + + foreach (var hitObject in Beatmap.HitObjects.Except(SelectedItems).Where(shouldBeSelected)) + { + Composer.Playfield.SetKeepAlive(hitObject, true); + SelectedItems.Add(hitObject); + } bool shouldBeSelected(HitObject hitObject) { From 0ade0492526b184b14f64e59a66e88a71fc1e5d3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 Oct 2022 14:02:49 +0900 Subject: [PATCH 482/583] Add test for selected hit object blueprint --- osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index a4f4c375bf..54ad4e25e4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -311,6 +311,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("jump editor clock", () => EditorClock.Seek(60000)); AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); assertSelectionIs(addedObjects.Skip(1)); + AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count); } private void assertSelectionIs(IEnumerable hitObjects) From 0f6a6287f246f974a9e21304846c6b0083d8d343 Mon Sep 17 00:00:00 2001 From: NullifiedJosh <86538544+NullifiedJosh@users.noreply.github.com> Date: Thu, 6 Oct 2022 18:17:33 +0800 Subject: [PATCH 483/583] Fix bugs and add test --- .../TestSceneDrawableCatchRuleset.cs | 59 +++++++++++++++++++ .../UI/DrawableCatchRuleset.cs | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs new file mode 100644 index 0000000000..b826a064e6 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Logging; +using osu.Framework.Testing; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneDrawableCatchRulesetWithRelax : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create drawable ruleset with relax mod", () => + { + Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List() { + new CatchModRelax() + }); + }); + AddUntilStep("wait for load", () => Child.IsLoaded); + } + + [Test] + public void TestBasic() + { + AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any() == false); + } + } + + [TestFixture] + public class TestSceneDrawableCatchRulesetWithoutRelax : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create drawable ruleset without relax mod", () => + { + Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List()); + }); + AddUntilStep("wait for load", () => Child.IsLoaded); + Logger.Log("Ready"); + } + + [Test] + public void TestBasic() + { + AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any()); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 07968e4bee..dfa6126d3f 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI : base(ruleset, beatmap, mods) { // Check if mods have RelaxMod instance - if (mods.OfType().Any()) + if (mods != null && mods.OfType().Any()) showMobileMapper = false; Direction.Value = ScrollingDirection.Down; From 6543171169b37a79b3b6ddb192dcd15b4332f4a5 Mon Sep 17 00:00:00 2001 From: NullifiedJosh <86538544+NullifiedJosh@users.noreply.github.com> Date: Thu, 6 Oct 2022 18:30:49 +0800 Subject: [PATCH 484/583] Fix formating. --- .../TestSceneDrawableCatchRuleset.cs | 38 +++++-------------- .../UI/DrawableCatchRuleset.cs | 2 +- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs index b826a064e6..8b794fd919 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Logging; using osu.Framework.Testing; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Mods; @@ -15,45 +14,28 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneDrawableCatchRulesetWithRelax : OsuTestScene + public class TestSceneDrawableCatchRuleset : OsuTestScene { - [SetUpSteps] - public void SetUpSteps() - { - AddStep("create drawable ruleset with relax mod", () => - { - Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List() { - new CatchModRelax() - }); - }); - AddUntilStep("wait for load", () => Child.IsLoaded); - } - [Test] - public void TestBasic() - { - AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any() == false); - } - } - - [TestFixture] - public class TestSceneDrawableCatchRulesetWithoutRelax : OsuTestScene - { - [SetUpSteps] - public void SetUpSteps() + public void TestWithoutRelax() { AddStep("create drawable ruleset without relax mod", () => { Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List()); }); AddUntilStep("wait for load", () => Child.IsLoaded); - Logger.Log("Ready"); + AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any()); } [Test] - public void TestBasic() + public void TestWithRelax() { - AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any()); + AddStep("create drawable ruleset with relax mod", () => + { + Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List { new CatchModRelax() }); + }); + AddUntilStep("wait for load", () => Child.IsLoaded); + AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any() == false); } } } diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index dfa6126d3f..ce1fa963e7 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - private bool showMobileMapper = true; + private readonly bool showMobileMapper = true; public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) From ea4dbc8c0f3ef669d86439a9b1a368c213d39a26 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 Oct 2022 19:46:10 +0900 Subject: [PATCH 485/583] Fix mania hold note head disappears --- .../Objects/Drawables/DrawableHoldNoteHead.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index 66cc93b033..ac646ea427 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // the hold note head should never change its visual state on its own due to the "freezing" mechanic // (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line). // it will be hidden along with its parenting hold note when required. + + // Set `LifetimeEnd` explicitly to a non-`double.MaxValue` because otherwise this DHO is automatically expired. + LifetimeEnd = double.PositiveInfinity; } public override bool OnPressed(KeyBindingPressEvent e) => false; // Handled by the hold note From 994db55b6d542c187d96d6905552f79d2a352d6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 02:19:43 +0900 Subject: [PATCH 486/583] Simplify check conditionals --- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ce1fa963e7..27f7886d79 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -27,15 +27,9 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - private readonly bool showMobileMapper = true; - public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { - // Check if mods have RelaxMod instance - if (mods != null && mods.OfType().Any()) - showMobileMapper = false; - Direction.Value = ScrollingDirection.Down; TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450); } @@ -43,7 +37,8 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - if (showMobileMapper) + // With relax mod, input maps directly to x position and left/right buttons are not used. + if (!Mods.Any(m => m is ModRelax)) KeyBindingInputManager.Add(new CatchTouchInputMapper()); } From f3262103c44ad9d430e8e3883ccb490c0916e23e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 02:21:31 +0900 Subject: [PATCH 487/583] Move test into existing catch touch test scene --- .../TestSceneCatchTouchInput.cs | 35 +++++++++++++--- .../TestSceneDrawableCatchRuleset.cs | 41 ------------------- 2 files changed, 29 insertions(+), 47 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index cbf6e8f202..cf6a8169c4 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -1,10 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests @@ -12,11 +17,11 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneCatchTouchInput : OsuTestScene { - private CatchTouchInputMapper catchTouchInputMapper = null!; - - [SetUpSteps] - public void SetUpSteps() + [Test] + public void TestBasic() { + CatchTouchInputMapper catchTouchInputMapper = null!; + AddStep("create input overlay", () => { Child = new CatchInputManager(new CatchRuleset().RulesetInfo) @@ -32,12 +37,30 @@ namespace osu.Game.Rulesets.Catch.Tests } }; }); + + AddStep("show overlay", () => catchTouchInputMapper.Show()); } [Test] - public void TestBasic() + public void TestWithoutRelax() { - AddStep("show overlay", () => catchTouchInputMapper.Show()); + AddStep("create drawable ruleset without relax mod", () => + { + Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List()); + }); + AddUntilStep("wait for load", () => Child.IsLoaded); + AddAssert("check touch input is shown", () => this.ChildrenOfType().Any()); + } + + [Test] + public void TestWithRelax() + { + AddStep("create drawable ruleset with relax mod", () => + { + Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List { new CatchModRelax() }); + }); + AddUntilStep("wait for load", () => Child.IsLoaded); + AddAssert("check touch input is not shown", () => !this.ChildrenOfType().Any()); } } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs deleted file mode 100644 index 8b794fd919..0000000000 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableCatchRuleset.cs +++ /dev/null @@ -1,41 +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.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Testing; -using osu.Game.Rulesets.Catch.Beatmaps; -using osu.Game.Rulesets.Catch.Mods; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestSceneDrawableCatchRuleset : OsuTestScene - { - [Test] - public void TestWithoutRelax() - { - AddStep("create drawable ruleset without relax mod", () => - { - Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List()); - }); - AddUntilStep("wait for load", () => Child.IsLoaded); - AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any()); - } - - [Test] - public void TestWithRelax() - { - AddStep("create drawable ruleset with relax mod", () => - { - Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List { new CatchModRelax() }); - }); - AddUntilStep("wait for load", () => Child.IsLoaded); - AddAssert("check if touch catcher is showing", () => this.ChildrenOfType().Any() == false); - } - } -} From 6164e0896a8ec69e87329f790e22624bba85225d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 7 Oct 2022 10:46:07 +0900 Subject: [PATCH 488/583] Don't reselect already selected items in SelectAll --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 3 +-- osu.Game/Skinning/Editor/SkinBlueprintContainer.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 6682748253..6adaeb1a83 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -131,8 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void SelectAll() { Composer.Playfield.KeepAllAlive(); - SelectedItems.Clear(); - SelectedItems.AddRange(Beatmap.HitObjects); + SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } protected override void OnBlueprintSelected(SelectionBlueprint blueprint) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 97522ddff8..2937b62eec 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -119,8 +119,7 @@ namespace osu.Game.Skinning.Editor protected override void SelectAll() { - SelectedItems.Clear(); - SelectedItems.AddRange(targetComponents.SelectMany(list => list)); + SelectedItems.AddRange(targetComponents.SelectMany(list => list).Except(SelectedItems).ToArray()); } /// From b27e70ca479e622b12046722d8832b8d0ee81b63 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 6 Oct 2022 21:11:00 -0700 Subject: [PATCH 489/583] Fix language settings dropdown not updating when changing language in first run setup --- .../Settings/Sections/General/LanguageSettings.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index 63f0dec953..0f77e6609b 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -44,9 +44,12 @@ namespace osu.Game.Overlays.Settings.Sections.General }, }; - if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var locale)) - locale = Language.en; - languageSelection.Current.Value = locale; + frameworkLocale.BindValueChanged(locale => + { + if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language)) + language = Language.en; + languageSelection.Current.Value = language; + }, true); languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode()); } From 7385ef3e1b033c37bc4bec0737ac446efe4eaa60 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 7 Oct 2022 14:26:19 +0900 Subject: [PATCH 490/583] Extract combo scale to virtual function --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a594363d4c..69937a0fba 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -149,16 +149,21 @@ namespace osu.Game.Rulesets.Mods float size = defaultFlashlightSize * sizeMultiplier; if (comboBasedSize) - { - if (combo >= 200) - size *= 0.625f; - else if (combo >= 100) - size *= 0.8125f; - } + size *= GetComboScaleFor(combo); return size; } + protected virtual float GetComboScaleFor(int combo) + { + if (combo >= 200) + return 0.625f; + if (combo >= 100) + return 0.8125f; + + return 1.0f; + } + private Vector2 flashlightPosition; protected Vector2 FlashlightPosition From c6b5fdc7d0cdab0d41cc9e41cf9e509433633427 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 7 Oct 2022 14:34:48 +0900 Subject: [PATCH 491/583] Adjust catch flashlight to closely match classic --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 1adc969f8f..ff957b9b73 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override BindableBool ComboBasedSize { get; } = new BindableBool(true); - public override float DefaultFlashlightSize => 350; + public override float DefaultFlashlightSize => 325; protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); @@ -44,7 +44,19 @@ namespace osu.Game.Rulesets.Catch.Mods : base(modFlashlight) { this.playfield = playfield; + FlashlightSize = new Vector2(0, GetSizeFor(0)); + FlashlightSmoothness = 1.4f; + } + + protected override float GetComboScaleFor(int combo) + { + if (combo >= 200) + return 0.770f; + if (combo >= 100) + return 0.885f; + + return 1.0f; } protected override void Update() From 5f3b58b7e009a0ed5c7d1028a01d89c7e754a2f4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 7 Oct 2022 14:44:45 +0900 Subject: [PATCH 492/583] Adjust taiko flashlight to closely match classic --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 1caacdd1d7..fca69e86cc 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override BindableBool ComboBasedSize { get; } = new BindableBool(true); - public override float DefaultFlashlightSize => 250; + public override float DefaultFlashlightSize => 200; protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); @@ -46,7 +46,9 @@ namespace osu.Game.Rulesets.Taiko.Mods : base(modFlashlight) { this.taikoPlayfield = taikoPlayfield; + FlashlightSize = getSizeFor(0); + FlashlightSmoothness = 1.4f; AddLayout(flashlightProperties); } From df3ad618e15782e28b08fa4271aa1e79560de6b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 17:49:00 +0900 Subject: [PATCH 493/583] Move `ColumnType` to constructor --- .../Editor/ManiaPlacementBlueprintTestScene.cs | 3 ++- .../Skinning/ColumnTestContainer.cs | 3 +-- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 3 ++- .../TestSceneDrawableManiaHitObject.cs | 3 ++- osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs | 2 -- osu.Game.Rulesets.Mania/UI/Column.cs | 13 ++++++------- osu.Game.Rulesets.Mania/UI/Stage.cs | 5 ++--- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 6e6e83f9cf..d6dd2664bc 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; - Add(column = new Column(0) + Add(column = new Column(0, ColumnType.Even) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index 1a3513d46c..33166c5aeb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -28,11 +28,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { InternalChildren = new[] { - this.column = new Column(column) + this.column = new Column(column, column % 2 == 0 ? ColumnType.Even : ColumnType.Odd) { Action = { Value = action }, AccentColour = Color4.Orange, - ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd, Alpha = showColumn ? 1 : 0 }, content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 2922d18713..ab2f63339f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -84,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index) { - var column = new Column(index) + var column = new Column(index, ColumnType.Even) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs index 223f8dae44..b9b28eb19f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests RelativeSizeAxes = Axes.Y, TimeRange = 2000, Clock = new FramedClock(clock), - Child = column = new Column(0) + Child = column = new Column(0, ColumnType.Even) { Action = { Value = ManiaAction.Key1 }, Height = 0.85f, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs index 0114987e3c..8f904530bc 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Beatmaps { public enum ColumnType diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index deb1b155b5..361210ff1d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -46,9 +46,14 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public Column(int index) + public readonly ColumnType ColumnType; + + public Color4 AccentColour { get; set; } + + public Column(int index, ColumnType columnType) { Index = index; + ColumnType = columnType; RelativeSizeAxes = Axes.Y; Width = COLUMN_WIDTH; @@ -92,12 +97,6 @@ namespace osu.Game.Rulesets.Mania.UI NewResult += OnNewResult; } - public ColumnType ColumnType { get; set; } - - public bool IsSpecial => ColumnType == ColumnType.Special; - - public Color4 AccentColour { get; set; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index c578bbb703..29dacc5094 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -120,13 +120,12 @@ namespace osu.Game.Rulesets.Mania.UI { var columnType = definition.GetTypeOfColumn(i); - var column = new Column(firstColumnIndex + i) + var column = new Column(firstColumnIndex + i, columnType) { RelativeSizeAxes = Axes.Both, - Width = 1, - ColumnType = columnType, AccentColour = columnColours[columnType], Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } + Width = 1, }; topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); From 7796a4c1097c0de6e780f282db5883a49357aa18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 18:24:49 +0900 Subject: [PATCH 494/583] Cache `StageDefinition` for consumption (and remove `ColumnType`) --- .../ManiaPlacementBlueprintTestScene.cs | 3 +- ...nTypeTest.cs => ManiaSpecialColumnTest.cs} | 34 +++++++++---------- .../Skinning/ColumnTestContainer.cs | 5 ++- .../TestSceneColumn.cs | 3 +- .../TestSceneDrawableManiaHitObject.cs | 3 +- .../Beatmaps/ColumnType.cs | 12 ------- .../Beatmaps/StageDefinition.cs | 19 ++--------- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 4 +-- .../Legacy/LegacyManiaColumnElement.cs | 21 +++++------- .../Legacy/ManiaLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Mania/UI/Column.cs | 13 +++---- osu.Game.Rulesets.Mania/UI/Stage.cs | 27 +++++++++------ 12 files changed, 61 insertions(+), 85 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{ManiaColumnTypeTest.cs => ManiaSpecialColumnTest.cs} (53%) delete mode 100644 osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index d6dd2664bc..976efc51b2 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; - Add(column = new Column(0, ColumnType.Even) + Add(column = new Column(0, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs similarity index 53% rename from osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs rename to osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs index e53deb5269..c83e8a51ed 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs @@ -10,43 +10,43 @@ using NUnit.Framework; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class ManiaColumnTypeTest + public class ManiaSpecialColumnTest { [TestCase(new[] { - ColumnType.Special + true }, 1)] [TestCase(new[] { - ColumnType.Odd, - ColumnType.Even, - ColumnType.Even, - ColumnType.Odd + false, + false, + false, + false }, 4)] [TestCase(new[] { - ColumnType.Odd, - ColumnType.Even, - ColumnType.Odd, - ColumnType.Special, - ColumnType.Odd, - ColumnType.Even, - ColumnType.Odd + false, + false, + false, + true, + false, + false, + false }, 7)] - public void Test(IEnumerable expected, int columns) + public void Test(IEnumerable special, int columns) { var definition = new StageDefinition { Columns = columns }; var results = getResults(definition); - Assert.AreEqual(expected, results); + Assert.AreEqual(special, results); } - private IEnumerable getResults(StageDefinition definition) + private IEnumerable getResults(StageDefinition definition) { for (int i = 0; i < definition.Columns; i++) - yield return definition.GetTypeOfColumn(i); + yield return definition.IsSpecialColumn(i); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index 33166c5aeb..b8d1313af5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -24,11 +24,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached] private readonly Column column; + [Cached] + private readonly StageDefinition stageDefinition = new StageDefinition { Columns = 1 }; + public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false) { InternalChildren = new[] { - this.column = new Column(column, column % 2 == 0 ? ColumnType.Even : ColumnType.Odd) + this.column = new Column(column, false) { Action = { Value = action }, AccentColour = Color4.Orange, diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index ab2f63339f..8fff87ae78 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -85,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index) { - var column = new Column(index, ColumnType.Even) + var column = new Column(index, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs index b9b28eb19f..bd34f1ef11 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests RelativeSizeAxes = Axes.Y, TimeRange = 2000, Clock = new FramedClock(clock), - Child = column = new Column(0, ColumnType.Even) + Child = column = new Column(0, false) { Action = { Value = ManiaAction.Key1 }, Height = 0.85f, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs deleted file mode 100644 index 8f904530bc..0000000000 --- a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Mania.Beatmaps -{ - public enum ColumnType - { - Even, - Odd, - Special - } -} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index 54e2d4686f..08ee97b32b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Beatmaps @@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// Defines properties for each stage in a . /// - public struct StageDefinition + public class StageDefinition { /// /// The number of s which this stage contains. @@ -23,20 +22,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The 0-based column index. /// Whether the column is a special column. - public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; - - /// - /// Get the type of column given a column index. - /// - /// The 0-based column index. - /// The type of the column. - public readonly ColumnType GetTypeOfColumn(int column) - { - if (IsSpecialColumn(column)) - return ColumnType.Special; - - int distanceToEdge = Math.Min(column, (Columns - 1) - column); - return distanceToEdge % 2 == 0 ? ColumnType.Odd : ColumnType.Even; - } + public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; } } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 21b362df00..6d0eb15b64 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -15,14 +15,14 @@ namespace osu.Game.Rulesets.Mania /// The intended for this component. /// May be null if the component is not a direct member of a . /// - public readonly StageDefinition? StageDefinition; + public readonly StageDefinition StageDefinition; /// /// Creates a new . /// /// The component. /// The intended for this component. May be null if the component is not a direct member of a . - public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null) + public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition stageDefinition = null) : base(component) { StageDefinition = stageDefinition; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs index ab953ccfb9..e227c80845 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -20,6 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy [Resolved] protected Column Column { get; private set; } + [Resolved] + private StageDefinition stage { get; set; } + /// /// The column type identifier to use for texture lookups, in the case of no user-provided configuration. /// @@ -28,19 +32,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy [BackgroundDependencyLoader] private void load() { - switch (Column.ColumnType) + if (Column.IsSpecial) + FallbackColumnIndex = "S"; + else { - case ColumnType.Special: - FallbackColumnIndex = "S"; - break; - - case ColumnType.Odd: - FallbackColumnIndex = "1"; - break; - - case ColumnType.Even: - FallbackColumnIndex = "2"; - break; + int distanceToEdge = Math.Min(Column.Index, (stage.Columns - 1) - Column.Index); + FallbackColumnIndex = distanceToEdge % 2 == 0 ? "1" : "2"; } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index dd5baa8150..a1b196e53e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy case ManiaSkinComponents.StageBackground: Debug.Assert(maniaComponent.StageDefinition != null); - return new LegacyStageBackground(maniaComponent.StageDefinition.Value); + return new LegacyStageBackground(maniaComponent.StageDefinition); case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 361210ff1d..57ada84767 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -6,7 +6,6 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -18,7 +17,6 @@ using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -26,7 +24,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { [Cached] - public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour + public class Column : ScrollingPlayfield, IKeyBindingHandler { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; @@ -46,14 +44,17 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public readonly ColumnType ColumnType; + /// + /// Whether this is a special (ie. scratch) column. + /// + public readonly bool IsSpecial; public Color4 AccentColour { get; set; } - public Column(int index, ColumnType columnType) + public Column(int index, bool isSpecial) { Index = index; - ColumnType = columnType; + IsSpecial = isSpecial; RelativeSizeAxes = Axes.Y; Width = COLUMN_WIDTH; diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 29dacc5094..9e51ed84db 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -19,7 +20,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Mania.UI /// public class Stage : ScrollingPlayfield { + [Cached] + public readonly StageDefinition Definition; + public const float COLUMN_SPACING = 1; public const float HIT_TARGET_POSITION = 110; @@ -40,12 +43,12 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Drawable barLineContainer; - private readonly Dictionary columnColours = new Dictionary - { - { ColumnType.Even, new Color4(6, 84, 0, 255) }, - { ColumnType.Odd, new Color4(94, 0, 57, 255) }, - { ColumnType.Special, new Color4(0, 48, 63, 255) } - }; + // private readonly Dictionary columnColours = new Dictionary + // { + // { ColumnType.Even, new Color4(6, 84, 0, 255) }, + // { ColumnType.Odd, new Color4(94, 0, 57, 255) }, + // { ColumnType.Special, new Color4(0, 48, 63, 255) } + // }; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); @@ -54,6 +57,7 @@ namespace osu.Game.Rulesets.Mania.UI public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) { this.firstColumnIndex = firstColumnIndex; + Definition = definition; Name = "Stage"; @@ -118,14 +122,15 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { - var columnType = definition.GetTypeOfColumn(i); + bool isSpecial = definition.IsSpecialColumn(i); - var column = new Column(firstColumnIndex + i, columnType) + var column = new Column(firstColumnIndex + i, isSpecial) { RelativeSizeAxes = Axes.Both, - AccentColour = columnColours[columnType], - Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } Width = 1, + // TODO: reimplement this somewhere. + //AccentColour = columnColours[isSpecial], + Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } }; topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); From 46c3cfe54d6a772b82c0a76f969627b3c408f30b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 18:26:27 +0900 Subject: [PATCH 495/583] Remove `StageDefinition` flow in `ManiaSkinComponent` --- .../Skinning/TestSceneStageBackground.cs | 3 +-- .../Skinning/TestSceneStageForeground.cs | 3 +-- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 12 +----------- .../Skinning/Legacy/LegacyStageBackground.cs | 7 ++----- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 +--- osu.Game.Rulesets.Mania/UI/Stage.cs | 4 ++-- 6 files changed, 8 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs index 687b3a747d..0744d7e2e7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Skinning; @@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }), + SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs index 6cbc172755..979c90c802 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Tests.Skinning @@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null) + SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 6d0eb15b64..f05edb4677 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -3,29 +3,19 @@ #nullable disable -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania { public class ManiaSkinComponent : GameplaySkinComponent { - /// - /// The intended for this component. - /// May be null if the component is not a direct member of a . - /// - public readonly StageDefinition StageDefinition; - /// /// Creates a new . /// /// The component. - /// The intended for this component. May be null if the component is not a direct member of a . - public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition stageDefinition = null) + public ManiaSkinComponent(ManiaSkinComponents component) : base(component) { - StageDefinition = stageDefinition; } protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs index 740ccbfe27..d039551cd7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs @@ -18,20 +18,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public class LegacyStageBackground : CompositeDrawable { - private readonly StageDefinition stageDefinition; - private Drawable leftSprite; private Drawable rightSprite; private ColumnFlow columnBackgrounds; - public LegacyStageBackground(StageDefinition stageDefinition) + public LegacyStageBackground() { - this.stageDefinition = stageDefinition; RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, StageDefinition stageDefinition) { string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value ?? "mania-stage-left"; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index a1b196e53e..5ecdf988f9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -113,8 +112,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy return new LegacyHitExplosion(); case ManiaSkinComponents.StageBackground: - Debug.Assert(maniaComponent.StageDefinition != null); - return new LegacyStageBackground(maniaComponent.StageDefinition); + return new LegacyStageBackground(); case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 9e51ed84db..a19d423e69 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X, Children = new Drawable[] { - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground()) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) { RelativeSizeAxes = Axes.Both }, @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, } }, - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) { RelativeSizeAxes = Axes.Both }, From 9c979044dcd93f53cb86be594b8b6da3f382761c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 19:27:16 +0900 Subject: [PATCH 496/583] Move `AccentColour` assignment to inside `Column` --- osu.Game.Rulesets.Mania/UI/Column.cs | 42 ++++++++++++++++++---------- osu.Game.Rulesets.Mania/UI/Stage.cs | 2 -- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 57ada84767..c0739f5ec3 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -3,23 +3,24 @@ #nullable disable -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.UI; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { @@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); public readonly ColumnHitObjectArea HitObjectArea; - internal readonly Container TopLevelContainer; - private readonly DrawablePool hitExplosionPool; + internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }; + private DrawablePool hitExplosionPool; private readonly OrderedHitPolicy hitPolicy; public Container UnderlayElements => HitObjectArea.UnderlayElements; - private readonly GameplaySampleTriggerSource sampleTriggerSource; + private GameplaySampleTriggerSource sampleTriggerSource; /// /// Whether this is a special (ie. scratch) column. @@ -59,6 +60,21 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y; Width = COLUMN_WIDTH; + hitPolicy = new OrderedHitPolicy(HitObjectContainer); + HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + // TODO: reimplement this somewhere. + AccentColour = skin.GetConfig() + var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; + + //AccentColour = columnColours[isSpecial], + foreach (var obj in HitObjectContainer.Objects) + obj.AccentColour.Value = AccentColour; + Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both @@ -70,18 +86,16 @@ namespace osu.Game.Rulesets.Mania.UI sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }, + HitObjectArea, new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, background, - TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, + TopLevelContainer, new ColumnTouchInputArea(this) }; - hitPolicy = new OrderedHitPolicy(HitObjectContainer); - TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); RegisterPool(10, 50); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index a19d423e69..52c8829df3 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -128,8 +128,6 @@ namespace osu.Game.Rulesets.Mania.UI { RelativeSizeAxes = Axes.Both, Width = 1, - // TODO: reimplement this somewhere. - //AccentColour = columnColours[isSpecial], Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } }; From 5c48d8931ad8dc5e7ba36cd06bcaf84052a77615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 19:42:43 +0900 Subject: [PATCH 497/583] Add `StageDefinition` to `ManiaSkinConfigurationLookup` and make column background colour lookup work --- .../Argon/ManiaArgonSkinTransformer.cs | 24 +++++++++++++++++++ .../Legacy/HitTargetInsetContainer.cs | 5 ++-- .../Skinning/Legacy/LegacyColumnBackground.cs | 5 ++-- .../Skinning/Legacy/LegacyHitTarget.cs | 9 +++---- .../Legacy/LegacyManiaColumnElement.cs | 2 +- .../Legacy/LegacyManiaJudgementPiece.cs | 5 ++-- .../Skinning/Legacy/LegacyNotePiece.cs | 5 ++-- .../Skinning/Legacy/LegacyStageBackground.cs | 14 +++++------ .../Skinning/Legacy/LegacyStageForeground.cs | 5 ++-- .../Legacy/ManiaLegacySkinTransformer.cs | 6 ++--- .../Skinning/ManiaSkinConfigExtensions.cs | 8 ++++--- .../Skinning/ManiaSkinConfigurationLookup.cs | 16 +++++++++---- osu.Game.Rulesets.Mania/UI/Column.cs | 9 ++++--- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 4 ++-- .../UI/Components/HitObjectArea.cs | 6 ++++- 15 files changed, 83 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index ec6aaf2ef7..57a317e87d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -1,8 +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 osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { @@ -33,5 +36,26 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon return base.GetDrawableComponent(component); } + + public override IBindable? GetConfig(TLookup lookup) + { + if (lookup is ManiaSkinConfigurationLookup maniaLookup) + { + switch (maniaLookup.Lookup) + { + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + if (maniaLookup.StageDefinition.IsSpecialColumn(maniaLookup.ColumnIndex ?? 0)) + return SkinUtils.As(new Bindable(Color4.Yellow)); + + // TODO: Add actual colours. + return SkinUtils.As(new Bindable(new Color4(RNG.NextSingle() * 0.5f, RNG.NextSingle() * 0.5f, RNG.NextSingle() * 0.5f, 1))); + } + + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(maniaLookup.StageDefinition.Columns, maniaLookup.Lookup, + maniaLookup.ColumnIndex)); + } + + return base.GetConfig(lookup); + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs index 362a265789..de6d048295 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -30,9 +31,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) { - hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition, stageDefinition)?.Value ?? Stage.HIT_TARGET_POSITION; direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs index f35cedab08..08fa9b9a73 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -30,9 +31,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) { - string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value + string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage, stageDefinition)?.Value ?? "mania-stage-light"; float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs index 611dac30b3..9819f09e10 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -23,15 +24,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private Container directionContainer; [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) { - string targetImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value + string targetImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitTargetImage, stageDefinition)?.Value ?? "mania-stage-hint"; - bool showJudgementLine = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value + bool showJudgementLine = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ShowJudgementLine, stageDefinition)?.Value ?? true; - Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.JudgementLineColour, stageDefinition)?.Value ?? Color4.White; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs index e227c80845..cfebad4add 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs @@ -42,6 +42,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } protected IBindable GetColumnSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) - => skin.GetManiaSkinConfig(lookup, Column.Index); + => skin.GetManiaSkinConfig(lookup, stage, Column.Index); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs index d09a73a693..f9653dab15 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -32,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, StageDefinition stageDefinition) { - float? scorePosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value; + float? scorePosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ScorePosition, stageDefinition)?.Value; if (scorePosition != null) scorePosition -= Stage.HIT_TARGET_POSITION + 150; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs index 41e149ea2f..779e1a152c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -35,9 +36,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) { - minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value; + minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth, stageDefinition))?.Value; InternalChild = directionContainer = new Container { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs index d039551cd7..01c2b599fa 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin, StageDefinition stageDefinition) { - string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value + string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage, stageDefinition)?.Value ?? "mania-stage-left"; - string rightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value + string rightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightStageImage, stageDefinition)?.Value ?? "mania-stage-right"; InternalChildren = new[] @@ -91,16 +91,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, StageDefinition stageDefinition) { - float leftLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1; - float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1; + float leftLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftLineWidth, stageDefinition, columnIndex)?.Value ?? 1; + float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, stageDefinition, columnIndex)?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = (rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m) || isLastColumn; - Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White; - Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black; + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, stageDefinition, columnIndex)?.Value ?? Color4.White; + Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, columnIndex)?.Value ?? Color4.Black; InternalChildren = new Drawable[] { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs index f7c611d551..57b76c5402 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -25,9 +26,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) { - string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value + string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage, stageDefinition)?.Value ?? "mania-stage-bottom"; sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 5ecdf988f9..352b205c0b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy isLegacySkin = new Lazy(() => GetConfig(SkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { - string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; + string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, new StageDefinition(), 0)?.Value ?? "mania-key1"; return this.GetAnimation(keyImage, true, true) != null; }); } @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (!hit_result_mapping.ContainsKey(result)) return null; - string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value + string filename = this.GetManiaSkinConfig(hit_result_mapping[result], new StageDefinition())?.Value ?? default_hit_result_skin_filenames[result]; var animation = this.GetAnimation(filename, true, true); @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); return base.GetConfig(lookup); } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs index 4d0c321116..541b679043 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs @@ -4,6 +4,7 @@ #nullable disable using osu.Framework.Bindables; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning @@ -15,9 +16,10 @@ namespace osu.Game.Rulesets.Mania.Skinning /// /// The skin from which configuration is retrieved. /// The value to retrieve. - /// If not null, denotes the index of the column to which the entry applies. - public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + /// The stage definition. + /// If not null, denotes the index of the column to which the entry applies. + public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, StageDefinition stageDefinition, int? columnIndex = null) => skin.GetConfig( - new ManiaSkinConfigurationLookup(lookup, index)); + new ManiaSkinConfigurationLookup(lookup, stageDefinition, columnIndex)); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs index e9005a3da0..8008c988d4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs @@ -3,6 +3,7 @@ #nullable disable +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; @@ -15,21 +16,28 @@ namespace osu.Game.Rulesets.Mania.Skinning /// public readonly LegacyManiaSkinConfigurationLookups Lookup; + /// + /// The stage containing the component which is performing this lookup. + /// + public readonly StageDefinition StageDefinition; + /// /// The intended index for the configuration. /// May be null if the configuration does not apply to a . /// - public readonly int? TargetColumn; + public readonly int? ColumnIndex; /// /// Creates a new . /// /// The lookup value. - /// The intended index for the configuration. May be null if the configuration does not apply to a . - public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) + /// The stage definition. + /// The intended index for the configuration. May be null if the configuration does not apply to a . + public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, StageDefinition stageDefinition, int? columnIndex = null) { Lookup = lookup; - TargetColumn = targetColumn; + StageDefinition = stageDefinition; + ColumnIndex = columnIndex; } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index c0739f5ec3..75d6d4c206 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -10,10 +10,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -65,11 +66,9 @@ namespace osu.Game.Rulesets.Mania.UI } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, StageDefinition stageDefinition) { - // TODO: reimplement this somewhere. - AccentColour = skin.GetConfig() - var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; + AccentColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, Index)?.Value ?? Color4.Black; //AccentColour = columnColours[isSpecial], foreach (var obj in HitObjectContainer.Objects) diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 871ec9f1a3..bde2308635 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Mania.UI if (i > 0) { float spacing = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1)) + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, stageDefinition, i - 1)) ?.Value ?? Stage.COLUMN_SPACING; columns[i].Margin = new MarginPadding { Left = spacing }; } float? width = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i)) + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, stageDefinition, i)) ?.Value; if (width == null) diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index 7f4b8eacde..69bf146951 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components protected readonly IBindable Direction = new Bindable(); public readonly HitObjectContainer HitObjectContainer; + [Resolved] + private StageDefinition stageDefinition { get; set; } + public HitObjectArea(HitObjectContainer hitObjectContainer) { InternalChild = new Container @@ -49,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components protected virtual void UpdateHitPosition() { float hitPosition = CurrentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition, stageDefinition))?.Value ?? Stage.HIT_TARGET_POSITION; Padding = Direction.Value == ScrollingDirection.Up From 1a0b953846f2ac67acfd93f228cc64e7efcd666d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 19:49:07 +0900 Subject: [PATCH 498/583] Remove unnecessary `Beatmap` parameter in `ManiaLegacySkinTransformer` --- .../Skinning/ColumnTestContainer.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 11 +++-------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index b8d1313af5..8f6a8c42b1 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning private readonly Column column; [Cached] - private readonly StageDefinition stageDefinition = new StageDefinition { Columns = 1 }; + private readonly StageDefinition stageDefinition = new StageDefinition { Columns = 2 }; public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false) { diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c6b20b1baf..746dda6733 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania switch (skin) { case LegacySkin: - return new ManiaLegacySkinTransformer(skin, beatmap); + return new ManiaLegacySkinTransformer(skin); case ArgonSkin: return new ManiaArgonSkinTransformer(skin); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 352b205c0b..0a59c10b5f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Scoring; @@ -19,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public class ManiaLegacySkinTransformer : LegacySkinTransformer { - private readonly ManiaBeatmap beatmap; - /// /// Mapping of to their corresponding /// value. @@ -59,15 +56,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy /// private readonly Lazy hasKeyTexture; - public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap) + public ManiaLegacySkinTransformer(ISkin skin) : base(skin) { - this.beatmap = (ManiaBeatmap)beatmap; - isLegacySkin = new Lazy(() => GetConfig(SkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { - string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, new StageDefinition(), 0)?.Value ?? "mania-key1"; + string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, new StageDefinition { Columns = 1 }, 0)?.Value ?? "mania-key1"; return this.GetAnimation(keyImage, true, true) != null; }); } @@ -149,7 +144,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(maniaLookup.StageDefinition.Columns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); return base.GetConfig(lookup); } From 5fe9b953a526447234ed3d83017bc8f259e99e7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Oct 2022 19:50:55 +0900 Subject: [PATCH 499/583] Add back triangles column colours via a transformer --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 ++ .../Argon/ManiaArgonSkinTransformer.cs | 3 -- .../Default/ManiaTrianglesSkinTransformer.cs | 42 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 746dda6733..7be4243ad6 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Skinning.Argon; +using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.Skinning.Legacy; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -67,6 +68,9 @@ namespace osu.Game.Rulesets.Mania { switch (skin) { + case TrianglesSkin: + return new ManiaTrianglesSkinTransformer(skin); + case LegacySkin: return new ManiaLegacySkinTransformer(skin); diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 57a317e87d..ea34d8d4c5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -50,9 +50,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon // TODO: Add actual colours. return SkinUtils.As(new Bindable(new Color4(RNG.NextSingle() * 0.5f, RNG.NextSingle() * 0.5f, RNG.NextSingle() * 0.5f, 1))); } - - return base.GetConfig(new LegacyManiaSkinConfigurationLookup(maniaLookup.StageDefinition.Columns, maniaLookup.Lookup, - maniaLookup.ColumnIndex)); } return base.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs new file mode 100644 index 0000000000..88f1f6ed26 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs @@ -0,0 +1,42 @@ +// 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 osu.Framework.Bindables; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Default +{ + public class ManiaTrianglesSkinTransformer : SkinTransformer + { + public ManiaTrianglesSkinTransformer(ISkin skin) + : base(skin) + { + } + + private readonly Color4 colourEven = new Color4(6, 84, 0, 255); + private readonly Color4 colourOdd = new Color4(94, 0, 57, 255); + private readonly Color4 colourSpecial = new Color4(0, 48, 63, 255); + + public override IBindable? GetConfig(TLookup lookup) + { + if (lookup is ManiaSkinConfigurationLookup maniaLookup) + { + switch (maniaLookup.Lookup) + { + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + int column = maniaLookup.ColumnIndex ?? 0; + + if (maniaLookup.StageDefinition.IsSpecialColumn(column)) + return SkinUtils.As(new Bindable(colourSpecial)); + + int distanceToEdge = Math.Min(column, (maniaLookup.StageDefinition.Columns - 1) - column); + return SkinUtils.As(new Bindable(distanceToEdge % 2 == 0 ? colourOdd : colourEven)); + } + } + + return base.GetConfig(lookup); + } + } +} From 2ae1aef0be4252e8b3bc98d61eea057ad7083d14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 19:14:31 +0900 Subject: [PATCH 500/583] Move column initialisation to ctor and fix remaining tests --- .../Editor/ManiaPlacementBlueprintTestScene.cs | 4 ++++ .../Editor/ManiaSelectionBlueprintTestScene.cs | 2 +- .../Editor/TestSceneManiaBeatSnapGrid.cs | 6 +++--- .../Editor/TestSceneManiaComposeScreen.cs | 2 +- .../Editor/TestSceneManiaHitObjectComposer.cs | 2 +- .../ManiaLegacyReplayTest.cs | 6 +++--- .../ManiaSpecialColumnTest.cs | 5 +---- .../Mods/TestSceneManiaModHoldOff.cs | 2 +- .../Skinning/ColumnTestContainer.cs | 2 +- .../Skinning/ManiaSkinnableTestScene.cs | 4 ++++ .../Skinning/TestSceneBarLine.cs | 2 +- .../Skinning/TestScenePlayfield.cs | 6 +++--- .../Skinning/TestSceneStage.cs | 2 +- .../TestSceneAutoGeneration.cs | 14 +++++++------- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 4 ++++ .../TestSceneDrawableManiaHitObject.cs | 5 +++++ .../TestSceneOutOfOrderHits.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 2 +- .../TestSceneTimingBasedNoteColouring.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 4 ++-- .../Beatmaps/StageDefinition.cs | 11 ++++++++++- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++-- 22 files changed, 58 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 976efc51b2..a759e95d17 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -30,6 +31,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; + [Cached] + private readonly StageDefinition stage = new StageDefinition(5); + protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 679a15e8cb..4cadcf138b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor protected ManiaSelectionBlueprintTestScene(int columns) { - var stageDefinitions = new List { new StageDefinition { Columns = columns } }; + var stageDefinitions = new List { new StageDefinition(columns) }; base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Up) { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index ec96205067..ef140995ec 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo(); [Cached(typeof(EditorBeatmap))] - private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()) + private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition(2)) { BeatmapInfo = { @@ -56,8 +56,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { Playfield = new ManiaPlayfield(new List { - new StageDefinition { Columns = 4 }, - new StageDefinition { Columns = 3 } + new StageDefinition(4), + new StageDefinition(3) }) { Clock = new FramedClock(new StopwatchClock()) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index e96a186ae4..e082b90d3b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { AddStep("setup compose screen", () => { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 }) + var beatmap = new ManiaBeatmap(new StageDefinition(4)) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, }; diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index fcc9e2e6c3..a3985be936 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }) + EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition(4)) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } }), diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs index b64006316e..7d1a934456 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase(ManiaAction.Key8)] public void TestEncodeDecodeSingleStage(params ManiaAction[] actions) { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 9 }); + var beatmap = new ManiaBeatmap(new StageDefinition(9)); var frame = new ManiaReplayFrame(0, actions); var legacyFrame = frame.ToLegacy(beatmap); @@ -38,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase(ManiaAction.Key8)] public void TestEncodeDecodeDualStage(params ManiaAction[] actions) { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 5 }); - beatmap.Stages.Add(new StageDefinition { Columns = 5 }); + var beatmap = new ManiaBeatmap(new StageDefinition(5)); + beatmap.Stages.Add(new StageDefinition(5)); var frame = new ManiaReplayFrame(0, actions); var legacyFrame = frame.ToLegacy(beatmap); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs index c83e8a51ed..3bd654e75e 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs @@ -35,10 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests }, 7)] public void Test(IEnumerable special, int columns) { - var definition = new StageDefinition - { - Columns = columns - }; + var definition = new StageDefinition(columns); var results = getResults(definition); Assert.AreEqual(special, results); } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index 7970d5b594..d27a79c41d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods private static ManiaBeatmap createRawBeatmap() { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + var beatmap = new ManiaBeatmap(new StageDefinition(1)); beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60 // Add test hit objects diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index 8f6a8c42b1..6fdf32dfbe 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning private readonly Column column; [Cached] - private readonly StageDefinition stageDefinition = new StageDefinition { Columns = 2 }; + private readonly StageDefinition stageDefinition = new StageDefinition(5); public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false) { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 9f235689b4..2c5535a65f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + [Cached] + private readonly StageDefinition stage = new StageDefinition(4); + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs index ff557638a9..1bfe55b074 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { var stageDefinitions = new List { - new StageDefinition { Columns = 4 }, + new StageDefinition(4), }; SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s => diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs index 62dadbc3dd..9817719c94 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { stageDefinitions = new List { - new StageDefinition { Columns = 2 } + new StageDefinition(2) }; SetContents(_ => new ManiaPlayfield(stageDefinitions)); @@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { stageDefinitions = new List { - new StageDefinition { Columns = 2 }, - new StageDefinition { Columns = 2 } + new StageDefinition(2), + new StageDefinition(2) }; SetContents(_ => new ManiaPlayfield(stageDefinitions)); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs index f3f1b9416f..07aa0b845f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) { - Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) + Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction) }; }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index 21ec85bbe6..3abeb8a5f6 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | - | // | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + var beatmap = new ManiaBeatmap(new StageDefinition(1)); beatmap.HitObjects.Add(new Note { StartTime = 1000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | * | // | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + var beatmap = new ManiaBeatmap(new StageDefinition(1)); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | - | - | // | | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + var beatmap = new ManiaBeatmap(new StageDefinition(2)); beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | * | * | // | | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + var beatmap = new ManiaBeatmap(new StageDefinition(2)); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | - | | // | | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + var beatmap = new ManiaBeatmap(new StageDefinition(2)); beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | * | | // | | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + var beatmap = new ManiaBeatmap(new StageDefinition(2)); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | * | | // | | | - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + var beatmap = new ManiaBeatmap(new StageDefinition(2)); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 8fff87ae78..3bac3c7650 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(typeof(IReadOnlyList))] private IReadOnlyList mods { get; set; } = Array.Empty(); + [Cached] + private readonly StageDefinition stage = new StageDefinition(1); + private readonly List columns = new List(); public TestSceneColumn() diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs index bd34f1ef11..ea033082a7 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs @@ -4,11 +4,13 @@ #nullable disable using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests private Column column; + [Cached] + private readonly StageDefinition stage = new StageDefinition(1); + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index a563dc3106..1f139b5b78 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests { AddStep("load player", () => { - Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }) + Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition(4)) { HitObjects = hitObjects, BeatmapInfo = diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index cf8947c1ed..6387dac957 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests { var specialAction = ManiaAction.Special1; - var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); + var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction); stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index e84d02775a..9f2e3d2502 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests { const double beat_length = 500; - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) + var beatmap = new ManiaBeatmap(new StageDefinition(1)) { HitObjects = { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 90cd7f57b5..632b7cdcc7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -93,10 +93,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap CreateBeatmap() { - beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }, originalTargetColumns); + beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns), originalTargetColumns); if (Dual) - beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns }); + beatmap.Stages.Add(new StageDefinition(TargetColumns)); return beatmap; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index 08ee97b32b..898b558eb3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Beatmaps @@ -15,7 +16,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The number of s which this stage contains. /// - public int Columns; + public readonly int Columns; + + public StageDefinition(int columns) + { + if (columns < 1) + throw new ArgumentException("Column count must be above zero.", nameof(columns)); + + Columns = columns; + } /// /// Whether the column index is a special column for this stage. diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 0a59c10b5f..1c06f3cffb 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy isLegacySkin = new Lazy(() => GetConfig(SkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { - string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, new StageDefinition { Columns = 1 }, 0)?.Value ?? "mania-key1"; + string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, new StageDefinition(1), 0)?.Value ?? "mania-key1"; return this.GetAnimation(keyImage, true, true) != null; }); } @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (!hit_result_mapping.ContainsKey(result)) return null; - string filename = this.GetManiaSkinConfig(hit_result_mapping[result], new StageDefinition())?.Value + string filename = this.GetManiaSkinConfig(hit_result_mapping[result], new StageDefinition(1))?.Value ?? default_hit_result_skin_filenames[result]; var animation = this.GetAnimation(filename, true, true); From 3947011baf594cec7b6f7ab4984f22d75e5305b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 19:21:38 +0900 Subject: [PATCH 501/583] Fix regression in legacy dual stage handling logic --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 +++--- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 9 +++++++-- .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 12 +++++++----- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 7be4243ad6..9d2182f276 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -71,11 +71,11 @@ namespace osu.Game.Rulesets.Mania case TrianglesSkin: return new ManiaTrianglesSkinTransformer(skin); - case LegacySkin: - return new ManiaLegacySkinTransformer(skin); - case ArgonSkin: return new ManiaArgonSkinTransformer(skin); + + case LegacySkin: + return new ManiaLegacySkinTransformer(skin, beatmap); } return null; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 1c06f3cffb..5a0478f025 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Scoring; @@ -56,9 +57,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy /// private readonly Lazy hasKeyTexture; - public ManiaLegacySkinTransformer(ISkin skin) + private readonly ManiaBeatmap beatmap; + + public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap) : base(skin) { + this.beatmap = (ManiaBeatmap)beatmap; + isLegacySkin = new Lazy(() => GetConfig(SkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { @@ -144,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return base.GetConfig(new LegacyManiaSkinConfigurationLookup(maniaLookup.StageDefinition.Columns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); return base.GetConfig(lookup); } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 45454be4a5..8e786c96d9 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -1,19 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Skinning { public class LegacyManiaSkinConfigurationLookup { - public readonly int Keys; + /// + /// Total columns across all stages. + /// + public readonly int TotalColumns; + public readonly LegacyManiaSkinConfigurationLookups Lookup; public readonly int? TargetColumn; - public LegacyManiaSkinConfigurationLookup(int keys, LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) + public LegacyManiaSkinConfigurationLookup(int totalColumns, LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) { - Keys = keys; + TotalColumns = totalColumns; Lookup = lookup; TargetColumn = targetColumn; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 1e096702b3..e1a630b643 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -128,8 +128,8 @@ namespace osu.Game.Skinning private IBindable? lookupForMania(LegacyManiaSkinConfigurationLookup maniaLookup) { - if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing)) - maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys); + if (!maniaConfigurations.TryGetValue(maniaLookup.TotalColumns, out var existing)) + maniaConfigurations[maniaLookup.TotalColumns] = existing = new LegacyManiaSkinConfiguration(maniaLookup.TotalColumns); switch (maniaLookup.Lookup) { From 276395f1afebe3902aa57245a4ccddd65b52d1cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 19:51:18 +0900 Subject: [PATCH 502/583] Fix column colour not updating on skin change --- osu.Game.Rulesets.Mania/UI/Column.cs | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 75d6d4c206..8c50ab919b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -65,14 +65,17 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }; } - [BackgroundDependencyLoader] - private void load(ISkinSource skin, StageDefinition stageDefinition) - { - AccentColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, Index)?.Value ?? Color4.Black; + [Resolved] + private ISkinSource skin { get; set; } - //AccentColour = columnColours[isSpecial], - foreach (var obj in HitObjectContainer.Objects) - obj.AccentColour.Value = AccentColour; + [Resolved] + private StageDefinition stageDefinition { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + skin.SourceChanged += onSourceChanged; + onSourceChanged(); Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { @@ -104,13 +107,25 @@ namespace osu.Game.Rulesets.Mania.UI RegisterPool(50, 250); } + private void onSourceChanged() + { + AccentColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, Index)?.Value ?? Color4.Black; + foreach (var obj in HitObjectContainer.Objects) + obj.AccentColour.Value = AccentColour; + } + protected override void LoadComplete() { base.LoadComplete(); - NewResult += OnNewResult; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + skin.SourceChanged += onSourceChanged; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From 532d101080e04405149a43c873f99aa4ba10dd5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 19:58:39 +0900 Subject: [PATCH 503/583] Remove unused class --- .../UI/Components/ColumnBackground.cs | 110 ------------------ 1 file changed, 110 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs deleted file mode 100644 index 5bd2d3ab48..0000000000 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -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.Graphics; -using osu.Game.Rulesets.UI.Scrolling; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Mania.UI.Components -{ - public class ColumnBackground : CompositeDrawable, IKeyBindingHandler, IHasAccentColour - { - private readonly IBindable action = new Bindable(); - - private Box background; - private Box backgroundOverlay; - - private readonly IBindable direction = new Bindable(); - - [BackgroundDependencyLoader] - private void load(IBindable action, IScrollingInfo scrollingInfo) - { - this.action.BindTo(action); - - InternalChildren = new[] - { - background = new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - }, - backgroundOverlay = new Box - { - Name = "Background Gradient Overlay", - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Blending = BlendingParameters.Additive, - Alpha = 0 - } - }; - - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(dir => - { - backgroundOverlay.Anchor = backgroundOverlay.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; - updateColours(); - }, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateColours(); - } - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - updateColours(); - } - } - - private void updateColours() - { - if (!IsLoaded) - return; - - background.Colour = AccentColour.Darken(5); - - var brightPoint = AccentColour.Opacity(0.6f); - var dimPoint = AccentColour.Opacity(0); - - backgroundOverlay.Colour = ColourInfo.GradientVertical( - direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint, - direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint); - } - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Action == action.Value) - backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - if (e.Action == action.Value) - backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); - } - } -} From 6b79f164612c9d4a9608650d4954d82e06b84773 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 20:02:02 +0900 Subject: [PATCH 504/583] Make `Column.AccentColour` bindable --- .../ManiaPlacementBlueprintTestScene.cs | 2 +- .../Skinning/ColumnTestContainer.cs | 2 +- .../TestSceneColumn.cs | 2 +- .../TestSceneDrawableManiaHitObject.cs | 2 +- .../Skinning/Argon/ArgonKeyArea.cs | 23 +++--- osu.Game.Rulesets.Mania/UI/Column.cs | 15 ++-- .../UI/Components/DefaultColumnBackground.cs | 12 +++- .../UI/Components/DefaultHitTarget.cs | 16 +++-- .../UI/Components/DefaultKeyArea.cs | 20 ++++-- .../UI/DefaultHitExplosion.cs | 71 ++++++++++--------- 10 files changed, 101 insertions(+), 64 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index a759e95d17..0e4f612999 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AccentColour = Color4.OrangeRed, + AccentColour = { Value = Color4.OrangeRed }, Clock = new FramedClock(new StopwatchClock()), // No scroll }); } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index 6fdf32dfbe..cefcce8319 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning this.column = new Column(column, false) { Action = { Value = action }, - AccentColour = Color4.Orange, + AccentColour = { Value = Color4.Orange }, Alpha = showColumn ? 1 : 0 }, content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 3bac3c7650..83491b6fe9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Tests Anchor = Anchor.Centre, Origin = Anchor.Centre, Height = 0.85f, - AccentColour = Color4.OrangeRed, + AccentColour = { Value = Color4.OrangeRed }, Action = { Value = action }, }; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs index ea033082a7..d273f5cb35 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Tests { Action = { Value = ManiaAction.Key1 }, Height = 0.85f, - AccentColour = Color4.Gray + AccentColour = { Value = Color4.Gray }, }, }; }); diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 0153cdbd68..9d16e84f1e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private Container bottomIcon = null!; private CircularContainer topIcon = null!; + private Bindable accentColour = null!; + [Resolved] private Column column { get; set; } = null!; @@ -55,7 +57,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { Name = "Key gradient", RelativeSizeAxes = Axes.Both, - Colour = column.AccentColour.Darken(0.6f), }, hitTargetLine = new Circle { @@ -80,7 +81,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, Blending = BlendingParameters.Additive, - Colour = column.AccentColour, Y = icon_vertical_offset, Children = new[] { @@ -134,6 +134,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); + + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => + { + background.Colour = colour.NewValue.Darken(0.6f); + bottomIcon.Colour = colour.NewValue; + }, true); } private void onDirectionChanged(ValueChangedEvent direction) @@ -159,11 +166,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon if (e.Action != column.Action.Value) return false; const double lighting_fade_in_duration = 50; - Color4 lightingColour = column.AccentColour.Lighten(0.9f); + Color4 lightingColour = accentColour.Value.Lighten(0.9f); background - .FadeColour(column.AccentColour.Lighten(0.4f), 40).Then() - .FadeColour(column.AccentColour, 150, Easing.OutQuint); + .FadeColour(accentColour.Value.Lighten(0.4f), 40).Then() + .FadeColour(accentColour.Value, 150, Easing.OutQuint); hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters @@ -201,9 +208,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon if (e.Action != column.Action.Value) return; const double lighting_fade_out_duration = 300; - Color4 lightingColour = column.AccentColour.Lighten(0.9f).Opacity(0); + Color4 lightingColour = accentColour.Value.Lighten(0.9f).Opacity(0); - background.FadeColour(column.AccentColour.Darken(0.6f), lighting_fade_out_duration, Easing.OutQuint); + background.FadeColour(accentColour.Value.Darken(0.6f), lighting_fade_out_duration, Easing.OutQuint); topIcon.ScaleTo(1f, 200, Easing.OutQuint); topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters @@ -221,7 +228,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Radius = 30, }, lighting_fade_out_duration, Easing.OutQuint); - bottomIcon.FadeColour(column.AccentColour, lighting_fade_out_duration, Easing.OutQuint); + bottomIcon.FadeColour(accentColour.Value, lighting_fade_out_duration, Easing.OutQuint); foreach (var circle in bottomIcon) { diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 8c50ab919b..81d9ae28ab 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI /// public readonly bool IsSpecial; - public Color4 AccentColour { get; set; } + public readonly Bindable AccentColour = new Bindable(Color4.Black); public Column(int index, bool isSpecial) { @@ -77,6 +77,13 @@ namespace osu.Game.Rulesets.Mania.UI skin.SourceChanged += onSourceChanged; onSourceChanged(); + AccentColour.BindValueChanged(colour => + { + // Manual transfer as hit objects may be moved between column and unbinding is non-trivial. + foreach (var obj in HitObjectContainer.Objects) + obj.AccentColour.Value = colour.NewValue; + }, true); + Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both @@ -109,9 +116,7 @@ namespace osu.Game.Rulesets.Mania.UI private void onSourceChanged() { - AccentColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, Index)?.Value ?? Color4.Black; - foreach (var obj in HitObjectContainer.Objects) - obj.AccentColour.Value = AccentColour; + AccentColour.Value = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, Index)?.Value ?? Color4.Black; } protected override void LoadComplete() @@ -139,7 +144,7 @@ namespace osu.Game.Rulesets.Mania.UI DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject; - maniaObject.AccentColour.Value = AccentColour; + maniaObject.AccentColour.Value = AccentColour.Value; maniaObject.CheckHittable = hitPolicy.IsHittable; } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs index 39d17db6be..3680e7ea0a 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components [Resolved] private Column column { get; set; } + private Bindable accentColour; + public DefaultColumnBackground() { RelativeSizeAxes = Axes.Both; @@ -55,9 +57,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components } }; - background.Colour = column.AccentColour.Darken(5); - brightColour = column.AccentColour.Opacity(0.6f); - dimColour = column.AccentColour.Opacity(0); + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => + { + background.Colour = colour.NewValue.Darken(5); + brightColour = colour.NewValue.Opacity(0.6f); + dimColour = colour.NewValue.Opacity(0); + }, true); direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs index 53fa86125f..97aa897782 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components private Container hitTargetLine; private Drawable hitTargetBar; + private Bindable accentColour; + [Resolved] private Column column { get; set; } @@ -54,12 +56,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components }, }; - hitTargetLine.EdgeEffect = new EdgeEffectParameters + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = column.AccentColour.Opacity(0.5f), - }; + hitTargetLine.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = colour.NewValue.Opacity(0.5f), + }; + }, true); direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 5a0fab2ff4..6196850f91 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components private Container keyIcon; private Drawable gradient; + private Bindable accentColour; + [Resolved] private Column column { get; set; } @@ -75,15 +77,19 @@ namespace osu.Game.Rulesets.Mania.UI.Components } }; - keyIcon.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = column.AccentColour.Opacity(0.5f), - }; - direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); + + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => + { + keyIcon.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = colour.NewValue.Opacity(0.5f), + }; + }); } private void onDirectionChanged(ValueChangedEvent direction) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index e83cd10d2d..59716ee3e2 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -32,6 +32,10 @@ namespace osu.Game.Rulesets.Mania.UI private CircularContainer largeFaint; private CircularContainer mainGlow1; + private CircularContainer mainGlow2; + private CircularContainer mainGlow3; + + private Bindable accentColour; public DefaultHitExplosion() { @@ -48,8 +52,6 @@ namespace osu.Game.Rulesets.Mania.UI const float roundness = 80; const float initial_height = 10; - var colour = Interpolation.ValueAt(0.4f, column.AccentColour, Color4.White, 0, 1); - InternalChildren = new Drawable[] { largeFaint = new CircularContainer @@ -61,13 +63,6 @@ namespace osu.Game.Rulesets.Mania.UI // we want our size to be very small so the glow dominates it. Size = new Vector2(default_large_faint_size), Blending = BlendingParameters.Additive, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.1f, column.AccentColour, Color4.White, 0, 1).Opacity(0.3f), - Roundness = 160, - Radius = 200, - }, }, mainGlow1 = new CircularContainer { @@ -76,15 +71,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both, Masking = true, Blending = BlendingParameters.Additive, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.6f, column.AccentColour, Color4.White, 0, 1), - Roundness = 20, - Radius = 50, - }, }, - new CircularContainer + mainGlow2 = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,15 +81,8 @@ namespace osu.Game.Rulesets.Mania.UI Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, Rotation = RNG.NextSingle(-angle_variance, angle_variance), - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colour, - Roundness = roundness, - Radius = 40, - }, }, - new CircularContainer + mainGlow3 = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -110,18 +91,44 @@ namespace osu.Game.Rulesets.Mania.UI Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, Rotation = RNG.NextSingle(-angle_variance, angle_variance), - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colour, - Roundness = roundness, - Radius = 40, - }, } }; direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); + + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => + { + largeFaint.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.1f, colour.NewValue, Color4.White, 0, 1).Opacity(0.3f), + Roundness = 160, + Radius = 200, + }; + mainGlow1.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.6f, colour.NewValue, Color4.White, 0, 1), + Roundness = 20, + Radius = 50, + }; + mainGlow2.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.4f, colour.NewValue, Color4.White, 0, 1), + Roundness = roundness, + Radius = 40, + }; + mainGlow3.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.4f, colour.NewValue, Color4.White, 0, 1), + Roundness = roundness, + Radius = 40, + }; + }, true); } private void onDirectionChanged(ValueChangedEvent direction) From 4a127f5d8123d5ed782505c1f3d9ae8ae5817a23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Oct 2022 22:41:20 +0900 Subject: [PATCH 505/583] Fix classic skin colours sourcing from triangles defaults --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 ++ .../Legacy/ManiaClassicSkinTransformer.cs | 38 +++++++++++++++++++ .../Legacy/ManiaLegacySkinTransformer.cs | 17 ++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 9d2182f276..c4a8b7c8fa 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -74,6 +74,9 @@ namespace osu.Game.Rulesets.Mania case ArgonSkin: return new ManiaArgonSkinTransformer(skin); + case DefaultLegacySkin: + return new ManiaClassicSkinTransformer(skin, beatmap); + case LegacySkin: return new ManiaLegacySkinTransformer(skin, beatmap); } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs new file mode 100644 index 0000000000..e57927897c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs @@ -0,0 +1,38 @@ +// 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.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Legacy +{ + public class ManiaClassicSkinTransformer : ManiaLegacySkinTransformer + { + public ManiaClassicSkinTransformer(ISkin skin, IBeatmap beatmap) + : base(skin, beatmap) + { + } + + public override IBindable GetConfig(TLookup lookup) + { + if (lookup is ManiaSkinConfigurationLookup maniaLookup) + { + var baseLookup = base.GetConfig(lookup); + + if (baseLookup != null) + return baseLookup; + + // default provisioning. + switch (maniaLookup.Lookup) + { + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + return SkinUtils.As(new Bindable(Color4.Black)); + } + } + + return base.GetConfig(lookup); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 5a0478f025..759a1c2b38 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -8,12 +8,14 @@ using System.Collections.Generic; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Legacy { @@ -149,7 +151,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); + { + var legacyLookup = + base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); + + if (legacyLookup != null) + return legacyLookup; + + // default legacy fallback. + switch (maniaLookup.Lookup) + { + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + return SkinUtils.As(new Bindable(Color4.Black)); + } + } return base.GetConfig(lookup); } From 0d21c0e49c682040034b97ee640cfbcbf6252403 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 14:10:39 +0900 Subject: [PATCH 506/583] Remove `StageDefinition` from configuration lookups I added this for future usage, but it turns out I can get the definitions directly from `ManiaBeatmap`. --- .../Skinning/Legacy/HitTargetInsetContainer.cs | 5 ++--- .../Skinning/Legacy/LegacyColumnBackground.cs | 5 ++--- .../Skinning/Legacy/LegacyHitTarget.cs | 9 ++++----- .../Skinning/Legacy/LegacyManiaColumnElement.cs | 2 +- .../Skinning/Legacy/LegacyManiaJudgementPiece.cs | 5 ++--- .../Skinning/Legacy/LegacyNotePiece.cs | 5 ++--- .../Skinning/Legacy/LegacyStageBackground.cs | 14 +++++++------- .../Skinning/Legacy/LegacyStageForeground.cs | 5 ++--- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++-- .../Skinning/ManiaSkinConfigExtensions.cs | 6 ++---- .../Skinning/ManiaSkinConfigurationLookup.cs | 10 +--------- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 4 ++-- .../UI/Components/HitObjectArea.cs | 6 +----- 13 files changed, 30 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs index de6d048295..362a265789 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/HitTargetInsetContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -31,9 +30,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition, stageDefinition)?.Value ?? Stage.HIT_TARGET_POSITION; + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs index 08fa9b9a73..f35cedab08 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -31,9 +30,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage, stageDefinition)?.Value + string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value ?? "mania-stage-light"; float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs index 9819f09e10..611dac30b3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitTarget.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -24,15 +23,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private Container directionContainer; [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string targetImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitTargetImage, stageDefinition)?.Value + string targetImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value ?? "mania-stage-hint"; - bool showJudgementLine = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ShowJudgementLine, stageDefinition)?.Value + bool showJudgementLine = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value ?? true; - Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.JudgementLineColour, stageDefinition)?.Value + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value ?? Color4.White; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs index cfebad4add..e227c80845 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs @@ -42,6 +42,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } protected IBindable GetColumnSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) - => skin.GetManiaSkinConfig(lookup, stage, Column.Index); + => skin.GetManiaSkinConfig(lookup, Column.Index); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs index f9653dab15..d09a73a693 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaJudgementPiece.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -33,9 +32,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, StageDefinition stageDefinition) + private void load(ISkinSource skin) { - float? scorePosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ScorePosition, stageDefinition)?.Value; + float? scorePosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value; if (scorePosition != null) scorePosition -= Stage.HIT_TARGET_POSITION + 150; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs index 779e1a152c..41e149ea2f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -36,9 +35,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth, stageDefinition))?.Value; + minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value; InternalChild = directionContainer = new Container { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs index 01c2b599fa..d039551cd7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin, StageDefinition stageDefinition) { - string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage, stageDefinition)?.Value + string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value ?? "mania-stage-left"; - string rightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightStageImage, stageDefinition)?.Value + string rightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value ?? "mania-stage-right"; InternalChildren = new[] @@ -91,16 +91,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, StageDefinition stageDefinition) + private void load(ISkinSource skin) { - float leftLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftLineWidth, stageDefinition, columnIndex)?.Value ?? 1; - float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, stageDefinition, columnIndex)?.Value ?? 1; + float leftLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1; + float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = (rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m) || isLastColumn; - Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, stageDefinition, columnIndex)?.Value ?? Color4.White; - Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, columnIndex)?.Value ?? Color4.Black; + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White; + Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black; InternalChildren = new Drawable[] { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs index 57b76c5402..f7c611d551 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -26,9 +25,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo, StageDefinition stageDefinition) + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage, stageDefinition)?.Value + string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value ?? "mania-stage-bottom"; sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 759a1c2b38..8dc81f2101 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy isLegacySkin = new Lazy(() => GetConfig(SkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { - string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, new StageDefinition(1), 0)?.Value ?? "mania-key1"; + string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; return this.GetAnimation(keyImage, true, true) != null; }); } @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (!hit_result_mapping.ContainsKey(result)) return null; - string filename = this.GetManiaSkinConfig(hit_result_mapping[result], new StageDefinition(1))?.Value + string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value ?? default_hit_result_skin_filenames[result]; var animation = this.GetAnimation(filename, true, true); diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs index 541b679043..e22bf63049 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs @@ -4,7 +4,6 @@ #nullable disable using osu.Framework.Bindables; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning @@ -16,10 +15,9 @@ namespace osu.Game.Rulesets.Mania.Skinning /// /// The skin from which configuration is retrieved. /// The value to retrieve. - /// The stage definition. /// If not null, denotes the index of the column to which the entry applies. - public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, StageDefinition stageDefinition, int? columnIndex = null) + public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null) => skin.GetConfig( - new ManiaSkinConfigurationLookup(lookup, stageDefinition, columnIndex)); + new ManiaSkinConfigurationLookup(lookup, columnIndex)); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs index 8008c988d4..4ec18f9b01 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; @@ -16,11 +15,6 @@ namespace osu.Game.Rulesets.Mania.Skinning /// public readonly LegacyManiaSkinConfigurationLookups Lookup; - /// - /// The stage containing the component which is performing this lookup. - /// - public readonly StageDefinition StageDefinition; - /// /// The intended index for the configuration. /// May be null if the configuration does not apply to a . @@ -31,12 +25,10 @@ namespace osu.Game.Rulesets.Mania.Skinning /// Creates a new . /// /// The lookup value. - /// The stage definition. /// The intended index for the configuration. May be null if the configuration does not apply to a . - public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, StageDefinition stageDefinition, int? columnIndex = null) + public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null) { Lookup = lookup; - StageDefinition = stageDefinition; ColumnIndex = columnIndex; } } diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index bde2308635..871ec9f1a3 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Mania.UI if (i > 0) { float spacing = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, stageDefinition, i - 1)) + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1)) ?.Value ?? Stage.COLUMN_SPACING; columns[i].Margin = new MarginPadding { Left = spacing }; } float? width = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, stageDefinition, i)) + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i)) ?.Value; if (width == null) diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index 69bf146951..7f4b8eacde 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -20,9 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components protected readonly IBindable Direction = new Bindable(); public readonly HitObjectContainer HitObjectContainer; - [Resolved] - private StageDefinition stageDefinition { get; set; } - public HitObjectArea(HitObjectContainer hitObjectContainer) { InternalChild = new Container @@ -53,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components protected virtual void UpdateHitPosition() { float hitPosition = CurrentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition, stageDefinition))?.Value + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value ?? Stage.HIT_TARGET_POSITION; Padding = Direction.Value == ScrollingDirection.Up From 13e0a59f707e38144bdfc709121f220f1196b360 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 14:19:00 +0900 Subject: [PATCH 507/583] Add note about why `LegacyManiaSkinConfigurationLookup` exist --- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 8e786c96d9..6f934d1a92 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -3,6 +3,11 @@ namespace osu.Game.Skinning { + /// + /// This class exists for the explicit purpose of ferrying information from ManiaBeatmap in a way LegacySkin can use it. + /// This is because half of the mania legacy skin implementation is in LegacySkin (osu.Game project) which doesn't have visibility + /// over ManiaBeatmap / StageDefinition. + /// public class LegacyManiaSkinConfigurationLookup { /// @@ -10,9 +15,10 @@ namespace osu.Game.Skinning /// public readonly int TotalColumns; - public readonly LegacyManiaSkinConfigurationLookups Lookup; public readonly int? TargetColumn; + public readonly LegacyManiaSkinConfigurationLookups Lookup; + public LegacyManiaSkinConfigurationLookup(int totalColumns, LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) { TotalColumns = totalColumns; From eea3d5adb827c40bbc1c46cb38011623e1e32a5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 14:26:29 +0900 Subject: [PATCH 508/583] Standardise column index naming and xmldoc --- .../Skinning/ManiaSkinConfigurationLookup.cs | 3 +- .../LegacyManiaSkinConfigurationLookup.cs | 11 +++- osu.Game/Skinning/LegacySkin.cs | 64 +++++++++---------- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs index 4ec18f9b01..59188f02f9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs @@ -16,8 +16,9 @@ namespace osu.Game.Rulesets.Mania.Skinning public readonly LegacyManiaSkinConfigurationLookups Lookup; /// - /// The intended index for the configuration. + /// The column which is being looked up. /// May be null if the configuration does not apply to a . + /// Note that this is the absolute index across all stages. /// public readonly int? ColumnIndex; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 6f934d1a92..c9e96ab3f1 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -15,15 +15,20 @@ namespace osu.Game.Skinning /// public readonly int TotalColumns; - public readonly int? TargetColumn; + /// + /// The column which is being looked up. + /// May be null if the configuration does not apply to a specific column. + /// Note that this is the absolute index across all stages. + /// + public readonly int? ColumnIndex; public readonly LegacyManiaSkinConfigurationLookups Lookup; - public LegacyManiaSkinConfigurationLookup(int totalColumns, LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) + public LegacyManiaSkinConfigurationLookup(int totalColumns, LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null) { TotalColumns = totalColumns; Lookup = lookup; - TargetColumn = targetColumn; + ColumnIndex = columnIndex; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1a630b643..646746a0f3 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -134,12 +134,12 @@ namespace osu.Game.Skinning switch (maniaLookup.Lookup) { case LegacyManiaSkinConfigurationLookups.ColumnWidth: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value])); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.ColumnIndex.Value])); case LegacyManiaSkinConfigurationLookups.ColumnSpacing: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(new Bindable(existing.ColumnSpacing[maniaLookup.TargetColumn.Value])); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(new Bindable(existing.ColumnSpacing[maniaLookup.ColumnIndex.Value])); case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); @@ -157,15 +157,15 @@ namespace osu.Game.Skinning return SkinUtils.As(getManiaImage(existing, "LightingN")); case LegacyManiaSkinConfigurationLookups.ExplosionScale: - Debug.Assert(maniaLookup.TargetColumn != null); + Debug.Assert(maniaLookup.ColumnIndex != null); if (GetConfig(SkinConfiguration.LegacySetting.Version)?.Value < 2.5m) return SkinUtils.As(new Bindable(1)); - if (existing.ExplosionWidth[maniaLookup.TargetColumn.Value] != 0) - return SkinUtils.As(new Bindable(existing.ExplosionWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + if (existing.ExplosionWidth[maniaLookup.ColumnIndex.Value] != 0) + return SkinUtils.As(new Bindable(existing.ExplosionWidth[maniaLookup.ColumnIndex.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); - return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.ColumnIndex.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); @@ -174,53 +174,53 @@ namespace osu.Game.Skinning return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn + 1}")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.ColumnIndex + 1}")); case LegacyManiaSkinConfigurationLookups.ColumnLightColour: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.ColumnIndex + 1}")); case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth: return SkinUtils.As(new Bindable(existing.MinimumColumnWidth)); case LegacyManiaSkinConfigurationLookups.NoteImage: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.ColumnIndex}")); case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}H")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.ColumnIndex}H")); case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}T")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.ColumnIndex}T")); case LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.ColumnIndex}L")); case LegacyManiaSkinConfigurationLookups.HoldNoteLightImage: return SkinUtils.As(getManiaImage(existing, "LightingL")); case LegacyManiaSkinConfigurationLookups.HoldNoteLightScale: - Debug.Assert(maniaLookup.TargetColumn != null); + Debug.Assert(maniaLookup.ColumnIndex != null); if (GetConfig(SkinConfiguration.LegacySetting.Version)?.Value < 2.5m) return SkinUtils.As(new Bindable(1)); - if (existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] != 0) - return SkinUtils.As(new Bindable(existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + if (existing.HoldNoteLightWidth[maniaLookup.ColumnIndex.Value] != 0) + return SkinUtils.As(new Bindable(existing.HoldNoteLightWidth[maniaLookup.ColumnIndex.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); - return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.ColumnIndex.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); case LegacyManiaSkinConfigurationLookups.KeyImage: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.ColumnIndex}")); case LegacyManiaSkinConfigurationLookups.KeyImageDown: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D")); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.ColumnIndex}D")); case LegacyManiaSkinConfigurationLookups.LeftStageImage: return SkinUtils.As(getManiaImage(existing, "StageLeft")); @@ -238,12 +238,12 @@ namespace osu.Game.Skinning return SkinUtils.As(getManiaImage(existing, "StageHint")); case LegacyManiaSkinConfigurationLookups.LeftLineWidth: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value])); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.ColumnIndex.Value])); case LegacyManiaSkinConfigurationLookups.RightLineWidth: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1])); + Debug.Assert(maniaLookup.ColumnIndex != null); + return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.ColumnIndex.Value + 1])); case LegacyManiaSkinConfigurationLookups.Hit0: case LegacyManiaSkinConfigurationLookups.Hit50: From dee01abab18c293eebb3320013bd2badbc9a531e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 14:51:17 +0900 Subject: [PATCH 509/583] Add method to get stage from column index --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs | 14 ++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 ++-- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 12 ++++++++++-- .../Default/ManiaTrianglesSkinTransformer.cs | 13 ++++++++++--- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 1 - osu.Game.Rulesets.Mania/UI/Column.cs | 6 +----- 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 4879ce6748..b5655a4579 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -60,5 +61,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps }, }; } + + public StageDefinition GetStageForColumnIndex(int column) + { + foreach (var stage in Stages) + { + if (column < stage.Columns) + return stage; + + column -= stage.Columns; + } + + throw new ArgumentOutOfRangeException(nameof(column), "Provided index exceeds all available stages"); + } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c4a8b7c8fa..6162184c9a 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mania switch (skin) { case TrianglesSkin: - return new ManiaTrianglesSkinTransformer(skin); + return new ManiaTrianglesSkinTransformer(skin, beatmap); case ArgonSkin: - return new ManiaArgonSkinTransformer(skin); + return new ManiaArgonSkinTransformer(skin, beatmap); case DefaultLegacySkin: return new ManiaClassicSkinTransformer(skin, beatmap); diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index ea34d8d4c5..80dc3978df 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -4,6 +4,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using osuTK.Graphics; @@ -11,9 +13,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public class ManiaArgonSkinTransformer : SkinTransformer { - public ManiaArgonSkinTransformer(ISkin skin) + private readonly ManiaBeatmap beatmap; + + public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap) : base(skin) { + this.beatmap = (ManiaBeatmap)beatmap; } public override Drawable? GetDrawableComponent(ISkinComponent component) @@ -44,7 +49,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (maniaLookup.Lookup) { case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: - if (maniaLookup.StageDefinition.IsSpecialColumn(maniaLookup.ColumnIndex ?? 0)) + int column = maniaLookup.ColumnIndex ?? 0; + var stage = beatmap.GetStageForColumnIndex(column); + + if (stage.IsSpecialColumn(column)) return SkinUtils.As(new Bindable(Color4.Yellow)); // TODO: Add actual colours. diff --git a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs index 88f1f6ed26..eb51179cea 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using osuTK.Graphics; @@ -10,9 +12,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default { public class ManiaTrianglesSkinTransformer : SkinTransformer { - public ManiaTrianglesSkinTransformer(ISkin skin) + private readonly ManiaBeatmap beatmap; + + public ManiaTrianglesSkinTransformer(ISkin skin, IBeatmap beatmap) : base(skin) { + this.beatmap = (ManiaBeatmap)beatmap; } private readonly Color4 colourEven = new Color4(6, 84, 0, 255); @@ -28,10 +33,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: int column = maniaLookup.ColumnIndex ?? 0; - if (maniaLookup.StageDefinition.IsSpecialColumn(column)) + var stage = beatmap.GetStageForColumnIndex(column); + + if (stage.IsSpecialColumn(column)) return SkinUtils.As(new Bindable(colourSpecial)); - int distanceToEdge = Math.Min(column, (maniaLookup.StageDefinition.Columns - 1) - column); + int distanceToEdge = Math.Min(column, (stage.Columns - 1) - column); return SkinUtils.As(new Bindable(distanceToEdge % 2 == 0 ? colourOdd : colourEven)); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 8dc81f2101..31cfcf9e16 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 81d9ae28ab..a622b8a155 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Skinning; @@ -68,9 +67,6 @@ namespace osu.Game.Rulesets.Mania.UI [Resolved] private ISkinSource skin { get; set; } - [Resolved] - private StageDefinition stageDefinition { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -116,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.UI private void onSourceChanged() { - AccentColour.Value = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, stageDefinition, Index)?.Value ?? Color4.Black; + AccentColour.Value = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, Index)?.Value ?? Color4.Black; } protected override void LoadComplete() From 9a92ff1681066b8ea9dfdb030b8674c5d241e79a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 16:24:43 +0900 Subject: [PATCH 510/583] Fix `SourceChanged` unbind directionality and add null check --- osu.Game.Rulesets.Mania/UI/Column.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index a622b8a155..89413f0f1b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -124,7 +124,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - skin.SourceChanged += onSourceChanged; + + if (skin != null) + skin.SourceChanged -= onSourceChanged; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From f1ea61b1a2bd1d277c9efa277075dd5e5d0a15df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 17:36:06 +0900 Subject: [PATCH 511/583] Remove unused colour code --- osu.Game.Rulesets.Mania/UI/Stage.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 52c8829df3..c4cbb74e57 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -43,13 +43,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Drawable barLineContainer; - // private readonly Dictionary columnColours = new Dictionary - // { - // { ColumnType.Even, new Color4(6, 84, 0, 255) }, - // { ColumnType.Odd, new Color4(94, 0, 57, 255) }, - // { ColumnType.Special, new Color4(0, 48, 63, 255) } - // }; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); private readonly int firstColumnIndex; From 6f8533ef6bc78450ef48fc74c3b64c0c8ea85102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 16:51:41 +0900 Subject: [PATCH 512/583] Remove pointless colour specifications in test scenes --- osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs | 2 -- .../Skinning/ManiaHitObjectTestScene.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index cefcce8319..d3e90170b2 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests.Skinning { @@ -34,7 +33,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning this.column = new Column(column, false) { Action = { Value = action }, - AccentColour = { Value = Color4.Orange }, Alpha = showColumn ? 1 : 0 }, content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index fd82041ad8..75175c43d8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -61,7 +61,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning c.Add(CreateHitObject().With(h => { h.HitObject.StartTime = Time.Current + 5000; - h.AccentColour.Value = Color4.Orange; })); }) }, From c1cb62cc35d75588f7462edceba8252762e3dc68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 16:54:08 +0900 Subject: [PATCH 513/583] Add basic argon note piece --- .../Skinning/Argon/ArgonHitTarget.cs | 3 +- .../Skinning/Argon/ArgonNotePiece.cs | 110 ++++++++++++++++++ .../Argon/ManiaArgonSkinTransformer.cs | 4 + 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs index 4750118583..7d7ef4a15e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.UI.Scrolling; using osuTK.Graphics; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private void load(IScrollingInfo scrollingInfo) { RelativeSizeAxes = Axes.X; - Height = DefaultNotePiece.NOTE_HEIGHT; + Height = ArgonNotePiece.NOTE_HEIGHT; InternalChildren = new[] { diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs new file mode 100644 index 0000000000..f560d2676c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs @@ -0,0 +1,110 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + internal class ArgonNotePiece : CompositeDrawable + { + public const float NOTE_HEIGHT = 42; + + public const float CORNER_RADIUS = 3; + + private readonly IBindable direction = new Bindable(); + private readonly IBindable accentColour = new Bindable(); + + private readonly Box colouredBox; + private readonly Box shadow; + + public ArgonNotePiece() + { + RelativeSizeAxes = Axes.X; + Height = NOTE_HEIGHT; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + shadow = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = 0.82f, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] + { + colouredBox = new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }, + new Circle + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = CORNER_RADIUS * 2, + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 4, + Icon = FontAwesome.Solid.AngleDown, + Size = new Vector2(20), + Scale = new Vector2(1, 0.7f) + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject) + { + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + + if (drawableObject != null) + { + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(onAccentChanged, true); + } + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up + ? Anchor.TopCentre + : Anchor.BottomCentre; + } + + private void onAccentChanged(ValueChangedEvent accent) + { + colouredBox.Colour = ColourInfo.GradientVertical( + accent.NewValue.Lighten(0.1f), + accent.NewValue + ); + + shadow.Colour = accent.NewValue.Darken(0.5f); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 80dc3978df..d280a16113 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -29,6 +29,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. switch (maniaComponent.Component) { + case ManiaSkinComponents.HoldNoteHead: + case ManiaSkinComponents.Note: + return new ArgonNotePiece(); + case ManiaSkinComponents.HitTarget: return new ArgonHitTarget(); From a10f9ebfa5b71039da9083464bf55f77d7784be3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 16:54:32 +0900 Subject: [PATCH 514/583] Update argon colours to roughly match design spec --- .../Argon/ManiaArgonSkinTransformer.cs | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index d280a16113..4843621a1e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; @@ -56,11 +55,49 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon int column = maniaLookup.ColumnIndex ?? 0; var stage = beatmap.GetStageForColumnIndex(column); - if (stage.IsSpecialColumn(column)) - return SkinUtils.As(new Bindable(Color4.Yellow)); + Color4 colour; - // TODO: Add actual colours. - return SkinUtils.As(new Bindable(new Color4(RNG.NextSingle() * 0.5f, RNG.NextSingle() * 0.5f, RNG.NextSingle() * 0.5f, 1))); + if (stage.IsSpecialColumn(column)) + colour = new Color4(159, 101, 255, 255); + else + { + switch (column % 8) + { + default: + colour = new Color4(240, 216, 0, 255); + break; + + case 1: + colour = new Color4(240, 101, 0, 255); + break; + + case 2: + colour = new Color4(240, 0, 130, 255); + break; + + case 3: + colour = new Color4(192, 0, 240, 255); + break; + + case 4: + colour = new Color4(178, 0, 240, 255); + break; + + case 5: + colour = new Color4(0, 96, 240, 255); + break; + + case 6: + colour = new Color4(0, 226, 240, 255); + break; + + case 7: + colour = new Color4(0, 240, 96, 255); + break; + } + } + + return SkinUtils.As(new Bindable(colour)); } } From 6c0923ec1aa421b3dc4252e8327ae1eb27005b37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 16:54:47 +0900 Subject: [PATCH 515/583] Add argon hold note pieces --- .../Skinning/Argon/ArgonHoldBodyPiece.cs | 90 ++++++++++++++++++ .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 91 +++++++++++++++++++ .../Argon/ManiaArgonSkinTransformer.cs | 6 ++ 3 files changed, 187 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs new file mode 100644 index 0000000000..10d5a4d267 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs @@ -0,0 +1,90 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + /// + /// Represents length-wise portion of a hold note. + /// + public class ArgonHoldBodyPiece : CompositeDrawable + { + protected readonly Bindable AccentColour = new Bindable(); + protected readonly IBindable IsHitting = new Bindable(); + + private Drawable background = null!; + private Box foreground = null!; + + public ArgonHoldBodyPiece() + { + Blending = BlendingParameters.Additive; + RelativeSizeAxes = Axes.Both; + + // Without this, the width of the body will be slightly larger than the head/tail. + Masking = true; + CornerRadius = ArgonNotePiece.CORNER_RADIUS; + } + + [BackgroundDependencyLoader(true)] + private void load(DrawableHitObject? drawableObject) + { + InternalChildren = new[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + foreground = new Box + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }; + + if (drawableObject != null) + { + var holdNote = (DrawableHoldNote)drawableObject; + + AccentColour.BindTo(drawableObject.AccentColour); + IsHitting.BindTo(holdNote.IsHitting); + } + + AccentColour.BindValueChanged(colour => + { + background.Colour = colour.NewValue.Opacity(0.2f); + foreground.Colour = colour.NewValue.Opacity(0.1f); + }, true); + + IsHitting.BindValueChanged(hitting => + { + const float animation_length = 50; + + foreground.ClearTransforms(); + + if (hitting.NewValue) + { + // wait for the next sync point + double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); + + using (foreground.BeginDelayedSequence(synchronisedOffset)) + { + foreground.FadeTo(1, animation_length).Then() + .FadeTo(0, animation_length) + .Loop(); + } + } + else + { + foreground.FadeOut(animation_length); + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs new file mode 100644 index 0000000000..e1068c6cd8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -0,0 +1,91 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + internal class ArgonHoldNoteTailPiece : CompositeDrawable + { + private readonly IBindable direction = new Bindable(); + private readonly IBindable accentColour = new Bindable(); + + private readonly Box colouredBox; + private readonly Box shadow; + + public ArgonHoldNoteTailPiece() + { + RelativeSizeAxes = Axes.X; + Height = ArgonNotePiece.NOTE_HEIGHT; + + CornerRadius = ArgonNotePiece.CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + shadow = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Height = 0.82f, + Masking = true, + CornerRadius = ArgonNotePiece.CORNER_RADIUS, + Children = new Drawable[] + { + colouredBox = new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }, + new Circle + { + RelativeSizeAxes = Axes.X, + Height = ArgonNotePiece.CORNER_RADIUS * 2, + }, + }; + } + + [BackgroundDependencyLoader(true)] + private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject) + { + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + + if (drawableObject != null) + { + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(onAccentChanged, true); + } + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up + ? Anchor.TopCentre + : Anchor.BottomCentre; + } + + private void onAccentChanged(ValueChangedEvent accent) + { + colouredBox.Colour = ColourInfo.GradientVertical( + accent.NewValue, + accent.NewValue.Darken(0.1f) + ); + + shadow.Colour = accent.NewValue.Darken(0.5f); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 4843621a1e..c2b258c57c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -28,6 +28,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. switch (maniaComponent.Component) { + case ManiaSkinComponents.HoldNoteBody: + return new ArgonHoldBodyPiece(); + + case ManiaSkinComponents.HoldNoteTail: + return new ArgonHoldNoteTailPiece(); + case ManiaSkinComponents.HoldNoteHead: case ManiaSkinComponents.Note: return new ArgonNotePiece(); From 4cb07b88390fcbba3f4f786b075748dd5aad1700 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 17:46:54 +0900 Subject: [PATCH 516/583] Specify column widths for argon --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index c2b258c57c..9123fa44d7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -55,11 +55,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (lookup is ManiaSkinConfigurationLookup maniaLookup) { + int column = maniaLookup.ColumnIndex ?? 0; + var stage = beatmap.GetStageForColumnIndex(column); + switch (maniaLookup.Lookup) { + case LegacyManiaSkinConfigurationLookups.ColumnWidth: + return SkinUtils.As(new Bindable( + stage.IsSpecialColumn(column) ? 80 : 60 + )); + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: - int column = maniaLookup.ColumnIndex ?? 0; - var stage = beatmap.GetStageForColumnIndex(column); Color4 colour; From a543222a2b2653045882c9b9e0ef58be7e8b723e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 18:03:48 +0900 Subject: [PATCH 517/583] Add ability to pad stage vertically --- .../Argon/ManiaArgonSkinTransformer.cs | 4 +++ osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 2 ++ osu.Game.Rulesets.Mania/UI/Stage.cs | 32 +++++++++++++++++++ .../LegacyManiaSkinConfigurationLookup.cs | 2 ++ 4 files changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 9123fa44d7..9ed324793b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -60,6 +60,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (maniaLookup.Lookup) { + case LegacyManiaSkinConfigurationLookups.StagePaddingBottom: + case LegacyManiaSkinConfigurationLookups.StagePaddingTop: + return SkinUtils.As(new Bindable(30)); + case LegacyManiaSkinConfigurationLookups.ColumnWidth: return SkinUtils.As(new Bindable( stage.IsSpecialColumn(column) ? 80 : 60 diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 871ec9f1a3..9b3f6d7033 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X; + Masking = true; + InternalChild = columns = new FillFlowContainer { RelativeSizeAxes = Axes.Y, diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index c4cbb74e57..1273cb3d32 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -130,6 +131,37 @@ namespace osu.Game.Rulesets.Mania.UI } } + private ISkinSource currentSkin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + currentSkin = skin; + + skin.SourceChanged += onSkinChanged; + onSkinChanged(); + } + + private void onSkinChanged() + { + float paddingTop = currentSkin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.StagePaddingTop))?.Value ?? 0; + float paddingBottom = currentSkin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.StagePaddingBottom))?.Value ?? 0; + + Padding = new MarginPadding + { + Top = paddingTop, + Bottom = paddingBottom, + }; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (currentSkin != null) + currentSkin.SourceChanged -= onSkinChanged; + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index c9e96ab3f1..3ec0ee6006 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -42,6 +42,8 @@ namespace osu.Game.Skinning HitPosition, ScorePosition, LightPosition, + StagePaddingTop, + StagePaddingBottom, HitTargetImage, ShowJudgementLine, KeyImage, From 59bbbf1c087b54f13d1e72a7c46defc0dde3c8f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 18:07:56 +0900 Subject: [PATCH 518/583] Improve colours and hit metrics on key area --- .../Skinning/Argon/ArgonHitTarget.cs | 3 ++ .../Skinning/Argon/ArgonKeyArea.cs | 30 ++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs index 7d7ef4a15e..9e449623d5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs @@ -21,6 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon RelativeSizeAxes = Axes.X; Height = ArgonNotePiece.NOTE_HEIGHT; + Masking = true; + CornerRadius = ArgonNotePiece.CORNER_RADIUS; + InternalChildren = new[] { new Box diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 9d16e84f1e..eef7e2ff7c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; @@ -53,18 +54,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Height = Stage.HIT_TARGET_POSITION, Children = new[] { - background = new Box + new Container { - Name = "Key gradient", + Masking = true, RelativeSizeAxes = Axes.Both, + CornerRadius = ArgonNotePiece.CORNER_RADIUS, + Child = background = new Box + { + Name = "Key gradient", + RelativeSizeAxes = Axes.Both, + }, }, hitTargetLine = new Circle { RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, + Origin = Anchor.BottomCentre, Colour = OsuColour.Gray(196 / 255f), - Height = 4, + Height = ArgonNotePiece.CORNER_RADIUS * 2, Masking = true, }, new Container @@ -137,10 +144,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon accentColour = column.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(colour => - { - background.Colour = colour.NewValue.Darken(0.6f); - bottomIcon.Colour = colour.NewValue; - }, true); + { + background.Colour = colour.NewValue.Darken(1f); + bottomIcon.Colour = colour.NewValue; + }, + true); } private void onDirectionChanged(ValueChangedEvent direction) @@ -169,8 +177,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Color4 lightingColour = accentColour.Value.Lighten(0.9f); background - .FadeColour(accentColour.Value.Lighten(0.4f), 40).Then() - .FadeColour(accentColour.Value, 150, Easing.OutQuint); + .FadeColour(accentColour.Value, 40).Then() + .FadeColour(accentColour.Value.Darken(0.4f), 150, Easing.OutQuint); hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters @@ -225,7 +233,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { Type = EdgeEffectType.Glow, Colour = lightingColour, - Radius = 30, + Radius = 25, }, lighting_fade_out_duration, Easing.OutQuint); bottomIcon.FadeColour(accentColour.Value, lighting_fade_out_duration, Easing.OutQuint); From f3e3ee81cbda6f9968896c31a172943e01ec2486 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 18:12:55 +0900 Subject: [PATCH 519/583] Add column and stage background to argon skin --- .../Skinning/Argon/ArgonColumnBackground.cs | 101 ++++++++++++++++++ .../Skinning/Argon/ArgonStageBackground.cs | 16 +++ .../Argon/ManiaArgonSkinTransformer.cs | 6 ++ 3 files changed, 123 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs new file mode 100644 index 0000000000..c6377f9152 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs @@ -0,0 +1,101 @@ +// 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.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.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ArgonColumnBackground : CompositeDrawable, IKeyBindingHandler + { + private readonly IBindable direction = new Bindable(); + + private Color4 brightColour; + private Color4 dimColour; + + private Box background = null!; + private Box backgroundOverlay = null!; + + [Resolved] + private Column column { get; set; } = null!; + + private Bindable accentColour = null!; + + public ArgonColumnBackground() + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = ArgonNotePiece.CORNER_RADIUS; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + InternalChildren = new[] + { + background = new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + }, + backgroundOverlay = new Box + { + Name = "Background Gradient Overlay", + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Blending = BlendingParameters.Additive, + Alpha = 0 + } + }; + + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => + { + background.Colour = colour.NewValue.Darken(5); + brightColour = colour.NewValue.Opacity(0.6f); + dimColour = colour.NewValue.Opacity(0); + }, true); + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft; + backgroundOverlay.Colour = ColourInfo.GradientVertical(brightColour, dimColour); + } + else + { + backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft; + backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour); + } + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == column.Action.Value) + 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); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs new file mode 100644 index 0000000000..1881695b14 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs @@ -0,0 +1,16 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ArgonStageBackground : CompositeDrawable + { + public ArgonStageBackground() + { + RelativeSizeAxes = Axes.Both; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 9ed324793b..45ee25e388 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -28,6 +28,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. switch (maniaComponent.Component) { + case ManiaSkinComponents.StageBackground: + return new ArgonStageBackground(); + + case ManiaSkinComponents.ColumnBackground: + return new ArgonColumnBackground(); + case ManiaSkinComponents.HoldNoteBody: return new ArgonHoldBodyPiece(); From 258c93557408a5aefaa8bc473c64cea7e582c532 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 18:29:07 +0900 Subject: [PATCH 520/583] Remove key area background (the column background is enough to work well) --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index eef7e2ff7c..72f5f81890 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; @@ -62,6 +61,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Child = background = new Box { Name = "Key gradient", + Alpha = 0, + Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, }, }, @@ -177,8 +178,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Color4 lightingColour = accentColour.Value.Lighten(0.9f); background - .FadeColour(accentColour.Value, 40).Then() - .FadeColour(accentColour.Value.Darken(0.4f), 150, Easing.OutQuint); + .FadeTo(1, 40).Then() + .FadeTo(0.8f, 150, Easing.OutQuint); hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters @@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon const double lighting_fade_out_duration = 300; Color4 lightingColour = accentColour.Value.Lighten(0.9f).Opacity(0); - background.FadeColour(accentColour.Value.Darken(0.6f), lighting_fade_out_duration, Easing.OutQuint); + background.FadeTo(0, lighting_fade_out_duration, Easing.OutQuint); topIcon.ScaleTo(1f, 200, Easing.OutQuint); topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters From b95743092d2e95110f5bf4379c20b29335f569ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 20:51:28 +0900 Subject: [PATCH 521/583] Add argon hit explosion --- .../Skinning/Argon/ArgonHitExplosion.cs | 99 +++++++++++++++++++ .../Argon/ManiaArgonSkinTransformer.cs | 3 + 2 files changed, 102 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs new file mode 100644 index 0000000000..132ef0d528 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs @@ -0,0 +1,99 @@ +// 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; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ArgonHitExplosion : CompositeDrawable, IHitExplosion + { + private const float default_large_faint_size = 0.8f; + + public override bool RemoveWhenNotAlive => true; + + [Resolved] + private Column column { get; set; } = null!; + + private readonly IBindable direction = new Bindable(); + + private Container largeFaint = null!; + + private Bindable accentColour = null!; + + public ArgonHitExplosion() + { + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.X; + Height = ArgonNotePiece.NOTE_HEIGHT; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + InternalChildren = new Drawable[] + { + largeFaint = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = ArgonNotePiece.CORNER_RADIUS, + Blending = BlendingParameters.Additive, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }, + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + + accentColour = column.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => + { + largeFaint.Colour = Interpolation.ValueAt(0.8f, colour.NewValue, Color4.White, 0, 1); + + largeFaint.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colour.NewValue, + Roundness = 40, + Radius = 60, + }; + }, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + Anchor = Anchor.TopCentre; + Y = ArgonNotePiece.NOTE_HEIGHT / 2; + } + else + { + Anchor = Anchor.BottomCentre; + Y = -ArgonNotePiece.NOTE_HEIGHT / 2; + } + } + + public void Animate(JudgementResult result) + { + this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 45ee25e388..37ab958eee 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -49,6 +49,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case ManiaSkinComponents.KeyArea: return new ArgonKeyArea(); + + case ManiaSkinComponents.HitExplosion: + return new ArgonHitExplosion(); } break; From e4e4c1b66169d842babd595697a70a7db0514f36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 21:39:29 +0900 Subject: [PATCH 522/583] Add very temporary mania judgement display for argon skin --- .../Skinning/Argon/ArgonJudgementPiece.cs | 193 ++++++++++++++++++ .../Argon/ManiaArgonSkinTransformer.cs | 4 + 2 files changed, 197 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs new file mode 100644 index 0000000000..e7dfec256d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -0,0 +1,193 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + { + protected readonly HitResult Result; + + protected SpriteText JudgementText { get; private set; } = null!; + + private RingExplosion? ringExplosion; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public ArgonJudgementPiece(HitResult result) + { + Result = result; + Origin = Anchor.Centre; + Y = 160; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + JudgementText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Result.GetDescription().ToUpperInvariant(), + Colour = colours.ForHitResult(Result), + Blending = BlendingParameters.Additive, + Spacing = new Vector2(10, 0), + Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), + }, + }; + + if (Result.IsHit()) + { + AddInternal(ringExplosion = new RingExplosion(Result) + { + Colour = colours.ForHitResult(Result), + }); + } + } + + /// + /// Plays the default animation for this judgement piece. + /// + /// + /// The base implementation only handles fade (for all result types) and misses. + /// Individual rulesets are recommended to implement their appropriate hit animations. + /// + public virtual void PlayAnimation() + { + switch (Result) + { + default: + JudgementText + .ScaleTo(Vector2.One) + .ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint); + break; + + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + this.RotateTo(0); + this.RotateTo(40, 800, Easing.InQuint); + break; + } + + this.FadeOutFromOne(800); + + ringExplosion?.PlayAnimation(); + } + + public Drawable? GetAboveHitObjectsProxiedContent() => null; + + private class RingExplosion : CompositeDrawable + { + private readonly float travel = 52; + + public RingExplosion(HitResult result) + { + const float thickness = 4; + + const float small_size = 9; + const float large_size = 14; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Blending = BlendingParameters.Additive; + + int countSmall = 0; + int countLarge = 0; + + switch (result) + { + case HitResult.Meh: + countSmall = 3; + travel *= 0.3f; + break; + + case HitResult.Ok: + case HitResult.Good: + countSmall = 4; + travel *= 0.6f; + break; + + case HitResult.Great: + case HitResult.Perfect: + countSmall = 4; + countLarge = 4; + break; + } + + for (int i = 0; i < countSmall; i++) + AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) }); + + for (int i = 0; i < countLarge; i++) + AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) }); + } + + public void PlayAnimation() + { + foreach (var c in InternalChildren) + { + const float start_position_ratio = 0.3f; + + float direction = RNG.NextSingle(0, 360); + float distance = RNG.NextSingle(travel / 2, travel); + + c.MoveTo(new Vector2( + MathF.Cos(direction) * distance * start_position_ratio, + MathF.Sin(direction) * distance * start_position_ratio + )); + + c.MoveTo(new Vector2( + MathF.Cos(direction) * distance, + MathF.Sin(direction) * distance + ), 600, Easing.OutQuint); + } + + this.FadeOutFromOne(1000, Easing.OutQuint); + } + + public class RingPiece : CircularContainer + { + public RingPiece(float thickness = 9) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Masking = true; + BorderThickness = thickness; + BorderColour = Color4.White; + + Child = new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + }; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 37ab958eee..cf08f7ea3e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK.Graphics; @@ -24,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { switch (component) { + case GameplaySkinComponent resultComponent: + return new ArgonJudgementPiece(resultComponent.Component); + case ManiaSkinComponent maniaComponent: // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. switch (maniaComponent.Component) From 6d14ccb8e0bbeba807255e423f1e9030d6bee39b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 21:46:02 +0900 Subject: [PATCH 523/583] Make special key slightly wider --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index cf08f7ea3e..93c3a94316 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case LegacyManiaSkinConfigurationLookups.ColumnWidth: return SkinUtils.As(new Bindable( - stage.IsSpecialColumn(column) ? 80 : 60 + stage.IsSpecialColumn(column) ? 120 : 60 )); case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: From e81f550150438741a9da4ee7e26a7899b9d9b673 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 19:35:01 +0900 Subject: [PATCH 524/583] Fix hit explosions not being cleaned up correctly when rewinding --- osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs index 28509d1f4e..a7b94f9f22 100644 --- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI { base.PrepareForUse(); + LifetimeStart = Time.Current; + (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(Result); this.Delay(DURATION).Then().Expire(); From 207c76bdecc83879384a52ee3fcc7d579bc2a7eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Oct 2022 20:17:35 +0900 Subject: [PATCH 525/583] Fix column lighting and key area not handling rewind correctly --- osu.Game.Rulesets.Mania/UI/Column.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 89413f0f1b..61585b2e5b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI private ISkinSource skin { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(GameHost host) { skin.SourceChanged += onSourceChanged; onSourceChanged(); @@ -82,7 +83,10 @@ namespace osu.Game.Rulesets.Mania.UI Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + // This is pretty dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind. + Clock = host.UpdateThread.Clock, + ProcessCustomClock = false, }; InternalChildren = new[] @@ -94,7 +98,10 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea, new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + // This is pretty dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind. + Clock = host.UpdateThread.Clock, + ProcessCustomClock = false, }, background, TopLevelContainer, From 98b578e8e6f37e389dc9ee3bfcc0602e9946cc24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 16:12:10 +0900 Subject: [PATCH 526/583] Remove left-over incorrect fallback logic This was moved to `ManiaClassicSkinTransformer`. --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 31cfcf9e16..1d39721a2b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Legacy { @@ -151,18 +150,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { if (lookup is ManiaSkinConfigurationLookup maniaLookup) { - var legacyLookup = - base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); - - if (legacyLookup != null) - return legacyLookup; - - // default legacy fallback. - switch (maniaLookup.Lookup) - { - case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: - return SkinUtils.As(new Bindable(Color4.Black)); - } + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex)); } return base.GetConfig(lookup); From c7405d1c1cc8ddcb0bf0a32799e115992318ae3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 17:00:10 +0900 Subject: [PATCH 527/583] Add missing immediate application of `AccentColour` in `DefaultKeyArea` Co-authored-by: Dan Balasescu --- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 6196850f91..600c9feb73 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components Radius = 5, Colour = colour.NewValue.Opacity(0.5f), }; - }); + }, true); } private void onDirectionChanged(ValueChangedEvent direction) From 4b772643e98999f04ad5b1cf927f5c64c4c84c1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 18:25:07 +0900 Subject: [PATCH 528/583] Use bindable flow for transfer of accent colour from columns to hit objects This fixes the case where changing a skin during gameplay runtime does not correctly convey colour information to off-screen `DrawableHitObject`s. --- osu.Game.Rulesets.Mania/UI/Column.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 89413f0f1b..c6bb3910e2 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -73,13 +73,6 @@ namespace osu.Game.Rulesets.Mania.UI skin.SourceChanged += onSourceChanged; onSourceChanged(); - AccentColour.BindValueChanged(colour => - { - // Manual transfer as hit objects may be moved between column and unbinding is non-trivial. - foreach (var obj in HitObjectContainer.Objects) - obj.AccentColour.Value = colour.NewValue; - }, true); - Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both @@ -142,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.UI DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject; - maniaObject.AccentColour.Value = AccentColour.Value; + maniaObject.AccentColour.BindTo(AccentColour); maniaObject.CheckHittable = hitPolicy.IsHittable; } From 52ad766f86c933c4621e5fac1dc423227273dded Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 18:25:54 +0900 Subject: [PATCH 529/583] Remove manual binding of nested objects in `DrawableManiaHitObject` This is now handled by `Column` itself. Leaving the logic here as well will cause a circular bindable flow and stack overflow. --- .../Objects/Drawables/DrawableManiaHitObject.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 6cd39d835d..73dc937a00 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -65,22 +65,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } - protected override void OnApply() - { - base.OnApply(); - - if (ParentHitObject != null) - AccentColour.BindTo(ParentHitObject.AccentColour); - } - - protected override void OnFree() - { - base.OnFree(); - - if (ParentHitObject != null) - AccentColour.UnbindFrom(ParentHitObject.AccentColour); - } - protected virtual void OnDirectionChanged(ValueChangedEvent e) { Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; From a8742f0cc31ba72e19e3d82d840cbcaf2acb5b55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 18:46:26 +0900 Subject: [PATCH 530/583] Adjust out-of-the-box background dim back down to 70% It was previously bumped from 60% to 80%, but I've recently felt that this is too high as a default, and takes away from storyboards and video backgrounds. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e3bfb6b1e9..1378e1691a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -120,7 +120,7 @@ namespace osu.Game.Configuration // Gameplay SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703. SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); - SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); + SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); From 58eaa18725f4535e9b3eb7d785803863b0bac393 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 18:44:16 +0900 Subject: [PATCH 531/583] Add transparency to column backgrounds --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs index c6377f9152..bfa3b34890 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon accentColour = column.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(colour => { - background.Colour = colour.NewValue.Darken(5); + background.Colour = colour.NewValue.Darken(3).Opacity(0.6f); brightColour = colour.NewValue.Opacity(0.6f); dimColour = colour.NewValue.Opacity(0); }, true); From f9b4f491a69fca8e49fa780a1a3da3fbeeec45d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 18:44:24 +0900 Subject: [PATCH 532/583] Increase overall corner radius slightly --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs index f560d2676c..454a6b012b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public const float NOTE_HEIGHT = 42; - public const float CORNER_RADIUS = 3; + public const float CORNER_RADIUS = 3.4f; private readonly IBindable direction = new Bindable(); private readonly IBindable accentColour = new Bindable(); From 12e9686092704b5e21f5020396d2d20e9d7162de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 18:44:29 +0900 Subject: [PATCH 533/583] Increase column spacing --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 93c3a94316..27926c11eb 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -73,6 +73,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (maniaLookup.Lookup) { + case LegacyManiaSkinConfigurationLookups.ColumnSpacing: + return SkinUtils.As(new Bindable(2)); + case LegacyManiaSkinConfigurationLookups.StagePaddingBottom: case LegacyManiaSkinConfigurationLookups.StagePaddingTop: return SkinUtils.As(new Bindable(30)); From e4657a7a3d0784afea6b56fd1f206addf2f9af0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 19:02:08 +0900 Subject: [PATCH 534/583] Fix argon key area lighting being hidden by other columns in one direction --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs | 2 -- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs index 132ef0d528..af179d5580 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public class ArgonHitExplosion : CompositeDrawable, IHitExplosion { - private const float default_large_faint_size = 0.8f; - public override bool RemoveWhenNotAlive => true; [Resolved] diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 72f5f81890..073b9af307 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -150,6 +150,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon bottomIcon.Colour = colour.NewValue; }, true); + + // Yes, proxy everything. + column.TopLevelContainer.Add(CreateProxy()); } private void onDirectionChanged(ValueChangedEvent direction) From a4f827c7fd22f892612171e42c9dd8f5e06bd223 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 19:02:15 +0900 Subject: [PATCH 535/583] Remove broken `KeyArea` test scene Use `TestSceneStage` instead. --- .../Skinning/TestSceneKeyArea.cs | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs deleted file mode 100644 index bbbd7edb7b..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.UI.Components; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Rulesets.Mania.Tests.Skinning -{ - public class TestSceneKeyArea : ManiaSkinnableTestScene - { - [BackgroundDependencyLoader] - private void load() - { - SetContents(_ => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.8f), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new ColumnTestContainer(0, ManiaAction.Key1) - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) - { - RelativeSizeAxes = Axes.Both - }, - }, - new ColumnTestContainer(1, ManiaAction.Key2) - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) - { - RelativeSizeAxes = Axes.Both - }, - }, - } - }); - } - } -} From 551b7f0d1c0e43ecf1e50dd6c66e219c40953a11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 19:08:55 +0900 Subject: [PATCH 536/583] Fix corner radius visible below hit target line --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 073b9af307..dd6aeadd65 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -50,7 +50,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, - Height = Stage.HIT_TARGET_POSITION, + // Ensure the area is tall enough to put the target line in the correct location. + // This is to also allow the main background component to overlap the target line + // and avoid an inner corner radius being shown below the target line. + Height = Stage.HIT_TARGET_POSITION + ArgonNotePiece.CORNER_RADIUS * 2, Children = new[] { new Container @@ -70,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, + Origin = Anchor.TopCentre, Colour = OsuColour.Gray(196 / 255f), Height = ArgonNotePiece.CORNER_RADIUS * 2, Masking = true, From 08c3f3ae6df6387210d8255dff878256293f8699 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 19:27:00 +0900 Subject: [PATCH 537/583] Adjust lighting and timing of key area in general --- .../Skinning/Argon/ArgonKeyArea.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index dd6aeadd65..badca71f36 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -65,7 +66,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { Name = "Key gradient", Alpha = 0, - Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, }, }, @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon accentColour = column.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(colour => { - background.Colour = colour.NewValue.Darken(1f); + background.Colour = colour.NewValue.Darken(0.2f); bottomIcon.Colour = colour.NewValue; }, true); @@ -180,18 +180,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (e.Action != column.Action.Value) return false; - const double lighting_fade_in_duration = 50; - Color4 lightingColour = accentColour.Value.Lighten(0.9f); + const double lighting_fade_in_duration = 70; + Color4 lightingColour = getLightingColour(); background - .FadeTo(1, 40).Then() - .FadeTo(0.8f, 150, Easing.OutQuint); + .FadeTo(1, lighting_fade_in_duration, Easing.OutQuint) + .Then() + .FadeTo(0.6f, 500, Easing.In); hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = lightingColour.Opacity(0.7f), + Colour = lightingColour.Opacity(0.4f), Radius = 20, }, lighting_fade_in_duration, Easing.OutQuint); @@ -210,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = lightingColour.Opacity(0.3f), + Colour = lightingColour.Opacity(0.2f), Radius = 60, }, lighting_fade_in_duration, Easing.OutQuint); } @@ -222,10 +223,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { if (e.Action != column.Action.Value) return; - const double lighting_fade_out_duration = 300; - Color4 lightingColour = accentColour.Value.Lighten(0.9f).Opacity(0); + const double lighting_fade_out_duration = 800; - background.FadeTo(0, lighting_fade_out_duration, Easing.OutQuint); + Color4 lightingColour = getLightingColour().Opacity(0); + + // background fades out faster than lighting elements to give better definition to the player. + background.FadeTo(0.3f, 50, Easing.OutQuint) + .Then() + .FadeOut(lighting_fade_out_duration, Easing.OutQuint); topIcon.ScaleTo(1f, 200, Easing.OutQuint); topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters @@ -255,5 +260,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon }, lighting_fade_out_duration, Easing.OutQuint); } } + + private Color4 getLightingColour() => Interpolation.ValueAt(0.2f, accentColour.Value, Color4.White, 0, 1); } } From c518fbf44a2bb03719fd2331c06bc3ca8cc52f76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 19:31:23 +0900 Subject: [PATCH 538/583] Adjust argon hold bodies to better define away from the stage background --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs index 10d5a4d267..7fbaf8ba64 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon AccentColour.BindValueChanged(colour => { - background.Colour = colour.NewValue.Opacity(0.2f); + background.Colour = colour.NewValue.Darken(1.5f); foreground.Colour = colour.NewValue.Opacity(0.1f); }, true); From 8101eea50dedc0a23bdd8bf63b12343be0bab97f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 20:04:39 +0900 Subject: [PATCH 539/583] Fix first glow of key area elements not working due to missing type --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index badca71f36..98e46c1eac 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Colour = OsuColour.Gray(196 / 255f), Height = ArgonNotePiece.CORNER_RADIUS * 2, Masking = true, + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow }, }, new Container { @@ -100,6 +101,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Size = new Vector2(icon_circle_size), Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow }, }, new Circle { @@ -108,6 +110,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Size = new Vector2(icon_circle_size), Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow }, }, new Circle { @@ -116,6 +119,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Size = new Vector2(icon_circle_size), Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow }, }, } }, @@ -128,6 +132,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Masking = true, BorderThickness = 4, BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow }, Children = new Drawable[] { new Box From d5490523ea8dd4a8f6d16541ff9ef61bc6d75efb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 20:05:08 +0900 Subject: [PATCH 540/583] Adjust hold notes further --- .../Skinning/Argon/ArgonHoldBodyPiece.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs index 7fbaf8ba64..c4b2181879 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon /// /// Represents length-wise portion of a hold note. /// - public class ArgonHoldBodyPiece : CompositeDrawable + public class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody { protected readonly Bindable AccentColour = new Bindable(); protected readonly IBindable IsHitting = new Bindable(); @@ -26,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public ArgonHoldBodyPiece() { - Blending = BlendingParameters.Additive; RelativeSizeAxes = Axes.Both; // Without this, the width of the body will be slightly larger than the head/tail. @@ -52,14 +52,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { var holdNote = (DrawableHoldNote)drawableObject; - AccentColour.BindTo(drawableObject.AccentColour); + AccentColour.BindTo(holdNote.AccentColour); IsHitting.BindTo(holdNote.IsHitting); } AccentColour.BindValueChanged(colour => { background.Colour = colour.NewValue.Darken(1.5f); - foreground.Colour = colour.NewValue.Opacity(0.1f); + foreground.Colour = colour.NewValue.Opacity(0.2f); }, true); IsHitting.BindValueChanged(hitting => @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon using (foreground.BeginDelayedSequence(synchronisedOffset)) { foreground.FadeTo(1, animation_length).Then() - .FadeTo(0, animation_length) + .FadeTo(0.5f, animation_length) .Loop(); } } @@ -86,5 +86,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon } }); } + + public void Recycle() + { + foreground.ClearTransforms(); + foreground.Alpha = 0; + } } } From 589b764b77803f65f5807b1cabd4efefd73a9de6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 20:30:38 +0900 Subject: [PATCH 541/583] Final tweaks because I don't know when to stop --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs index 98e46c1eac..7670c9bdf2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs @@ -189,9 +189,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Color4 lightingColour = getLightingColour(); background + .FlashColour(accentColour.Value.Lighten(0.8f), 200, Easing.OutQuint) .FadeTo(1, lighting_fade_in_duration, Easing.OutQuint) .Then() - .FadeTo(0.6f, 500, Easing.In); + .FadeTo(0.8f, 500); hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint); hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters From 588713497ddab075e99ae4dd123d0a7c82a4bce8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 20:43:26 +0900 Subject: [PATCH 542/583] Attempt to make hold notes more legible at low background dims --- .../Skinning/Argon/ArgonColumnBackground.cs | 2 +- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs index bfa3b34890..598a765d3c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon accentColour = column.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(colour => { - background.Colour = colour.NewValue.Darken(3).Opacity(0.6f); + background.Colour = colour.NewValue.Darken(3).Opacity(0.8f); brightColour = colour.NewValue.Opacity(0.6f); dimColour = colour.NewValue.Opacity(0); }, true); diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs index c4b2181879..757190c4ae 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs @@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon // Without this, the width of the body will be slightly larger than the head/tail. Masking = true; CornerRadius = ArgonNotePiece.CORNER_RADIUS; + Blending = BlendingParameters.Additive; } [BackgroundDependencyLoader(true)] @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon AccentColour.BindValueChanged(colour => { - background.Colour = colour.NewValue.Darken(1.5f); + background.Colour = colour.NewValue.Darken(1.2f); foreground.Colour = colour.NewValue.Opacity(0.2f); }, true); From e1e12f41e94c2fe7909048867ca217a414bbae51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 21:00:02 +0900 Subject: [PATCH 543/583] Add more comprehensive inline comment --- osu.Game.Rulesets.Mania/UI/Column.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index be26c41ceb..3d46bdaa7b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -71,15 +71,14 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load(GameHost host) { + SkinnableDrawable keyArea; + skin.SourceChanged += onSourceChanged; onSourceChanged(); Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both, - // This is pretty dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind. - Clock = host.UpdateThread.Clock, - ProcessCustomClock = false, }; InternalChildren = new[] @@ -89,18 +88,18 @@ namespace osu.Game.Rulesets.Mania.UI // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), HitObjectArea, - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) + keyArea = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both, - // This is pretty dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind. - Clock = host.UpdateThread.Clock, - ProcessCustomClock = false, }, background, TopLevelContainer, new ColumnTouchInputArea(this) }; + applyGameWideClock(background); + applyGameWideClock(keyArea); + TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); RegisterPool(10, 50); @@ -108,6 +107,18 @@ namespace osu.Game.Rulesets.Mania.UI RegisterPool(10, 50); RegisterPool(10, 50); RegisterPool(50, 250); + + // Some elements don't handle rewind correctly and fixing them is non-trivial. + // In the future we need a better solution to this, but as a temporary work-around, give these components the game-wide + // clock so they don't need to worry about rewind. + // This only works because they handle OnPressed/OnReleased which results in a correct state while rewinding. + // + // This is kinda dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind. + void applyGameWideClock(Drawable drawable) + { + drawable.Clock = host.UpdateThread.Clock; + drawable.ProcessCustomClock = false; + } } private void onSourceChanged() From 072b64b8d4d7cc68002e4caaafba18512ae19a65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 21:16:11 +0900 Subject: [PATCH 544/583] Update all dependencies --- ...su.Game.Rulesets.EmptyFreeform.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 4 ++-- ...u.Game.Rulesets.EmptyScrolling.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 4 ++-- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 4 ++-- .../osu.Game.Rulesets.Catch.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Mania.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Osu.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Taiko.Tests.csproj | 4 ++-- osu.Game.Tests/osu.Game.Tests.csproj | 6 +++--- .../osu.Game.Tournament.Tests.csproj | 4 ++-- osu.Game/osu.Game.csproj | 18 +++++++++--------- 12 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index b8604169aa..936808f38b 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -9,9 +9,9 @@ false - + - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 4117452579..35e7742172 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,9 +9,9 @@ false - + - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 0b119c8680..c1044965b5 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -9,9 +9,9 @@ false - + - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 4117452579..35e7742172 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,9 +9,9 @@ false - + - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 36ffd3b5b6..d62d422f33 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,9 +7,9 @@ - + - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index d45f8a9692..c9db824615 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,9 +1,9 @@  - + - + WinExe diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 2951076591..0d7b03d830 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,9 +1,9 @@  - + - + WinExe diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index c2973644cf..1eb1c85d93 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,10 +1,10 @@  - + - + WinExe diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index e8eaff4368..38e61f5624 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,9 +1,9 @@  - + - + WinExe diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 2572864e09..bdf8cc5136 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,11 +1,11 @@  - - + + - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 5512b26863..bdef46a6b2 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,9 +4,9 @@ osu.Game.Tournament.Tests.TournamentTestRunner - + - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 26738673c8..efdb6c6995 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,15 +18,15 @@ - + - + - - - - + + + + @@ -34,13 +34,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + From cac5e734951d96101b931eb044bcc6811783a1c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 21:28:25 +0900 Subject: [PATCH 545/583] Update props references --- osu.Android.props | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c0be002bb5..2a678f1c61 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index af1e21edab..47872e4ff7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -83,10 +83,10 @@ - + - + From cff38e532a6be67e444b12df77dbfd5faabc90a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2022 22:56:38 +0900 Subject: [PATCH 546/583] Attempt to remove console output --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33ec3d6602..56b3ebe87b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: run: dotnet build -c Debug -warnaserror osu.Desktop.slnf - name: Test - run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" + run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0 shell: pwsh # Attempt to upload results even if test fails. From 903c8612c89565f7ba25c337304abef567ed75db Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 7 Oct 2022 20:34:31 +0200 Subject: [PATCH 547/583] Move endTime to next timing point --- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index dec81d9bbd..2a2aaaecba 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Objects int currentBeat = 0; // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; From 63ffaa69292afc14bfd9c8b4174b11b39cd620de Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 7 Oct 2022 20:38:32 +0200 Subject: [PATCH 548/583] Respect OmitFirstBarLine effect --- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 2a2aaaecba..85bff630e3 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -33,6 +31,7 @@ namespace osu.Game.Rulesets.Objects double lastHitTime = 1 + lastObject.GetEndTime(); var timingPoints = beatmap.ControlPointInfo.TimingPoints; + var effectPoints = beatmap.ControlPointInfo.EffectPoints; if (timingPoints.Count == 0) return; @@ -40,14 +39,22 @@ namespace osu.Game.Rulesets.Objects for (int i = 0; i < timingPoints.Count; i++) { TimingControlPoint currentTimingPoint = timingPoints[i]; + EffectControlPoint? currentEffectPoint = effectPoints.FirstOrDefault(p => p.Time == currentTimingPoint.Time); int currentBeat = 0; // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; + double startTime = currentTimingPoint.Time; + double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; - for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) + if (currentEffectPoint != null && currentEffectPoint.OmitFirstBarLine) + { + startTime += barLength; + } + + for (double t = startTime; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { double roundedTime = Math.Round(t, MidpointRounding.AwayFromZero); From f889f0df364614214445733214d68ad94bc94416 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 7 Oct 2022 20:41:45 +0200 Subject: [PATCH 549/583] Add tests for the BarLineGenerator --- .../TestSceneBarLineGeneration.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs new file mode 100644 index 0000000000..4bff9c9646 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs @@ -0,0 +1,87 @@ +// 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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneBarLineGeneration : OsuTestScene + { + [Test] + public void TestCloseBarLineGeneration() + { + const double start_time = 1000; + + var beatmap = new Beatmap + { + HitObjects = + { + new Hit + { + Type = HitType.Centre, + StartTime = start_time + } + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + }; + + beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint()); + beatmap.ControlPointInfo.Add(start_time + 1, new TimingControlPoint()); + + var barlines = new BarLineGenerator(beatmap).BarLines; + + AddAssert("first barline generated", () => barlines.Any(b => b.StartTime == start_time)); + AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + 1)); + } + + [Test] + public void TestOmitBarLineEffectPoint() + { + const double start_time = 1000; + const double beat_length = 500; + + const int time_signature_numerator = 4; + + var beatmap = new Beatmap + { + HitObjects = + { + new Hit + { + Type = HitType.Centre, + StartTime = start_time + } + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + }; + + beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint + { + BeatLength = beat_length, + TimeSignature = new TimeSignature(time_signature_numerator) + }); + + beatmap.ControlPointInfo.Add(start_time, new EffectControlPoint { OmitFirstBarLine = true }); + + var barlines = new BarLineGenerator(beatmap).BarLines; + + AddAssert("first barline ommited", () => !barlines.Any(b => b.StartTime == start_time)); + AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + (beat_length * time_signature_numerator))); + } + } +} From 9d45f1ec12747df117d44b3a2b870ed20f709dc3 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 7 Oct 2022 22:08:12 +0200 Subject: [PATCH 550/583] Fix code style --- osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs index 4bff9c9646..095fddc33f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Tests }, }; - beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint + beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint { BeatLength = beat_length, TimeSignature = new TimeSignature(time_signature_numerator) @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var barlines = new BarLineGenerator(beatmap).BarLines; - AddAssert("first barline ommited", () => !barlines.Any(b => b.StartTime == start_time)); + AddAssert("first barline ommited", () => barlines.All(b => b.StartTime != start_time)); AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + (beat_length * time_signature_numerator))); } } From 098a56a78494613413471ecdde26aef5f827ee48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 Oct 2022 21:24:57 +0300 Subject: [PATCH 551/583] Add custom container extension support --- .../Containers/Markdown/OsuMarkdownContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 62fefe201d..3855ed6d4e 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -5,6 +5,7 @@ using Markdig; using Markdig.Extensions.AutoLinks; +using Markdig.Extensions.CustomContainers; using Markdig.Extensions.EmphasisExtras; using Markdig.Extensions.Footnotes; using Markdig.Extensions.Tables; @@ -32,6 +33,12 @@ namespace osu.Game.Graphics.Containers.Markdown /// protected virtual bool Autolinks => false; + /// + /// Allows this markdown container to parse custom containers (used for flags and infoboxes). + /// + /// + protected virtual bool CustomContainers => false; + public OsuMarkdownContainer() { LineSpacing = 21; @@ -107,6 +114,9 @@ namespace osu.Game.Graphics.Containers.Markdown if (Autolinks) pipeline = pipeline.UseAutoLinks(); + if (CustomContainers) + pipeline.UseCustomContainers(); + return pipeline.Build(); } } From a9aba743514d13de403b6685d1475afa914beb63 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 Oct 2022 21:26:20 +0300 Subject: [PATCH 552/583] Add markdown country flag support --- .../Markdown/OsuMarkdownTextFlowContainer.cs | 27 +++++++++++++++++++ .../Wiki/Markdown/WikiMarkdownContainer.cs | 7 +++++ 2 files changed, 34 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index db8abfb269..fb8f13ab84 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -3,6 +3,9 @@ #nullable disable +using System; +using System.Linq; +using Markdig.Extensions.CustomContainers; using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -11,6 +14,9 @@ using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osuTK; namespace osu.Game.Graphics.Containers.Markdown { @@ -33,6 +39,27 @@ namespace osu.Game.Graphics.Containers.Markdown protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); + protected override void AddCustomComponent(CustomContainerInline inline) + { + if (!(inline.FirstChild is LiteralInline literal)) + { + base.AddCustomComponent(inline); + return; + } + + string[] attributes = literal.Content.ToString().Trim(' ', '{', '}').Split(); + string flagAttribute = attributes.SingleOrDefault(a => a.StartsWith(@"flag", StringComparison.Ordinal)); + + if (flagAttribute == null) + { + base.AddCustomComponent(inline); + return; + } + + string flag = flagAttribute.Split('=').Last().Trim('"'); + AddDrawable(new DrawableFlag(Enum.Parse(flag)) { Size = new Vector2(20, 15) }); + } + private class OsuMarkdownInlineCode : Container { [Resolved] diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 4f1083a75c..15c455416c 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -4,6 +4,7 @@ #nullable disable using System.Linq; +using Markdig.Extensions.CustomContainers; using Markdig.Extensions.Yaml; using Markdig.Syntax; using Markdig.Syntax.Inlines; @@ -16,6 +17,7 @@ namespace osu.Game.Overlays.Wiki.Markdown public class WikiMarkdownContainer : OsuMarkdownContainer { protected override bool Footnotes => true; + protected override bool CustomContainers => true; public string CurrentPath { @@ -26,6 +28,11 @@ namespace osu.Game.Overlays.Wiki.Markdown { switch (markdownObject) { + case CustomContainer: + // infoboxes are parsed into CustomContainer objects, but we don't have support for infoboxes yet. + // todo: add support for infobox. + break; + case YamlFrontMatterBlock yamlFrontMatterBlock: container.Add(new WikiNoticeContainer(yamlFrontMatterBlock)); break; From 13083813bdbdaf6c39520fb3e5467c3f10e9e8b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 Oct 2022 21:26:42 +0300 Subject: [PATCH 553/583] Update hardcoded article page --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 558bff2f3c..27cd74bb1f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Online Locale = "en", Subtitle = "Article styling criteria", Markdown = - "# Formatting\n\n*For the writing standards, see: [Article style criteria/Writing](../Writing)*\n\n*Notice: This article uses [RFC 2119](https://tools.ietf.org/html/rfc2119 \"IETF Tools\") to describe requirement levels.*\n\n## Locales\n\nListed below are the properly-supported locales for the wiki:\n\n| File Name | Locale Name | Native Script |\n| :-- | :-- | :-- |\n| `en.md` | English | English |\n| `ar.md` | Arabic | اَلْعَرَبِيَّةُ |\n| `be.md` | Belarusian | Беларуская мова |\n| `bg.md` | Bulgarian | Български |\n| `cs.md` | Czech | Česky |\n| `da.md` | Danish | Dansk |\n| `de.md` | German | Deutsch |\n| `gr.md` | Greek | Ελληνικά |\n| `es.md` | Spanish | Español |\n| `fi.md` | Finnish | Suomi |\n| `fr.md` | French | Français |\n| `hu.md` | Hungarian | Magyar |\n| `id.md` | Indonesian | Bahasa Indonesia |\n| `it.md` | Italian | Italiano |\n| `ja.md` | Japanese | 日本語 |\n| `ko.md` | Korean | 한국어 |\n| `nl.md` | Dutch | Nederlands |\n| `no.md` | Norwegian | Norsk |\n| `pl.md` | Polish | Polski |\n| `pt.md` | Portuguese | Português |\n| `pt-br.md` | Brazilian Portuguese | Português (Brasil) |\n| `ro.md` | Romanian | Română |\n| `ru.md` | Russian | Русский |\n| `sk.md` | Slovak | Slovenčina |\n| `sv.md` | Swedish | Svenska |\n| `th.md` | Thai | ไทย |\n| `tr.md` | Turkish | Türkçe |\n| `uk.md` | Ukrainian | Українська мова |\n| `vi.md` | Vietnamese | Tiếng Việt |\n| `zh.md` | Chinese (Simplified) | 简体中文 |\n| `zh-tw.md` | Traditional Chinese (Taiwan) | 繁體中文(台灣) |\n\n*Note: The website will give readers their selected language's version of an article. If it is not available, the English version will be given.*\n\n### Content parity\n\nTranslations are subject to strict content parity with their English article, in the sense that they must have the same message, regardless of grammar and syntax. Any changes to the translations' meanings must be accompanied by equivalent changes to the English article.\n\nThere are some cases where the content is allowed to differ:\n\n- Articles originally written in a language other than English (in this case, English should act as the translation)\n- Explanations of English words that are common terms in the osu! community\n- External links\n- Tags\n- Subcommunity-specific explanations\n\n## Front matter\n\nFront matter must be placed at the very top of the file. It is written in [YAML](https://en.wikipedia.org/wiki/YAML#Example \"YAML Wikipedia article\") and describes additional information about the article. This must be surrounded by three hyphens (`---`) on the lines above and below it, and an empty line must follow it before the title heading.\n\n### Articles that need help\n\n*Note: Avoid translating English articles with this tag. In addition to this, this tag should be added when the translation needs its own clean up.*\n\nThe `needs_cleanup` tag may be added to articles that need rewriting or formatting help. It is also acceptable to open an issue on GitHub for this purpose. This tag must be written as shown below:\n\n```yaml\nneeds_cleanup: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be done to remove the tag.\n\n### Outdated articles\n\n*Note: Avoid translating English articles with this tag. If the English article has this tag, the translation must also have this tag.*\n\nTranslated articles that are outdated must use the `outdated` tag when the English variant is updated. English articles may also become outdated when the content they contain is misleading or no longer relevant. This tag must be written as shown below:\n\n```yaml\noutdated: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be updated to remove the tag.\n\n### Tagging articles\n\nTags help the website's search engine query articles better. Tags should be written in the same language as the article and include the original list of tags. Tags should use lowercase letters where applicable.\n\nFor example, an article called \"Beatmap discussion\" may include the following tags:\n\n```yaml\ntags:\n - beatmap discussions\n - modding V2\n - MV2\n```\n\n### Translations without reviews\n\n*Note: Wiki maintainers will determine and apply this mark prior to merging.*\n\nSometimes, translations are added to the wiki without review from other native speakers of the language. In this case, the `no_native_review` mark is added to let future translators know that it may need to be checked again. This tag must be written as shown below:\n\n```yaml\nno_native_review: true\n```\n\n## Article naming\n\n*See also: [Folder names](#folder-names) and [Titles](#titles)*\n\nArticle titles should be singular and use sentence case. See [Wikipedia's naming conventions article](https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(plurals) \"Wikipedia\") for more details.\n\nArticle titles should match the folder name it is in (spaces may replace underscores (`_`) where appropriate). If the folder name changes, the article title should be changed to match it and vice versa.\n\n---\n\nContest and tournament articles are an exception. The folder name must use abbreviations, acronyms, or initialisms. The article's title must be the full name of the contest or tournament.\n\n## Folder and file structure\n\n### Folder names\n\n*See also: [Article naming](#article-naming)*\n\nFolder names must be in English and use sentence case.\n\nFolder names must only use these characters:\n\n- uppercase and lowercase letters\n- numbers\n- underscores (`_`)\n- hyphens (`-`)\n- exclamation marks (`!`)\n\n### Article file names\n\nThe file name of an article can be found in the `File Name` column of the [locales section](#locales). The location of a translated article must be placed in the same folder as the English article.\n\n### Index articles\n\nAn index article must be created if the folder is intended to only hold other articles. Index articles must contain a list of articles that are inside its own folder. They may also contain other information, such as a lead paragraph or descriptions of the linked articles.\n\n### Disambiguation articles\n\n[Disambiguation](/wiki/Disambiguation) articles must be placed in the `/wiki/Disambiguation` folder. The main page must be updated to include the disambiguation article. Refer to [Disambiguation/Mod](/wiki/Disambiguation/Mod) as an example.\n\nRedirects must be updated to have the ambiguous keyword(s) redirect to the disambiguation article.\n\nArticles linked from a disambiguation article must have a [For other uses](#for-other-uses) hatnote.\n\n## HTML\n\nHTML must not be used, with exception for [comments](#comments). The structure of the article must be redone if HTML is used.\n\n### Comments\n\nHTML comments should be used for marking to-dos, but may also be used to annotate text. They should be on their own line, but can be placed inline in a paragraph. If placed inline, the start of the comment must not have a space.\n\nBad example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\nGood example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\n## Editing\n\n### End of line sequence\n\n*Caution: Uploading Markdown files using `CRLF` (carriage return and line feed) via GitHub will result in those files using `CRLF`. To prevent this, set the line ending to `LF` (line feed) before uploading.*\n\nMarkdown files must be checked in using the `LF` end of line sequence.\n\n### Escaping\n\nMarkdown syntax should be escaped as needed. However, article titles are parsed as plain text and so must not be escaped.\n\n### Paragraphs\n\nEach paragraph must be followed by one empty line.\n\n### Line breaks\n\nLine breaks must use a backslash (`\\`).\n\nLine breaks must be used sparingly.\n\n## Hatnote\n\n*Not to be confused with [Notice](#notice).*\n\nHatnotes are short notes placed at the top of an article or section to help readers navigate to related articles or inform them about related topics.\n\nHatnotes must be italicised and be placed immediately after the heading. If multiple hatnotes are used, they must be on the same paragraph separated with a line break.\n\n### Main page\n\n*Main page* hatnotes direct the reader to the main article of a topic. When this hatnote is used, it implies that the section it is on is a summary of what the linked page is about. This hatnote should have only one link. These must be formatted as follows:\n\n```markdown\n*Main page: {article}*\n\n*Main pages: {article} and {article}*\n```\n\n### See also\n\n*See also* hatnotes suggest to readers other points of interest from a given article or section. These must be formatted as follows:\n\n```markdown\n*See also: {article}*\n\n*See also: {article} and {article}*\n```\n\n### For see\n\n*For see* hatnotes are similar to *see also* hatnotes, but are generally more descriptive and direct. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*For {description}, see: {article}`*\n\n*For {description}, see: {article} and {article}`*\n```\n\n### Not to be confused with\n\n*Not to be confused with* hatnotes help distinguish ambiguous or misunderstood article titles or sections. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*Not to be confused with {article}.*\n\n*Not to be confused with {article} or {article}.*\n```\n\n### For other uses\n\n*For other uses* hatnotes are similar to *not to be confused with* hatnotes, but links directly to the [disambiguation article](#disambiguation-articles). This hatnote must only link to the disambiguation article. These must be formatted as follows:\n\n```markdown\n*For other uses, see {disambiguation article}.*\n```\n\n## Notice\n\n*Not to be confused with [Hatnote](#hatnote).*\n\nA notice should be placed where appropriate in a section, but must start off the paragraph and use italics. Notices may contain bolding where appropriate, but should be kept to a minimum. Notices must be written as complete sentences. Thus, unlike most [hatnotes](#hatnotes), must use a full stop (`.`) or an exclamation mark (`!`) if appropriate. Anything within the same paragraph of a notice must also be italicised. These must be formatted as follows:\n\n```markdown\n*Note: {note}.*\n\n*Notice: {notice}.*\n\n*Caution: {caution}.*\n\n*Warning: {warning}.*\n```\n\n- `Note` should be used for factual or trivial details.\n- `Notice` should be used for reminders or to draw attention to something that the reader should be made aware of.\n- `Caution` should be used to warn the reader to avoid unintended consequences.\n- `Warning` should be used to warn the reader that action may be taken against them.\n\n## Emphasising\n\n### Bold\n\nBold must use double asterisks (`**`).\n\nLead paragraphs may bold the first occurrence of the article's title.\n\n### Italics\n\nItalics must use single asterisks (`*`).\n\nNames of work or video games should be italicised. osu!—the game—is exempt from this.\n\nThe first occurrence of an abbreviation, acronym, or initialism may be italicised.\n\nItalics may also be used to provide emphasis or help with readability.\n\n## Headings\n\nAll headings must use sentence case.\n\nHeadings must use the [ATX (hash) style](https://github.github.com/gfm/#atx-headings \"GitHub\") and must have an empty line before and after the heading. The title heading is an exception when it is on the first line. If this is the case, there only needs to be an empty line after the title heading.\n\nHeadings must not exceed a heading level of 5 and must not be used to style or format text.\n\n### Titles\n\n*See also: [Article naming](#article-naming)*\n\n*Caution: Titles are parsed as plain text; they must not be escaped.*\n\nThe first heading in all articles must be a level 1 heading, being the article's title. All headings afterwards must be [section headings](#sections). Titles must not contain formatting, links, or images.\n\nThe title heading must be on the first line, unless [front matter](#front-matter) is being used. If that is the case, the title heading must go after it and have an empty line before the title heading.\n\n### Sections\n\nSection headings must use levels 2 to 5. The section heading proceeding the [title heading](#titles) must be a level 2 heading. Unlike titles, section headings may have small image icons.\n\nSection headings must not skip a heading level (i.e. do not go from a level 2 heading to a level 4 heading) and must not contain formatting or links.\n\n*Notice: On the website, heading levels 4 and 5 will not appear in the table of contents. They cannot be linked to directly either.*\n\n## Lists\n\nLists should not go over 4 levels of indentation and should not have an empty line in between each item.\n\nFor nested lists, bullets or numbers must align with the item content of their parent lists.\n\nThe following example was done incorrectly (take note of the spacing before the bullet):\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\nThe following example was done correctly:\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\n### Bulleted\n\nBulleted lists must use a hyphen (`-`). These must then be followed by one space. (Example shown below.)\n\n```markdown\n- osu!\n - Hit circle\n - Combo number\n - Approach circle\n - Slider\n - Hit circles\n - Slider body\n - Slider ticks\n - Spinner\n- osu!taiko\n```\n\n### Numbered\n\nThe numbers in a numbered list must be incremented to represent their step.\n\n```markdown\n1. Download the osu! installer.\n2. Run the installer.\n 1. To change the installation location, click the text underneath the progression bar.\n 2. The installer will prompt for a new location, choose the installation folder.\n3. osu! will start up once installation is complete.\n4. Sign in.\n```\n\n### Mixed\n\nCombining both bulleted and numbered lists should be done sparingly.\n\n```markdown\n1. Download a skin from the forums.\n2. Load the skin file into osu!.\n - If the file is a `.zip`, unzip it and move the contents into the `Skins/` folder (found in your osu! installation folder).\n - If the file is a `.osk`, open it on your desktop or drag-and-drop it into the game client.\n3. Open osu!, if it is not opened, and select the skin in the options.\n - This may have been completed if you opened the `.osk` file or drag-and-dropped it into the game client.\n```\n\n## Code\n\nThe markup for code is a grave mark (`` ` ``). To put grave marks in code, use double grave marks instead. If the grave mark is at the start or end, pad it with one space. (Example shown below.)\n\n```markdown\n`` ` ``\n`` `Space` ``\n```\n\n### Keyboard keys\n\n*Notice: When denoting the letter itself, and not the keyboard key, use quotation marks instead.*\n\nWhen representing keyboard keys, use capital letters for single characters and title case for modifiers. Use the plus symbol (`+`) (without code) to represent key combinations. (Example shown below.)\n\n```markdown\npippi is spelt with a lowercase \"p\" like peppy.\n\nPress `Ctrl` + `O` to open the open dialog.\n```\n\nWhen representing a space or the spacebar, use `` `Space` ``.\n\n### Button and menu text\n\nWhen copying the text from a menu or button, the letter casing should be copied as it appears. (Example shown below.)\n\n```markdown\nThe `osu!direct` button is visible in the main menu on the right side, if you have an active osu!supporter tag.\n```\n\n### Folder and directory names\n\nWhen copying the name of a folder or directory, the letter casing should be copied as it appears, but prefer lowercased paths when possible. Directory paths must not be absolute (i.e. do not start the directory name from the drive letter or from the root folder). (Example shown below.)\n\n```markdown\nosu! is installed in the `AppData/Local` folder by default, unless specified otherwise during installation.\n```\n\n### Keywords and commands\n\nWhen copying a keyword or command, the letter casing should be copied as it appears or how someone normally would type it. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nAs of now, the `Name` and `Author` commands in the skin configuration file (`skin.ini`) do nothing.\n```\n\n### File names\n\nWhen copying the name of a file, the letter casing should be copied as it appears. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nTo play osu!, double click the `osu!.exe` icon.\n```\n\n### File extensions\n\n*Notice: File formats (not to be confused with file extensions) must be written in capital letters without the prefixed fullstop (`.`).*\n\nFile extensions must be prefixed with a fullstop (`.`) and be followed by the file extension in lowercase letters. (Example shown below.)\n\n```markdown\nThe JPG (or JPEG) file format has the `.jpg` (or `.jpeg`) extension.\n```\n\n### Chat channels\n\nWhen copying the name of a chat channel, start it with a hash (`#`), followed by the channel name in lowercase letters. (Example shown below.)\n\n```markdown\n`#lobby` is where you can advertise your multi room.\n```\n\n## Preformatted text (code blocks)\n\n*Notice: Syntax highlighting for preformatted text is not implemented on the website yet.*\n\nPreformatted text (also known as code blocks) must be fenced using three grave marks. They should set the language identifier for syntax highlighting.\n\n## Links\n\nThere are two types of links: inline and reference. Inline has two styles.\n\nThe following is an example of both inline styles:\n\n```markdown\n[Game Modifiers](/wiki/Game_Modifiers)\n\n\n```\n\nThe following is an example of the reference style:\n\n```markdown\n[Game Modifiers][game mods link]\n\n[game mods link]: /wiki/Game_Modifiers\n```\n\n---\n\nLinks must use the inline style if they are only referenced once. The inline angle brackets style should be avoided. References to reference links must be placed at the bottom of the article.\n\n### Internal links\n\n*Note: Internal links refer to links that stay inside the `https://osu.ppy.sh/` domain.*\n\n#### Wiki links\n\nAll links that point to an wiki article should start with `/wiki/` followed by the path to get to the article you are targeting. Relative links may also be used. Some examples include the following:\n\n```markdown\n[FAQ](/wiki/FAQ)\n[pippi](/wiki/Mascots#-pippi)\n[Beatmaps](../)\n[Pattern](./Pattern)\n```\n\nWiki links must not use redirects and must not have a trailing forward slash (`/`).\n\nBad examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/ASC)\n[Developers](/wiki/Developers/)\n[Developers](/wiki/Developers/#game-client-developers)\n```\n\nGood examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/Article_styling_criteria)\n[Developers](/wiki/Developers)\n[Developers](/wiki/Developers#game-client-developers)\n```\n\n##### Sub-article links\n\nWiki links that point to a sub-article should include the parent article's folder name in its link text. See the following example:\n\n```markdown\n*See also: [Beatmap Editor/Design](/wiki/Beatmap_Editor/Design)*\n```\n\n##### Section links\n\n*Notice: On the website, heading levels 4 and 5 are not given the id attribute. This means that they can not be linked to directly.*\n\nWiki links that point to a section of an article may use the section sign symbol (`§`). See the following example:\n\n```markdown\n*For timing rules, see: [Ranking Criteria § Timing](/wiki/Ranking_Criteria#timing)*\n```\n\n#### Other osu! links\n\nThe URL from the address bar of your web browser should be copied as it is when linking to other osu! web pages. The `https://osu.ppy.sh` part of the URL must be kept.\n\n##### User profiles\n\nAll usernames must be linked on first occurrence. Other occurrences are optional, but must be consistent throughout the entire article for all usernames. If it is difficult to determine the user's id, it may be skipped over.\n\nWhen linking to a user profile, the user's id number must be used. Use the new website (`https://osu.ppy.sh/users/{username})`) to get the user's id.\n\nThe link text of the user link should be the user's current name.\n\n##### Difficulties\n\nWhenever linking to a single difficulty, use this format as the link text:\n\n```\n{artist} - {title} ({creator}) [{difficuty_name}]\n```\n\nThe link must actually link to that difficulty. Beatmap difficulty URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}#{mode}/{BeatmapID}\n```\n\nThe difficulty name may be left outside of the link text, but doing so must be consistent throughout the entire article.\n\n##### Beatmaps\n\nWhenever linking to a beatmap, use this format as the link text:\n\n```\n{artist} - {title} ({creator})\n```\n\nAll beatmap URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}\n```\n\n### External links\n\n*Notice: External links refers to links that go outside the `https://osu.ppy.sh/` domain.*\n\nThe `https` protocol must be used, unless the site does not support it. External links must be a clean and direct link to a reputable source. The link text should be the title of the page it is linking to. The URL from the address bar of your web browser should be copied as it is when linking to other external pages.\n\nThere are no visual differences between external and osu! web links. Due to this, the website name should be included in the title text. See the following example:\n\n```markdown\n*For more information about music theory, see: [Music theory](https://en.wikipedia.org/wiki/Music_theory \"Wikipedia\")*\n```\n\n## Images\n\nThere are two types of image links: inline and reference. Examples:\n\n**Inline style:**\n\n```markdown\n![](/wiki/shared/flag/AU.gif)\n```\n\n**Reference style:**\n\n```markdown\n![][flag_AU]\n\n[flag_AU]: /wiki/shared/flag/AU.gif\n```\n\nImages should use the inline linking style. References to reference links must be placed at the bottom of the article.\n\nImages must be placed in a folder named `img`, located in the article's folder. Images that are used in multiple articles should be stored in the `/wiki/shared/` folder.\n\n### Image caching\n\nImages on the website are cached for up to 60 days. The cached image is matched with the image link's URL.\n\nWhen updating an image, either change the image's name or append a query string to the URL. In both cases, all translations linking to the updated image should also be updated.\n\n### Formats and quality\n\nImages should use the JPG format at quality 8 (80 or 80%, depending on the program). If the image contains transparency or has text that must be readable, use the PNG format instead. If the image contains an animation, the GIF format can be used; however, this should be used sparingly as these may take longer to load or can be bigger then the [max file size](#file-size).\n\n### File size\n\nImages must be under 1 megabyte, otherwise they will fail to load. Downscaling and using JPG at 80% is almost always under the size limit.\n\nAll images should be optimised as much as possible. Use [jpeg-archive](https://github.com/danielgtaylor/jpeg-archive \"GitHub\") to compress JPEG images. For consistency, use the following command for jpeg-archive:\n\n```sh\njpeg-recompress -am smallfry \n```\n\nWhere `` is the file name to be compressed and `` is the compressed file name.\n\n### File names\n\n*Notice: File extensions must use lowercase letters, otherwise they will fail to load!*\n\nUse hyphens (`-`) when spacing words. When naming an image, the file name should be meaningful or descriptive but short.\n\n### Formatting and positioning\n\n*Note: It is currently not possible to float an image or have text wrap around it.*\n\nImages on the website will be centred when it is on a single line, by themself. Otherwise, they will be positioned inline with the paragraph. The following example will place the image in the center:\n\n```markdown\nInstalling osu! is easy. First, download the installer from the download page.\n\n![](img/download-page.jpg)\n\nThen locate the installer and run it.\n```\n\n### Alt text\n\nImages should have alt text unless it is for decorative purposes.\n\n### Captions\n\nImages are given captions on the website if they fulfill these conditions:\n\n1. The image is by itself.\n2. The image is not inside a heading.\n3. The image has title text.\n\nCaptions are assumed via the title text, which must be in plain text. Images with captions are also centred with the image on the website.\n\n### Max image width\n\nThe website's max image width is the width of the article body. Images should be no wider than 800 pixels.\n\n### Annotating images\n\nWhen annotating images, use *Torus Regular*. For Chinese, Korean, Japanese characters, use *Microsoft YaHei*.\n\nAnnotating images should be avoided, as it is difficult for translators (and other editors) to edit them.\n\n#### Translating annotated images\n\nWhen translating annotated images, the localised image version must be placed in the same directory as the original version (i.e. the English version). The filename of a localised image version must start with the original version's name, followed by a hyphen, followed by the locale name (in capital letters). See the following examples:\n\n- `hardrock-mod-vs-easy-mod.jpg` for English\n- `hardrock-mod-vs-easy-mod-DE.jpg` for German\n- `hardrock-mod-vs-easy-mod-ZH-TW.jpg` for Traditional Chinese\n\n### Screenshots of gameplay\n\nAll screenshots of gameplay must be done in the stable build, unless it is for a specific feature that is unavailable in the stable build. You should use the in-game screenshot feature (`F12`).\n\n#### Game client settings\n\n*Note: If you do not want to change your current settings for the wiki, you can move your `osu!..cfg` out of the osu! folder and move it back later.*\n\nYou must set these settings before taking a screenshot of the game client (settings not stated below are assumed to be at their defaults):\n\n- Select language: `English`\n- Prefer metadata in original language: `Enabled`\n- Resolution: `1280x720`\n- Fullscreen mode: `Disabled`\n- Parallax: `Disabled`\n- Menu tips: `Disabled`\n- Seasonal backgrounds: `Never`\n- Always show key overlay: `Enabled`\n- Current skin: `Default` (first option)\n\n*Notice to translators: If you are translating an article containing screenshots of the game, you may set the game client's language to the language you are translating in.*\n\n### Image links\n\nImages must not be part of a link text.\n\nFlag icons next to user links must be separate from the link text. See the following example:\n\n```markdown\n![][flag_AU] [peppy](https://osu.ppy.sh/users/2)\n```\n\n### Flag icons\n\n*For a list of flag icons, see: [issue \\#328](https://github.com/ppy/osu-wiki/issues/328 \"GitHub\")*\n\nThe flag icons use the two letter code (in all capital letters) and end with `.gif`. When adding a flag inline, use this format:\n\n```markdown\n![](/wiki/shared/flag/xx.gif)\n```\n\nWhere `xx` is the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 \"Wikipedia\") two-lettered country code for the flag.\n\nThe full country name should be added in the title text. The country code in the alternate text is optional, but must be applied to all flag icons in the article.\n\n## Tables\n\nTables on the website only support headings along the first row.\n\nTables must not be beautified (do not pad cells with extra spaces to make their widths uniform). They must have a vertical bar (`|`) on the left and right sides and the text of each cell must be padded with one space on both sides. Empty cells must use a vertical bar (`|`) followed by two spaces then another vertical bar (`|`).\n\nThe delimiter row (the next line after the table heading) must use only three characters per column (and be padded with a space on both sides), which must look like one of the following:\n\n- `:--` (for left align)\n- `:-:` (for centre align)\n- `--:` (for right align)\n\n---\n\nThe following is an example of what a table should look like:\n\n```markdown\n| Team \"Picturesque\" Red | Score | Team \"Statuesque\" Blue | Average Beatmap Stars |\n| :-- | :-: | --: | :-- |\n| **peppy** | 5 - 2 | pippi | 9.3 stars |\n| Aiko | 1 - 6 | **Alisa** | 4.2 stars |\n| Ryūta | 3 - 4 | **Yuzu** | 5.1 stars |\n| **Taikonator** | 7 - 0 | Tama | 13.37 stars |\n| Maria | No Contest | Mocha | |\n```\n\n## Blockquotes\n\nThe blockquote is limited to quoting text from someone. It must not be used to format text otherwise.\n\n## Thematic breaks\n\nThe thematic break (also known as the horizontal rule or line) should be used sparingly. A few uses of the thematic break may include (but is not limited to):\n\n- separating images from text\n- separating multiple images that follow one another\n- shifting the topic within a section\n\nThese must have an empty line before and after the markup. Thematic breaks must use only three hyphens, as depicted below:\n\n```markdown\n---\n```\n" + "# Formatting\n\n*For the writing standards, see: [Article style criteria/Writing](../Writing)*\\\n*Notice: This article uses [RFC 2119](https://tools.ietf.org/html/rfc2119) to describe requirement levels.*\n\n## Locales\n\nListed below are the properly-supported locales for the wiki:\n\n| File Name | Locale Name | Native Script |\n| :-- | :-- | :-- |\n| `en.md` | English | English |\n| `ar.md` | Arabic | \u0627\u064e\u0644\u0652\u0639\u064e\u0631\u064e\u0628\u0650\u064a\u064e\u0651\u0629\u064f |\n| `be.md` | Belarusian | \u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f \u043c\u043e\u0432\u0430 |\n| `bg.md` | Bulgarian | \u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438 |\n| `cs.md` | Czech | \u010cesky |\n| `da.md` | Danish | Dansk |\n| `de.md` | German | Deutsch |\n| `el.md` | Greek | \u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac |\n| `es.md` | Spanish | Espa\u00f1ol |\n| `fi.md` | Finnish | Suomi |\n| `fr.md` | French | Fran\u00e7ais |\n| `hu.md` | Hungarian | Magyar |\n| `id.md` | Indonesian | Bahasa Indonesia |\n| `it.md` | Italian | Italiano |\n| `ja.md` | Japanese | \u65e5\u672c\u8a9e |\n| `ko.md` | Korean | \ud55c\uad6d\uc5b4 |\n| `nl.md` | Dutch | Nederlands |\n| `no.md` | Norwegian | Norsk |\n| `pl.md` | Polish | Polski |\n| `pt.md` | Portuguese | Portugu\u00eas |\n| `pt-br.md` | Brazilian Portuguese | Portugu\u00eas (Brasil) |\n| `ro.md` | Romanian | Rom\u00e2n\u0103 |\n| `ru.md` | Russian | \u0420\u0443\u0441\u0441\u043a\u0438\u0439 |\n| `sk.md` | Slovak | Sloven\u010dina |\n| `sv.md` | Swedish | Svenska |\n| `th.md` | Thai | \u0e44\u0e17\u0e22 |\n| `tr.md` | Turkish | T\u00fcrk\u00e7e |\n| `uk.md` | Ukrainian | \u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430 \u043c\u043e\u0432\u0430 |\n| `vi.md` | Vietnamese | Ti\u1ebfng Vi\u1ec7t |\n| `zh.md` | Chinese (Simplified) | \u7b80\u4f53\u4e2d\u6587 |\n| `zh-tw.md` | Traditional Chinese (Taiwan) | \u7e41\u9ad4\u4e2d\u6587\uff08\u53f0\u7063\uff09 |\n\n*Note: The website will give readers their selected language's version of an article. If it is not available, the English version will be given.*\n\n## Front matter\n\nFront matter must be placed at the very top of the file. It is written in [YAML](https://en.wikipedia.org/wiki/YAML#Example) and describes additional information about the article. This must be surrounded by three hyphens (`---`) on the lines above and below it, and an empty line must follow it before the title heading.\n\n### Articles that need help\n\n*Note: Avoid translating English articles with this tag. In addition to this, this tag should be added when the translation needs its own clean up.*\n\nThe `needs_cleanup` tag may be added to articles that need rewriting or formatting help. It is also acceptable to open an issue on GitHub for this purpose. This tag must be written as shown below:\n\n```yaml\nneeds_cleanup: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be done to remove the tag.\n\n### Outdated articles\n\n*Note: Avoid translating English articles with this tag. If the English article has this tag, the translation must also have this tag.*\n\nEnglish articles may become outdated when the content they contain is misleading or no longer relevant. These should receive an `outdated` tag, which must be written as shown below:\n\n```yaml\noutdated: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be updated to remove the tag.\n\n### Outdated translations\n\nTranslated articles that are outdated must use the `outdated_translation` tag when the English variant is updated, except for minor wording, grammar changes, and the like, that do not affect the meaning of the article.\n\n```yaml\noutdated_translation: true\n```\n\nWhen outdating translations, they must also receive an `outdated_since` tag that points to the first commit where the English version is updated.\n\n```yaml\noutdated_since: 29eac89cd535f8b071ca000af8fe4f0be22bdc9b\n```\n\n### Tagging articles\n\nTags help the website's search engine query articles better. Tags should be written in the same language as the article and include the original list of tags. Tags should use lowercase letters where applicable.\n\nFor example, an article called \"Beatmap discussion\" may include the following tags:\n\n```yaml\ntags:\n - beatmap discussions\n - modding V2\n - MV2\n```\n\n### Translations without reviews\n\n*Note: Wiki maintainers will determine and apply this mark prior to merging.*\n\nSometimes, translations are added to the wiki without review from other native speakers of the language. In this case, the `no_native_review` mark is added to let future translators know that it may need to be checked again. This tag must be written as shown below:\n\n```yaml\nno_native_review: true\n```\n\n## Article naming\n\n*See also: [Folder names](#folder-names) and [Titles](#titles)*\n\nArticle titles should be singular and use sentence case. See [Wikipedia's naming conventions article](https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(plurals)) for more details.\n\nArticle titles should match the folder name it is in (spaces may replace underscores (`_`) where appropriate). If the folder name changes, the article title should be changed to match it and vice versa.\n\n---\n\nContest and tournament articles are an exception. The folder name must use abbreviations, acronyms, or initialisms. The article's title must be the full name of the contest or tournament.\n\n## Folder and file structure\n\n### Folder names\n\n*See also: [Article naming](#article-naming)*\n\nFolder names must be in English and use sentence case.\n\nFolder names must only use these characters:\n\n- uppercase and lowercase letters\n- numbers\n- underscores (`_`)\n- hyphens (`-`)\n- exclamation marks (`!`)\n\n### Article file names\n\nThe file name of an article can be found in the `File Name` column of the [locales section](#locales). The location of a translated article must be placed in the same folder as the English article.\n\n### Index articles\n\nAn index article must be created if the folder is intended to only hold other articles. Index articles must contain a list of articles that are inside its own folder. They may also contain other information, such as a lead paragraph or descriptions of the linked articles.\n\n### Disambiguation articles\n\n[Disambiguation](/wiki/Disambiguation) articles must be placed in the `/wiki/Disambiguation` folder. The main page must be updated to include the disambiguation article. Refer to [Disambiguation/Mod](/wiki/Disambiguation/Mod) as an example.\n\nRedirects must be updated to have the ambiguous keyword(s) redirect to the disambiguation article.\n\nArticles linked from a disambiguation article must have a [For other uses](#for-other-uses) hatnote.\n\n## HTML\n\nHTML must not be used, with exception for [comments](#comments). The structure of the article must be redone if HTML is used.\n\n### Comments\n\nHTML comments should be used for marking to-dos, but may also be used to annotate text. They should be on their own line, but can be placed inline in a paragraph. If placed inline, the start of the comment must not have a space.\n\nBad example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\nGood example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\n## Editing\n\n### End of line sequence\n\n*Caution: Uploading Markdown files using `CRLF` (carriage return and line feed) via GitHub will result in those files using `CRLF`. To prevent this, set the line ending to `LF` (line feed) before uploading.*\n\nMarkdown files must be checked in using the `LF` end of line sequence.\n\n### Escaping\n\nMarkdown syntax should be escaped as needed. However, article titles are parsed as plain text and so must not be escaped.\n\n### Paragraphs\n\nEach paragraph must be followed by one empty line.\n\n### Line breaks\n\nLine breaks must use a backslash (`\\`).\n\nLine breaks must be used sparingly.\n\n## Hatnote\n\n*Not to be confused with [Notice](#notice).*\n\nHatnotes are short notes placed at the top of an article or section to help readers navigate to related articles or inform them about related topics.\n\nHatnotes must be italicised and be placed immediately after the heading. If multiple hatnotes are used, they must be on the same paragraph separated with a line break.\n\n### Main page\n\n*Main page* hatnotes direct the reader to the main article of a topic. When this hatnote is used, it implies that the section it is on is a summary of what the linked page is about. This hatnote should have only one link. These must be formatted as follows:\n\n```markdown\n*Main page: {article}*\n\n*Main pages: {article} and {article}*\n```\n\n### See also\n\n*See also* hatnotes suggest to readers other points of interest from a given article or section. These must be formatted as follows:\n\n```markdown\n*See also: {article}*\n\n*See also: {article} and {article}*\n```\n\n### For see\n\n*For see* hatnotes are similar to *see also* hatnotes, but are generally more descriptive and direct. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*For {description}, see: {article}*\n\n*For {description}, see: {article} and {article}*\n```\n\n### Not to be confused with\n\n*Not to be confused with* hatnotes help distinguish ambiguous or misunderstood article titles or sections. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*Not to be confused with {article}.*\n\n*Not to be confused with {article} or {article}.*\n```\n\n### For other uses\n\n*For other uses* hatnotes are similar to *not to be confused with* hatnotes, but links directly to the [disambiguation article](#disambiguation-articles). This hatnote must only link to the disambiguation article. These must be formatted as follows:\n\n```markdown\n*For other uses, see {disambiguation article}.*\n```\n\n## Notice\n\n*Not to be confused with [Hatnote](#hatnote).*\n\nA notice should be placed where appropriate in a section, but must start off the paragraph and use italics. Notices may contain bolding where appropriate, but should be kept to a minimum. Notices must be written as complete sentences. Thus, unlike most [hatnotes](#hatnote), notices must use a full stop (`.`) or an exclamation mark (`!`) if appropriate. Anything within the same paragraph of a notice must also be italicised. These must be formatted as follows:\n\n```markdown\n*Note: {note}.*\n\n*Notice: {notice}.*\n\n*Caution: {caution}.*\n\n*Warning: {warning}.*\n```\n\n- `Note` should be used for factual or trivial details.\n- `Notice` should be used for reminders or to draw attention to something that the reader should be made aware of.\n- `Caution` should be used to warn the reader to avoid unintended consequences.\n- `Warning` should be used to warn the reader that action may be taken against them.\n\n## Stacked hatnotes and notices\n\nMultiple hatnotes and notices may be stacked when necessary. When doing this, they must be stacked without blank lines and use trailing backslashes:\n\n```markdown\n*Warning: {warning}.*\\\n*See also: {article}*\n```\n\nIn many cases, it may be more fitting to embed extraneous hatnotes or notices into paragraph text instead of stacking many of them.\n\n## Emphasising\n\n### Bolding\n\nBold text must use double asterisks (`**`).\n\nLead paragraphs may bold the first occurrence of the article's title.\n\n### Italics\n\nItalics must use single asterisks (`*`).\n\nThe first occurrence of an abbreviation, acronym, or initialism may be italicised.\n\nItalics may also be used to provide emphasis or help with readability.\n\nNames of work or video games should be italicised. osu! \u2014 the game \u2014 is exempt from this.\n\nAs an example, when referring to songs in the format of `{artist} - {title}`, the whole part is a reference to the work and should therefore be italicised:\n\n```markdown\n*cYsmix - triangles* is a one of the three intro songs that can be heard when starting the game client.\n```\n\nArtist names are otherwise generally not italicised. This means that in free-form references, only the title should be italicised, because the artist name is then not part of the name of the work:\n\n```markdown\n*Blue Zenith* by xi is an infamous song in the osu! community due to a famous score set by a top player on a certain beatmap.\n```\n\n### Emphasis and links\n\nLinked text appears in a different colour which already provides emphasis, and therefore does not need further emphasis:\n\n```markdown\n[Camellia - OOPARTS](https://cametek.bandcamp.com/track/parts) is an example of a song officially created specifically for osu!, otherwise known as an *osu! original*, since it was specifically commissioned for the osu! World Cup 2020 tiebreaker.\n```\n\nThis however does not apply if the referenced work is not the only part of the link:\n\n```markdown\n[Voltaeyx's beatmap of *TheFatRat - Mayday (feat. Laura Brehm)*](https://osu.ppy.sh/beatmapsets/756794) amassed considerable popularity in 2018 due to its unique overlapping slider patterns.\n```\n\nThe above type of construction should be used sparingly, and must not be used in places with many links, such as tables or lists.\n\n## Headings\n\nAll headings must use sentence case.\n\nHeadings must use the [ATX (hash) style](https://github.github.com/gfm/#atx-headings) and must have an empty line before and after the heading. The title heading is an exception when it is on the first line. If this is the case, there only needs to be an empty line after the title heading.\n\nHeadings must not exceed a heading level of 5 and must not be used to style or format text.\n\n### Titles\n\n*See also: [Article naming](#article-naming)*\\\n*Caution: Titles are parsed as plain text; they must not be escaped.*\n\nThe first heading in all articles must be a level 1 heading, being the article's title. All headings afterwards must be [section headings](#sections). Titles must not contain formatting, links, or images.\n\nThe title heading must be on the first line, unless [front matter](#front-matter) is being used. If that is the case, the title heading must go after it and have an empty line before the title heading.\n\n### Sections\n\nSection headings must use levels 2 to 5. The section heading proceeding the [title heading](#titles) must be a level 2 heading. Unlike titles, section headings may have small image icons.\n\nSection headings must not skip a heading level (i.e. do not go from a level 2 heading to a level 4 heading) and must not contain formatting or links.\n\n*Notice: On the website, heading levels 4 and 5 will not appear in the table of contents.*\n\n### Custom identifiers\n\nIt is possible to redefine a section's identifier, which is used for linking to it directly. Custom identifiers should be used in case the automatically generated ones are too long or contain tricky punctuation marks or images:\n\n\n\n```markdown\n## My cooldown has passed. How do I appeal? {#appeal}\n\n## Common restriction reasons and cooldowns {#common-reasons}\n\n## Ideas for a multiplayer match {id=\u0438\u0434\u0435\u0438-\u0434\u043b\u044f-\u043c\u0443\u043b\u044c\u0442\u0438\u043f\u043b\u0435\u0435\u0440\u0430} \n```\n\nThis feature can also be used for tagging a specific part of the article which doesn't have a heading. Use it sparingly:\n\n```markdown\n> That's it! You're well on your way to becoming an osu! rhythm champion!\n{#tutorial-quote}\n```\n\n## Lists\n\nLists should not go over 4 levels of indentation and should not have an empty line in between each item.\n\nFor nested lists, bullets or numbers must align with the item content of their parent lists.\n\nThe following example was done incorrectly (take note of the spacing before the bullet):\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\nThe following example was done correctly:\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\n### Bulleted\n\nBulleted lists must use a hyphen (`-`). These must then be followed by one space. (Example shown below.)\n\n```markdown\n- osu!\n - Hit circle\n - Combo number\n - Approach circle\n - Slider\n - Hit circles\n - Slider body\n - Slider ticks\n - Spinner\n- osu!taiko\n```\n\n### Numbered\n\nThe numbers in a numbered list must be incremented to represent their step.\n\n```markdown\n1. Download the osu! installer.\n2. Run the installer.\n 1. To change the installation location, click the text underneath the progression bar.\n 2. The installer will prompt for a new location, choose the installation folder.\n3. osu! will start up once installation is complete.\n4. Sign in.\n```\n\n### Mixed\n\nCombining both bulleted and numbered lists should be done sparingly.\n\n```markdown\n1. Download a skin from the forums.\n2. Load the skin file into osu!.\n - If the file is a `.zip`, unzip it and move the contents into the `Skins/` folder (found in your osu! installation folder).\n - If the file is a `.osk`, open it on your desktop or drag-and-drop it into the game client.\n3. Open osu!, if it is not opened, and select the skin in the options.\n - This may have been completed if you opened the `.osk` file or drag-and-dropped it into the game client.\n```\n\n## Code\n\nThe markup for code is a grave mark (`` ` ``). To put grave marks in code, use double grave marks instead. If the grave mark is at the start or end, pad it with one space. (Example shown below.)\n\n```markdown\n`` ` ``\n`` `Space` ``\n```\n\n### Keyboard keys\n\n*Notice: When denoting the letter itself, and not the keyboard key, use quotation marks instead.*\n\nWhen representing keyboard keys, use capital letters for single characters and title case for modifiers. Use the plus symbol (`+`) (without code) to represent key combinations. (Example shown below.)\n\n```markdown\npippi is spelt with a lowercase \"p\" like peppy.\n\nPress `Ctrl` + `O` to open the open dialog.\n```\n\nWhen representing a space or the spacebar, use `` `Space` ``.\n\n### Button and menu text\n\nWhen copying the text from a menu or button, the letter casing should be copied as it appears. (Example shown below.)\n\n```markdown\nThe `osu!direct` button is visible in the main menu on the right side, if you have an active osu!supporter tag.\n```\n\n### Folder and directory names\n\nWhen copying the name of a folder or directory, the letter casing should be copied as it appears, but prefer lowercased paths when possible. Directory paths must not be absolute (i.e. do not start the directory name from the drive letter or from the root folder). (Example shown below.)\n\n```markdown\nosu! is installed in the `AppData/Local` folder by default, unless specified otherwise during installation.\n```\n\n### Keywords and commands\n\nWhen copying a keyword or command, the letter casing should be copied as it appears or how someone normally would type it. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nAs of now, the `Name` and `Author` commands in the skin configuration file (`skin.ini`) do nothing.\n```\n\n### File names\n\nWhen copying the name of a file, the letter casing should be copied as it appears. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nTo play osu!, double click the `osu!.exe` icon.\n```\n\n### File extensions\n\n*Notice: File formats (not to be confused with file extensions) must be written in capital letters without the prefixed fullstop (`.`).*\n\nFile extensions must be prefixed with a fullstop (`.`) and be followed by the file extension in lowercase letters. (Example shown below.)\n\n```markdown\nThe JPG (or JPEG) file format has the `.jpg` (or `.jpeg`) extension.\n```\n\n### Chat channels\n\nWhen copying the name of a chat channel, start it with a hash (`#`), followed by the channel name in lowercase letters. (Example shown below.)\n\n```markdown\n`#lobby` is where you can advertise your multi room.\n```\n\n## Preformatted text (code blocks)\n\n*Notice: Syntax highlighting for preformatted text is not implemented on the website yet.*\n\nPreformatted text (also known as code blocks) must be fenced using three grave marks. They should set the language identifier for syntax highlighting.\n\n## Links\n\n*See also: [Footnotes](#footnotes)*\n\nThere are two types of links: inline and reference. Inline has two styles.\n\nThe following is an example of both inline styles:\n\n```markdown\n[Game modifier](/wiki/Game_modifier)\n\n\n```\n\nThe following is an example of the reference style:\n\n```markdown\n[Game modifier][game mods link]\n\n[game mods link]: /wiki/Game_modifier\n```\n\n---\n\nLinks must use the inline style if they are only referenced once. The inline angle brackets style should be avoided. References to reference links must be placed at the bottom of the article.\n\n### Internal links\n\n*Note: Internal links refer to links that stay inside the `https://osu.ppy.sh/` domain.*\n\n#### Wiki links\n\nAll links that point to an wiki article should start with `/wiki/` followed by the path to get to the article you are targeting. Relative links may also be used. Some examples include the following:\n\n```markdown\n[FAQ](/wiki/FAQ)\n[pippi](/wiki/Mascots#pippi)\n[Beatmaps](../)\n[Pattern](./Pattern)\n```\n\nWiki links must not use redirects and must not have a trailing forward slash (`/`).\n\nBad examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/ASC)\n[Developers](/wiki/Developers/)\n[Developers](/wiki/Developers/#game-client-developers)\n```\n\nGood examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/Article_styling_criteria)\n[Developers](/wiki/People/The_Team/Developers)\n[Developers](/wiki/People/The_Team/Developers#game-client-developers)\n```\n\n##### Sub-article links\n\nWiki links that point to a sub-article should include the parent article's folder name in its link text. See the following example:\n\n```markdown\n*See also: [Beatmap Editor/Design](/wiki/Client/Beatmap_editor/Design)*\n```\n\n##### Section links\n\n*Notice: On the website, heading levels 4 and 5 are not given the id attribute. This means that they can not be linked to directly.*\n\nWiki links that point to a section of an article may use the section sign symbol (`\u00a7`). See the following example:\n\n```markdown\n*For timing rules, see: [Ranking Criteria \u00a7 Timing](/wiki/Ranking_Criteria#timing)*\n```\n\n#### Other osu! links\n\nThe URL from the address bar of your web browser should be copied as it is when linking to other osu! web pages. The `https://osu.ppy.sh` part of the URL must be kept.\n\n##### User profiles\n\nAll usernames must be linked on first occurrence. Other occurrences are optional, but must be consistent throughout the entire article for all usernames. If it is difficult to determine the user's id, it may be skipped over.\n\nWhen linking to a user profile, the user's id number must be used. Use the new website (`https://osu.ppy.sh/users/{username})`) to get the user's id.\n\nThe link text of the user link should be the user's current name.\n\n##### Difficulties\n\nWhenever linking to a single difficulty, use this format as the link text:\n\n```\n{artist} - {title} ({creator}) [{difficulty_name}]\n```\n\nThe link must actually link to that difficulty. Beatmap difficulty URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}#{mode}/{BeatmapID}\n```\n\nThe difficulty name may be left outside of the link text, but doing so must be consistent throughout the entire article.\n\n##### Beatmaps\n\nWhenever linking to a beatmap, use this format as the link text:\n\n```\n{artist} - {title} ({creator})\n```\n\nAll beatmap URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}\n```\n\n### External links\n\n*Notice: External links refers to links that go outside the `https://osu.ppy.sh/` domain.*\n\nThe `https` protocol must be used, unless the site does not support it. External links must be a clean and direct link to a reputable source. The URL from the address bar of your web browser should be copied as it is when linking to other external pages.\n\nThere are no visual differences between external and osu! web links. Due to this, the website name should be included in the title text. See the following example:\n\n```markdown\n*For more information about music theory, see: [Music theory](https://en.wikipedia.org/wiki/Music_theory)*\n```\n\n## Images\n\nThere are two types of image links: inline and reference. Examples:\n\n**Inline style:**\n\n```markdown\n![Gold crown](/wiki/shared/crown-gold.png \"1st place\")\n```\n\n**Reference style:**\n\n```markdown\n![Gold crown][GCrown]\n\n[GCrown]: /wiki/shared/crown-gold.png \"1st place\"\n```\n\nImages should use the inline linking style. Reference link definitions must be placed at the bottom of the article.\n\nAll block images on the page (that have nothing else on the same line) are combined into a single gallery, which can be navigated using arrow icons on both sides of the screen, keyboard shortcuts, or screen swipes on mobile devices.\n\n### Alternative and title text\n\nThe text in the first pair of square brackets (*alternative text*) should describe the image literally. It is used by screen readers or when the image fails to load. It can be omitted if it is identical to the title text or if the image is included only for decorative purposes.\n\nThe text in the quotation marks (*title text*) should give additional context to the image or indicate its meaning. It is displayed as a tooltip when hovering over the image and used as a caption if applicable. It does not support any markdown formatting.\n\n### Display\n\nIf an image is the sole content of a paragraph, it displays as a centred block. Otherwise, it flows with the surrounding inline text.\n\nBlock images with title text display the title text as a caption below the image. Avoid adding [HTML comment](#comments) or any other text on the same line as the image, as this will cause the caption not to be rendered.\n\nBlock images are commonly paired with [infobox](#infoboxes) formatting to reduce their initial size and float them to the side of other content:\n\n```markdown\n::: Infobox\n![](img/mod-response.png \"An example of a response to a mod\")\n:::\n```\n\n### Image caching\n\nImages on the website are cached for up to 60 days. The cached image is matched with the image link's URL.\n\nWhen updating an image, either change the image's name or append a query string to the URL. In both cases, all translations linking to the updated image should also be updated.\n\n### Formats and quality\n\nImages should use the JPG format at quality 8 (80 or 80%, depending on the program). If the image contains transparency or has text that must be readable, use the PNG format instead. If the image contains an animation, the GIF format can be used; however, this should be used sparingly as these may take longer to load or can be bigger then the [max file size](#file-size).\n\n### File size\n\nImages must be under 1 megabyte, otherwise they will fail to load. Downscaling and using JPG at 80% is almost always under the size limit.\n\nAll images should be optimised as much as possible. Use [jpeg-archive](https://github.com/danielgtaylor/jpeg-archive) to compress JPEG images. For consistency, use the following command for jpeg-archive:\n\n```sh\njpeg-recompress -am smallfry \n```\n\nWhere `` is the file name to be compressed and `` is the compressed file name.\n\n### File names\n\n*Notice: File extensions must use lowercase letters, otherwise they will fail to load!*\n\nUse hyphens (`-`) when spacing words. When naming an image, the file name should be meaningful or descriptive but short.\n\nImages must be placed in a folder named `img` under the article's folder. Images that are used in multiple articles should be stored in the `/wiki/shared` folder.\n\n### Max image width\n\nThe website's max image width is the width of the article body. Images should be no wider than 800 pixels.\n\n### Annotating images\n\nWhen annotating images, use *Torus Regular*. For Chinese, Korean, Japanese characters, use *Microsoft YaHei*.\n\nAnnotating images should be avoided, as it is difficult for translators (and other editors) to edit them.\n\n#### Translating annotated images\n\nWhen translating annotated images, the localised image version must be placed in the same directory as the original version (i.e. the English version). The filename of a localised image version must start with the original version's name, followed by a hyphen, followed by the locale name (in capital letters). See the following examples:\n\n- `hardrock-mod-vs-easy-mod.jpg` for English\n- `hardrock-mod-vs-easy-mod-DE.jpg` for German\n- `hardrock-mod-vs-easy-mod-ZH-TW.jpg` for Traditional Chinese\n\n### Screenshots of gameplay\n\nAll screenshots of gameplay must be done in the stable build, unless it is for a specific feature that is unavailable in the stable build. You should use the in-game screenshot feature (`F12`).\n\n#### Game client settings\n\n*Note: If you do not want to change your current settings for the wiki, you can move your `osu!..cfg` out of the osu! folder and move it back later.*\n\nYou must set these settings before taking a screenshot of the game client (settings not stated below are assumed to be at their defaults):\n\n- Select language: `English`\n- Prefer metadata in original language: `Enabled`\n- Resolution: `1280x720`\n- Fullscreen mode: `Disabled`\n- Parallax: `Disabled`\n- Menu tips: `Disabled`\n- Seasonal backgrounds: `Never`\n- Always show key overlay: `Enabled`\n- Current skin: `Default` (first option)\n\n*Notice to translators: If you are translating an article containing screenshots of the game, you may set the game client's language to the language you are translating in.*\n\n### Image links\n\nImages must not be part of a link text.\n\n## Flag icons\n\nThe flag icons use the two letter code (in all capital letters) to match a certain territory. When adding a flag inline, use this format:\n\n```markdown\n::{ flag=XX }::\n```\n\nWhere `XX` is the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) two-lettered country code for the flag.\n\n## Tables\n\nTables on the website only support headings along the first row.\n\nTables must not be beautified (do not pad cells with extra spaces to make their widths uniform). They must have a vertical bar (`|`) on the left and right sides and the text of each cell must be padded with one space on both sides. Empty cells must use a vertical bar (`|`) followed by two spaces then another vertical bar (`|`).\n\nThe delimiter row (the next line after the table heading) must use only three characters per column (and be padded with a space on both sides), which must look like one of the following:\n\n- `:--` (for left align)\n- `:-:` (for centre align)\n- `--:` (for right align)\n\n---\n\nThe following is an example of what a table should look like:\n\n```markdown\n| Team \"Picturesque\" Red | Score | Team \"Statuesque\" Blue | Average Beatmap Stars |\n| :-- | :-: | --: | :-- |\n| **peppy** | 5 - 2 | pippi | 9.3 stars |\n| Aiko | 1 - 6 | **Alisa** | 4.2 stars |\n| Ry\u016bta | 3 - 4 | **Yuzu** | 5.1 stars |\n| **Taikonator** | 7 - 0 | Tama | 13.37 stars |\n| Maria | No Contest | Mocha | |\n```\n\n## Infoboxes\n\nAn infobox is a fixed-width block which is aligned to the right side of the article. It may contain a relevant image, which explains the surrounding text, or a block of navigation that links to other articles from the same category.\n\nExample use, rendered on the right:\n\n\n\n::: Infobox\n![](/wiki/shared/mods/SD.png \"Sudden Death mod icon\")\n:::\n\n```markdown\n::: Infobox\n![](/wiki/shared/mods/SD.png \"Sudden Death mod icon\")\n:::\n```\n\nInfoboxes should be used with caution in the following cases:\n\n- Short sections: the next section's heading appears below any infoboxes, leaving a large gap after the text.\n- Several images at once: instead, use individual infoboxes for better design.\n\n## Footnotes\n\nFootnotes are short notes located at the end of the page. They are used for citing sources, or providing background information that would otherwise disrupt the flow of the article. Footnotes may contain text formatting and links.\n\nIn the osu! wiki, footnotes are implemented using special syntax (`[^identifier]`). Footnotes can use any identifier, but they will automatically be rendered as superscripts with increasing numbers in order of their first appearance. Translations must not modify identifiers of footnotes.\n\nFootnote references are placed directly after the words, phrases, or sentences they explain, with no space in between. These references must be placed after punctuation, except for parentheses, when they pertain to the contents inside, and dashes.\n\nThe footnotes themselves must be placed in a separate second-level heading at the end of the article. Depending on the content, the heading used may be `References`, `Notes`, or `Notes and references`.\n\nCorrect usage examples:\n\n```markdown\nThe osu! wiki is a project that was meant to replace the old FAQ system.[^wiki-faq] It was named after the rhythm game osu![^osu] and the largest open online encyclopedia, Wikipedia. From the very start, it had attracted skillful translators[^wiki-tl] and editors.\n\n## References\n\n[^wiki-faq]: https://osu.ppy.sh/community/forums/topics/68525\n[^wiki-tl]: https://osu.ppy.sh/community/forums/posts/1177500\n[^osu]: https://osu.ppy.sh/community/forums/posts/1178153\n```\n\n### Citations\n\nCitations, or references, are used to identify a source of information. Citations via footnotes should be preferred over inline links.\n\nReferences should whenever applicable specify author, date, service/platform, and title. The exact format may vary depending on the referenced material with a preference for brevity.\n\nExamples:\n\n```markdown\nThe first version of the osu!api was made available on July 2, 2013.[^api-first-usage] It had received critical acclaim from users.[^api-praise] A new version of API, released several years later, contains many more capabilities.[^api-v2-2020] Endpoint versioning is common among web APIs.[^web-api]\n\n## References\n\n[^api-first-usage]: [Forum thread by peppy (2013-07-02) \"osu!api open beta\"](https://osu.ppy.sh/community/forums/posts/2403913)\n[^api-praise]: [Forum post by Menchi (2013-11-02) in \"osu!api open beta\"](https://osu.ppy.sh/community/forums/posts/2662247)\n[^api-v2-2020]: [Tweet by @ppy (2020-03-20)](https://twitter.com/ppy/status/1263083636363948032)\n[^web-api]: [\"Web API\" on Wikipedia](https://en.wikipedia.org/wiki/Web_API)\n```\n\n### Notes\n\nFootnotes may be used for storing explanations or tangential remarks which cannot be inlined without worsening the article's readability, or are less significant than the article itself. Such footnotes may use free-form text.\n\nExample:\n\n```markdown\nA tournament must not be organised and run by an inexperienced team of unaccomplished and irreputable staff.[^staff]\n\n## Notes\n\n[^staff]: An *inexperienced* staff member is loosely defined as someone who has been playing osu! for less than an hour in total.\n```\n\n## Blockquotes\n\nThe blockquote is limited to [quoting someone or something](/wiki/Article_styling_criteria/Writing#block-quotation). It must not be used to format text otherwise.\n\n```markdown\n> plz enjoy game\n\n\u2014rrtyui\n```\n\n## Thematic breaks\n\nThe thematic break (also known as the horizontal rule or line) should be used sparingly. A few uses of the thematic break may include (but is not limited to):\n\n- separating images from text\n- separating multiple images that follow one another\n- shifting the topic within a section\n\nThese must have an empty line before and after the markup. Thematic breaks must use only three hyphens, as depicted below:\n\n```markdown\n---\n```\n" }; // From https://osu.ppy.sh/api/v2/wiki/en/Article_styling_criteria From 0309751d551afdec7467b6811fff8f330a5619fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 Oct 2022 21:50:31 +0300 Subject: [PATCH 554/583] Add test coverage --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 39432ee059..31a866b8f5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -189,6 +189,16 @@ Line after image"; }); } + [Test] + public void TestFlag() + { + AddStep("Add flag", () => + { + markdownContainer.CurrentPath = @"https://dev.ppy.sh"; + markdownContainer.Text = "::{flag=\"AU\"}::"; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From b22e201289ec3e82e0742c8837ca9528060ce92f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 8 Oct 2022 23:43:23 +0200 Subject: [PATCH 555/583] Fixed stream convert float precision edge case --- .../Editor/TestSceneSliderStreamConversion.cs | 31 +++++++++++++++++++ .../Sliders/SliderSelectionBlueprint.cs | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index 51871dd9e5..0601dc6068 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -148,6 +148,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } + [Test] + public void TestFloatEdgeCaseConversion() + { + Slider slider = null; + + AddStep("select first slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + + AddStep("change to these specific circumstances", () => + { + EditorBeatmap.Difficulty.SliderMultiplier = 1; + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(slider.StartTime); + timingPoint.BeatLength = 352.941176470588; + slider.Path.ControlPoints[^1].Position = new Vector2(-110, 16); + slider.Path.ExpectedDistance.Value = 100; + }); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.25), + (time: 0.5, pathPosition: 0.5), + (time: 0.75, pathPosition: 0.75), + (time: 1, pathPosition: 1))); + } + private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles) { if (EditorBeatmap.HitObjects.Contains(slider)) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7c289b5b05..265a1d21b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount(); double pathPosition = positionWithRepeats - (int)positionWithRepeats; // every second span is in the reverse direction - need to reverse the path position. - if (Precision.AlmostBigger(positionWithRepeats % 2, 1)) + if (positionWithRepeats % 2 >= 1) pathPosition = 1 - pathPosition; Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition); From c89a55043e68e7af3b8a656d803e5e54568d4b60 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 9 Oct 2022 15:28:12 +0300 Subject: [PATCH 556/583] Fix smoke being blocked with "Relax" mod enabled --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index dec965e567..c86609abd7 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -3,8 +3,10 @@ #nullable disable +using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -60,17 +62,14 @@ namespace osu.Game.Rulesets.Osu { public bool AllowUserPresses = true; + private static readonly OsuAction[] all_actions = (OsuAction[])Enum.GetValues(typeof(OsuAction)); + + protected override IEnumerable BlockedActions => !AllowUserPresses ? all_actions.Where(a => a != OsuAction.Smoke) : Enumerable.Empty(); + public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { } - - protected override bool Handle(UIEvent e) - { - if (!AllowUserPresses) return false; - - return base.Handle(e); - } } } From 2d4f390372b9624fdd35dc148b599c25cef5f65d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 9 Oct 2022 17:14:16 +0300 Subject: [PATCH 557/583] Remove non-smoke key bindings on "Relax" mod instead --- .../ManiaInputTestScene.cs | 4 +++- osu.Game.Rulesets.Osu/OsuInputManager.cs | 24 +++++++++++++++---- .../Bindings/DatabasedKeyBindingContainer.cs | 6 ++--- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ++-- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index eb380c07a6..e456659ac4 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -3,9 +3,11 @@ #nullable disable +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests @@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests { } - protected override void ReloadMappings() + protected override void ReloadMappings(IQueryable realmKeyBindings) { KeyBindings = DefaultKeyBindings; } diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index c86609abd7..14b1c0f1a5 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -11,6 +10,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu @@ -60,16 +60,30 @@ namespace osu.Game.Rulesets.Osu private class OsuKeyBindingContainer : RulesetKeyBindingContainer { - public bool AllowUserPresses = true; + private bool allowUserPresses = true; - private static readonly OsuAction[] all_actions = (OsuAction[])Enum.GetValues(typeof(OsuAction)); - - protected override IEnumerable BlockedActions => !AllowUserPresses ? all_actions.Where(a => a != OsuAction.Smoke) : Enumerable.Empty(); + public bool AllowUserPresses + { + get => allowUserPresses; + set + { + allowUserPresses = value; + ReloadMappings(); + } + } public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { } + + protected override void ReloadMappings(IQueryable realmKeyBindings) + { + base.ReloadMappings(realmKeyBindings); + + if (!AllowUserPresses) + KeyBindings = KeyBindings.Where(b => b.GetAction() == OsuAction.Smoke).ToList(); + } } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 14a041b459..4f079ab435 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,13 +55,13 @@ namespace osu.Game.Input.Bindings { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. - reloadMappings(sender.AsQueryable()); + ReloadMappings(sender.AsQueryable()); }); base.LoadComplete(); } - protected override void ReloadMappings() => reloadMappings(queryRealmKeyBindings(realm.Realm)); + protected sealed override void ReloadMappings() => ReloadMappings(queryRealmKeyBindings(realm.Realm)); private IQueryable queryRealmKeyBindings(Realm realm) { @@ -70,7 +70,7 @@ namespace osu.Game.Input.Bindings .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } - private void reloadMappings(IQueryable realmKeyBindings) + protected virtual void ReloadMappings(IQueryable realmKeyBindings) { var defaults = DefaultKeyBindings.ToList(); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 1a97153f2f..64ac021204 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -230,9 +230,9 @@ namespace osu.Game.Rulesets.UI { } - protected override void ReloadMappings() + protected override void ReloadMappings(IQueryable realmKeyBindings) { - base.ReloadMappings(); + base.ReloadMappings(realmKeyBindings); KeyBindings = KeyBindings.Where(b => RealmKeyBindingStore.CheckValidForGameplay(b.KeyCombination)).ToList(); } From 5d2e3dcf4af4adac45faa2265bce4e25e5701037 Mon Sep 17 00:00:00 2001 From: "D.Headley" Date: Sun, 9 Oct 2022 22:52:54 +0200 Subject: [PATCH 558/583] Remove leftover Enum --- osu.Game/Skinning/HUDSkinComponents.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 osu.Game/Skinning/HUDSkinComponents.cs diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs deleted file mode 100644 index 586882d790..0000000000 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -namespace osu.Game.Skinning -{ - public enum HUDSkinComponents - { - ComboCounter, - ScoreCounter, - AccuracyCounter, - HealthDisplay, - SongProgress, - BarHitErrorMeter, - ColourHitErrorMeter, - } -} From 0b5f8e1c93e47176ad132ea79a62da4db4c419fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 13:12:12 +0900 Subject: [PATCH 559/583] Remove duplicated colour in osu!mania argon skin column colouring There will probably be more change to colours, but let's just fix this for now. --- .../Argon/ManiaArgonSkinTransformer.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 27926c11eb..ae313e0b91 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -89,13 +90,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Color4 colour; + const int total_colours = 7; + if (stage.IsSpecialColumn(column)) colour = new Color4(159, 101, 255, 255); else { - switch (column % 8) + switch (column % total_colours) { - default: + case 0: colour = new Color4(240, 216, 0, 255); break; @@ -112,20 +115,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon break; case 4: - colour = new Color4(178, 0, 240, 255); - break; - - case 5: colour = new Color4(0, 96, 240, 255); break; - case 6: + case 5: colour = new Color4(0, 226, 240, 255); break; - case 7: + case 6: colour = new Color4(0, 240, 96, 255); break; + + default: + throw new ArgumentOutOfRangeException(); } } From 26a473e85e7958ba9fd93d85ec5700d000e57e10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 14:24:32 +0900 Subject: [PATCH 560/583] Update resources (fixes osu!mania flashlight crash) --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2a678f1c61..692e817f8f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index efdb6c6995..35fdb82230 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 47872e4ff7..105a20c9a4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 33620b7bd62a90249dc2c81cbc0763bfdc0015cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 15:24:17 +0900 Subject: [PATCH 561/583] Fix slider ball facing incorrect direction during rewinding in editor Closes https://github.com/ppy/osu/issues/20648. --- .../Objects/Drawables/DrawableSliderBall.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 6bfb4e8aae..a2fe623897 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -186,17 +186,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Vector2? lastPosition; + private bool rewinding; + public void UpdateProgress(double completionProgress) { Position = drawableSlider.HitObject.CurvePositionAt(completionProgress); var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); + if (Clock.ElapsedFrameTime != 0) + rewinding = Clock.ElapsedFrameTime < 0; + // Ensure the value is substantially high enough to allow for Atan2 to get a valid angle. if (diff.LengthFast < 0.01f) return; - ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); + ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0); lastPosition = Position; } } From 5a5f3af27d3aa3392f1a45a5388c575c3494c389 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 15:42:08 +0900 Subject: [PATCH 562/583] Rename property and add xmldoc --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 6 +++-- osu.Game.Rulesets.Osu/OsuInputManager.cs | 28 +++++++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index fac1cbfd47..753de6231a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); + + public override Type[] IncompatibleMods => + base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. @@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods return; } - osuInputManager.AllowUserPresses = false; + osuInputManager.AllowGameplayInputs = false; } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 14b1c0f1a5..1e59e19246 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -19,9 +19,16 @@ namespace osu.Game.Rulesets.Osu { public IEnumerable PressedActions => KeyBindingContainer.PressedActions; - public bool AllowUserPresses + /// + /// Whether gameplay input buttons should be allowed. + /// Defaults to true, generally used for mods like Relax which turn off main inputs. + /// + /// + /// Of note, auxiliary inputs like the "smoke" key are left usable. + /// + public bool AllowGameplayInputs { - set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value; + set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs = value; } /// @@ -60,14 +67,21 @@ namespace osu.Game.Rulesets.Osu private class OsuKeyBindingContainer : RulesetKeyBindingContainer { - private bool allowUserPresses = true; + private bool allowGameplayInputs = true; - public bool AllowUserPresses + /// + /// Whether gameplay input buttons should be allowed. + /// Defaults to true, generally used for mods like Relax which turn off main inputs. + /// + /// + /// Of note, auxiliary inputs like the "smoke" key are left usable. + /// + public bool AllowGameplayInputs { - get => allowUserPresses; + get => allowGameplayInputs; set { - allowUserPresses = value; + allowGameplayInputs = value; ReloadMappings(); } } @@ -81,7 +95,7 @@ namespace osu.Game.Rulesets.Osu { base.ReloadMappings(realmKeyBindings); - if (!AllowUserPresses) + if (!AllowGameplayInputs) KeyBindings = KeyBindings.Where(b => b.GetAction() == OsuAction.Smoke).ToList(); } } From f6a8cc3f32d4acfdb2253f593679ab83583d8c6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 16:05:38 +0900 Subject: [PATCH 563/583] Add test showing broken input in HUD overlay when hidden --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index da6604a653..a984f508ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Tests.Gameplay; @@ -148,6 +149,42 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); } + [Test] + public void TestInputDoesntWorkWhenHUDHidden() + { + SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType().Single(); + + bool seeked = false; + + createNew(); + + AddStep("bind seek", () => + { + seeked = false; + + var progress = getSongProgress(); + + progress.ShowHandle = true; + progress.OnSeek += _ => seeked = true; + }); + + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + + AddStep("attempt seek", () => + { + InputManager.MoveMouseTo(getSongProgress()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("seek not performed", () => !seeked); + + AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); + + AddStep("attempt seek", () => InputManager.Click(MouseButton.Left)); + AddAssert("seek performed", () => seeked); + } + [Test] public void TestHiddenHUDDoesntBlockComponentUpdates() { From 20adc522b9e7e054483c6387ff9f21d64f4367f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 15:57:41 +0900 Subject: [PATCH 564/583] Fix HUD components being interactive even when the HUD is visually hidden --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3fbb051c3b..7833c2d7fa 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -39,6 +39,10 @@ namespace osu.Game.Screens.Play /// public float BottomScoringElementsHeight { get; private set; } + // HUD uses AlwaysVisible on child components so they can be in an updated state for next display. + // Without blocking input, this would also allow them to be interacted with in such a state. + public override bool PropagatePositionalInputSubTree => ShowHud.Value; + public readonly KeyCounterDisplay KeyCounter; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; From b783fdf667d03aa6cddc3ccf1fffa704e7552c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 16:20:17 +0900 Subject: [PATCH 565/583] Fix clicking a leaderboard score at song select transtiioning briefly to main menu - Regressed in #20567. - Closes #20651. --- osu.Game/OsuGame.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 939d3a63ed..1716e48395 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -563,6 +563,15 @@ namespace osu.Game // This should be able to be performed from song select, but that is disabled for now // due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios). + // + // As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select. + // This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the + // song select leaderboard). + IEnumerable validScreens = + Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset) + ? new[] { typeof(SongSelect) } + : Array.Empty(); + PerformFromScreen(screen => { Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score"); @@ -580,7 +589,7 @@ namespace osu.Game screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false)); break; } - }); + }, validScreens: validScreens); } public override Task Import(params ImportTask[] imports) From eae32ca4839c3779176a69a0099a257e8f789192 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Mon, 10 Oct 2022 09:39:40 +0200 Subject: [PATCH 566/583] Switch `DefinitelyBigger` to `AlmostBigger` to account for fp errors --- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 85bff630e3..58d00801a5 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Objects startTime += barLength; } - for (double t = startTime; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) + for (double t = startTime; Precision.AlmostBigger(endTime, t); t += barLength, currentBeat++) { double roundedTime = Math.Round(t, MidpointRounding.AwayFromZero); From 28185178d60ab2ff188fc15d7ebd417ff9aacdfa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Oct 2022 16:51:50 +0900 Subject: [PATCH 567/583] Add support for weird storyboards which have backwards events Closes https://github.com/ppy/osu/issues/20632. Not adding test coverage because this is just weird. --- osu.Game/Storyboards/CommandTimeline.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 4d0da9597b..d1a1edcd03 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -27,7 +27,10 @@ namespace osu.Game.Storyboards public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue) { if (endTime < startTime) - return; + { + (startTime, endTime) = (endTime, startTime); + (startValue, endValue) = (endValue, startValue); + } commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue }); From d389808427e6d693240df30a8412f432e0b08a1f Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Mon, 10 Oct 2022 14:00:26 +0200 Subject: [PATCH 568/583] Make effect point search more efficient --- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 58d00801a5..9105a78741 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Objects double lastHitTime = 1 + lastObject.GetEndTime(); var timingPoints = beatmap.ControlPointInfo.TimingPoints; - var effectPoints = beatmap.ControlPointInfo.EffectPoints; if (timingPoints.Count == 0) return; @@ -39,14 +38,13 @@ namespace osu.Game.Rulesets.Objects for (int i = 0; i < timingPoints.Count; i++) { TimingControlPoint currentTimingPoint = timingPoints[i]; - EffectControlPoint? currentEffectPoint = effectPoints.FirstOrDefault(p => p.Time == currentTimingPoint.Time); + EffectControlPoint? currentEffectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTimingPoint.Time); int currentBeat = 0; - // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object + // Stop on the next timing point, or if there is no next timing point stop slightly past the last object double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; double startTime = currentTimingPoint.Time; - double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; if (currentEffectPoint != null && currentEffectPoint.OmitFirstBarLine) From 5a4196fd51f4631ee60a94e856fa82bc9bdcdc20 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Mon, 10 Oct 2022 15:34:29 +0200 Subject: [PATCH 569/583] Make `currentEffectPoint` non-nullable --- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 9105a78741..c2709db747 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects for (int i = 0; i < timingPoints.Count; i++) { TimingControlPoint currentTimingPoint = timingPoints[i]; - EffectControlPoint? currentEffectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTimingPoint.Time); + EffectControlPoint currentEffectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTimingPoint.Time); int currentBeat = 0; // Stop on the next timing point, or if there is no next timing point stop slightly past the last object @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Objects double startTime = currentTimingPoint.Time; double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; - if (currentEffectPoint != null && currentEffectPoint.OmitFirstBarLine) + if (currentEffectPoint.OmitFirstBarLine) { startTime += barLength; } From be5a413753346fcb330bf81c71b40fe9aec83ce0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Oct 2022 01:35:35 +0900 Subject: [PATCH 570/583] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2a678f1c61..f71adff23b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index efdb6c6995..cb414ebe31 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 47872e4ff7..32e22d6c6f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + @@ -82,7 +82,7 @@ - + From d700040a0d5da69a5d8e8b7396abc398b90e62fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Oct 2022 01:38:37 +0900 Subject: [PATCH 571/583] Make country code parsing resilient to invalid cases --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 2 +- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 31a866b8f5..863b352618 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -195,7 +195,7 @@ Line after image"; AddStep("Add flag", () => { markdownContainer.CurrentPath = @"https://dev.ppy.sh"; - markdownContainer.Text = "::{flag=\"AU\"}::"; + markdownContainer.Text = "::{flag=\"AU\"}:: ::{flag=\"ZZ\"}::"; }); } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index fb8f13ab84..9d7b47281f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -57,7 +57,11 @@ namespace osu.Game.Graphics.Containers.Markdown } string flag = flagAttribute.Split('=').Last().Trim('"'); - AddDrawable(new DrawableFlag(Enum.Parse(flag)) { Size = new Vector2(20, 15) }); + + if (!Enum.TryParse(flag, out var countryCode)) + countryCode = CountryCode.Unknown; + + AddDrawable(new DrawableFlag(countryCode) { Size = new Vector2(20, 15) }); } private class OsuMarkdownInlineCode : Container From 32adc1ec14a558950f0e78e7aec2f6ffda540c48 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Mon, 10 Oct 2022 13:36:52 -0400 Subject: [PATCH 572/583] Fix division by 0 --- .../Difficulty/ManiaPerformanceCalculator.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index a925e7c0ac..5d7fec3ede 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - scoreAccuracy = customAccuracy; + scoreAccuracy = customAccuracy(); // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. @@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// /// Accuracy used to weight judgements independently from the score's actual accuracy. /// - private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); + private double customAccuracy() + { + if (totalHits==0) + return 0; + + return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); + } } } From 430eb44446e22a2e2cca0b9a7f9ab317a6f65c48 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Mon, 10 Oct 2022 13:45:16 -0400 Subject: [PATCH 573/583] slight formatting --- .../Difficulty/ManiaPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 5d7fec3ede..5e10e7a8ec 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -75,8 +75,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// private double customAccuracy() { - if (totalHits==0) - return 0; + if (totalHits == 0) + return 0; return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } From cc54fc5c1b8d491203e19cbd3e1f05034e121691 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Mon, 10 Oct 2022 13:52:01 -0400 Subject: [PATCH 574/583] rename method to reflect being a method --- .../Difficulty/ManiaPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 5e10e7a8ec..d6c2dc1d9e 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - scoreAccuracy = customAccuracy(); + scoreAccuracy = calculateCustomAccuracy(); // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// /// Accuracy used to weight judgements independently from the score's actual accuracy. /// - private double customAccuracy() + private double calculateCustomAccuracy() { if (totalHits == 0) return 0; From ed2b39a22012656d03f3238f4f0c422f9fed5af8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Oct 2022 13:53:18 +0900 Subject: [PATCH 575/583] Remove whitespace --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index d6c2dc1d9e..440dec82af 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -79,6 +79,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty return 0; return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); - } + } } } From 1a24762f9b75582c0dd0bee7368835c9e04f0730 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 11 Oct 2022 14:11:45 +0900 Subject: [PATCH 576/583] Improve drag box selection logic `AllowDeselectionDuringDrag` is remove. Instead, selected hit objects are not automatically deselected when clock is seeked to a later time (the hit object is dead). Update drag box selection even if mouse is not moved (in case clock is running or scroll wheel is used). --- .../Compose/Components/BlueprintContainer.cs | 23 +++++++++++-------- .../Components/ComposeBlueprintContainer.cs | 20 ++++++++++++++-- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8aecc75824..b15cecd506 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -106,11 +107,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual DragBox CreateDragBox() => new DragBox(); - /// - /// Whether this component is in a state where items outside a drag selection should be deselected. If false, selection will only be added to. - /// - protected virtual bool AllowDeselectionDuringDrag => true; - protected override bool OnMouseDown(MouseDownEvent e) { bool selectionPerformed = performMouseDownActions(e); @@ -389,12 +385,19 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var blueprint in SelectionBlueprints) { - if (blueprint.IsSelected && !AllowDeselectionDuringDrag) - continue; + switch (blueprint.State) + { + case SelectionState.Selected: + // Selection is preserved even after blueprint becomes dead. + if (!quad.Contains(blueprint.ScreenSpaceSelectionPoint)) + blueprint.Deselect(); + break; - bool shouldBeSelected = blueprint.IsAlive && blueprint.IsPresent && quad.Contains(blueprint.ScreenSpaceSelectionPoint); - if (blueprint.IsSelected != shouldBeSelected) - blueprint.ToggleSelection(); + case SelectionState.NotSelected: + if (blueprint.IsAlive && blueprint.IsPresent && quad.Contains(blueprint.ScreenSpaceSelectionPoint)) + blueprint.Select(); + break; + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 43ead88d54..c8870d46a8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private PlacementBlueprint currentPlacement; private InputManager inputManager; + private DragEvent lastDragEvent; + /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. @@ -83,8 +85,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - protected override bool AllowDeselectionDuringDrag => !EditorClock.IsRunning; - protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) { base.TransferBlueprintFor(hitObject, drawableObject); @@ -120,6 +120,18 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + lastDragEvent = e; + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + lastDragEvent = null; + } + /// /// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints). /// @@ -236,6 +248,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); + // trigger every frame so drags continue to update selection while seeking time. + if (lastDragEvent != null) + OnDrag(lastDragEvent); + if (currentPlacement != null) { switch (currentPlacement.PlacementActive) From 6ab29a62d04b66130928cdb463b7b48424025fb5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 11 Oct 2022 14:23:17 +0900 Subject: [PATCH 577/583] Anchor drag box on time (catch, mania) Not done for taiko because I cannot figure out how it should work with the overlapping scroll algorithm. --- .../Edit/CatchBlueprintContainer.cs | 3 + .../Edit/ManiaBlueprintContainer.cs | 3 + .../Compose/Components/ScrollingDragBox.cs | 63 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs index 58f493b4b8..20d432d62e 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -36,5 +37,7 @@ namespace osu.Game.Rulesets.Catch.Edit return base.CreateHitObjectBlueprintFor(hitObject); } + + protected sealed override DragBox CreateDragBox() => new ScrollingDragBox((CatchPlayfield)Composer.Playfield); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index ad75afff8e..c3fad1e22b 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -33,5 +34,7 @@ namespace osu.Game.Rulesets.Mania.Edit } protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); + + protected sealed override DragBox CreateDragBox() => new ScrollingDragBox((ManiaPlayfield)Composer.Playfield); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs b/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs new file mode 100644 index 0000000000..2d2c88247b --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs @@ -0,0 +1,63 @@ +// 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 osu.Framework.Input.Events; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// A that scrolls along with the scrolling playfield. + /// + public class ScrollingDragBox : DragBox + { + public double MinTime { get; private set; } + + public double MaxTime { get; private set; } + + private double? startTime; + + private readonly ScrollingPlayfield playfield; + + public ScrollingDragBox(ScrollingPlayfield playfield) + { + this.playfield = playfield; + } + + public override void HandleDrag(MouseButtonEvent e) + { + base.HandleDrag(e); + + startTime ??= playfield.TimeAtScreenSpacePosition(e.ScreenSpaceMouseDownPosition); + double endTime = playfield.TimeAtScreenSpacePosition(e.ScreenSpaceMousePosition); + + MinTime = Math.Min(startTime.Value, endTime); + MaxTime = Math.Max(startTime.Value, endTime); + + var startPos = ToLocalSpace(playfield.ScreenSpacePositionAtTime(startTime.Value)); + var endPos = ToLocalSpace(playfield.ScreenSpacePositionAtTime(endTime)); + + switch (playfield.ScrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + Box.Y = Math.Min(startPos.Y, endPos.Y); + Box.Height = Math.Max(startPos.Y, endPos.Y) - Box.Y; + break; + + case ScrollingDirection.Left: + case ScrollingDirection.Right: + Box.X = Math.Min(startPos.X, endPos.X); + Box.Width = Math.Max(startPos.X, endPos.X) - Box.X; + break; + } + } + + public override void Hide() + { + base.Hide(); + startTime = null; + } + } +} From a44ba579c5c39f8878e3f93f51614a270a13fc66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Oct 2022 11:29:17 +0900 Subject: [PATCH 578/583] Fix beatmap update button not respecting user "prefer no video" setting Closes #20701. --- osu.Game/Database/ModelDownloader.cs | 2 +- .../Screens/Select/Carousel/UpdateBeatmapSetButton.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 877c90a534..6cba8fe819 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -45,7 +45,7 @@ namespace osu.Game.Database public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null); - public void DownloadAsUpdate(TModel originalModel) => Download(originalModel, false, originalModel); + public void DownloadAsUpdate(TModel originalModel, bool minimiseDownloadSize) => Download(originalModel, minimiseDownloadSize, originalModel); protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel) { diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 3c4ed4734b..023d3627b0 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -2,12 +2,14 @@ // 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; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -43,11 +45,15 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft; } + private Bindable preferNoVideo = null!; + [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { const float icon_size = 14; + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); + Content.Anchor = Anchor.CentreLeft; Content.Origin = Anchor.CentreLeft; @@ -104,7 +110,7 @@ namespace osu.Game.Screens.Select.Carousel return; } - beatmapDownloader.DownloadAsUpdate(beatmapSetInfo); + beatmapDownloader.DownloadAsUpdate(beatmapSetInfo, preferNoVideo.Value); attachExistingDownload(); }; } From 6f6290a21a51477dfb1546d0305afcb7b8ea5c07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Oct 2022 15:37:22 +0900 Subject: [PATCH 579/583] Use async flow for storing key binding changes to realm --- .../Overlays/Settings/Sections/Input/KeyBindingRow.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 9ff47578e9..2fea2e34b2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -387,14 +387,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (bindTarget != null) bindTarget.IsBinding = true; } - private void updateStoreFromButton(KeyButton button) - { - realm.Run(r => - { - var binding = r.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); - r.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); - }); - } + private void updateStoreFromButton(KeyButton button) => + realm.WriteAsync(r => r.Find(button.KeyBinding.ID).KeyCombinationString = button.KeyBinding.KeyCombinationString); private void updateIsDefaultValue() { From f060e6a780c43057eea6cab2e140c8b6ca8da26e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Oct 2022 16:31:37 +0900 Subject: [PATCH 580/583] Implement hold "sliding" samples in osu!mania --- .../Objects/Drawables/DrawableHoldNote.cs | 45 +++++++++++++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 15 ------- osu.Game/Rulesets/Objects/HitObject.cs | 16 +++++++ 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 19792086a7..48647f9f5f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -4,12 +4,14 @@ #nullable disable using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Audio; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private Container tailContainer; private Container tickContainer; + private PausableSkinnableSound slidingSample; + /// /// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed. /// @@ -108,6 +112,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + slidingSample = new PausableSkinnableSound { Looping = true } }); maskedContents.AddRange(new[] @@ -118,6 +123,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + isHitting.BindValueChanged(updateSlidingSample, true); + } + protected override void OnApply() { base.OnApply(); @@ -322,5 +334,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables HoldStartTime = null; isHitting.Value = false; } + + protected override void LoadSamples() + { + // Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being. + + if (HitObject.SampleControlPoint == null) + { + throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + } + + slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + } + + public override void StopAllSamples() + { + base.StopAllSamples(); + slidingSample?.Stop(); + } + + private void updateSlidingSample(ValueChangedEvent tracking) + { + if (tracking.NewValue) + slidingSample?.Play(); + else + slidingSample?.Stop(); + } + + protected override void OnFree() + { + slidingSample.Samples = null; + base.OnFree(); + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e3c1b1e168..6c2be8a49a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -34,21 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray(); - public IList CreateSlidingSamples() - { - var slidingSamples = new List(); - - var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); - if (normalSample != null) - slidingSamples.Add(normalSample.With("sliderslide")); - - var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); - if (whistleSample != null) - slidingSamples.Add(whistleSample.With("sliderwhistle")); - - return slidingSamples; - } - private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index d20e0616e5..0f79e58201 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; @@ -198,6 +199,21 @@ namespace osu.Game.Rulesets.Objects /// [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); + + public IList CreateSlidingSamples() + { + var slidingSamples = new List(); + + var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + if (normalSample != null) + slidingSamples.Add(normalSample.With("sliderslide")); + + var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + if (whistleSample != null) + slidingSamples.Add(whistleSample.With("sliderwhistle")); + + return slidingSamples; + } } public static class HitObjectExtensions From f41b79688f184ab9182ef4ffde6e7ce8bd797bfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Oct 2022 16:54:41 +0900 Subject: [PATCH 581/583] Avoid casting by accepting all `Playfield`s but throwing on a bad choice --- osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs | 3 +-- osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs | 3 +-- osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs | 5 +++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs index 20d432d62e..a0a11424d0 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -5,7 +5,6 @@ using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -38,6 +37,6 @@ namespace osu.Game.Rulesets.Catch.Edit return base.CreateHitObjectBlueprintFor(hitObject); } - protected sealed override DragBox CreateDragBox() => new ScrollingDragBox((CatchPlayfield)Composer.Playfield); + protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index c3fad1e22b..f438d6497c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -35,6 +34,6 @@ namespace osu.Game.Rulesets.Mania.Edit protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); - protected sealed override DragBox CreateDragBox() => new ScrollingDragBox((ManiaPlayfield)Composer.Playfield); + protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs b/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs index 2d2c88247b..58bfaf56ff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ScrollingDragBox.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Input.Events; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Screens.Edit.Compose.Components @@ -20,9 +21,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly ScrollingPlayfield playfield; - public ScrollingDragBox(ScrollingPlayfield playfield) + public ScrollingDragBox(Playfield playfield) { - this.playfield = playfield; + this.playfield = playfield as ScrollingPlayfield ?? throw new ArgumentException("Playfield must be of type {nameof(ScrollingPlayfield)} to use this class.", nameof(playfield)); } public override void HandleDrag(MouseButtonEvent e) From fcb9e2cc00894cf686a9fb73cbe27fd8af6dc766 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 11 Oct 2022 22:39:53 +0900 Subject: [PATCH 582/583] Refactor blueprint container drag code --- .../Compose/Components/BlueprintContainer.cs | 28 +++++++---- .../Components/ComposeBlueprintContainer.cs | 24 +--------- .../Components/EditorBlueprintContainer.cs | 5 ++ .../Timeline/TimelineBlueprintContainer.cs | 47 ++++++------------- 4 files changed, 38 insertions(+), 66 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b15cecd506..43ad270c16 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -170,11 +170,15 @@ namespace osu.Game.Screens.Edit.Compose.Components finishSelectionMovement(); } + private MouseButtonEvent lastDragEvent; + protected override bool OnDragStart(DragStartEvent e) { if (e.Button == MouseButton.Right) return false; + lastDragEvent = e; + if (movementBlueprints != null) { isDraggingBlueprint = true; @@ -189,22 +193,14 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnDrag(DragEvent e) { - if (e.Button == MouseButton.Right) - return; - - if (DragBox.State == Visibility.Visible) - { - DragBox.HandleDrag(e); - UpdateSelectionFromDragBox(); - } + lastDragEvent = e; moveCurrentSelection(e); } protected override void OnDragEnd(DragEndEvent e) { - if (e.Button == MouseButton.Right) - return; + lastDragEvent = null; if (isDraggingBlueprint) { @@ -215,6 +211,18 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.Hide(); } + protected override void Update() + { + base.Update(); + + if (lastDragEvent != null && DragBox.State == Visibility.Visible) + { + lastDragEvent.Target = this; + DragBox.HandleDrag(lastDragEvent); + UpdateSelectionFromDragBox(); + } + } + /// /// Called whenever a drag operation completes, before any change transaction is committed. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index c8870d46a8..ec07da43a0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -12,7 +12,6 @@ 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.Events; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; @@ -37,9 +36,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected new EditorSelectionHandler SelectionHandler => (EditorSelectionHandler)base.SelectionHandler; private PlacementBlueprint currentPlacement; - private InputManager inputManager; - - private DragEvent lastDragEvent; /// /// Positional input must be received outside the container's bounds, @@ -68,8 +64,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - inputManager = GetContainingInputManager(); - Beatmap.HitObjectAdded += hitObjectAdded; // updates to selected are handled for us by SelectionHandler. @@ -120,18 +114,6 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - protected override void OnDrag(DragEvent e) - { - base.OnDrag(e); - lastDragEvent = e; - } - - protected override void OnDragEnd(DragEndEvent e) - { - base.OnDragEnd(e); - lastDragEvent = null; - } - /// /// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints). /// @@ -234,7 +216,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition() { - var snapResult = Composer.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position); + var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position); // if no time was found from positional snapping, we should still quantize to the beat. snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null); @@ -248,10 +230,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); - // trigger every frame so drags continue to update selection while seeking time. - if (lastDragEvent != null) - OnDrag(lastDragEvent); - if (currentPlacement != null) { switch (currentPlacement.PlacementActive) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 6adaeb1a83..7423b368b4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -27,6 +28,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private HitObjectUsageEventBuffer usageEventBuffer; + protected InputManager InputManager { get; private set; } + protected EditorBlueprintContainer(HitObjectComposer composer) { Composer = composer; @@ -42,6 +45,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); + InputManager = GetContainingInputManager(); + Beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectRemoved += RemoveBlueprintFor; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 31990bfd35..b79c2675c8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -29,10 +29,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved(CanBeNull = true)] private Timeline timeline { get; set; } - private DragEvent lastDragEvent; private Bindable placement; private SelectionBlueprint placementBlueprint; + private bool hitObjectDragged; + /// /// Positional input must be received outside the container's bounds, /// in order to handle timeline blueprints which are stacked offscreen. @@ -98,24 +99,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnDragStart(e); } - protected override void OnDrag(DragEvent e) - { - handleScrollViaDrag(e); - - base.OnDrag(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - base.OnDragEnd(e); - lastDragEvent = null; - } - protected override void Update() { - // trigger every frame so drags continue to update selection while playback is scrolling the timeline. - if (lastDragEvent != null) - OnDrag(lastDragEvent); + if (IsDragged || hitObjectDragged) + handleScrollViaDrag(); if (Composer != null && timeline != null) { @@ -170,7 +157,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { return new TimelineHitObjectBlueprint(item) { - OnDragHandled = handleScrollViaDrag, + OnDragHandled = e => hitObjectDragged = e != null, }; } @@ -197,24 +184,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - private void handleScrollViaDrag(DragEvent e) + private void handleScrollViaDrag() { - lastDragEvent = e; + if (timeline == null) return; - if (lastDragEvent == null) - return; + var timelineQuad = timeline.ScreenSpaceDrawQuad; + float mouseX = InputManager.CurrentState.Mouse.Position.X; - if (timeline != null) - { - var timelineQuad = timeline.ScreenSpaceDrawQuad; - float mouseX = e.ScreenSpaceMousePosition.X; - - // scroll if in a drag and dragging outside visible extents - if (mouseX > timelineQuad.TopRight.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < timelineQuad.TopLeft.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); - } + // scroll if in a drag and dragging outside visible extents + if (mouseX > timelineQuad.TopRight.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < timelineQuad.TopLeft.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); } private class SelectableAreaBackground : CompositeDrawable From 84fdd2e107dc3045a295eef5668bea8cd58e24cd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 11 Oct 2022 17:16:57 +0300 Subject: [PATCH 583/583] Improve flashlight display on break periods --- .../Mods/CatchModFlashlight.cs | 6 +-- .../Mods/ManiaModFlashlight.cs | 6 +-- .../Mods/OsuModFlashlight.cs | 6 +-- .../Mods/TaikoModFlashlight.cs | 12 +++--- osu.Game/Rulesets/Mods/ModFlashlight.cs | 38 ++++++++----------- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index ff957b9b73..a9e9e8fbd5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Mods { this.playfield = playfield; - FlashlightSize = new Vector2(0, GetSizeFor(0)); + FlashlightSize = new Vector2(0, GetSize()); FlashlightSmoothness = 1.4f; } @@ -66,9 +66,9 @@ namespace osu.Game.Rulesets.Catch.Mods FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this); } - protected override void OnComboChange(ValueChangedEvent e) + protected override void UpdateFlashlightSize(float size) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 6eaede2112..947915cdf9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Mods public ManiaFlashlight(ManiaModFlashlight modFlashlight) : base(modFlashlight) { - FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0)); + FlashlightSize = new Vector2(DrawWidth, GetSize()); AddLayout(flashlightProperties); } @@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Mania.Mods } } - protected override void OnComboChange(ValueChangedEvent e) + protected override void UpdateFlashlightSize(float size) { - this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, size), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 66f367c79b..1a86901d9c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods { followDelay = modFlashlight.FollowDelay.Value; - FlashlightSize = new Vector2(0, GetSizeFor(0)); + FlashlightSize = new Vector2(0, GetSize()); FlashlightSmoothness = 1.4f; } @@ -83,9 +83,9 @@ namespace osu.Game.Rulesets.Osu.Mods return base.OnMouseMove(e); } - protected override void OnComboChange(ValueChangedEvent e) + protected override void UpdateFlashlightSize(float size) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index fca69e86cc..98f954ad29 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -47,21 +47,21 @@ namespace osu.Game.Rulesets.Taiko.Mods { this.taikoPlayfield = taikoPlayfield; - FlashlightSize = getSizeFor(0); + FlashlightSize = adjustSize(GetSize()); FlashlightSmoothness = 1.4f; AddLayout(flashlightProperties); } - private Vector2 getSizeFor(int combo) + private Vector2 adjustSize(float size) { // Preserve flashlight size through the playfield's aspect adjustment. - return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } - protected override void OnComboChange(ValueChangedEvent e) + protected override void UpdateFlashlightSize(float size) { - this.TransformTo(nameof(FlashlightSize), getSizeFor(e.NewValue), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), adjustSize(size), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Mods FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre); ClearTransforms(targetMember: nameof(FlashlightSize)); - FlashlightSize = getSizeFor(Combo.Value); + FlashlightSize = adjustSize(Combo.Value); flashlightProperties.Validate(); } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 69937a0fba..d58a901154 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -12,7 +11,6 @@ using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.OpenGL.Vertices; @@ -20,6 +18,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -84,8 +83,6 @@ namespace osu.Game.Rulesets.Mods flashlight.Combo.BindTo(Combo); drawableRuleset.KeyBindingInputManager.Add(flashlight); - - flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } protected abstract Flashlight CreateFlashlight(); @@ -100,8 +97,6 @@ namespace osu.Game.Rulesets.Mods public override bool RemoveCompletedTransforms => false; - public List Breaks = new List(); - private readonly float defaultFlashlightSize; private readonly float sizeMultiplier; private readonly bool comboBasedSize; @@ -119,37 +114,36 @@ namespace osu.Game.Rulesets.Mods shader = shaderManager.Load("PositionAndColour", FragmentShader); } + [Resolved] + private Player? player { get; set; } + + private readonly IBindable isBreakTime = new BindableBool(); + protected override void LoadComplete() { base.LoadComplete(); - Combo.ValueChanged += OnComboChange; + Combo.ValueChanged += _ => UpdateFlashlightSize(GetSize()); - using (BeginAbsoluteSequence(0)) + if (player != null) { - foreach (var breakPeriod in Breaks) - { - if (!breakPeriod.HasEffect) - continue; - - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; - - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); - } + isBreakTime.BindTo(player.IsBreakTime); + isBreakTime.BindValueChanged(_ => UpdateFlashlightSize(GetSize()), true); } } - protected abstract void OnComboChange(ValueChangedEvent e); + protected abstract void UpdateFlashlightSize(float size); protected abstract string FragmentShader { get; } - protected float GetSizeFor(int combo) + protected float GetSize() { float size = defaultFlashlightSize * sizeMultiplier; - if (comboBasedSize) - size *= GetComboScaleFor(combo); + if (isBreakTime.Value) + size *= 2.5f; + else if (comboBasedSize) + size *= GetComboScaleFor(Combo.Value); return size; }