From a4c6850ab2e64fc2a195744af8b02fd6c61a4726 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 11:34:09 +0200 Subject: [PATCH 01/75] made the SampleControlPoint and DifficultyControlPoint obsolete --- .../Legacy/DistanceObjectPatternGenerator.cs | 11 ++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 32 +++++++--- .../Beatmaps/TaikoBeatmapConverter.cs | 11 ++-- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 + osu.Game/Rulesets/Objects/HitObject.cs | 61 ++++++++++++------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 15 ++++- .../Objects/Types/IHasSliderVelocity.cs | 15 +++++ 7 files changed, 105 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 2bdd0e16ad..9e031c2b4d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -49,15 +49,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Debug.Assert(distanceData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint; double beatLength; -#pragma warning disable 618 - if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) -#pragma warning restore 618 - beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + if (hitObject.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value; + else if (hitObject is IHasSliderVelocity hasSliderVelocity) + beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else - beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity; + beatLength = timingPoint.BeatLength; SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6c2be8a49a..6952033aec 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using Newtonsoft.Json; using osu.Framework.Caching; @@ -15,13 +16,14 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasPathWithRepeats + public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity { public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; @@ -134,6 +136,13 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; + public double SliderVelocity { get; set; } = 1; + + /// + /// Whether to generate ticks on this . + /// + public bool GenerateTicks = true; + [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -151,15 +160,24 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); -#pragma warning disable 618 - var legacyDifficultyPoint = DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; -#pragma warning restore 618 - double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; - bool generateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + } + + protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) + { + base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); + + DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; +#pragma warning disable 618 + var legacyDifficultyPoint = difficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; +#pragma warning restore 618 + + SliderVelocity = difficultyControlPoint.SliderVelocity; + GenerateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 6a35e9376b..362ddccaf1 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -177,15 +177,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); - DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint; double beatLength; -#pragma warning disable 618 - if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) -#pragma warning restore 618 - beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + if (obj.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value; + else if (obj is IHasSliderVelocity hasSliderVelocity) + beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else - beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity; + beatLength = timingPoint.BeatLength; double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index a9bdd21b64..c17eea0e85 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -85,7 +85,10 @@ namespace osu.Game.Beatmaps.Formats this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList(); foreach (var hitObject in this.beatmap.HitObjects) + { + hitObject.ApplyLegacyInfo(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); + } } /// diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 25f538d211..aea564a4b9 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -16,6 +16,7 @@ using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -80,6 +81,12 @@ namespace osu.Game.Rulesets.Objects public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public double? LegacyBpmMultiplier { get; private set; } + /// /// Whether this is in Kiai time. /// @@ -105,25 +112,6 @@ namespace osu.Game.Rulesets.Objects /// The cancellation token. public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) { - var legacyInfo = controlPointInfo as LegacyControlPointInfo; - - if (legacyInfo != null) - DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); - else if (ReferenceEquals(DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) - DifficultyControlPoint = new DifficultyControlPoint(); - - DifficultyControlPoint.Time = StartTime; - - ApplyDefaultsToSelf(controlPointInfo, difficulty); - - // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time. - if (legacyInfo != null) - SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); - else if (ReferenceEquals(SampleControlPoint, SampleControlPoint.DEFAULT)) - SampleControlPoint = new SampleControlPoint(); - - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - nestedHitObjects.Clear(); CreateNestedHitObjects(cancellationToken); @@ -164,9 +152,6 @@ namespace osu.Game.Rulesets.Objects foreach (var nested in nestedHitObjects) nested.StartTime += offset; - - DifficultyControlPoint.Time = time.NewValue; - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; } } @@ -178,6 +163,38 @@ namespace osu.Game.Rulesets.Objects HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } + /// + /// Applies legacy information to this HitObject. + /// This method gets called at the end of before applying defaults. + /// + /// The control points. + /// The difficulty settings to use. + /// The cancellation token. + public void ApplyLegacyInfo(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) + { + var legacyInfo = controlPointInfo as LegacyControlPointInfo; + + DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; +#pragma warning disable 618 + if (difficultyControlPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyControlPoint) +#pragma warning restore 618 + LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; + + ApplyLegacyInfoToSelf(controlPointInfo, difficulty); + + // This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time. + SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; + + foreach (var hitSampleInfo in Samples) + { + sampleControlPoint.ApplyTo(hitSampleInfo); + } + } + + protected virtual void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) + { + } + protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken) { } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index bd2713a7d1..11e1f0beae 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,10 +9,11 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, IHasSliderVelocity { /// /// Scoring distance with a speed-adjusted beat length of 1 second. @@ -40,17 +41,27 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; + public double SliderVelocity { get; set; } = 1; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; } + protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) + { + base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); + + DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; + SliderVelocity = difficultyControlPoint.SliderVelocity; + } + public double LegacyLastTickOffset => 36; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs new file mode 100644 index 0000000000..a7195dab4b --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.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. + +namespace osu.Game.Rulesets.Objects.Types; + +/// +/// A HitObject that has a slider velocity multiplier. +/// +public interface IHasSliderVelocity +{ + /// + /// The slider velocity multiplier. + /// + double SliderVelocity { get; set; } +} From ea1e6e9798b7773bdf130febf4026edbd1caac52 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 12:12:46 +0200 Subject: [PATCH 02/75] Add LegacyContext --- .../Legacy/DistanceObjectPatternGenerator.cs | 5 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 13 +-- .../Beatmaps/TaikoBeatmapConverter.cs | 5 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 16 +++- osu.Game/Beatmaps/Legacy/LegacyContext.cs | 32 +++++++ osu.Game/Context/IContext.cs | 10 +++ osu.Game/Context/IHasContext.cs | 88 +++++++++++++++++++ osu.Game/Rulesets/Objects/HitObject.cs | 15 +--- 8 files changed, 155 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Beatmaps/Legacy/LegacyContext.cs create mode 100644 osu.Game/Context/IContext.cs create mode 100644 osu.Game/Context/IHasContext.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 9e031c2b4d..b3a68269f7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy @@ -51,8 +52,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double beatLength; - if (hitObject.LegacyBpmMultiplier.HasValue) - beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value; + if (hitObject.HasContext()) + beatLength = timingPoint.BeatLength * hitObject.GetContext().BpmMultiplier; else if (hitObject is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6952033aec..4010218f6e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -138,11 +138,6 @@ namespace osu.Game.Rulesets.Osu.Objects public double SliderVelocity { get; set; } = 1; - /// - /// Whether to generate ticks on this . - /// - public bool GenerateTicks = true; - [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -162,9 +157,10 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity; + bool generateTicks = !HasContext() || GetContext().GenerateTicks; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) @@ -172,12 +168,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; -#pragma warning disable 618 - var legacyDifficultyPoint = difficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; -#pragma warning restore 618 - SliderVelocity = difficultyControlPoint.SliderVelocity; - GenerateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 362ddccaf1..ef73ffd517 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -16,6 +16,7 @@ using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Taiko.Beatmaps { @@ -179,8 +180,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); double beatLength; - if (obj.LegacyBpmMultiplier.HasValue) - beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value; + if (obj.HasContext()) + beatLength = timingPoint.BeatLength * obj.GetContext().BpmMultiplier; else if (obj is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c17eea0e85..6f948e4d23 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats @@ -86,11 +87,24 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in this.beatmap.HitObjects) { - hitObject.ApplyLegacyInfo(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); + applyLegacyInfoToHitObject(hitObject); hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); } } + private void applyLegacyInfoToHitObject(HitObject hitObject) + { + var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; + + DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT; +#pragma warning disable 618 + if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) +#pragma warning restore 618 + hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks)); + + hitObject.ApplyLegacyInfo(beatmap.ControlPointInfo, beatmap.Difficulty); + } + /// /// Some `BeatmapInfo` members have default values that differ from the default values used by stable. /// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case diff --git a/osu.Game/Beatmaps/Legacy/LegacyContext.cs b/osu.Game/Beatmaps/Legacy/LegacyContext.cs new file mode 100644 index 0000000000..eeb02bdcb7 --- /dev/null +++ b/osu.Game/Beatmaps/Legacy/LegacyContext.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 osu.Game.Context; + +namespace osu.Game.Beatmaps.Legacy; + +public class LegacyContext : IContext +{ + public LegacyContext(double bpmMultiplier, bool generateTicks) + { + BpmMultiplier = bpmMultiplier; + GenerateTicks = generateTicks; + } + + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public double BpmMultiplier { get; } + + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public bool GenerateTicks { get; } + + public IContext Copy() + { + return new LegacyContext(BpmMultiplier, GenerateTicks); + } +} diff --git a/osu.Game/Context/IContext.cs b/osu.Game/Context/IContext.cs new file mode 100644 index 0000000000..f70cdea9f8 --- /dev/null +++ b/osu.Game/Context/IContext.cs @@ -0,0 +1,10 @@ +namespace osu.Game.Context; + +public interface IContext +{ + /// + /// Makes a deep copy of this context. + /// + /// The deep copy of this context. + public IContext Copy(); +} diff --git a/osu.Game/Context/IHasContext.cs b/osu.Game/Context/IHasContext.cs new file mode 100644 index 0000000000..bee81ea555 --- /dev/null +++ b/osu.Game/Context/IHasContext.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace osu.Game.Context +{ + public abstract class ContextContainer + { + /// + /// The contexts of this container. + /// The objects always have the type of their key. + /// + private readonly Dictionary contexts; + + protected ContextContainer() + { + contexts = new Dictionary(); + } + + /// + /// Checks whether this object has the context with type T. + /// + /// The type to check the context of. + /// Whether the context object with type T exists in this object. + public bool HasContext() where T : IContext + { + return contexts.ContainsKey(typeof(T)); + } + + /// + /// Gets the context with type T. + /// + /// The type to get the context of. + /// If the context does not exist in this hit object. + /// The context object with type T. + public T GetContext() where T : IContext + { + return (T)contexts[typeof(T)]; + } + + /// + /// Tries to get the context with type T. + /// + /// The found context with type T. + /// The type to get the context of. + /// Whether the context exists in this object. + public bool TryGetContext(out T context) where T : IContext + { + if (contexts.TryGetValue(typeof(T), out var context2)) + { + context = (T)context2; + return true; + } + + context = default!; + return false; + } + + /// + /// Sets the context object of type T. + /// + /// The context type to set. + /// The context object to store in this object. + public void SetContext(T context) where T : IContext + { + contexts[typeof(T)] = context; + } + + /// + /// Removes the context of type T from this object. + /// + /// The type to remove the context of. + /// Whether a context was removed. + public bool RemoveContext() where T : IContext + { + return RemoveContext(typeof(T)); + } + + /// + /// Removes the context of type T from this object. + /// + /// The type to remove the context of. + /// Whether a context was removed. + public bool RemoveContext(Type t) + { + return contexts.Remove(t); + } + } +} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index aea564a4b9..fb7a5739b4 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; +using osu.Game.Context; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Objects /// HitObjects may contain more properties for which you should be checking through the IHas* types. /// /// - public class HitObject + public class HitObject : ContextContainer { /// /// A small adjustment to the start time of control points to account for rounding/precision errors. @@ -81,12 +82,6 @@ namespace osu.Game.Rulesets.Objects public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; - /// - /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. - /// DO NOT USE THIS UNLESS 100% SURE. - /// - public double? LegacyBpmMultiplier { get; private set; } - /// /// Whether this is in Kiai time. /// @@ -174,12 +169,6 @@ namespace osu.Game.Rulesets.Objects { var legacyInfo = controlPointInfo as LegacyControlPointInfo; - DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; -#pragma warning disable 618 - if (difficultyControlPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyControlPoint) -#pragma warning restore 618 - LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; - ApplyLegacyInfoToSelf(controlPointInfo, difficulty); // This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time. From 891b87a5ff97e9eaeb7a0de15472217e97b2540c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 12:52:21 +0200 Subject: [PATCH 03/75] remove ApplyLegacyInfo method --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ----- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 36 ++++++++++++++++--- osu.Game/Rulesets/Objects/HitObject.cs | 26 -------------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 8 ----- 4 files changed, 32 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4010218f6e..a90d60cd1b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -163,14 +163,6 @@ namespace osu.Game.Rulesets.Osu.Objects TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } - protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) - { - base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); - - DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; - SliderVelocity = difficultyControlPoint.SliderVelocity; - } - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 6f948e4d23..dad23df282 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -17,6 +17,7 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps.Formats { @@ -27,6 +28,11 @@ namespace osu.Game.Beatmaps.Formats /// public const int EARLY_VERSION_TIMING_OFFSET = 24; + /// + /// A small adjustment to the start time of control points to account for rounding/precision errors. + /// + private const double control_point_leniency = 1; + internal static RulesetStore RulesetStore; private Beatmap beatmap; @@ -87,12 +93,11 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in this.beatmap.HitObjects) { - applyLegacyInfoToHitObject(hitObject); - hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); + applyLegacyInfoAndDefaults(hitObject); } } - private void applyLegacyInfoToHitObject(HitObject hitObject) + private void applyLegacyInfoAndDefaults(HitObject hitObject) { var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; @@ -102,7 +107,30 @@ namespace osu.Game.Beatmaps.Formats #pragma warning restore 618 hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks)); - hitObject.ApplyLegacyInfo(beatmap.ControlPointInfo, beatmap.Difficulty); + if (hitObject is IHasSliderVelocity hasSliderVelocity) + hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity; + + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; + + foreach (var hitSampleInfo in hitObject.Samples) + { + sampleControlPoint.ApplyTo(hitSampleInfo); + } + + if (hitObject is not IHasRepeats hasRepeats) return; + + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + { + double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; + sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(time) : SampleControlPoint.DEFAULT; + + foreach (var hitSampleInfo in hasRepeats.NodeSamples[i]) + { + sampleControlPoint.ApplyTo(hitSampleInfo); + } + } } /// diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fb7a5739b4..2298a7ef12 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -158,32 +158,6 @@ namespace osu.Game.Rulesets.Objects HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } - /// - /// Applies legacy information to this HitObject. - /// This method gets called at the end of before applying defaults. - /// - /// The control points. - /// The difficulty settings to use. - /// The cancellation token. - public void ApplyLegacyInfo(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) - { - var legacyInfo = controlPointInfo as LegacyControlPointInfo; - - ApplyLegacyInfoToSelf(controlPointInfo, difficulty); - - // This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time. - SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; - - foreach (var hitSampleInfo in Samples) - { - sampleControlPoint.ApplyTo(hitSampleInfo); - } - } - - protected virtual void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) - { - } - protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken) { } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 11e1f0beae..47aabf1599 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -54,14 +54,6 @@ namespace osu.Game.Rulesets.Objects.Legacy Velocity = scoringDistance / timingPoint.BeatLength; } - protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) - { - base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty); - - DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT; - SliderVelocity = difficultyControlPoint.SliderVelocity; - } - public double LegacyLastTickOffset => 36; } } From bf1951fc38200c8372df7ada3ce19bc8c4a9443a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 12:54:45 +0200 Subject: [PATCH 04/75] Fix incorrect xmldoc --- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index c454439c5c..6cd4d74a31 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// - /// The speed multiplier at this control point. + /// The default sample bank at this control point. /// public string SampleBank { @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps.ControlPoints } /// - /// The default sample bank at this control point. + /// The default sample volume at this control point. /// public readonly BindableInt SampleVolumeBindable = new BindableInt(100) { From 97910d6be6b96bcb56d77d8489f41f80cd58f1b4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 13:06:37 +0200 Subject: [PATCH 05/75] remove unused directives --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - osu.Game/Rulesets/Objects/HitObject.cs | 2 -- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 1 - 3 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a90d60cd1b..73ac184c57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -15,7 +15,6 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 2298a7ef12..0502610eab 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -16,8 +16,6 @@ using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Legacy; using osu.Game.Context; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 47aabf1599..f602cbac09 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,6 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Objects.Legacy { From c37875bee824e8c00bbc0d07273dbd98cbef5930 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 15:53:36 +0200 Subject: [PATCH 06/75] remove hitobject SampleControlPoint usage from LegacyBeatmapEncoder --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 072e442dea..04786cc9fb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); - writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); + writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); @@ -268,7 +268,12 @@ namespace osu.Game.Beatmaps.Formats { foreach (var hitObject in hitObjects) { - yield return hitObject.SampleControlPoint; + if (hitObject.Samples.Count > 0) + { + int volume = hitObject.Samples.Max(o => o.Volume); + int customIndex = hitObject.Samples.OfType().Max(o => o.CustomSampleBank); + yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex }; + } foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects)) yield return nested; From a6346171575e76ba0bf1b043040bbd0db8188b4e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 16:01:06 +0200 Subject: [PATCH 07/75] Fix file header notice --- osu.Game/Context/{IHasContext.cs => ContextContainer.cs} | 5 ++++- osu.Game/Context/IContext.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) rename osu.Game/Context/{IHasContext.cs => ContextContainer.cs} (94%) diff --git a/osu.Game/Context/IHasContext.cs b/osu.Game/Context/ContextContainer.cs similarity index 94% rename from osu.Game/Context/IHasContext.cs rename to osu.Game/Context/ContextContainer.cs index bee81ea555..7ff0280bcf 100644 --- a/osu.Game/Context/IHasContext.cs +++ b/osu.Game/Context/ContextContainer.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace osu.Game.Context diff --git a/osu.Game/Context/IContext.cs b/osu.Game/Context/IContext.cs index f70cdea9f8..61b1b11f43 100644 --- a/osu.Game/Context/IContext.cs +++ b/osu.Game/Context/IContext.cs @@ -1,4 +1,7 @@ -namespace osu.Game.Context; +// 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.Context; public interface IContext { From ebe1d852f53a80f3775f0ff602e95e8a74f3ca65 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 16:01:43 +0200 Subject: [PATCH 08/75] remove other usages of hitobject SampleControlPoint --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 6 +++--- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 8 +------- osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs | 1 - osu.Game/Rulesets/UI/Playfield.cs | 4 ++-- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 5b59a81f91..a2ae1764dd 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield break; // Samples that allow themselves to be overridden by control points have a volume of 0. - int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); + int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume); double samplePlayTime = sampledHitObject.GetEndTime(); EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index f810f51027..91dd7754d0 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Edit /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. protected void BeginPlacement(bool commitStart = false) { - var nearestSampleControlPoint = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.SampleControlPoint?.DeepClone() as SampleControlPoint; - - HitObject.SampleControlPoint = nearestSampleControlPoint ?? new SampleControlPoint(); + // Take the hitnormal sample of the last hit object + var lastHitNormal = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + HitObject.Samples.Add(lastHitNormal); placementHandler.BeginPlacement(HitObject); if (commitStart) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f6c3452e48..79fc778287 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -357,13 +357,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (samples.Length <= 0) return; - 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}."); - } - - Samples.Samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + Samples.Samples = samples.Cast().ToArray(); } private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples(); diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index e1c03e49e3..d4510a4519 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -52,7 +52,6 @@ namespace osu.Game.Rulesets.UI return; var samples = nextObject.Samples - .Select(s => nextObject.SampleControlPoint.ApplyTo(s)) .Cast() .ToArray(); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index b1c3b78e67..6016a53918 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -293,10 +293,10 @@ namespace osu.Game.Rulesets.UI { // prepare sample pools ahead of time so we're not initialising at runtime. foreach (var sample in hitObject.Samples) - prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + prepareSamplePool(sample); foreach (var sample in hitObject.AuxiliarySamples) - prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + prepareSamplePool(sample); foreach (var nestedObject in hitObject.NestedHitObjects) preloadSamples(nestedObject); From 065464d90cae0c7ea887be51b7ca171011f70560 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:12:53 +0200 Subject: [PATCH 09/75] Fixed DifficultyPointPiece --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 ++- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 9 ++- .../Objects/Types/IHasSliderVelocity.cs | 4 + .../Timeline/DifficultyPointPiece.cs | 23 ++++-- .../Timeline/HitObjectPointPiece.cs | 77 +++++++++---------- .../Components/Timeline/SamplePointPiece.cs | 5 +- .../Timeline/TimelineHitObjectBlueprint.cs | 45 ++++------- 7 files changed, 90 insertions(+), 82 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 73ac184c57..efb98ba888 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -135,7 +136,13 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; - public double SliderVelocity { get; set; } = 1; + public BindableDouble SliderVelocityBindable = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index f602cbac09..94d21a06ed 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -40,7 +41,13 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; - public double SliderVelocity { get; set; } = 1; + public BindableDouble SliderVelocityBindable = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs index a7195dab4b..c0ac5036ee 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.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 osu.Framework.Bindables; + namespace osu.Game.Rulesets.Objects.Types; /// @@ -12,4 +14,6 @@ public interface IHasSliderVelocity /// The slider velocity multiplier. /// double SliderVelocity { get; set; } + + BindableNumber SliderVelocityBindable { get; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index d3cdd461ea..4741b75641 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -13,12 +13,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Timing; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -29,13 +31,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly BindableNumber speedMultiplier; public DifficultyPointPiece(HitObject hitObject) - : base(hitObject.DifficultyControlPoint) { HitObject = hitObject; - speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy(); + speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityBindable.GetBoundCopy(); } + protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; + protected override void LoadComplete() { base.LoadComplete(); @@ -78,7 +81,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Spacing = new Vector2(0, 15), Children = new Drawable[] { - sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new DifficultyControlPoint().SliderVelocityBindable) + sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }) { KeyboardStep = 0.1f }, @@ -94,11 +102,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. - var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray(); + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).Where(o => o is IHasSliderVelocity).ToArray(); // even if there are multiple objects selected, we can still display a value if they all have the same value. - var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null; + var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1 ? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable : null; if (selectedPointBindable != null) { @@ -117,7 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in relevantObjects) { - h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value; + ((IHasSliderVelocity)h).SliderVelocity = val.NewValue.Value; beatmap.Update(h); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index 5b0a5729c8..bd699c2e88 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -7,59 +7,54 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Compose.Components.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline; + +public partial class HitObjectPointPiece : CircularContainer { - public partial class HitObjectPointPiece : CircularContainer + protected OsuSpriteText Label { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - private readonly ControlPoint point; + AutoSizeAxes = Axes.Both; - protected OsuSpriteText Label { get; private set; } + Color4 colour = GetRepresentingColour(colours); - protected HitObjectPointPiece(ControlPoint point) + InternalChildren = new Drawable[] { - this.point = point; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.Both; - - Color4 colour = point.GetRepresentingColour(colours); - - InternalChildren = new Drawable[] + new Container { - new Container + AutoSizeAxes = Axes.X, + Height = 16, + Masking = true, + CornerRadius = 8, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] { - AutoSizeAxes = Axes.X, - Height = 16, - Masking = true, - CornerRadius = 8, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] + new Box { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - Label = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding(5), - Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), - Colour = colours.B5, - } + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Colour = colours.B5, } - }, - }; - } + } + }, + }; + + protected virtual Color4 GetRepresentingColour(OsuColour colours) + { + return colours.Yellow; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 314137a565..50278bffc0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -13,10 +13,12 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -28,13 +30,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly BindableNumber volume; public SamplePointPiece(HitObject hitObject) - : base(hitObject.SampleControlPoint) { HitObject = hitObject; volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); } + protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 4e5087c004..de659cddb8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -102,6 +102,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }, + sampleOverrideDisplay = new SamplePointPiece(Item) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopCentre + }, }); if (item is IHasDuration) @@ -111,6 +116,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled = e => OnDragHandled?.Invoke(e) }); } + + if (item is IHasSliderVelocity) + { + AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomCentre + } + ); + } } protected override void LoadComplete() @@ -208,36 +223,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (Item is IHasRepeats repeats) updateRepeats(repeats); } - - if (!ReferenceEquals(difficultyControlPoint, Item.DifficultyControlPoint)) - { - difficultyControlPoint = Item.DifficultyControlPoint; - difficultyOverrideDisplay?.Expire(); - - if (Item.DifficultyControlPoint != null && Item is IHasDistance) - { - AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.BottomCentre - }); - } - } - - if (!ReferenceEquals(sampleControlPoint, Item.SampleControlPoint)) - { - sampleControlPoint = Item.SampleControlPoint; - sampleOverrideDisplay?.Expire(); - - if (Item.SampleControlPoint != null) - { - AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopCentre - }); - } - } } private void updateRepeats(IHasRepeats repeats) From c23a7b014eb7a6a4b984bafc85459044cf29323e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:17:07 +0200 Subject: [PATCH 10/75] add missing } --- .../Edit/Compose/Components/Timeline/HitObjectPointPiece.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index bd699c2e88..f7854705a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -52,6 +52,7 @@ public partial class HitObjectPointPiece : CircularContainer } }, }; + } protected virtual Color4 GetRepresentingColour(OsuColour colours) { From 66eda40cdf1911c2c4139c00fa9ebc8478058168 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:22:22 +0200 Subject: [PATCH 11/75] fix implementations of IHasSliderVelocity --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index efb98ba888..e8eb197186 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; - public BindableDouble SliderVelocityBindable = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); public double SliderVelocity { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 94d21a06ed..7ddd372dc9 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; - public BindableDouble SliderVelocityBindable = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); public double SliderVelocity { From 755ad25dbe22987fd1cd5e0c851adf1f99698640 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 18:27:20 +0200 Subject: [PATCH 12/75] clean code --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index de659cddb8..1c36bec53e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }, - sampleOverrideDisplay = new SamplePointPiece(Item) + new SamplePointPiece(Item) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (item is IHasSliderVelocity) { - AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) + AddInternal(new DifficultyPointPiece(Item) { Anchor = Anchor.TopLeft, Origin = Anchor.BottomCentre @@ -202,12 +202,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline colouredComponents.Colour = OsuColour.ForegroundTextColourFor(averageColour); } - private SamplePointPiece? sampleOverrideDisplay; - private DifficultyPointPiece? difficultyOverrideDisplay; - - private DifficultyControlPoint difficultyControlPoint = null!; - private SampleControlPoint sampleControlPoint = null!; - protected override void Update() { base.Update(); From e4b64bdc3e0b3e3074c76a8286e986acfdeedb57 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 25 Apr 2023 19:06:29 +0200 Subject: [PATCH 13/75] clean up code stuff --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 1 - osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - .../Components/Timeline/TimelineHitObjectBlueprint.cs | 9 ++++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index b3a68269f7..2427812ecd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Utils; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e8eb197186..247cf94f59 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using System.Text.Json.Serialization; using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 1c36bec53e..c2106e0598 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -120,11 +120,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (item is IHasSliderVelocity) { AddInternal(new DifficultyPointPiece(Item) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.BottomCentre - } - ); + { + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomCentre + }); } } From e27c4dfa101fe132f76614e32815c6a34ba455bc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 11:46:05 +0200 Subject: [PATCH 14/75] Invoke ApplyDefaultsToSelf --- osu.Game/Rulesets/Objects/HitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0502610eab..7702635057 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Objects /// The cancellation token. public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) { + ApplyDefaultsToSelf(controlPointInfo, difficulty); + nestedHitObjects.Clear(); CreateNestedHitObjects(cancellationToken); From 6c7094868163c7aeda18116d352739ba8309d57c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:10:57 +0200 Subject: [PATCH 15/75] Remove IContext & add IHasGenerateTicks --- .../Legacy/DistanceObjectPatternGenerator.cs | 4 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +- .../Beatmaps/TaikoBeatmapConverter.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +- osu.Game/Beatmaps/Legacy/LegacyContext.cs | 32 ------- osu.Game/Context/ContextContainer.cs | 91 ------------------- osu.Game/Context/IContext.cs | 13 --- osu.Game/Rulesets/Objects/HitObject.cs | 9 +- .../Objects/Types/IHasGenerateTicks.cs | 17 ++++ 9 files changed, 37 insertions(+), 147 deletions(-) delete mode 100644 osu.Game/Beatmaps/Legacy/LegacyContext.cs delete mode 100644 osu.Game/Context/ContextContainer.cs delete mode 100644 osu.Game/Context/IContext.cs create mode 100644 osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 2427812ecd..20f39deed7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double beatLength; - if (hitObject.HasContext()) - beatLength = timingPoint.BeatLength * hitObject.GetContext().BpmMultiplier; + if (hitObject.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value; else if (hitObject is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 247cf94f59..dd75a86f96 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -15,14 +15,13 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity + public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; @@ -143,6 +142,8 @@ namespace osu.Game.Rulesets.Osu.Objects set => SliderVelocityBindable.Value = value; } + public bool GenerateTicks { get; set; } + [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -162,10 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity; - bool generateTicks = !HasContext() || GetContext().GenerateTicks; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index ef73ffd517..2cf1d7a6ab 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -180,8 +180,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); double beatLength; - if (obj.HasContext()) - beatLength = timingPoint.BeatLength * obj.GetContext().BpmMultiplier; + if (obj.LegacyBpmMultiplier.HasValue) + beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value; else if (obj is IHasSliderVelocity hasSliderVelocity) beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity; else diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index dad23df282..9a1935f929 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -105,7 +105,11 @@ namespace osu.Game.Beatmaps.Formats #pragma warning disable 618 if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) #pragma warning restore 618 - hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks)); + { + hitObject.LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; + if (hitObject is IHasGenerateTicks hasGenerateTicks) + hasGenerateTicks.GenerateTicks = legacyDifficultyControlPoint.GenerateTicks; + } if (hitObject is IHasSliderVelocity hasSliderVelocity) hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity; diff --git a/osu.Game/Beatmaps/Legacy/LegacyContext.cs b/osu.Game/Beatmaps/Legacy/LegacyContext.cs deleted file mode 100644 index eeb02bdcb7..0000000000 --- a/osu.Game/Beatmaps/Legacy/LegacyContext.cs +++ /dev/null @@ -1,32 +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.Game.Context; - -namespace osu.Game.Beatmaps.Legacy; - -public class LegacyContext : IContext -{ - public LegacyContext(double bpmMultiplier, bool generateTicks) - { - BpmMultiplier = bpmMultiplier; - GenerateTicks = generateTicks; - } - - /// - /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. - /// DO NOT USE THIS UNLESS 100% SURE. - /// - public double BpmMultiplier { get; } - - /// - /// Whether or not slider ticks should be generated at this control point. - /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). - /// - public bool GenerateTicks { get; } - - public IContext Copy() - { - return new LegacyContext(BpmMultiplier, GenerateTicks); - } -} diff --git a/osu.Game/Context/ContextContainer.cs b/osu.Game/Context/ContextContainer.cs deleted file mode 100644 index 7ff0280bcf..0000000000 --- a/osu.Game/Context/ContextContainer.cs +++ /dev/null @@ -1,91 +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; - -namespace osu.Game.Context -{ - public abstract class ContextContainer - { - /// - /// The contexts of this container. - /// The objects always have the type of their key. - /// - private readonly Dictionary contexts; - - protected ContextContainer() - { - contexts = new Dictionary(); - } - - /// - /// Checks whether this object has the context with type T. - /// - /// The type to check the context of. - /// Whether the context object with type T exists in this object. - public bool HasContext() where T : IContext - { - return contexts.ContainsKey(typeof(T)); - } - - /// - /// Gets the context with type T. - /// - /// The type to get the context of. - /// If the context does not exist in this hit object. - /// The context object with type T. - public T GetContext() where T : IContext - { - return (T)contexts[typeof(T)]; - } - - /// - /// Tries to get the context with type T. - /// - /// The found context with type T. - /// The type to get the context of. - /// Whether the context exists in this object. - public bool TryGetContext(out T context) where T : IContext - { - if (contexts.TryGetValue(typeof(T), out var context2)) - { - context = (T)context2; - return true; - } - - context = default!; - return false; - } - - /// - /// Sets the context object of type T. - /// - /// The context type to set. - /// The context object to store in this object. - public void SetContext(T context) where T : IContext - { - contexts[typeof(T)] = context; - } - - /// - /// Removes the context of type T from this object. - /// - /// The type to remove the context of. - /// Whether a context was removed. - public bool RemoveContext() where T : IContext - { - return RemoveContext(typeof(T)); - } - - /// - /// Removes the context of type T from this object. - /// - /// The type to remove the context of. - /// Whether a context was removed. - public bool RemoveContext(Type t) - { - return contexts.Remove(t); - } - } -} diff --git a/osu.Game/Context/IContext.cs b/osu.Game/Context/IContext.cs deleted file mode 100644 index 61b1b11f43..0000000000 --- a/osu.Game/Context/IContext.cs +++ /dev/null @@ -1,13 +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.Context; - -public interface IContext -{ - /// - /// Makes a deep copy of this context. - /// - /// The deep copy of this context. - public IContext Copy(); -} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 7702635057..aaeffa49fc 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -16,7 +16,6 @@ using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Context; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Objects /// HitObjects may contain more properties for which you should be checking through the IHas* types. /// /// - public class HitObject : ContextContainer + public class HitObject { /// /// A small adjustment to the start time of control points to account for rounding/precision errors. @@ -80,6 +79,12 @@ namespace osu.Game.Rulesets.Objects public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public double? LegacyBpmMultiplier { get; set; } + /// /// Whether this is in Kiai time. /// diff --git a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs new file mode 100644 index 0000000000..5de7d348c5 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.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.Rulesets.Objects.Types +{ + /// + /// A type of which may or may not generate ticks. + /// + public interface IHasGenerateTicks + { + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public bool GenerateTicks { get; set; } + } +} From 39d9f0c3f5d689506a0971d22d9e40fab01ecee4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:22:13 +0200 Subject: [PATCH 16/75] removing using --- .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 20f39deed7..91b7be6e8f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy From 87ca0f5335ebd07abe29c3958bf2b5cd88f947ef Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:45:58 +0200 Subject: [PATCH 17/75] Update SamplePointPiece.cs --- .../Components/Timeline/SamplePointPiece.cs | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 50278bffc0..b02cfb505e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; @@ -26,14 +26,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public readonly HitObject HitObject; - private readonly Bindable bank; - private readonly BindableNumber volume; + private readonly BindableList samplesBindable; public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; - volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); - bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); + samplesBindable = hitObject.SamplesBindable.GetBoundCopy(); } protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; @@ -41,8 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - volume.BindValueChanged(_ => updateText()); - bank.BindValueChanged(_ => updateText(), true); + samplesBindable.BindCollectionChanged((_, _) => updateText(), true); } protected override bool OnClick(ClickEvent e) @@ -53,7 +50,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { - Label.Text = $"{bank.Value} {volume.Value}"; + Label.Text = $"{GetBankValue(samplesBindable)} {GetVolumeValue(samplesBindable)}"; + } + + public static string? GetBankValue(IEnumerable samples) + { + return samples.FirstOrDefault()?.Bank; + } + + public static int GetVolumeValue(ICollection samples) + { + return samples.Count == 0 ? 0 : samples.Max(o => o.Volume); } public Popover GetPopover() => new SampleEditPopover(HitObject); @@ -92,7 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label = "Bank Name", }, - volume = new IndeterminateSliderWithTextBoxInput("Volume", new SampleControlPoint().SampleVolumeBindable) + volume = new IndeterminateSliderWithTextBoxInput("Volume", new BindableInt(100) + { + MinValue = 0, + MaxValue = 100, + }) } } }; @@ -103,14 +114,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); + var relevantSamples = relevantObjects.Select(h => h.Samples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. - string? commonBank = getCommonBank(relevantControlPoints); + string? commonBank = getCommonBank(relevantSamples); if (!string.IsNullOrEmpty(commonBank)) bank.Current.Value = commonBank; - int? commonVolume = getCommonVolume(relevantControlPoints); + int? commonVolume = getCommonVolume(relevantSamples); if (commonVolume != null) volume.Current.Value = commonVolume.Value; @@ -120,9 +131,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateBankFor(relevantObjects, val.NewValue); updateBankPlaceholderText(relevantObjects); }); - // on commit, ensure that the value is correct by sourcing it from the objects' control points again. + // on commit, ensure that the value is correct by sourcing it from the objects' samples again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantControlPoints); + bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantSamples); volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } @@ -133,8 +144,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); } - private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; - private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? relevantControlPoints.First().SampleVolume : null; + private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; + private static int? getCommonVolume(IList[] relevantSamples) => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; private void updateBankFor(IEnumerable objects, string? newBank) { @@ -145,7 +156,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in objects) { - h.SampleControlPoint.SampleBank = newBank; + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newBank: newBank); + } + beatmap.Update(h); } @@ -154,7 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateBankPlaceholderText(IEnumerable objects) { - string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray()); + string? commonBank = getCommonBank(objects.Select(h => h.Samples).ToArray()); bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } @@ -167,7 +182,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in objects) { - h.SampleControlPoint.SampleVolume = newVolume.Value; + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newVolume: newVolume.Value); + } + beatmap.Update(h); } From d97daee96be2c10f90709cc30beceb1f369ae225 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 13:55:39 +0200 Subject: [PATCH 18/75] remove all non-test usage of SampleControlPoint --- .../Objects/Drawables/DrawableHoldNote.cs | 8 +------- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 10 ---------- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 1 - .../Objects/Drawables/DrawableSlider.cs | 10 ++-------- .../Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs | 7 ++++--- 7 files changed, 9 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 6e1c6cf80f..372ef1e164 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -350,13 +350,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { // 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(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index e444287b73..8cf64a6a7e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -311,17 +311,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var splitControlPoints = controlPoints.Take(index + 1).ToList(); controlPoints.RemoveRange(0, index); - // Turn the control points which were split off into a new slider. - var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); - var difficultyPoint = (DifficultyControlPoint)HitObject.DifficultyControlPoint.DeepClone(); - var newSlider = new Slider { StartTime = HitObject.StartTime, Position = HitObject.Position + splitControlPoints[0].Position, NewCombo = HitObject.NewCombo, - SampleControlPoint = samplePoint, - DifficultyControlPoint = difficultyPoint, LegacyLastTickOffset = HitObject.LegacyLastTickOffset, Samples = HitObject.Samples.Select(s => s.With()).ToList(), RepeatCount = HitObject.RepeatCount, @@ -378,15 +372,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition); - var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); - samplePoint.Time = time; - editorBeatmap.Add(new HitCircle { StartTime = time, Position = position, NewCombo = i == 0 && HitObject.NewCombo, - SampleControlPoint = samplePoint, Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList() }); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6d5280e528..2a6d6ce4c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Edit StartTime = firstHitObject.StartTime, Position = firstHitObject.Position, NewCombo = firstHitObject.NewCombo, - SampleControlPoint = firstHitObject.SampleControlPoint, Samples = firstHitObject.Samples, }; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a7b02596d5..664a8146e7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -133,14 +133,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // 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}."); - } - - Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + Samples.Samples = HitObject.TailSamples.Cast().ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a5193f1b6e..0ceda1d4b0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + spinningSample.Samples = HitObject.CreateSpinningSamples().Cast().ToArray(); spinningSample.Frequency.Value = spinning_sample_initial_frequency; } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index ed6f8a9a6a..55924c19c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects return new[] { - SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin") + referenceSample.With("spinnerspin") }; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs index 4809791af8..92f2b74568 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Game.Audio; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; @@ -17,12 +18,12 @@ namespace osu.Game.Rulesets.Taiko.UI public void Play(HitType hitType) { - var hitObject = GetMostValidObject(); + var hitSample = GetMostValidObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (hitObject == null) + if (hitSample == null) return; - PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) }); + PlaySamples(new ISampleInfo[] { new HitSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL, hitSample.Bank, volume: hitSample.Volume) }); } public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead"); From c6fc1806595df9be3e3cb738fae20a0efaa8839d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 14:21:52 +0200 Subject: [PATCH 19/75] remove all test usages of SampleControlPoint --- .../Editor/TestSceneObjectMerging.cs | 3 +- .../Editor/TestSceneSliderSplitting.cs | 10 ++----- .../Editor/TestSceneSliderStreamConversion.cs | 3 +- .../Formats/LegacyBeatmapDecoderTest.cs | 6 ++-- .../Editing/TestSceneEditorClipboard.cs | 4 --- .../Visual/Editing/TestSceneEditorSaving.cs | 10 ------- ...estSceneHitObjectSamplePointAdjustments.cs | 29 ++++++++++++------- .../TestSceneGameplaySampleTriggerSource.cs | 6 ++-- osu.Game/Audio/HitSampleInfo.cs | 5 ++-- osu.Game/Rulesets/Objects/HitObject.cs | 1 - 10 files changed, 32 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index e7ac38c20e..b05c755bfd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -138,8 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) - && mergedSlider.Samples.SequenceEqual(slider1.Samples) - && mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint); + && mergedSlider.Samples.SequenceEqual(slider1.Samples); }); AddAssert("slider end is at same completion for last slider", () => diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index 6cb77c7b92..a104433ea9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -181,10 +181,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { if (slider is null) return; - slider.SampleControlPoint.SampleBank = "soft"; - slider.SampleControlPoint.SampleVolume = 70; - sample = new HitSampleInfo("hitwhistle"); - slider.Samples.Add(sample); + sample = new HitSampleInfo("hitwhistle", "soft", volume: 70); + slider.Samples.Add(sample.With()); }); AddStep("select added slider", () => @@ -207,9 +205,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("sliders have hitsounds", hasHitsounds); bool hasHitsounds() => sample is not null && - EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == "soft" && - o.SampleControlPoint.SampleVolume == 70 && - o.Samples.Contains(sample)); + EditorBeatmap.HitObjects.All(o => o.Samples.Contains(sample)); } private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index 53465d43c9..a162d9a491 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -199,8 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Precision.AlmostEquals(circle.StartTime, time, 1) && Precision.AlmostEquals(circle.Position, position, 0.01f) && circle.NewCombo == startsNewCombo - && circle.Samples.SequenceEqual(slider.HeadCircle.Samples) - && circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint); + && circle.Samples.SequenceEqual(slider.HeadCircle.Samples); } private bool sliderRestored(Slider slider) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 518981980b..622a1837eb 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -480,7 +480,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); } - static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0]; } [Test] @@ -498,7 +498,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); } - static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0]; } [Test] @@ -518,7 +518,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); } - static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0]; } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index d26bb6bb8a..3c98a83fa0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -95,10 +95,6 @@ namespace osu.Game.Tests.Visual.Editing var path = slider.Path; return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints); }); - - // see `HitObject.control_point_leniency`. - AddAssert("sample control point has correct time", () => Precision.AlmostEquals(slider.SampleControlPoint.Time, slider.GetEndTime(), 1)); - AddAssert("difficulty control point has correct time", () => slider.DifficultyControlPoint.Time == slider.StartTime); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index b396b382ff..64c48e74cf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -122,19 +122,9 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); - // After placement these must be non-default as defaults are read-only. - AddAssert("Placed object has non-default control points", () => - !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && - !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); - ReloadEditorToSameBeatmap(); AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); - - // After placement these must be non-default as defaults are read-only. - AddAssert("Placed object still has non-default control points", () => - !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && - !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index e8dcc6f19b..7403cad52f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -7,6 +7,7 @@ using System.Linq; using Humanizer; using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; @@ -39,10 +40,9 @@ namespace osu.Game.Tests.Visual.Editing { StartTime = 0, Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, - SampleControlPoint = new SampleControlPoint + Samples = new List { - SampleBank = "normal", - SampleVolume = 80 + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 80) } }); @@ -50,10 +50,9 @@ namespace osu.Game.Tests.Visual.Editing { StartTime = 500, Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, - SampleControlPoint = new SampleControlPoint + Samples = new List { - SampleBank = "soft", - SampleVolume = 60 + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft", volume: 60) } }); }); @@ -96,7 +95,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("unify sample volume", () => { foreach (var h in EditorBeatmap.HitObjects) - h.SampleControlPoint.SampleVolume = 50; + { + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newVolume: 50); + } + } }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); @@ -136,7 +140,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("unify sample bank", () => { foreach (var h in EditorBeatmap.HitObjects) - h.SampleControlPoint.SampleBank = "soft"; + { + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newBank: "soft"); + } + } }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); @@ -248,7 +257,7 @@ namespace osu.Game.Tests.Visual.Editing private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); - return h.SampleControlPoint.SampleVolume == volume; + return h.Samples.All(o => o.Volume == volume); }); private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => @@ -265,7 +274,7 @@ namespace osu.Game.Tests.Visual.Editing private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); - return h.SampleControlPoint.SampleBank == bank; + return h.Samples.All(o => o.Bank == bank); }); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 31133f00d9..114c554d28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -73,8 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay new HitCircle { StartTime = t += spacing, - Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, - SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft") }, }, new HitCircle { @@ -84,8 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay { StartTime = t += spacing, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }), - Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }, - SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, "soft") }, }, }); diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index efa5562cb8..e9c06152cc 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Utils; namespace osu.Game.Audio @@ -32,7 +33,7 @@ namespace osu.Game.Audio /// /// The bank to load the sample from. /// - public readonly string? Bank; + public readonly string Bank; /// /// An optional suffix to provide priority lookup. Falls back to non-suffixed . @@ -47,7 +48,7 @@ namespace osu.Game.Audio public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0) { Name = name; - Bank = bank; + Bank = bank ?? SampleControlPoint.DEFAULT_BANK; Suffix = suffix; Volume = volume; } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index aaeffa49fc..73ecc28404 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -76,7 +76,6 @@ namespace osu.Game.Rulesets.Objects /// public virtual IList AuxiliarySamples => ImmutableList.Empty; - public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; /// From 5accb05f45d81d3e7fc385cdb53053d9aa9ee2c6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 14:28:48 +0200 Subject: [PATCH 20/75] fix invalidoperation exception --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 04786cc9fb..a4783046c4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -271,7 +271,9 @@ namespace osu.Game.Beatmaps.Formats if (hitObject.Samples.Count > 0) { int volume = hitObject.Samples.Max(o => o.Volume); - int customIndex = hitObject.Samples.OfType().Max(o => o.CustomSampleBank); + int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo) + ? hitObject.Samples.OfType().Max(o => o.CustomSampleBank) + : 0; yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex }; } From 1b4f4372d5304553768c8b83bd6d460bad5acfc0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 14:32:12 +0200 Subject: [PATCH 21/75] fixed sample control point applying --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9a1935f929..6b9e21f916 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -118,10 +118,7 @@ namespace osu.Game.Beatmaps.Formats SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; - foreach (var hitSampleInfo in hitObject.Samples) - { - sampleControlPoint.ApplyTo(hitSampleInfo); - } + hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); if (hitObject is not IHasRepeats hasRepeats) return; @@ -130,10 +127,7 @@ namespace osu.Game.Beatmaps.Formats double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(time) : SampleControlPoint.DEFAULT; - foreach (var hitSampleInfo in hasRepeats.NodeSamples[i]) - { - sampleControlPoint.ApplyTo(hitSampleInfo); - } + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); } } From 9f8d7bccbab8e011ca69288476fae517a62b1c38 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 17:34:02 +0200 Subject: [PATCH 22/75] fix usings --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 1 - osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 1 - .../Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 8cf64a6a7e..6685507ee0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2cf1d7a6ab..362ddccaf1 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -16,7 +16,6 @@ using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Taiko.Beatmaps { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 7403cad52f..7a0418cfec 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -4,12 +4,12 @@ #nullable disable using System.Linq; +using System.Collections.Generic; using Humanizer; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets; From c44f71a7374f370831a72b3c13d0343368e6f86b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 17:55:38 +0200 Subject: [PATCH 23/75] remove all regular usage of DifficultyControlPoint --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 15 ++++++++++++--- .../Sliders/SliderPlacementBlueprint.cs | 8 +++----- .../Beatmaps/TaikoBeatmapConverter.cs | 4 +++- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 13 +++++++++++-- ...estSceneHitObjectDifficultyPointAdjustments.cs | 5 +---- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ++++- .../Rulesets/Edit/DistancedHitObjectComposer.cs | 3 ++- osu.Game/Rulesets/Objects/HitObject.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 12 ++++-------- 10 files changed, 42 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 74d6565600..7a577f8a83 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdateHitObjectFromPath(JuiceStream hitObject) { // The SV setting may need to be changed for the current path. - var svBindable = hitObject.DifficultyControlPoint.SliderVelocityBindable; + var svBindable = hitObject.SliderVelocityBindable; double svToVelocityFactor = hitObject.Velocity / svBindable.Value; double requiredVelocity = path.ComputeRequiredVelocity(); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 96e2d5c4e5..f8af161ad5 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -16,7 +17,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class JuiceStream : CatchHitObject, IHasPathWithRepeats + public class JuiceStream : CatchHitObject, IHasPathWithRepeats, IHasSliderVelocity { /// /// Positional distance that results in a duration of one second, before any speed adjustments. @@ -27,6 +28,14 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } + [JsonIgnore] private double velocityFactor; @@ -34,10 +43,10 @@ namespace osu.Game.Rulesets.Catch.Objects private double tickDistanceFactor; [JsonIgnore] - public double Velocity => velocityFactor * DifficultyControlPoint.SliderVelocity; + public double Velocity => velocityFactor * SliderVelocity; [JsonIgnore] - public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity; + public double TickDistance => tickDistanceFactor * SliderVelocity; /// /// The length of one span of this . diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 77393efeb3..50514865e1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -83,11 +82,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case SliderPlacementState.Initial: BeginPlacement(); - var nearestDifficultyPoint = editorBeatmap.HitObjects - .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)? - .DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; + double? nearestSliderVelocity = (editorBeatmap.HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocity; - HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint(); + HitObject.SliderVelocity = nearestSliderVelocity ?? 1; HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); // Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation. diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 362ddccaf1..05fb314342 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -64,7 +64,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps foreach (HitObject hitObject in original.HitObjects) { - double nextScrollSpeed = hitObject.DifficultyControlPoint.SliderVelocity; + if (hitObject is not IHasSliderVelocity hasSliderVelocity) continue; + + double nextScrollSpeed = hasSliderVelocity.SliderVelocity; EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime); if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision)) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 3325eda7cf..5613bb190a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Threading; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -15,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoStrongableHitObject, IHasPath + public class DrumRoll : TaikoStrongableHitObject, IHasPath, IHasSliderVelocity { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -35,6 +36,14 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double Velocity { get; private set; } + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + + public double SliderVelocity + { + get => SliderVelocityBindable.Value; + set => SliderVelocityBindable.Value = value; + } + /// /// Numer of ticks per beat length. /// @@ -52,7 +61,7 @@ namespace osu.Game.Rulesets.Taiko.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; + double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; tickSpacing = timingPoint.BeatLength / TickRate; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index ab82678eb9..f34e286f50 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -61,10 +61,7 @@ namespace osu.Game.Tests.Visual.Editing new PathControlPoint(new Vector2(100, 0)) } }, - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = 2 - } + SliderVelocity = 2 }); }); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a4783046c4..a681429d02 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -249,7 +249,10 @@ namespace osu.Game.Beatmaps.Formats yield break; foreach (var hitObject in hitObjects) - yield return hitObject.DifficultyControlPoint; + { + if (hitObject is IHasSliderVelocity hasSliderVelocity) + yield return new DifficultyControlPoint { Time = hitObject.StartTime, SliderVelocity = hasSliderVelocity.SliderVelocity }; + } } void extractDifficultyControlPoints(IEnumerable hitObjects) diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index aa47b4f424..5aa9e3c179 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.OSD; using osu.Game.Overlays.Settings.Sections; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.TernaryButtons; namespace osu.Game.Rulesets.Edit @@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Edit public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) { - return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 + return (float)(100 * (useReferenceSliderVelocity && referenceObject is IHasSliderVelocity hasSliderVelocity ? hasSliderVelocity.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor); } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 73ecc28404..4822ec3919 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Objects /// public virtual IList AuxiliarySamples => ImmutableList.Empty; - public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; + public DifficultyControlPoint DifficultyControlPoint { get; set; } = DifficultyControlPoint.DEFAULT; /// /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index c2106e0598..61a3931839 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -373,17 +372,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasRepeats repeatHitObject: double proposedDuration = time - hitObject.StartTime; - if (e.CurrentState.Keyboard.ShiftPressed) + if (e.CurrentState.Keyboard.ShiftPressed && hitObject is IHasSliderVelocity hasSliderVelocity) { - if (ReferenceEquals(hitObject.DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) - hitObject.DifficultyControlPoint = new DifficultyControlPoint(); + double newVelocity = hasSliderVelocity.SliderVelocity * (repeatHitObject.Duration / proposedDuration); - double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); - - if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity)) + if (Precision.AlmostEquals(newVelocity, hasSliderVelocity.SliderVelocity)) return; - hitObject.DifficultyControlPoint.SliderVelocity = newVelocity; + hasSliderVelocity.SliderVelocity = newVelocity; beatmap.Update(hitObject); } else From 354cd238742546522b78b74bd03238b49180551e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 26 Apr 2023 18:17:02 +0200 Subject: [PATCH 24/75] removed all usage of hitobject's DifficultyControlPoint --- .../TestSceneJuiceStreamPlacementBlueprint.cs | 4 ++-- .../TestSceneJuiceStreamSelectionBlueprint.cs | 4 ++-- .../TestSceneObjectOrderedHitPolicy.cs | 2 +- .../TestSceneSliderFollowCircleInput.cs | 3 +-- .../TestSceneSliderInput.cs | 3 +-- .../TestSceneStartTimeOrderedHitPolicy.cs | 2 +- ...tSceneHitObjectComposerDistanceSnapping.cs | 22 ++++++------------- .../Editing/TestSceneEditorClipboard.cs | 1 - ...ceneHitObjectDifficultyPointAdjustments.cs | 8 +++---- osu.Game/Rulesets/Objects/HitObject.cs | 2 -- 10 files changed, 19 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs index 18d3d29bdc..2426f8c886 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1])); AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0])); AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1])); - AddAssert("default slider velocity", () => lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("default slider velocity", () => lastObject.SliderVelocityBindable.IsDefault); } [Test] @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor addPlacementSteps(times, positions); addPathCheckStep(times, positions); - AddAssert("slider velocity changed", () => !lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault); } [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index f25b66c360..beba5811fe 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor double[] times = { 100, 300 }; float[] positions = { 200, 300 }; addBlueprintStep(times, positions); - AddAssert("default slider velocity", () => hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault); addDragStartStep(times[1], positions[1]); AddMouseMoveStep(times[1], 400); - AddAssert("slider velocity changed", () => !hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); + AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 5d9316a21b..ee70441688 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -439,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public TestSlider() { - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }; + SliderVelocity = 0.1f; DefaultsApplied += _ => { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs index a32f0a13b8..fc2e6d1f72 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -47,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider_start, Position = new Vector2(0, 0), - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity }, + SliderVelocity = velocity, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 5f27cdc191..d83926ab9b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -8,7 +8,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -350,7 +349,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider_start, Position = new Vector2(0, 0), - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }, + SliderVelocity = 0.1f, Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 29e6fc4301..f4257a9ee7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public TestSlider() { - DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }; + SliderVelocity = 0.1f; DefaultsApplied += _ => { diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index f556f6e2fe..6399507aa0 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; @@ -74,12 +75,9 @@ namespace osu.Game.Tests.Editing [TestCase(2)] public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier) { - assertSnapDistance(100, new HitObject + assertSnapDistance(100, new Slider { - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = multiplier - } + SliderVelocity = multiplier }, false); } @@ -87,12 +85,9 @@ namespace osu.Game.Tests.Editing [TestCase(2)] public void TestSpeedMultiplierDoesChangeDistanceSnap(float multiplier) { - assertSnapDistance(100 * multiplier, new HitObject + assertSnapDistance(100 * multiplier, new Slider { - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = multiplier - } + SliderVelocity = multiplier }, true); } @@ -114,12 +109,9 @@ namespace osu.Game.Tests.Editing const float base_distance = 100; const float slider_velocity = 1.2f; - var referenceObject = new HitObject + var referenceObject = new Slider { - DifficultyControlPoint = new DifficultyControlPoint - { - SliderVelocity = slider_velocity - } + SliderVelocity = slider_velocity }; assertSnapDistance(base_distance * slider_velocity, referenceObject, true); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 3c98a83fa0..c4c05278b5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index f34e286f50..3b998b4219 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -8,10 +8,10 @@ using Humanizer; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -97,8 +97,8 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("unify slider velocity", () => { - foreach (var h in EditorBeatmap.HitObjects) - h.DifficultyControlPoint.SliderVelocity = 1.5; + foreach (var h in EditorBeatmap.HitObjects.OfType()) + h.SliderVelocity = 1.5; }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); - return h.DifficultyControlPoint.SliderVelocity == velocity; + return h is IHasSliderVelocity hasSliderVelocity && hasSliderVelocity.SliderVelocity == velocity; }); } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 4822ec3919..095ec1ff1b 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -76,8 +76,6 @@ namespace osu.Game.Rulesets.Objects /// public virtual IList AuxiliarySamples => ImmutableList.Empty; - public DifficultyControlPoint DifficultyControlPoint { get; set; } = DifficultyControlPoint.DEFAULT; - /// /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. /// DO NOT USE THIS UNLESS 100% SURE. From f7c84030ac3c6cd1156f496540169840ed73421d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 28 Apr 2023 17:22:34 +0200 Subject: [PATCH 25/75] remove bank default value --- osu.Game/Audio/HitSampleInfo.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index e9c06152cc..efa5562cb8 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Utils; namespace osu.Game.Audio @@ -33,7 +32,7 @@ namespace osu.Game.Audio /// /// The bank to load the sample from. /// - public readonly string Bank; + public readonly string? Bank; /// /// An optional suffix to provide priority lookup. Falls back to non-suffixed . @@ -48,7 +47,7 @@ namespace osu.Game.Audio public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0) { Name = name; - Bank = bank ?? SampleControlPoint.DEFAULT_BANK; + Bank = bank; Suffix = suffix; Volume = volume; } From 3b0ba4b38bc886538d62e56fbbfdf7b081cf5d84 Mon Sep 17 00:00:00 2001 From: Terochi Date: Sat, 29 Apr 2023 19:52:22 +0200 Subject: [PATCH 26/75] Improved logic for `ApplyStateChange` for skin editor --- .../SkinEditor/SkinEditorChangeHandler.cs | 65 ++++++++++++++++--- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index d1a1850796..151557c9e1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.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.IO; using System.Linq; @@ -56,20 +57,64 @@ namespace osu.Game.Overlays.SkinEditor if (deserializedContent == null) return; - SerialisedDrawableInfo[] skinnableInfo = deserializedContent.ToArray(); - Drawable[] targetComponents = firstTarget.Components.OfType().ToArray(); + SerialisedDrawableInfo[] skinnableInfos = deserializedContent.ToArray(); + ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray(); - if (!skinnableInfo.Select(s => s.Type).SequenceEqual(targetComponents.Select(d => d.GetType()))) + // Store indexes based on type for later lookup + + var skinnableInfoIndexes = new Dictionary>(); + var targetComponentsIndexes = new Dictionary>(); + + for (int i = 0; i < skinnableInfos.Length; i++) { - // Perform a naive full reload for now. - firstTarget.Reload(skinnableInfo); + Type lookup = skinnableInfos[i].Type; + + if (!skinnableInfoIndexes.TryGetValue(lookup, out List? infoIndexes)) + skinnableInfoIndexes.Add(lookup, infoIndexes = new List()); + + infoIndexes.Add(i); } - else - { - int i = 0; - foreach (var drawable in targetComponents) - drawable.ApplySerialisedInfo(skinnableInfo[i++]); + for (int i = 0; i < targetComponents.Length; i++) + { + Type lookup = targetComponents[i].GetType(); + + if (!targetComponentsIndexes.TryGetValue(lookup, out List? componentIndexes)) + targetComponentsIndexes.Add(lookup, componentIndexes = new List()); + + componentIndexes.Add(i); + } + + foreach ((Type lookup, List infoIndexes) in skinnableInfoIndexes) + { + if (!targetComponentsIndexes.TryGetValue(lookup, out List? componentIndexes)) + componentIndexes = new List(0); + + int j = 0; + + for (int i = 0; i < infoIndexes.Count; i++) + { + if (i >= componentIndexes.Count) + // Add new component + firstTarget.Add((ISerialisableDrawable)skinnableInfos[infoIndexes[i]].CreateInstance()); + else + // Modify existing component + ((Drawable)targetComponents[componentIndexes[j++]]).ApplySerialisedInfo(skinnableInfos[infoIndexes[i]]); + } + + // Remove extra components + for (; j < componentIndexes.Count; j++) + firstTarget.Remove(targetComponents[componentIndexes[j]], false); + } + + foreach ((Type lookup, List componentIndexes) in targetComponentsIndexes) + { + if (skinnableInfoIndexes.ContainsKey(lookup)) + continue; + + // Remove extra components that weren't removed above + for (int i = 0; i < componentIndexes.Count; i++) + firstTarget.Remove(targetComponents[componentIndexes[i]], false); } } } From 17e4b75dfd5f55756e7d67316566bbb1f195a9dc Mon Sep 17 00:00:00 2001 From: Terochi Date: Sat, 29 Apr 2023 20:54:19 +0200 Subject: [PATCH 27/75] Save first state when editing --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 2b23ce290f..f0ad92d4da 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -235,10 +235,7 @@ namespace osu.Game.Overlays.SkinEditor }, true); canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true); - SelectedComponents.BindCollectionChanged((_, _) => - { - canCopy.Value = canCut.Value = SelectedComponents.Any(); - }, true); + SelectedComponents.BindCollectionChanged((_, _) => canCopy.Value = canCut.Value = SelectedComponents.Any(), true); clipboard.Content.BindValueChanged(content => canPaste.Value = !string.IsNullOrEmpty(content.NewValue), true); @@ -345,6 +342,7 @@ namespace osu.Game.Overlays.SkinEditor changeHandler = new SkinEditorChangeHandler(skinComponentsContainer); changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler.SaveState(); content.Child = new SkinBlueprintContainer(skinComponentsContainer); @@ -479,12 +477,18 @@ namespace osu.Game.Overlays.SkinEditor protected void Cut() { + if (!canCut.Value) + return; + Copy(); DeleteItems(SelectedComponents.ToArray()); } protected void Copy() { + if (!canCopy.Value) + return; + clipboard.Content.Value = JsonConvert.SerializeObject(SelectedComponents.Cast().Select(s => s.CreateSerialisedInfo()).ToArray()); } @@ -500,6 +504,9 @@ namespace osu.Game.Overlays.SkinEditor protected void Paste() { + if (!canPaste.Value) + return; + changeHandler?.BeginChange(); var drawableInfo = JsonConvert.DeserializeObject(clipboard.Content.Value); From ffcc8e91b242a22a17701566ac284a62d8f057c1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 29 Apr 2023 23:51:49 +0200 Subject: [PATCH 28/75] fix legacy parser incorrectly assigning sample info for sliders --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 68ca6bc506..ba5de6c14b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } if (split.Length > 10) - readCustomSampleBanks(split[10], bankInfo); + readCustomSampleBanks(split[10], bankInfo, true); // One node for each repeat + the start and end nodes int nodes = repeatCount + 2; @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return result; } - private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) + private void readCustomSampleBanks(string str, SampleBankInfo bankInfo, bool banksOnly = false) { if (string.IsNullOrEmpty(str)) return; @@ -202,6 +202,8 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.BankForNormal = stringBank; bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; + if (banksOnly) return; + if (split.Length > 2) bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]); From 92efd04f3138b62d49458fb296021e7101fb3105 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 29 Apr 2023 23:52:24 +0200 Subject: [PATCH 29/75] fix sample of drumroll ticks being bankless --- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 5 ++++- .../Objects/TaikoStrongableHitObject.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 5613bb190a..c1a78f46b2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,9 +3,11 @@ #nullable disable +using System.Linq; using osu.Game.Rulesets.Objects.Types; using System.Threading; using osu.Framework.Bindables; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -90,7 +92,8 @@ namespace osu.Game.Rulesets.Taiko.Objects FirstTick = first, TickSpacing = tickSpacing, StartTime = t, - IsStrong = IsStrong + IsStrong = IsStrong, + Samples = Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToList() }); first = false; diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index d4d59d5d44..0043f231d2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects if (IsStrongBindable.Value != strongSamples.Any()) { if (IsStrongBindable.Value) - Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + Samples.Add(GetSampleInfo(HitSampleInfo.HIT_FINISH)); else { foreach (var sample in strongSamples) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 095ec1ff1b..774ff9dc1d 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -204,6 +204,17 @@ namespace osu.Game.Rulesets.Objects return slidingSamples; } + + /// + /// Create a SampleInfo based on the sample settings of the hit normal sample in . + /// + /// The name of the sample. + /// A populated . + protected HitSampleInfo GetSampleInfo(string sampleName) + { + var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName); + } } public static class HitObjectExtensions From a6e780a1b9c5740e74c17d9c9f098a9e30e4871f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 29 Apr 2023 23:52:30 +0200 Subject: [PATCH 30/75] Update CheckMutedObjectsTest.cs --- .../Editing/Checks/CheckMutedObjectsTest.cs | 104 +----------------- 1 file changed, 6 insertions(+), 98 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 1e1c214c30..5a3ef619d1 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -37,45 +37,6 @@ namespace osu.Game.Tests.Editing.Checks cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); } - [Test] - public void TestNormalControlPointVolume() - { - var hitCircle = new HitCircle - { - StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertOk(new List { hitCircle }); - } - - [Test] - public void TestLowControlPointVolume() - { - var hitCircle = new HitCircle - { - StartTime = 1000, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertLowVolume(new List { hitCircle }); - } - - [Test] - public void TestMutedControlPointVolume() - { - var hitCircle = new HitCircle - { - StartTime = 2000, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertMuted(new List { hitCircle }); - } - [Test] public void TestNormalSampleVolume() { @@ -122,7 +83,7 @@ namespace osu.Game.Tests.Editing.Checks var sliderHead = new SliderHeadCircle { StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -135,7 +96,7 @@ namespace osu.Game.Tests.Editing.Checks var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; slider.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -155,13 +116,13 @@ namespace osu.Game.Tests.Editing.Checks var sliderTick = new SliderTick { StartTime = 250, - Samples = new List { new HitSampleInfo("slidertick") } + Samples = new List { new HitSampleInfo("slidertick", volume: volume_regular) } }; sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail. + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } // Applies to the tail. }; slider.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -174,14 +135,14 @@ namespace osu.Game.Tests.Editing.Checks var sliderHead = new SliderHeadCircle { StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); var sliderTick = new SliderTick { StartTime = 250, - Samples = new List { new HitSampleInfo("slidertick") } + Samples = new List { new HitSampleInfo("slidertick", volume: volume_regular) } }; sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -194,59 +155,6 @@ namespace osu.Game.Tests.Editing.Checks assertMutedPassive(new List { slider }); } - [Test] - public void TestMutedControlPointVolumeSliderHead() - { - var sliderHead = new SliderHeadCircle - { - StartTime = 2000, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var sliderTick = new SliderTick - { - StartTime = 2250, - Samples = new List { new HitSampleInfo("slidertick") } - }; - sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500) - { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } - }; - slider.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertMuted(new List { slider }); - } - - [Test] - public void TestMutedControlPointVolumeSliderTail() - { - var sliderHead = new SliderHeadCircle - { - StartTime = 0, - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var sliderTick = new SliderTick - { - StartTime = 250, - Samples = new List { new HitSampleInfo("slidertick") } - }; - sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); - - // Ends after the 5% control point. - var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) - { - Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } - }; - slider.ApplyDefaults(cpi, new BeatmapDifficulty()); - - assertMutedPassive(new List { slider }); - } - private void assertOk(List hitObjects) { Assert.That(check.Run(getContext(hitObjects)), Is.Empty); From 83111223e0d9ef4ea85c16746a6d79c0e31b6bd2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 01:08:52 +0200 Subject: [PATCH 31/75] fix null sample --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 91dd7754d0..96128c6981 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -76,7 +76,8 @@ namespace osu.Game.Rulesets.Edit { // Take the hitnormal sample of the last hit object var lastHitNormal = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - HitObject.Samples.Add(lastHitNormal); + if (lastHitNormal != null) + HitObject.Samples[0] = lastHitNormal; placementHandler.BeginPlacement(HitObject); if (commitStart) From 9a9e02b110431b32bcf9281db2349ef1418c8abe Mon Sep 17 00:00:00 2001 From: Terochi Date: Sun, 30 Apr 2023 02:00:35 +0200 Subject: [PATCH 32/75] Added tests --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 89 ++++++++++++++----- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 119b753d70..71eabd776a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,12 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Settings; @@ -97,15 +100,10 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.PressButton(MouseButton.Left); }); - AddStep("Drag to bottom right", () => - { - InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4)); - }); + AddStep("Drag to bottom right", + () => { InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4)); }); - AddStep("Release button", () => - { - InputManager.ReleaseButton(MouseButton.Left); - }); + AddStep("Release button", () => { InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("First two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box1, box2 })); @@ -115,15 +113,9 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.PressButton(MouseButton.Left); }); - AddStep("Drag to top left", () => - { - InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4)); - }); + AddStep("Drag to top left", () => { InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4)); }); - AddStep("Release button", () => - { - InputManager.ReleaseButton(MouseButton.Left); - }); + AddStep("Release button", () => { InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("Last two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 })); @@ -147,10 +139,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Three black boxes added", () => targetContainer.Components.OfType().Count(), () => Is.EqualTo(3)); - AddStep("Store black box blueprints", () => - { - blueprints = skinEditor.ChildrenOfType().Where(b => b.Item is BigBlackBox).ToArray(); - }); + AddStep("Store black box blueprints", () => { blueprints = skinEditor.ChildrenOfType().Where(b => b.Item is BigBlackBox).ToArray(); }); AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item)); @@ -182,6 +171,48 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2)); } + [Test] + public void TestUndo() + { + SkinComponentsContainer firstTarget = null!; + TestSkinEditorChangeHandler changeHandler = null!; + byte[] defaultState = null!; + IEnumerable testComponents = null!; + + AddStep("Load necessary things", () => + { + firstTarget = Player.ChildrenOfType().First(); + changeHandler = new TestSkinEditorChangeHandler(firstTarget); + changeHandler.SaveState(); + defaultState = changeHandler.GetCurrentState(); + testComponents = new[] { targetContainer.Components.First(), targetContainer.Components[targetContainer.Components.Count / 2], targetContainer.Components.Last() }; + }); + + AddStep("Press undo", () => InputManager.Keys(PlatformAction.Undo)); + AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + + AddStep("Add big black boxes", () => + { + InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); + AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + + AddStep("Select components", () => skinEditor.SelectedComponents.AddRange(testComponents)); + AddStep("Bring to front", () => skinEditor.BringSelectionToFront()); + + AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); + AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + + AddStep("Remove components", () => testComponents.ForEach(c => firstTarget.Remove(c, false))); + + AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); + AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + } + [TestCase(false)] [TestCase(true)] public void TestBringToFront(bool alterSelectionOrder) @@ -269,5 +300,23 @@ namespace osu.Game.Tests.Visual.Gameplay } protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler + { + public TestSkinEditorChangeHandler(Drawable targetScreen) + : base(targetScreen) + { + } + + public byte[] GetCurrentState() + { + using var stream = new MemoryStream(); + + WriteCurrentStateToStream(stream); + byte[] newState = stream.ToArray(); + + return newState; + } + } } } From 8ec2154965cd79939daa44adb7d0c678606bfae8 Mon Sep 17 00:00:00 2001 From: Terochi Date: Sun, 30 Apr 2023 02:01:18 +0200 Subject: [PATCH 33/75] Fixed `ApplyStateChange` to happen in correct order --- .../SkinEditor/SkinEditorChangeHandler.cs | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 151557c9e1..f38f6b0911 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -62,19 +62,8 @@ namespace osu.Game.Overlays.SkinEditor // Store indexes based on type for later lookup - var skinnableInfoIndexes = new Dictionary>(); var targetComponentsIndexes = new Dictionary>(); - for (int i = 0; i < skinnableInfos.Length; i++) - { - Type lookup = skinnableInfos[i].Type; - - if (!skinnableInfoIndexes.TryGetValue(lookup, out List? infoIndexes)) - skinnableInfoIndexes.Add(lookup, infoIndexes = new List()); - - infoIndexes.Add(i); - } - for (int i = 0; i < targetComponents.Length; i++) { Type lookup = targetComponents[i].GetType(); @@ -85,35 +74,34 @@ namespace osu.Game.Overlays.SkinEditor componentIndexes.Add(i); } - foreach ((Type lookup, List infoIndexes) in skinnableInfoIndexes) + var indexCounting = new Dictionary(); + + var empty = new List(0); + + for (int i = 0; i < skinnableInfos.Length; i++) { + Type lookup = skinnableInfos[i].Type; + if (!targetComponentsIndexes.TryGetValue(lookup, out List? componentIndexes)) - componentIndexes = new List(0); + componentIndexes = empty; - int j = 0; + if (!indexCounting.ContainsKey(lookup)) + indexCounting.Add(lookup, 0); - for (int i = 0; i < infoIndexes.Count; i++) - { - if (i >= componentIndexes.Count) - // Add new component - firstTarget.Add((ISerialisableDrawable)skinnableInfos[infoIndexes[i]].CreateInstance()); - else - // Modify existing component - ((Drawable)targetComponents[componentIndexes[j++]]).ApplySerialisedInfo(skinnableInfos[infoIndexes[i]]); - } - - // Remove extra components - for (; j < componentIndexes.Count; j++) - firstTarget.Remove(targetComponents[componentIndexes[j]], false); + if (i >= componentIndexes.Count) + // Add new component + firstTarget.Add((ISerialisableDrawable)skinnableInfos[i].CreateInstance()); + else + // Modify existing component + ((Drawable)targetComponents[componentIndexes[indexCounting[lookup]++]]).ApplySerialisedInfo(skinnableInfos[i]); } foreach ((Type lookup, List componentIndexes) in targetComponentsIndexes) { - if (skinnableInfoIndexes.ContainsKey(lookup)) - continue; + indexCounting.TryGetValue(lookup, out int i); // Remove extra components that weren't removed above - for (int i = 0; i < componentIndexes.Count; i++) + for (; i < componentIndexes.Count; i++) firstTarget.Remove(targetComponents[componentIndexes[i]], false); } } From 585318400cacf70533aefef88d2880f30d4f909b Mon Sep 17 00:00:00 2001 From: Terochi Date: Sun, 30 Apr 2023 02:32:20 +0200 Subject: [PATCH 34/75] Refactor tests --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 71eabd776a..45cc329f53 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -183,15 +183,22 @@ namespace osu.Game.Tests.Visual.Gameplay { firstTarget = Player.ChildrenOfType().First(); changeHandler = new TestSkinEditorChangeHandler(firstTarget); + changeHandler.SaveState(); defaultState = changeHandler.GetCurrentState(); - testComponents = new[] { targetContainer.Components.First(), targetContainer.Components[targetContainer.Components.Count / 2], targetContainer.Components.Last() }; + + testComponents = new[] + { + targetContainer.Components.First(), + targetContainer.Components[targetContainer.Components.Count / 2], + targetContainer.Components.Last() + }; }); AddStep("Press undo", () => InputManager.Keys(PlatformAction.Undo)); AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); - AddStep("Add big black boxes", () => + AddStep("Add components", () => { InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); @@ -203,12 +210,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Select components", () => skinEditor.SelectedComponents.AddRange(testComponents)); AddStep("Bring to front", () => skinEditor.BringSelectionToFront()); - AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); AddStep("Remove components", () => testComponents.ForEach(c => firstTarget.Remove(c, false))); - AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); } From 949dc1c0f96e31d0a20323d83f3fa8a98ab95592 Mon Sep 17 00:00:00 2001 From: Terochi Date: Sun, 30 Apr 2023 12:05:55 +0200 Subject: [PATCH 35/75] Improved logic --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 25 +++++--- .../SkinEditor/SkinEditorChangeHandler.cs | 57 +++++++++---------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 45cc329f53..4a0be3bb68 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestUndo() + public void TestUndoEditHistory() { SkinComponentsContainer firstTarget = null!; TestSkinEditorChangeHandler changeHandler = null!; @@ -205,17 +205,28 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left); }); - AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + revertAndCheckUnchanged(); + + AddStep("Move components", () => + { + changeHandler.BeginChange(); + testComponents.ForEach(c => ((Drawable)c).Position += Vector2.One); + changeHandler.EndChange(); + }); + revertAndCheckUnchanged(); AddStep("Select components", () => skinEditor.SelectedComponents.AddRange(testComponents)); AddStep("Bring to front", () => skinEditor.BringSelectionToFront()); - AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + revertAndCheckUnchanged(); AddStep("Remove components", () => testComponents.ForEach(c => firstTarget.Remove(c, false))); - AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + revertAndCheckUnchanged(); + + void revertAndCheckUnchanged() + { + AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); + AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + } } [TestCase(false)] diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index f38f6b0911..6285080d36 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -60,49 +60,48 @@ namespace osu.Game.Overlays.SkinEditor SerialisedDrawableInfo[] skinnableInfos = deserializedContent.ToArray(); ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray(); - // Store indexes based on type for later lookup + // Store components based on type for later lookup + var typedComponents = new Dictionary>(); - var targetComponentsIndexes = new Dictionary>(); - - for (int i = 0; i < targetComponents.Length; i++) + foreach (var component in targetComponents) { - Type lookup = targetComponents[i].GetType(); + Type lookup = component.GetType(); - if (!targetComponentsIndexes.TryGetValue(lookup, out List? componentIndexes)) - targetComponentsIndexes.Add(lookup, componentIndexes = new List()); + if (!typedComponents.TryGetValue(lookup, out List? typeComponents)) + typedComponents.Add(lookup, typeComponents = new List()); - componentIndexes.Add(i); + typeComponents.Add(component); } - var indexCounting = new Dictionary(); + // Remove all components + for (int i = targetComponents.Length - 1; i >= 0; i--) + firstTarget.Remove(targetComponents[i], false); - var empty = new List(0); + // Keeps count of how many components for each type were already revived + Dictionary typedComponentCounter = typedComponents.Keys.ToDictionary(t => t, _ => 0); - for (int i = 0; i < skinnableInfos.Length; i++) + foreach (var skinnableInfo in skinnableInfos) { - Type lookup = skinnableInfos[i].Type; + Type lookup = skinnableInfo.Type; - if (!targetComponentsIndexes.TryGetValue(lookup, out List? componentIndexes)) - componentIndexes = empty; + if (!typedComponents.TryGetValue(lookup, out List? typeComponents)) + { + firstTarget.Add((ISerialisableDrawable)skinnableInfo.CreateInstance()); + continue; + } - if (!indexCounting.ContainsKey(lookup)) - indexCounting.Add(lookup, 0); + int typeComponentsUsed = typedComponentCounter[lookup]++; - if (i >= componentIndexes.Count) - // Add new component - firstTarget.Add((ISerialisableDrawable)skinnableInfos[i].CreateInstance()); + ISerialisableDrawable component; + + if (typeComponentsUsed < typeComponents.Count) + // Re-use unused component + ((Drawable)(component = typeComponents[typeComponentsUsed])).ApplySerialisedInfo(skinnableInfo); else - // Modify existing component - ((Drawable)targetComponents[componentIndexes[indexCounting[lookup]++]]).ApplySerialisedInfo(skinnableInfos[i]); - } + // Create new one + component = (ISerialisableDrawable)skinnableInfo.CreateInstance(); - foreach ((Type lookup, List componentIndexes) in targetComponentsIndexes) - { - indexCounting.TryGetValue(lookup, out int i); - - // Remove extra components that weren't removed above - for (; i < componentIndexes.Count; i++) - firstTarget.Remove(targetComponents[componentIndexes[i]], false); + firstTarget.Add(component); } } } From 1eb2e35dffb3215b3c58987fa6b244710babc49a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 16:03:58 +0200 Subject: [PATCH 36/75] fix ticks not being generated by default --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index dd75a86f96..98e536de38 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => SliderVelocityBindable.Value = value; } - public bool GenerateTicks { get; set; } + public bool GenerateTicks { get; set; } = true; [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } From e7a478ce9c7e8dc20be47173233f0700fd6d88e9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 16:04:03 +0200 Subject: [PATCH 37/75] Update convert-samples-expected-conversion.json --- .../Testing/Beatmaps/convert-samples-expected-conversion.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index 6f1d45ad8c..4d298bb671 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -10,7 +10,7 @@ ["Gameplay/soft-hitnormal"], ["Gameplay/drum-hitnormal"] ], - "Samples": ["Gameplay/-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, @@ -19,7 +19,7 @@ ["Gameplay/soft-hitnormal"], ["Gameplay/drum-hitnormal"] ], - "Samples": ["Gameplay/-hitnormal"] + "Samples": ["Gameplay/normal-hitnormal"] }] }, { "StartTime": 3750.0, From 8f02bd80f967db674c7d1655c3f5f72645b77c76 Mon Sep 17 00:00:00 2001 From: Terochi Date: Sun, 30 Apr 2023 16:10:59 +0200 Subject: [PATCH 38/75] Addressed changes --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 29 +++++++++---- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 15 ++----- .../SkinEditor/SkinEditorChangeHandler.cs | 41 +++++++++++-------- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 4a0be3bb68..f7eb42872d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -100,10 +100,15 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.PressButton(MouseButton.Left); }); - AddStep("Drag to bottom right", - () => { InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4)); }); + AddStep("Drag to bottom right", () => + { + InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4)); + }); - AddStep("Release button", () => { InputManager.ReleaseButton(MouseButton.Left); }); + AddStep("Release button", () => + { + InputManager.ReleaseButton(MouseButton.Left); + }); AddAssert("First two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box1, box2 })); @@ -113,9 +118,15 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.PressButton(MouseButton.Left); }); - AddStep("Drag to top left", () => { InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4)); }); + AddStep("Drag to top left", () => + { + InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4)); + }); - AddStep("Release button", () => { InputManager.ReleaseButton(MouseButton.Left); }); + AddStep("Release button", () => + { + InputManager.ReleaseButton(MouseButton.Left); + }); AddAssert("Last two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 })); @@ -139,7 +150,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Three black boxes added", () => targetContainer.Components.OfType().Count(), () => Is.EqualTo(3)); - AddStep("Store black box blueprints", () => { blueprints = skinEditor.ChildrenOfType().Where(b => b.Item is BigBlackBox).ToArray(); }); + AddStep("Store black box blueprints", () => + { + blueprints = skinEditor.ChildrenOfType().Where(b => b.Item is BigBlackBox).ToArray(); + }); AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item)); @@ -184,7 +198,6 @@ namespace osu.Game.Tests.Visual.Gameplay firstTarget = Player.ChildrenOfType().First(); changeHandler = new TestSkinEditorChangeHandler(firstTarget); - changeHandler.SaveState(); defaultState = changeHandler.GetCurrentState(); testComponents = new[] @@ -225,7 +238,7 @@ namespace osu.Game.Tests.Visual.Gameplay void revertAndCheckUnchanged() { AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index f0ad92d4da..2b23ce290f 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -235,7 +235,10 @@ namespace osu.Game.Overlays.SkinEditor }, true); canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true); - SelectedComponents.BindCollectionChanged((_, _) => canCopy.Value = canCut.Value = SelectedComponents.Any(), true); + SelectedComponents.BindCollectionChanged((_, _) => + { + canCopy.Value = canCut.Value = SelectedComponents.Any(); + }, true); clipboard.Content.BindValueChanged(content => canPaste.Value = !string.IsNullOrEmpty(content.NewValue), true); @@ -342,7 +345,6 @@ namespace osu.Game.Overlays.SkinEditor changeHandler = new SkinEditorChangeHandler(skinComponentsContainer); changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); - changeHandler.SaveState(); content.Child = new SkinBlueprintContainer(skinComponentsContainer); @@ -477,18 +479,12 @@ namespace osu.Game.Overlays.SkinEditor protected void Cut() { - if (!canCut.Value) - return; - Copy(); DeleteItems(SelectedComponents.ToArray()); } protected void Copy() { - if (!canCopy.Value) - return; - clipboard.Content.Value = JsonConvert.SerializeObject(SelectedComponents.Cast().Select(s => s.CreateSerialisedInfo()).ToArray()); } @@ -504,9 +500,6 @@ namespace osu.Game.Overlays.SkinEditor protected void Paste() { - if (!canPaste.Value) - return; - changeHandler?.BeginChange(); var drawableInfo = JsonConvert.DeserializeObject(clipboard.Content.Value); diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 6285080d36..8c7f7bbab7 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.SkinEditor return; components = new BindableList { BindTarget = firstTarget.Components }; - components.BindCollectionChanged((_, _) => SaveState()); + components.BindCollectionChanged((_, _) => SaveState(), true); } protected override void WriteCurrentStateToStream(MemoryStream stream) @@ -61,47 +61,52 @@ namespace osu.Game.Overlays.SkinEditor ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray(); // Store components based on type for later lookup - var typedComponents = new Dictionary>(); + var typedComponents = new Dictionary>(); - foreach (var component in targetComponents) + for (int i = targetComponents.Length - 1; i >= 0; i--) { + Drawable component = (Drawable)targetComponents[i]; Type lookup = component.GetType(); - if (!typedComponents.TryGetValue(lookup, out List? typeComponents)) - typedComponents.Add(lookup, typeComponents = new List()); + if (!typedComponents.TryGetValue(lookup, out Stack? typeComponents)) + typedComponents.Add(lookup, typeComponents = new Stack()); - typeComponents.Add(component); + typeComponents.Push(component); } // Remove all components for (int i = targetComponents.Length - 1; i >= 0; i--) firstTarget.Remove(targetComponents[i], false); - // Keeps count of how many components for each type were already revived - Dictionary typedComponentCounter = typedComponents.Keys.ToDictionary(t => t, _ => 0); - foreach (var skinnableInfo in skinnableInfos) { Type lookup = skinnableInfo.Type; - if (!typedComponents.TryGetValue(lookup, out List? typeComponents)) + if (!typedComponents.TryGetValue(lookup, out Stack? typeComponents)) { firstTarget.Add((ISerialisableDrawable)skinnableInfo.CreateInstance()); continue; } - int typeComponentsUsed = typedComponentCounter[lookup]++; - - ISerialisableDrawable component; - - if (typeComponentsUsed < typeComponents.Count) + if (typeComponents.TryPop(out Drawable? component)) + { // Re-use unused component - ((Drawable)(component = typeComponents[typeComponentsUsed])).ApplySerialisedInfo(skinnableInfo); + component.ApplySerialisedInfo(skinnableInfo); + } else + { // Create new one - component = (ISerialisableDrawable)skinnableInfo.CreateInstance(); + component = skinnableInfo.CreateInstance(); + } - firstTarget.Add(component); + firstTarget.Add((ISerialisableDrawable)component); + } + + foreach ((Type _, Stack typeComponents) in typedComponents) + { + // Dispose extra components + foreach (var component in typeComponents) + component.Dispose(); } } } From fb4b681cc527c1b9430d2537e474d77f9035286c Mon Sep 17 00:00:00 2001 From: Terochi Date: Sun, 30 Apr 2023 16:36:01 +0200 Subject: [PATCH 39/75] Use `Queue` instead of `Stack` --- .../SkinEditor/SkinEditorChangeHandler.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 8c7f7bbab7..18ffdb0edc 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -61,17 +61,16 @@ namespace osu.Game.Overlays.SkinEditor ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray(); // Store components based on type for later lookup - var typedComponents = new Dictionary>(); + var typedComponents = new Dictionary>(); - for (int i = targetComponents.Length - 1; i >= 0; i--) + foreach (ISerialisableDrawable component in targetComponents) { - Drawable component = (Drawable)targetComponents[i]; Type lookup = component.GetType(); - if (!typedComponents.TryGetValue(lookup, out Stack? typeComponents)) - typedComponents.Add(lookup, typeComponents = new Stack()); + if (!typedComponents.TryGetValue(lookup, out Queue? typeComponents)) + typedComponents.Add(lookup, typeComponents = new Queue()); - typeComponents.Push(component); + typeComponents.Enqueue((Drawable)component); } // Remove all components @@ -82,13 +81,13 @@ namespace osu.Game.Overlays.SkinEditor { Type lookup = skinnableInfo.Type; - if (!typedComponents.TryGetValue(lookup, out Stack? typeComponents)) + if (!typedComponents.TryGetValue(lookup, out Queue? typeComponents)) { firstTarget.Add((ISerialisableDrawable)skinnableInfo.CreateInstance()); continue; } - if (typeComponents.TryPop(out Drawable? component)) + if (typeComponents.TryDequeue(out Drawable? component)) { // Re-use unused component component.ApplySerialisedInfo(skinnableInfo); @@ -102,7 +101,7 @@ namespace osu.Game.Overlays.SkinEditor firstTarget.Add((ISerialisableDrawable)component); } - foreach ((Type _, Stack typeComponents) in typedComponents) + foreach ((Type _, Queue typeComponents) in typedComponents) { // Dispose extra components foreach (var component in typeComponents) From 79f3cfec91e58bc67cb3a1df1d099fc08e991938 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 16:43:26 +0200 Subject: [PATCH 40/75] fix 0 velocity juicestream --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 7a577f8a83..75ee0546dd 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // // The value is clamped here by the bindable min and max values. // In case the required velocity is too large, the path is not preserved. - svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); + svBindable.Value = requiredVelocity == 0 ? 1 : Math.Ceiling(requiredVelocity / svToVelocityFactor); path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); From 139a1d7e6d6ec8f5d5c13de881f4ddfdd2542bc4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 17:46:47 +0200 Subject: [PATCH 41/75] fix legacy encoder writing sample info while not writing node samples --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a681429d02..c5a8cc89be 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -476,16 +476,16 @@ namespace osu.Game.Beatmaps.Formats if (curveData != null) { - for (int i = 0; i < curveData.NodeSamples.Count; i++) + for (int i = 0; i < curveData.SpanCount() + 1; i++) { - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + writer.Write(FormattableString.Invariant($"{(i < curveData.NodeSamples.Count ? (int)toLegacyHitSoundType(curveData.NodeSamples[i]) : 0)}")); + writer.Write(i != curveData.SpanCount() ? "|" : ","); } - for (int i = 0; i < curveData.NodeSamples.Count; i++) + for (int i = 0; i < curveData.SpanCount() + 1; i++) { - writer.Write(getSampleBank(curveData.NodeSamples[i], true)); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + writer.Write(i < curveData.NodeSamples.Count ? getSampleBank(curveData.NodeSamples[i], true) : "0:0"); + writer.Write(i != curveData.SpanCount() ? "|" : ","); } } } From 4a0ff046ae302ceb2b2eb379055ec119eb240b4a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 30 Apr 2023 19:20:42 +0200 Subject: [PATCH 42/75] pass new hitobject properties through beatmap converters --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs | 4 +++- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs | 6 +++++- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 7774a7da09..2c8ef9eae0 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps var xPositionData = obj as IHasXPosition; var yPositionData = obj as IHasYPosition; var comboData = obj as IHasCombo; + var sliderVelocityData = obj as IHasSliderVelocity; switch (obj) { @@ -41,7 +42,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0, - LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y + LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y, + SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1 }.Yield(); case IHasDuration endTime: diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index e9518895be..d03ee81f0d 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { var positionData = original as IHasPosition; var comboData = original as IHasCombo; + var sliderVelocityData = original as IHasSliderVelocity; + var generateTicksData = original as IHasGenerateTicks; switch (original) { @@ -47,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in Date: Sun, 30 Apr 2023 19:32:24 +0200 Subject: [PATCH 43/75] add min and max value to SliderVelocity --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 7 ++++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 7 ++++++- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index f8af161ad5..169e99c90c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -28,7 +28,12 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } - public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }; public double SliderVelocity { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 98e536de38..4189f8ba1e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -134,7 +134,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool OnlyJudgeNestedObjects = true; - public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }; public double SliderVelocity { diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index c1a78f46b2..b4a12fd314 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -38,7 +38,12 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double Velocity { get; private set; } - public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1); + public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1) + { + Precision = 0.01, + MinValue = 0.1, + MaxValue = 10 + }; public double SliderVelocity { From 2a947571546bd883716e7a182a52100a68210548 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 00:49:01 +0200 Subject: [PATCH 44/75] Make sure the first object you place has bank and volume --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 96128c6981..bdcb334738 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit HitObject = hitObject; // adding the default hit sample should be the case regardless of the ruleset. - HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK, volume: 100)); RelativeSizeAxes = Axes.Both; From cef9f73d346cdafb9e4d5c7aa3f4a5a25f82ef1a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 12:31:27 +0200 Subject: [PATCH 45/75] dont assign custom sample bank and volume to hitobjects in non-mania gamemodes this makes it easier to edit hitsounds in the stable editor after export because the sample control point effects wont get overwritten by the properties of the hitobject --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index c5a8cc89be..28f3a8d951 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -520,6 +520,14 @@ namespace osu.Game.Beatmaps.Formats string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty; int volume = samples.FirstOrDefault()?.Volume ?? 100; + // We want to ignore custom sample banks and volume when not encoding to the mania game mode, + // because they cause unexpected results in the editor and are already satisfied by the control points. + if (onlineRulesetID != 3) + { + customSampleBank = "0"; + volume = 0; + } + sb.Append(':'); sb.Append(FormattableString.Invariant($"{customSampleBank}:")); sb.Append(FormattableString.Invariant($"{volume}:")); From 4d52ce769bcea52ee6e10cbc3826a4d570f48707 Mon Sep 17 00:00:00 2001 From: Terochi Date: Mon, 1 May 2023 12:53:58 +0200 Subject: [PATCH 46/75] Revert `SaveState()` calling on initialization --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 1 + osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index f7eb42872d..7b37b6624d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -198,6 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay firstTarget = Player.ChildrenOfType().First(); changeHandler = new TestSkinEditorChangeHandler(firstTarget); + changeHandler.SaveState(); defaultState = changeHandler.GetCurrentState(); testComponents = new[] diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 18ffdb0edc..3dddf639cc 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.SkinEditor return; components = new BindableList { BindTarget = firstTarget.Components }; - components.BindCollectionChanged((_, _) => SaveState(), true); + components.BindCollectionChanged((_, _) => SaveState()); } protected override void WriteCurrentStateToStream(MemoryStream stream) From 27cfadca16476bc9cbe069294bbc3b141c9cdcc7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 14:14:57 +0200 Subject: [PATCH 47/75] add sample info to Banana and SpinnerBonusTick --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 8 +++----- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 6 ------ osu.Game/Rulesets/Objects/HitObject.cs | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index af03c9acab..e137204c32 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -22,11 +22,9 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new CatchBananaJudgement(); - private static readonly List samples = new List { new BananaHitSampleInfo() }; - - public Banana() + public Banana(int volume = 100) { - Samples = samples; + Samples = new List { new BananaHitSampleInfo(volume) }; } // override any external colour changes with banananana @@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Objects public override IEnumerable LookupNames => lookup_names; - public BananaHitSampleInfo(int volume = 0) + public BananaHitSampleInfo(int volume = 100) : base(string.Empty, volume: volume) { } diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index b45f95a8e6..08febeabbf 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects { cancellationToken.ThrowIfCancellationRequested(); - AddNested(new Banana + AddNested(new Banana(GetSampleInfo().Volume) { StartTime = time, BananaIndex = i, diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 55924c19c9..df5898fd67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects AddNested(i < SpinsRequired ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration } - : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration }); + : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration, Samples = new[] { GetSampleInfo("spinnerbonus") } }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 81cdf5755b..00ceccaf7b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -11,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { - public SpinnerBonusTick() - { - Samples.Add(new HitSampleInfo("spinnerbonus")); - } - public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 774ff9dc1d..a4cb976d50 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Objects /// /// The name of the sample. /// A populated . - protected HitSampleInfo GetSampleInfo(string sampleName) + protected HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) { var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName); From 8302bb1f37b5c5c18d85134fc0a5834b7c4caaa9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 14:56:29 +0200 Subject: [PATCH 48/75] dont encode custom sample bank for objects without legacy samples --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 28f3a8d951..343dd7b082 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -173,9 +172,6 @@ namespace osu.Game.Beatmaps.Formats private void handleControlPoints(TextWriter writer) { - if (beatmap.ControlPointInfo.Groups.Count == 0) - return; - var legacyControlPoints = new LegacyControlPointInfo(); foreach (var point in beatmap.ControlPointInfo.AllControlPoints) legacyControlPoints.Add(point.Time, point.DeepClone()); @@ -199,6 +195,8 @@ namespace osu.Game.Beatmaps.Formats legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); } + int lastCustomSampleIndex = 0; + foreach (var group in legacyControlPoints.Groups) { var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); @@ -227,6 +225,12 @@ namespace osu.Game.Beatmaps.Formats // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); + // Inherit the previous sample bank if the current sample bank is not set + int customSampleBank = toLegacyCustomSampleBank(tempHitSample); + if (customSampleBank < 0) + customSampleBank = lastCustomSampleIndex; + lastCustomSampleIndex = customSampleBank; + // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; if (effectPoint.KiaiMode) @@ -236,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{timingPoint.TimeSignature.Numerator},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); - writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); + writer.Write(FormattableString.Invariant($"{customSampleBank},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); @@ -276,7 +280,8 @@ namespace osu.Game.Beatmaps.Formats int volume = hitObject.Samples.Max(o => o.Volume); int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo) ? hitObject.Samples.OfType().Max(o => o.CustomSampleBank) - : 0; + : -1; + yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex }; } @@ -516,7 +521,7 @@ namespace osu.Game.Beatmaps.Formats if (!banksOnly) { - string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))); + int customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))); string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty; int volume = samples.FirstOrDefault()?.Volume ?? 100; @@ -524,7 +529,7 @@ namespace osu.Game.Beatmaps.Formats // because they cause unexpected results in the editor and are already satisfied by the control points. if (onlineRulesetID != 3) { - customSampleBank = "0"; + customSampleBank = 0; volume = 0; } @@ -580,12 +585,12 @@ namespace osu.Game.Beatmaps.Formats } } - private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) + private int toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) { if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy) - return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture); + return legacy.CustomSampleBank; - return "0"; + return 0; } } } From 8c21fddb5e1d98a0158e664b44b0c7331c2338f7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 May 2023 16:53:38 +0200 Subject: [PATCH 49/75] remove all redundancies from encoded control points --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 343dd7b082..6b228a56a1 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -170,6 +171,24 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } + private struct LegacyControlPointProperties + { + internal double SliderVelocity { get; set; } + internal int TimingSignature { get; init; } + internal int SampleBank { get; init; } + internal int CustomSampleBank { get; init; } + internal int SampleVolume { get; init; } + internal LegacyEffectFlags EffectFlags { get; init; } + + internal bool IsRedundant(LegacyControlPointProperties other) => + SliderVelocity == other.SliderVelocity && + TimingSignature == other.TimingSignature && + SampleBank == other.SampleBank && + CustomSampleBank == other.CustomSampleBank && + SampleVolume == other.SampleVolume && + EffectFlags == other.EffectFlags; + } + private void handleControlPoints(TextWriter writer) { var legacyControlPoints = new LegacyControlPointInfo(); @@ -195,41 +214,44 @@ namespace osu.Game.Beatmaps.Formats legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); } - int lastCustomSampleIndex = 0; + LegacyControlPointProperties lastControlPointProperties = new LegacyControlPointProperties(); foreach (var group in legacyControlPoints.Groups) { var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); + var controlPointProperties = getLegacyControlPointProperties(group, groupTimingPoint != null); // If the group contains a timing control point, it needs to be output separately. if (groupTimingPoint != null) { writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},")); writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},")); - outputControlPointAt(groupTimingPoint.Time, true); + outputControlPointAt(controlPointProperties, true); + lastControlPointProperties = controlPointProperties; + lastControlPointProperties.SliderVelocity = 1; } + if (controlPointProperties.IsRedundant(lastControlPointProperties)) + continue; + // Output any remaining effects as secondary non-timing control point. - var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time); writer.Write(FormattableString.Invariant($"{group.Time},")); - writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},")); - outputControlPointAt(group.Time, false); + writer.Write(FormattableString.Invariant($"{-100 / controlPointProperties.SliderVelocity},")); + outputControlPointAt(controlPointProperties, false); + lastControlPointProperties = controlPointProperties; } - void outputControlPointAt(double time, bool isTimingPoint) + LegacyControlPointProperties getLegacyControlPointProperties(ControlPointGroup group, bool updateSampleBank) { + double time = group.Time; + var timingPoint = legacyControlPoints.TimingPointAt(time); + var difficultyPoint = legacyControlPoints.DifficultyPointAt(time); var samplePoint = legacyControlPoints.SamplePointAt(time); var effectPoint = legacyControlPoints.EffectPointAt(time); - var timingPoint = legacyControlPoints.TimingPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); - - // Inherit the previous sample bank if the current sample bank is not set int customSampleBank = toLegacyCustomSampleBank(tempHitSample); - if (customSampleBank < 0) - customSampleBank = lastCustomSampleIndex; - lastCustomSampleIndex = customSampleBank; // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; @@ -238,12 +260,26 @@ namespace osu.Game.Beatmaps.Formats if (timingPoint.OmitFirstBarLine) effectFlags |= LegacyEffectFlags.OmitFirstBarLine; - writer.Write(FormattableString.Invariant($"{timingPoint.TimeSignature.Numerator},")); - writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); - writer.Write(FormattableString.Invariant($"{customSampleBank},")); - writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); - writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},")); - writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); + return new LegacyControlPointProperties + { + SliderVelocity = difficultyPoint.SliderVelocity, + TimingSignature = timingPoint.TimeSignature.Numerator, + SampleBank = updateSampleBank ? (int)toLegacySampleBank(tempHitSample.Bank) : lastControlPointProperties.SampleBank, + // Inherit the previous custom sample bank if the current custom sample bank is not set + CustomSampleBank = customSampleBank >= 0 ? customSampleBank : lastControlPointProperties.CustomSampleBank, + SampleVolume = tempHitSample.Volume, + EffectFlags = effectFlags + }; + } + + void outputControlPointAt(LegacyControlPointProperties controlPoint, bool isTimingPoint) + { + writer.Write(FormattableString.Invariant($"{controlPoint.TimingSignature.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{controlPoint.SampleBank.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{controlPoint.CustomSampleBank.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{controlPoint.SampleVolume.ToString(CultureInfo.InvariantCulture)},")); + writer.Write(FormattableString.Invariant($"{(isTimingPoint ? "1" : "0")},")); + writer.Write(FormattableString.Invariant($"{((int)controlPoint.EffectFlags).ToString(CultureInfo.InvariantCulture)}")); writer.WriteLine(); } From 8a536c1cdb75496ad56b056baa83fc2d14376deb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 15:09:24 +0900 Subject: [PATCH 50/75] Fix non-block namespace usage --- .../Timeline/HitObjectPointPiece.cs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index f7854705a4..4b357d3a62 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -11,51 +11,52 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Compose.Components.Timeline; - -public partial class HitObjectPointPiece : CircularContainer +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - protected OsuSpriteText Label { get; private set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) + public partial class HitObjectPointPiece : CircularContainer { - AutoSizeAxes = Axes.Both; + protected OsuSpriteText Label { get; private set; } - Color4 colour = GetRepresentingColour(colours); - - InternalChildren = new Drawable[] + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - new Container - { - AutoSizeAxes = Axes.X, - Height = 16, - Masking = true, - CornerRadius = 8, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] - { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - Label = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding(5), - Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), - Colour = colours.B5, - } - } - }, - }; - } + AutoSizeAxes = Axes.Both; - protected virtual Color4 GetRepresentingColour(OsuColour colours) - { - return colours.Yellow; + Color4 colour = GetRepresentingColour(colours); + + InternalChildren = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + Height = 16, + Masking = true, + CornerRadius = 8, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + new Box + { + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Colour = colours.B5, + } + } + }, + }; + } + + protected virtual Color4 GetRepresentingColour(OsuColour colours) + { + return colours.Yellow; + } } } From c2ad8c23201814a3ce3103584e2bacc51c30b020 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 08:41:30 +0200 Subject: [PATCH 51/75] Fix comment 1 osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs Co-authored-by: Dean Herbert --- osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs index 5de7d348c5..c587654cda 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types public interface IHasGenerateTicks { /// - /// Whether or not slider ticks should be generated at this control point. + /// Whether or not slider ticks should be generated by this object. /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). /// public bool GenerateTicks { get; set; } From 2e018c8b0696b2394465ef31d1f49aad7c514627 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 08:41:47 +0200 Subject: [PATCH 52/75] Fix comment 2 osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs Co-authored-by: Dean Herbert --- osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs index c587654cda..3ac8b8a086 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Objects.Types { /// - /// A type of which may or may not generate ticks. + /// A type of which explicitly specifies whether it should generate ticks. /// public interface IHasGenerateTicks { From e3c51b96526223674cada703130544646b768f97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 16:26:56 +0900 Subject: [PATCH 53/75] Add ability to toggle snaking in slider test scene --- .../TestSceneSlider.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 1e9f931b74..4ad78a3190 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -15,6 +15,10 @@ using osuTK.Graphics; using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; using osu.Game.Beatmaps.Legacy; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,6 +26,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Configuration; namespace osu.Game.Rulesets.Osu.Tests { @@ -30,6 +35,27 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; + private readonly BindableBool snakingIn = new BindableBool(); + private readonly BindableBool snakingOut = new BindableBool(); + + [SetUpSteps] + public void SetUpSteps() + { + AddToggleStep("toggle snaking", v => + { + snakingIn.Value = v; + snakingOut.Value = v; + }); + } + + [BackgroundDependencyLoader] + private void load() + { + var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull(); + config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn); + config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + } + [Test] public void TestVariousSliders() { From 1a04be15c74b009bb9e329bf0d35b0031abe5328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 16:27:17 +0900 Subject: [PATCH 54/75] Fix fade in delay for first slider end circle being incorrect when snaking disabled --- .../Objects/Drawables/DrawableSliderRepeat.cs | 11 +++++++---- .../Objects/Drawables/DrawableSliderTail.cs | 8 +++++++- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 5 +---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 3446d41fb4..d474db0526 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -87,12 +87,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateInitialTransforms() { + // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. + bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + animDuration = Math.Min(300, HitObject.SpanDuration); - this.Animate( - d => d.FadeIn(animDuration), - d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf) - ); + this + .FadeOut() + .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 2c1b68e05a..da4feae242 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - CirclePiece.FadeInFromZero(HitObject.TimeFadeIn); + // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. + bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + + CirclePiece + .FadeOut() + .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .FadeIn(HitObject.TimeFadeIn); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 35bec92354..b5374333c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -39,11 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects } else { - // taken from osu-stable - const float first_end_circle_preempt_adjust = 2 / 3f; - // The first end circle should fade in with the slider. - TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust; + TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt; } } From a619812cab7f881c2968db977e2bb5322299b03e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 May 2023 16:36:43 +0900 Subject: [PATCH 55/75] Fix nullability and remove extra preempt from `SliderEndCircle` calculation --- .../Objects/Drawables/DrawableSliderRepeat.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index d474db0526..fc4863f164 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -88,13 +88,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateInitialTransforms() { // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. - bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0; animDuration = Math.Min(300, HitObject.SpanDuration); this .FadeOut() - .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0) .FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index da4feae242..d9501f7d58 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -92,11 +92,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.UpdateInitialTransforms(); // When snaking in is enabled, the first end circle needs to be delayed until the snaking completes. - bool delayFadeIn = DrawableSlider.SliderBody!.SnakingIn.Value && HitObject.RepeatIndex == 0; + bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0; CirclePiece .FadeOut() - .Delay(delayFadeIn ? Slider!.TimePreempt / 3f : 0) + .Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0) .FadeIn(HitObject.TimeFadeIn); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index b5374333c9..f52c3ab382 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects else { // The first end circle should fade in with the slider. - TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt; + TimePreempt += StartTime - slider.StartTime; } } From d2d81bb82c6fe9fcca7e7c8f4ac3879994aa8aa4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 12:29:11 +0200 Subject: [PATCH 56/75] remove redundant zero check in sv calculation --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 75ee0546dd..7a577f8a83 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // // The value is clamped here by the bindable min and max values. // In case the required velocity is too large, the path is not preserved. - svBindable.Value = requiredVelocity == 0 ? 1 : Math.Ceiling(requiredVelocity / svToVelocityFactor); + svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); From 90d98cd3294ec30533017d83382d60525305f98c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 2 May 2023 12:41:39 +0200 Subject: [PATCH 57/75] remove constructor argument from Banana --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 8 +++++--- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index e137204c32..e4aa778902 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -22,9 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new CatchBananaJudgement(); - public Banana(int volume = 100) + private static readonly List samples = new List { new BananaHitSampleInfo() }; + + public Banana() { - Samples = new List { new BananaHitSampleInfo(volume) }; + Samples = samples; } // override any external colour changes with banananana @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects } } - private class BananaHitSampleInfo : HitSampleInfo, IEquatable + public class BananaHitSampleInfo : HitSampleInfo, IEquatable { private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" }; diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 08febeabbf..5bd4ac86f5 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -1,7 +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.Collections.Generic; using System.Threading; +using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -35,10 +37,11 @@ namespace osu.Game.Rulesets.Catch.Objects { cancellationToken.ThrowIfCancellationRequested(); - AddNested(new Banana(GetSampleInfo().Volume) + AddNested(new Banana { StartTime = time, BananaIndex = i, + Samples = new List { new Banana.BananaHitSampleInfo(GetSampleInfo().Volume) } }); time += spacing; From 9c4312b407bb208917a8abf42283018bdf47bd56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 12:37:07 +0900 Subject: [PATCH 58/75] Add support for flipping colour of reverse arrow on legacy default skin when combo colour is too bright --- .../Skinning/Legacy/LegacyReverseArrow.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index fbe094ef81..e6166e9441 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -3,11 +3,13 @@ using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -18,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Drawable proxy = null!; + private Bindable accentColour = null!; + + private bool textureIsDefaultSkin; + + private Drawable arrow = null!; + [BackgroundDependencyLoader] private void load(ISkinSource skinSource) { @@ -26,7 +34,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName; var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); - InternalChild = skin?.GetAnimation(lookupName, true, true) ?? Empty(); + + InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true) ?? Empty()); + textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; } protected override void LoadComplete() @@ -39,6 +49,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { drawableHitObject.HitObjectApplied += onHitObjectApplied; onHitObjectApplied(drawableHitObject); + + accentColour = drawableHitObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(c => + { + arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White; + }, true); } } From 16c624fb61a7dd02460e799dd48570162f3eb2a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:02:05 +0900 Subject: [PATCH 59/75] Ensure `static` banana samples are not mutated --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index e4aa778902..4c66c054e1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new CatchBananaJudgement(); - private static readonly List samples = new List { new BananaHitSampleInfo() }; + private static readonly IList default_banana_samples = new List { new BananaHitSampleInfo() }.AsReadOnly(); public Banana() { - Samples = samples; + Samples = default_banana_samples; } // override any external colour changes with banananana From 588a4e6196cc038928fa0d68481b72cae0cba4e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:19:13 +0900 Subject: [PATCH 60/75] Move pragma disable to top of `LegacyBeatmapDecoder` Makes more sense as it's used multiple times in the class. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index d20f7e198b..9dcd97459f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -3,6 +3,8 @@ #nullable disable +#pragma warning disable 618 + using System; using System.Collections.Generic; using System.IO; @@ -102,9 +104,8 @@ namespace osu.Game.Beatmaps.Formats var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT; -#pragma warning disable 618 + if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) -#pragma warning restore 618 { hitObject.LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier; if (hitObject is IHasGenerateTicks hasGenerateTicks) @@ -494,9 +495,7 @@ namespace osu.Game.Beatmaps.Formats int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID; -#pragma warning disable 618 addControlPoint(time, new LegacyDifficultyControlPoint(onlineRulesetID, beatLength) -#pragma warning restore 618 { SliderVelocity = speedMultiplier, }, timingChange); From cca15f930c5ab8a4ef7a3558613bf4bf12f84d33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:26:50 +0900 Subject: [PATCH 61/75] Refactor `applyLegacyInfoAndDefaults` for legibility --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9dcd97459f..90be90fcaf 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -101,9 +101,14 @@ namespace osu.Game.Beatmaps.Formats private void applyLegacyInfoAndDefaults(HitObject hitObject) { - var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo; + DifficultyControlPoint difficultyControlPoint = DifficultyControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = SampleControlPoint.DEFAULT; - DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT; + if (beatmap.ControlPointInfo is LegacyControlPointInfo legacyInfo) + { + difficultyControlPoint = legacyInfo.DifficultyPointAt(hitObject.StartTime); + sampleControlPoint = legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency); + } if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) { @@ -117,18 +122,17 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - SampleControlPoint sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) : SampleControlPoint.DEFAULT; - hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); - if (hitObject is not IHasRepeats hasRepeats) return; - - for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + if (hitObject is IHasRepeats hasRepeats) { - double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; - sampleControlPoint = legacyInfo != null ? legacyInfo.SamplePointAt(time) : SampleControlPoint.DEFAULT; + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + { + double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; + sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT; - hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); + } } } From 48fd99818ea448a6f21fa2ecb9e67bd8ef260e0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:30:45 +0900 Subject: [PATCH 62/75] Split out default and sample application --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 90be90fcaf..5e98025c9a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -95,20 +95,14 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in this.beatmap.HitObjects) { - applyLegacyInfoAndDefaults(hitObject); + applyDefaults(hitObject); + applySamples(hitObject); } } - private void applyLegacyInfoAndDefaults(HitObject hitObject) + private void applyDefaults(HitObject hitObject) { - DifficultyControlPoint difficultyControlPoint = DifficultyControlPoint.DEFAULT; - SampleControlPoint sampleControlPoint = SampleControlPoint.DEFAULT; - - if (beatmap.ControlPointInfo is LegacyControlPointInfo legacyInfo) - { - difficultyControlPoint = legacyInfo.DifficultyPointAt(hitObject.StartTime); - sampleControlPoint = legacyInfo.SamplePointAt(hitObject.GetEndTime() + control_point_leniency); - } + DifficultyControlPoint difficultyControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(hitObject.StartTime) ?? DifficultyControlPoint.DEFAULT; if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint) { @@ -121,6 +115,11 @@ namespace osu.Game.Beatmaps.Formats hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity; hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + } + + private void applySamples(HitObject hitObject) + { + SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); @@ -129,9 +128,9 @@ namespace osu.Game.Beatmaps.Formats for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) { double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency; - sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT; + var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT; - hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => sampleControlPoint.ApplyTo(o)).ToList(); + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList(); } } } From f930c4bd0aaa4137ce2c2fbcbe5870a7d919fefb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 13:33:31 +0900 Subject: [PATCH 63/75] Move `struct` to bottom of file --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 6b228a56a1..7fbcca9adb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -92,7 +92,8 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); - writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); + writer.WriteLine(FormattableString.Invariant( + $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); @@ -171,24 +172,6 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } - private struct LegacyControlPointProperties - { - internal double SliderVelocity { get; set; } - internal int TimingSignature { get; init; } - internal int SampleBank { get; init; } - internal int CustomSampleBank { get; init; } - internal int SampleVolume { get; init; } - internal LegacyEffectFlags EffectFlags { get; init; } - - internal bool IsRedundant(LegacyControlPointProperties other) => - SliderVelocity == other.SliderVelocity && - TimingSignature == other.TimingSignature && - SampleBank == other.SampleBank && - CustomSampleBank == other.CustomSampleBank && - SampleVolume == other.SampleVolume && - EffectFlags == other.EffectFlags; - } - private void handleControlPoints(TextWriter writer) { var legacyControlPoints = new LegacyControlPointInfo(); @@ -243,11 +226,10 @@ namespace osu.Game.Beatmaps.Formats LegacyControlPointProperties getLegacyControlPointProperties(ControlPointGroup group, bool updateSampleBank) { - double time = group.Time; - var timingPoint = legacyControlPoints.TimingPointAt(time); - var difficultyPoint = legacyControlPoints.DifficultyPointAt(time); - var samplePoint = legacyControlPoints.SamplePointAt(time); - var effectPoint = legacyControlPoints.EffectPointAt(time); + var timingPoint = legacyControlPoints.TimingPointAt(group.Time); + var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time); + var samplePoint = legacyControlPoints.SamplePointAt(group.Time); + var effectPoint = legacyControlPoints.EffectPointAt(group.Time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); @@ -628,5 +610,23 @@ namespace osu.Game.Beatmaps.Formats return 0; } + + private struct LegacyControlPointProperties + { + internal double SliderVelocity { get; set; } + internal int TimingSignature { get; init; } + internal int SampleBank { get; init; } + internal int CustomSampleBank { get; init; } + internal int SampleVolume { get; init; } + internal LegacyEffectFlags EffectFlags { get; init; } + + internal bool IsRedundant(LegacyControlPointProperties other) => + SliderVelocity == other.SliderVelocity && + TimingSignature == other.TimingSignature && + SampleBank == other.SampleBank && + CustomSampleBank == other.CustomSampleBank && + SampleVolume == other.SampleVolume && + EffectFlags == other.EffectFlags; + } } } From 384693a431757767d27594ef8906cd3d1ace0861 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 14:30:57 +0900 Subject: [PATCH 64/75] Fix test failure in `MultiplayerMatchSongSelect` due to multiple overlays present https://github.com/ppy/osu/actions/runs/4868337922/jobs/8681736995?pr=23308. --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index c0b6a0beab..9b130071cc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -98,6 +98,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0); + + // A previous test's mod overlay could still be fading out. + AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType().Count() == 1); + assertHasFreeModButton(allowedMod, false); assertHasFreeModButton(requiredMod, false); } From a3efae36907fc596061ffadf7e552d6c078c360a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 14:33:30 +0900 Subject: [PATCH 65/75] Fix potentially incorrect thread access in `OsuTabControlCheckbox` https://github.com/ppy/osu/actions/runs/4868337922/jobs/8681736829. --- .../UserInterface/OsuTabControlCheckbox.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index fa58ae27f2..71bc18737f 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -49,11 +49,10 @@ namespace osu.Game.Graphics.UserInterface private const float transition_length = 500; private Sample sampleChecked; private Sample sampleUnchecked; + private readonly SpriteIcon icon; public OsuTabControlCheckbox() { - SpriteIcon icon; - AutoSizeAxes = Axes.Both; Children = new Drawable[] @@ -85,14 +84,6 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.BottomLeft, } }; - - Current.ValueChanged += selected => - { - icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; - text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); - - updateFade(); - }; } [BackgroundDependencyLoader] @@ -103,6 +94,14 @@ namespace osu.Game.Graphics.UserInterface sampleChecked = audio.Samples.Get(@"UI/check-on"); sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + + Current.ValueChanged += selected => + { + icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; + text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); + + updateFade(); + }; } protected override bool OnHover(HoverEvent e) From bede1292de37f4be3270c2e86c215783e3ef52d8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 2 May 2023 22:24:07 -0700 Subject: [PATCH 66/75] Fix overlay scroll back button not absorbing hover from behind --- osu.Game/Overlays/OverlayScrollContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index 9752e04f44..ba7486a57f 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -185,6 +185,8 @@ namespace osu.Game.Overlays content.ScaleTo(1, 1000, Easing.OutElastic); base.OnMouseUp(e); } + + protected override bool OnHover(HoverEvent e) => true; } } } From 1c74f6e8ea60fe91335c95883bc8d434e51b07ac Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 2 May 2023 22:54:42 -0700 Subject: [PATCH 67/75] Fix regressed button hover fade in --- osu.Game/Overlays/OverlayScrollContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index ba7486a57f..2a615f0e12 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -186,7 +186,11 @@ namespace osu.Game.Overlays base.OnMouseUp(e); } - protected override bool OnHover(HoverEvent e) => true; + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + return true; + } } } } From 453143813fafc7a62736ab473a883a194d7e643d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 14:57:31 +0900 Subject: [PATCH 68/75] Extend input handling of osu! logo to full border area This is the easiest way to make this happen. It does mean the pink area is being drawn behind the white border, but I haven't found a scenario where this is noticeable. Closes #4217. --- osu.Game/Screens/Menu/OsuLogo.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 9430a1cda8..33dc23d88d 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -35,6 +35,12 @@ namespace osu.Game.Screens.Menu private const double transition_length = 300; + /// + /// The osu! logo sprite has a shadow included in its texture. + /// This adjustment vector is used to match the precise edge of the border of the logo. + /// + private static readonly Vector2 scale_adjust = new Vector2(0.96f); + private readonly Sprite logo; private readonly CircularContainer logoContainer; private readonly Container logoBounceContainer; @@ -150,7 +156,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = visualizer_default_alpha, - Size = new Vector2(0.96f) + Size = scale_adjust }, new Container { @@ -162,7 +168,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.88f), + Scale = scale_adjust, Masking = true, Children = new Drawable[] { From 5f781bd6de1ef4c2e067eec5e985722500b7b192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 May 2023 09:26:54 +0200 Subject: [PATCH 69/75] Move callback to `LoadComplete()` Is the more correct place for `BindValueChanged()` callbacks. --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 71bc18737f..8eb6b792c6 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -94,14 +94,19 @@ namespace osu.Game.Graphics.UserInterface sampleChecked = audio.Samples.Get(@"UI/check-on"); sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + } - Current.ValueChanged += selected => + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(selected => { icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); updateFade(); - }; + }); } protected override bool OnHover(HoverEvent e) From de1b28bcb284e9e07b4b36eb9a3513a5f7228732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 May 2023 09:27:23 +0200 Subject: [PATCH 70/75] Fix incorrect initial state of checkbox This only ever barely used to work without the `(..., true)` in `master` because of haphazard operation ordering. --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 8eb6b792c6..c9e1f74917 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -106,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); updateFade(); - }); + }, true); } protected override bool OnHover(HoverEvent e) From 5757eb75299234da77876e70de818ae53d4e8728 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 18:20:12 +0900 Subject: [PATCH 71/75] Update a few more instances of `0.96f` scale constants --- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- osu.Game/Screens/Menu/OsuLogo.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index da44161507..2a6ebecb92 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = Color4.DarkBlue, - Size = new Vector2(0.96f) + Size = OsuLogo.SCALE_ADJUST, }, new Circle { diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 33dc23d88d..277b8bf888 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu /// The osu! logo sprite has a shadow included in its texture. /// This adjustment vector is used to match the precise edge of the border of the logo. /// - private static readonly Vector2 scale_adjust = new Vector2(0.96f); + public static readonly Vector2 SCALE_ADJUST = new Vector2(0.96f); private readonly Sprite logo; private readonly CircularContainer logoContainer; @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = visualizer_default_alpha, - Size = scale_adjust + Size = SCALE_ADJUST }, new Container { @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = scale_adjust, + Scale = SCALE_ADJUST, Masking = true, Children = new Drawable[] { @@ -412,7 +412,7 @@ namespace osu.Game.Screens.Menu public void Impact() { impactContainer.FadeOutFromOne(250, Easing.In); - impactContainer.ScaleTo(0.96f); + impactContainer.ScaleTo(SCALE_ADJUST); impactContainer.ScaleTo(1.12f, 250); } From cd31cff8cdd4c26c64183f640178b604e638682e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 18:41:29 +0900 Subject: [PATCH 72/75] Fix event subscriptions not being cleaned up in `DrawableCarouselBeatmap` The handling of cleanup is performed only the `Item_Set` method. This was already correctly called for `DrawableCarouselBeatmapSet`, but not for the class in question here. This would cause runaway memory usage at song select when opening many beatmaps to show their difficulties. For simplicity, we don't yet pool these (and generate the drawables each time a set is opened) which isn't great but likely will be improved upon when we update the visual / filtering of the carousel. But this simplicity caused the memory usage to blow out until exiting back to the main menu when cleanup would finally occur. --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index f08d14720b..55d442215b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -250,6 +250,9 @@ namespace osu.Game.Screens.Select.Carousel { base.Dispose(isDisposing); starDifficultyCancellationSource?.Cancel(); + + // This is important to clean up event subscriptions. + Item = null; } } } From 444f66b0eefdce3c4defaf46fffa927a886f6f51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 May 2023 18:45:09 +0900 Subject: [PATCH 73/75] Move to base class for added safety --- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 --- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 10 +++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 55d442215b..f08d14720b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -250,9 +250,6 @@ namespace osu.Game.Screens.Select.Carousel { base.Dispose(isDisposing); starDifficultyCancellationSource?.Cancel(); - - // This is important to clean up event subscriptions. - Item = null; } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index f065926eb7..0c3de5848b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Select.Carousel item = value; - if (IsLoaded) + if (IsLoaded && !IsDisposed) UpdateItem(); } } @@ -165,5 +165,13 @@ namespace osu.Game.Screens.Select.Carousel Item.State.Value = CarouselItemState.Selected; return true; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + // This is important to clean up event subscriptions. + Item = null; + } } } From 27fabd99fbc996705e5f700ebdc96f028c035872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 May 2023 19:21:16 +0200 Subject: [PATCH 74/75] Rename variables for legibility Having `typedComponents` and `typeComponents` next to each other is asking for trouble. --- .../Overlays/SkinEditor/SkinEditorChangeHandler.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index 3dddf639cc..f3c5bc9ce2 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -61,16 +61,16 @@ namespace osu.Game.Overlays.SkinEditor ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray(); // Store components based on type for later lookup - var typedComponents = new Dictionary>(); + var componentsPerTypeLookup = new Dictionary>(); foreach (ISerialisableDrawable component in targetComponents) { Type lookup = component.GetType(); - if (!typedComponents.TryGetValue(lookup, out Queue? typeComponents)) - typedComponents.Add(lookup, typeComponents = new Queue()); + if (!componentsPerTypeLookup.TryGetValue(lookup, out Queue? componentsOfSameType)) + componentsPerTypeLookup.Add(lookup, componentsOfSameType = new Queue()); - typeComponents.Enqueue((Drawable)component); + componentsOfSameType.Enqueue((Drawable)component); } // Remove all components @@ -81,13 +81,13 @@ namespace osu.Game.Overlays.SkinEditor { Type lookup = skinnableInfo.Type; - if (!typedComponents.TryGetValue(lookup, out Queue? typeComponents)) + if (!componentsPerTypeLookup.TryGetValue(lookup, out Queue? componentsOfSameType)) { firstTarget.Add((ISerialisableDrawable)skinnableInfo.CreateInstance()); continue; } - if (typeComponents.TryDequeue(out Drawable? component)) + if (componentsOfSameType.TryDequeue(out Drawable? component)) { // Re-use unused component component.ApplySerialisedInfo(skinnableInfo); @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.SkinEditor firstTarget.Add((ISerialisableDrawable)component); } - foreach ((Type _, Queue typeComponents) in typedComponents) + foreach ((Type _, Queue typeComponents) in componentsPerTypeLookup) { // Dispose extra components foreach (var component in typeComponents) From 1d4d31e35c8b85eb5104734a25b971ec8c827ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 May 2023 19:22:52 +0200 Subject: [PATCH 75/75] Trim comments Leaving only the ones that add anything useful and do not restate the code verbatim. --- osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs index f3c5bc9ce2..673ba873c4 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.SkinEditor SerialisedDrawableInfo[] skinnableInfos = deserializedContent.ToArray(); ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray(); - // Store components based on type for later lookup + // Store components based on type for later reuse var componentsPerTypeLookup = new Dictionary>(); foreach (ISerialisableDrawable component in targetComponents) @@ -73,7 +73,6 @@ namespace osu.Game.Overlays.SkinEditor componentsOfSameType.Enqueue((Drawable)component); } - // Remove all components for (int i = targetComponents.Length - 1; i >= 0; i--) firstTarget.Remove(targetComponents[i], false); @@ -87,23 +86,22 @@ namespace osu.Game.Overlays.SkinEditor continue; } + // Wherever possible, attempt to reuse existing component instances. if (componentsOfSameType.TryDequeue(out Drawable? component)) { - // Re-use unused component component.ApplySerialisedInfo(skinnableInfo); } else { - // Create new one component = skinnableInfo.CreateInstance(); } firstTarget.Add((ISerialisableDrawable)component); } + // Dispose components which were not reused. foreach ((Type _, Queue typeComponents) in componentsPerTypeLookup) { - // Dispose extra components foreach (var component in typeComponents) component.Dispose(); }