From cf3aaff7bd1bf796f826d18f8a56c7a07d1359c6 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 16:01:23 +0200 Subject: [PATCH 001/161] Add floating fruits mod --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 ++- .../Mods/CatchModFloatingFruits.cs | 23 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f4ddbd3021..6206815728 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -126,7 +126,8 @@ namespace osu.Game.Rulesets.Catch case ModType.Fun: return new Mod[] { - new MultiMod(new ModWindUp(), new ModWindDown()) + new MultiMod(new ModWindUp(), new ModWindDown()), + new CatchModFloatingFruits() }; default: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs new file mode 100644 index 0000000000..4e25739521 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -0,0 +1,23 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModFloatingFruits : Mod, IApplicableToPlayer + { + public override string Name => "Floating Fruits"; + public override string Acronym => "FF"; + public override string Description => "The fruits are... floating?"; + public override double ScoreMultiplier => 1; + public override IconUsage? Icon => FontAwesome.Brands.Fly; + + public void ApplyToPlayer(Player player) + { + player.DrawableRuleset.Anchor = Anchor.Centre; + player.DrawableRuleset.Origin = Anchor.Centre; + player.DrawableRuleset.Scale = new osuTK.Vector2(1, -1); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dd3f58439b..59231a1c30 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play protected HealthProcessor HealthProcessor { get; private set; } - protected DrawableRuleset DrawableRuleset { get; private set; } + public DrawableRuleset DrawableRuleset { get; set; } protected HUDOverlay HUDOverlay { get; private set; } From 5d274dbce86b3b8b03abb72f176e57a4209c4576 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 16:38:28 +0200 Subject: [PATCH 002/161] replace IApplicableToPlayer with IApplicableToDrawableRuleset --- .../Mods/CatchModFloatingFruits.cs | 13 ++++++++----- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4e25739521..4e34cbe9e2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,11 +1,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModFloatingFruits : Mod, IApplicableToPlayer + public class CatchModFloatingFruits : Mod, IApplicableToDrawableRuleset { public override string Name => "Floating Fruits"; public override string Acronym => "FF"; @@ -13,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon => FontAwesome.Brands.Fly; - public void ApplyToPlayer(Player player) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - player.DrawableRuleset.Anchor = Anchor.Centre; - player.DrawableRuleset.Origin = Anchor.Centre; - player.DrawableRuleset.Scale = new osuTK.Vector2(1, -1); + drawableRuleset.Anchor = Anchor.Centre; + drawableRuleset.Origin = Anchor.Centre; + drawableRuleset.Scale = new osuTK.Vector2(1, -1); } + } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 59231a1c30..dd3f58439b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play protected HealthProcessor HealthProcessor { get; private set; } - public DrawableRuleset DrawableRuleset { get; set; } + protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } From eec77b052790f720fa716d10b96aad540b9cb083 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 23:55:39 +0200 Subject: [PATCH 003/161] replace icon --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4e34cbe9e2..4c46e24f1b 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override string Acronym => "FF"; public override string Description => "The fruits are... floating?"; public override double ScoreMultiplier => 1; - public override IconUsage? Icon => FontAwesome.Brands.Fly; + public override IconUsage? Icon => FontAwesome.Solid.Cloud; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From bbf2ec369b748dcb424f370178ec5402a5c46493 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 17:13:59 +0900 Subject: [PATCH 004/161] Remove SkinReloadableDrawable inheritance from DHO --- .../Objects/Drawables/DrawableHitObject.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ba2b8423d0..1369623a62 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -25,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : SkinReloadableDrawable + public abstract class DrawableHitObject : PoolableDrawable { /// /// Invoked after this 's applied has had its defaults applied. @@ -178,12 +179,15 @@ namespace osu.Game.Rulesets.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, ISkinSource skinSource) { config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); // Explicit non-virtual function call. base.AddInternal(Samples = new PausableSkinnableSound()); + + CurrentSkin = skinSource; + CurrentSkin.SourceChanged += onSkinSourceChanged; } protected override void LoadAsyncComplete() @@ -536,17 +540,19 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); + #region Skinning + protected ISkinSource CurrentSkin { get; private set; } + + private void onSkinSourceChanged() => Scheduler.AddOnce(() => + { UpdateComboColour(); - ApplySkin(skin, allowFallback); + ApplySkin(CurrentSkin, true); if (IsLoaded) updateState(State.Value, true); - } + }); protected void UpdateComboColour() { @@ -616,6 +622,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Samples.Stop(); } + #endregion + protected override void Update() { base.Update(); @@ -811,6 +819,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject != null) HitObject.DefaultsApplied -= onDefaultsApplied; + + CurrentSkin.SourceChanged -= onSkinSourceChanged; } } From b877a2973739c4cb8a32393bbb021392f536be82 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 17:55:01 +0900 Subject: [PATCH 005/161] Factor out pooling and lifetime management logic of DHO to a base class --- .../Objects/Drawables/DrawableHitObject.cs | 116 +++-------------- .../Objects/Pooling/DrawableObject.cs | 121 ++++++++++++++++++ 2 files changed, 141 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1369623a62..312ed93e45 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -20,13 +19,14 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.UI; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : PoolableDrawable + public abstract class DrawableHitObject : DrawableObject { /// /// Invoked after this 's applied has had its defaults applied. @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject => lifetimeEntry?.HitObject; + public HitObject HitObject => Entry?.HitObject; /// /// The parenting , if any. @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The scoring result of this . /// - public JudgementResult Result => lifetimeEntry?.Result; + public JudgementResult Result => Entry?.Result; /// /// The relative X position of this hit object for sample playback balance adjustment. @@ -126,8 +126,6 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly Bindable userPositionalHitSounds = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); - public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); @@ -142,18 +140,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; - /// - /// Whether a is currently applied. - /// - private bool hasEntryApplied; - - /// - /// The controlling the lifetime of the currently-attached . - /// - /// Even if it is not null, it may not be fully applied until loaded ( is false). - [CanBeNull] - private HitObjectLifetimeEntry lifetimeEntry; - [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -167,15 +153,13 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) + : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) { - if (initialHitObject != null) - { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); + if (Entry != null) ensureEntryHasResult(); - } } [BackgroundDependencyLoader] @@ -190,14 +174,6 @@ namespace osu.Game.Rulesets.Objects.Drawables CurrentSkin.SourceChanged += onSkinSourceChanged; } - protected override void LoadAsyncComplete() - { - base.LoadAsyncComplete(); - - if (lifetimeEntry != null && !hasEntryApplied) - Apply(lifetimeEntry); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -231,22 +207,15 @@ namespace osu.Game.Rulesets.Objects.Drawables Apply(new SyntheticHitObjectEntry(hitObject)); } - /// - /// Applies a new to be represented by this . - /// - public void Apply([NotNull] HitObjectLifetimeEntry newEntry) + protected sealed override void OnApply(HitObjectLifetimeEntry entry) { - free(); - - lifetimeEntry = newEntry; - // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. - if (newEntry is SyntheticHitObjectEntry) - lifetimeEntry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + if (entry is SyntheticHitObjectEntry) + entry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; - LifetimeStart = lifetimeEntry.LifetimeStart; - LifetimeEnd = lifetimeEntry.LifetimeEnd; + LifetimeStart = entry.LifetimeStart; + LifetimeEnd = entry.LifetimeEnd; ensureEntryHasResult(); @@ -297,17 +266,10 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } - - hasEntryApplied = true; } - /// - /// Removes the currently applied - /// - private void free() + protected sealed override void OnFree(HitObjectLifetimeEntry entry) { - if (!hasEntryApplied) return; - StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) comboIndexBindable.UnbindFrom(combo.ComboIndexBindable); @@ -339,22 +301,8 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(); ParentHitObject = null; - lifetimeEntry = null; clearExistingStateTransforms(); - - hasEntryApplied = false; - } - - protected sealed override void FreeAfterUse() - { - base.FreeAfterUse(); - - // Freeing while not in a pool would cause the DHO to not be usable elsewhere in the hierarchy without being re-applied. - if (!IsInPool) - return; - - free(); } /// @@ -402,8 +350,8 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { - Debug.Assert(lifetimeEntry != null); - Apply(lifetimeEntry); + Debug.Assert(Entry != null); + Apply(Entry); DefaultsApplied?.Invoke(this); } @@ -486,7 +434,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. + /// The local drawable hierarchy is recursively delayed to for convenience. /// /// By default this will fade in the object from zero with no duration. /// @@ -661,30 +609,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); - public override double LifetimeStart - { - get => base.LifetimeStart; - set => setLifetime(value, LifetimeEnd); - } - - public override double LifetimeEnd - { - get => base.LifetimeEnd; - set => setLifetime(LifetimeStart, value); - } - - private void setLifetime(double lifetimeStart, double lifetimeEnd) - { - base.LifetimeStart = lifetimeStart; - base.LifetimeEnd = lifetimeEnd; - - if (lifetimeEntry != null) - { - lifetimeEntry.LifetimeStart = lifetimeStart; - lifetimeEntry.LifetimeEnd = lifetimeEnd; - } - } - /// /// A safe offset prior to the start time of at which this may begin displaying contents. /// By default, s are assumed to display their contents within 10 seconds prior to the start time of . @@ -692,7 +616,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// /// Only has an effect if this is not being pooled. /// For pooled s, use instead. @@ -808,9 +732,9 @@ namespace osu.Game.Rulesets.Objects.Drawables private void ensureEntryHasResult() { - Debug.Assert(lifetimeEntry != null); - lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + Debug.Assert(Entry != null); + Entry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs new file mode 100644 index 0000000000..b29e6a6c3c --- /dev/null +++ b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Diagnostics; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; + +namespace osu.Game.Rulesets.Objects.Pooling +{ + /// + /// A that is controlled by to implement drawable pooling and replay rewinding. + /// + /// The type storing state and controlling this drawable. + public abstract class DrawableObject : PoolableDrawable where TEntry : LifetimeEntry + { + /// + /// The entry holding essential state of this . + /// + protected TEntry? Entry { get; private set; } + + /// + /// Whether is applied to this . + /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. + /// + protected bool HasEntryApplied { get; private set; } + + public override double LifetimeStart + { + get => base.LifetimeStart; + set => setLifetime(value, LifetimeEnd); + } + + public override double LifetimeEnd + { + get => base.LifetimeEnd; + set => setLifetime(LifetimeStart, value); + } + + public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + + protected DrawableObject(TEntry? initialEntry = null) + { + Entry = initialEntry; + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + if (Entry != null && !HasEntryApplied) + Apply(Entry); + } + + /// + /// Applies a new entry to be represented by this drawable. + /// If there is an existing entry applied, the entry will be replaced. + /// + public void Apply(TEntry entry) + { + freeIfInUse(); + + setLifetime(entry.LifetimeStart, entry.LifetimeEnd); + Entry = entry; + + OnApply(entry); + + HasEntryApplied = true; + } + + protected sealed override void FreeAfterUse() + { + base.FreeAfterUse(); + + if (IsInPool) + freeIfInUse(); + } + + /// + /// Invoked to apply a new entry to this drawable. + /// + protected virtual void OnApply(TEntry entry) + { + } + + /// + /// Invoked to revert application of the entry to this drawable. + /// + protected virtual void OnFree(TEntry entry) + { + } + + private void setLifetime(double start, double end) + { + base.LifetimeStart = start; + base.LifetimeEnd = end; + + if (Entry != null) + { + Entry.LifetimeStart = start; + Entry.LifetimeEnd = end; + } + } + + private void freeIfInUse() + { + if (!HasEntryApplied) return; + + Debug.Assert(Entry != null); + + OnFree(Entry); + + Entry = null; + setLifetime(double.MaxValue, double.MaxValue); + + HasEntryApplied = false; + } + } +} From bc0e1d8c37272acf7a75c048036c512d42777bcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Apr 2021 15:06:39 +0900 Subject: [PATCH 006/161] Remove dead newline --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4c46e24f1b..e4c41bed6d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -21,6 +21,5 @@ namespace osu.Game.Rulesets.Catch.Mods drawableRuleset.Origin = Anchor.Centre; drawableRuleset.Scale = new osuTK.Vector2(1, -1); } - } } From ae2fd2f2e1f4315d4c1b90a56d412fa7b54cc9f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 18:46:59 +0900 Subject: [PATCH 007/161] Ensure source is set on reset --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5cd17d92c4..6677116399 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { + ChangeSource(SourceClock); Seek(0); // Manually stop the source in order to not affect the IsPaused state. From fdb5490e51f1edf5b09aed6ecaf428e7ff03e8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Apr 2021 21:56:08 +0200 Subject: [PATCH 008/161] Attempt to explain source initialisation better --- osu.Game/Screens/Play/GameplayClockContainer.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6677116399..6d60c09521 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -63,8 +63,7 @@ namespace osu.Game.Screens.Play /// public virtual void Start() { - // Ensure that the source clock is set. - ChangeSource(SourceClock); + ensureSourceClockSet(); if (!AdjustableSource.IsRunning) { @@ -100,7 +99,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - ChangeSource(SourceClock); + ensureSourceClockSet(); Seek(0); // Manually stop the source in order to not affect the IsPaused state. @@ -116,6 +115,15 @@ namespace osu.Game.Screens.Play /// The new source. protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); + /// + /// Ensures that the is set to . + /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, + /// but not the actual source clock. + /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, + /// but it is not yet set on the adjustable source there. + /// + private void ensureSourceClockSet() => ChangeSource(SourceClock); + protected override void Update() { if (!IsPaused.Value) From 04958a043f8ce1f5e24a65c438f0ad4c4305e4f3 Mon Sep 17 00:00:00 2001 From: subfluid <76847113+subfluid@users.noreply.github.com> Date: Fri, 23 Apr 2021 20:54:06 -0700 Subject: [PATCH 009/161] Fix Spelling Error 'passses' line 20 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d6af2aeba..c539f9f4d8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commo This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. -**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. +**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: From 0ccdfeea57f3a983e1735ce1d3c0aca7b7959da2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 14:33:56 +0900 Subject: [PATCH 010/161] Fix code quality issues --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index e4c41bed6d..63203dd57c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,9 +1,12 @@ -using osu.Framework.Graphics; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Rulesets.Catch.Mods { @@ -19,7 +22,8 @@ namespace osu.Game.Rulesets.Catch.Mods { drawableRuleset.Anchor = Anchor.Centre; drawableRuleset.Origin = Anchor.Centre; - drawableRuleset.Scale = new osuTK.Vector2(1, -1); + + drawableRuleset.Scale = new Vector2(1, -1); } } } From 2ae144be8e035801a3131b90c92542f3c075c03f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 14:38:00 +0900 Subject: [PATCH 011/161] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 721e13a759..e0b07549f4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 75a3e45941..9a43d5f031 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 69676a1aed..2c41b3ef26 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 1ec99577ce48557e47ebcb510ac3894be81cd7db Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:05:11 +0800 Subject: [PATCH 012/161] Incorrect path on Android --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index e45893b97a..7026179259 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -25,6 +25,6 @@ Please check: *please attach logs here, which are located at:* - `%AppData%/osu/logs` *(on Windows),* - `~/.local/share/osu/logs` *(on Linux & macOS).* -- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*, +- `Android/data/sh.ppy.osulazer/files/logs` *(on Android)*, - on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer) --> From 2e2f843e223ead183607062f4fbb43aa9b994a3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 09:26:49 +0300 Subject: [PATCH 013/161] Refine android game logs path in contributing guidelines --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c327f01b3..e14be20642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in * the in-game logs, which are located at: * `%AppData%/osu/logs` (on Windows), * `~/.local/share/osu/logs` (on Linux and macOS), - * `Android/Data/sh.ppy.osulazer/logs` (on Android), + * `Android/data/sh.ppy.osulazer/files/logs` (on Android), * on iOS they can be obtained by connecting your device to your desktop and [copying the `logs` directory from the app's own document storage using iTunes](https://support.apple.com/en-us/HT201301#copy-to-computer), * your system specifications (including the operating system and platform you are playing on), * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug), From 7e3a611f95cf9a1ec5503d692bc919c6dc6fb71c Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 16:23:52 +0800 Subject: [PATCH 014/161] Add snap color option for osu!mania --- .../ManiaPlacementBlueprintTestScene.cs | 12 +++ .../ManiaSelectionBlueprintTestScene.cs | 12 +++ .../TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../Editor/TestSceneManiaHitObjectComposer.cs | 7 +- .../Editor/TestSceneNotePlacementBlueprint.cs | 2 +- .../Editor/TestSceneNoteSelectionBlueprint.cs | 2 +- .../ManiaInputTestScene.cs | 13 +++ .../Mods/TestSceneManiaModPerfect.cs | 4 +- .../Skinning/ManiaSkinnableTestScene.cs | 12 +++ .../Skinning/TestSceneHoldNote.cs | 2 +- .../Skinning/TestSceneNote.cs | 2 +- .../TestSceneAutoGeneration.cs | 24 +++--- .../TestSceneColumn.cs | 4 +- .../TestSceneHoldNoteInput.cs | 75 +++++++++--------- .../TestSceneNotes.cs | 17 +++- .../TestSceneOutOfOrderHits.cs | 10 +-- .../TestSceneStage.cs | 4 +- .../Beatmaps/ManiaBeatmapConverter.cs | 4 +- .../Legacy/DistanceObjectPatternGenerator.cs | 4 +- .../Legacy/EndTimeObjectPatternGenerator.cs | 4 +- .../Legacy/HitObjectPatternGenerator.cs | 2 +- .../ManiaRulesetConfigManager.cs | 4 +- .../Blueprints/HoldNotePlacementBlueprint.cs | 5 +- .../Edit/Blueprints/NotePlacementBlueprint.cs | 5 +- .../Edit/HoldNoteCompositionTool.cs | 7 +- .../Edit/ManiaHitObjectComposer.cs | 4 +- .../Edit/NoteCompositionTool.cs | 8 +- .../ManiaSettingsSubsection.cs | 5 ++ .../Mods/ManiaModInvert.cs | 2 +- .../Objects/Drawables/DrawableNote.cs | 30 +++++++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 11 ++- osu.Game.Rulesets.Mania/Objects/Note.cs | 79 +++++++++++++++++++ osu.Game.Rulesets.Mania/Objects/TailNote.cs | 5 ++ .../UI/DrawableManiaRuleset.cs | 5 ++ osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs | 11 +++ .../TestSceneManageCollectionsDialog.cs | 12 +-- 37 files changed, 314 insertions(+), 99 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index ece523e84c..8e94d6bbb6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -28,6 +30,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -41,6 +46,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 176fbba921..453f8e36e6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -15,6 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -26,6 +31,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index 87c74a12cf..eb36e19048 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(null); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 24f4c6858e..9674985d09 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneHoldNoteSelectionBlueprint() { - var holdNote = new HoldNote { Column = 0, Duration = 1000 }; + var holdNote = new HoldNote(null) { Column = 0, Duration = 1000 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index aaf96c63a6..1f3f4842c7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("setup beatmap", () => { composer.EditorBeatmap.Clear(); - composer.EditorBeatmap.Add(new HoldNote + composer.EditorBeatmap.Add(new HoldNote(Beatmap.Value.Beatmap) { Column = 1, EndTime = 200 @@ -201,9 +201,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestComposer() { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 }); InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) + EditorBeatmap = new EditorBeatmap(beatmap) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } }, @@ -211,7 +212,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }; for (int i = 0; i < 10; i++) - EditorBeatmap.Add(new Note { StartTime = 125 * i }); + EditorBeatmap.Add(new Note(beatmap) { StartTime = 125 * i }); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 36c34a8fb9..08a22ea28e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -55,6 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(null); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 0e47a12a8e..caf89599be 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneNoteSelectionBlueprint() { - var note = new Note { Column = 0 }; + var note = new Note(null) { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); DrawableNote drawableObject; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 9049bb3a82..ea7432f3a7 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,15 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { public abstract class ManiaInputTestScene : OsuTestScene { + [Cached] + protected readonly Bindable configColourCode = new Bindable(); private readonly Container content; protected override Container Content => content ?? base.Content; @@ -18,6 +24,13 @@ namespace osu.Game.Rulesets.Mania.Tests base.Content.Add(content = new LocalInputManager(keys)); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 2e3b21aed7..23d0e82439 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] - public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note { StartTime = 1000 }), shouldMiss); + public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note(Beatmap.Value.Beatmap) { StartTime = 1000 }), shouldMiss); [TestCase(false)] [TestCase(true)] - public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 3000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 1d84a2dfcb..3b19386da6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,6 +7,8 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -38,6 +43,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index e88ff8e2ac..e13d0bbd7f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning protected override DrawableManiaHitObject CreateHitObject() { - var note = new HoldNote { Duration = 1000 }; + var note = new HoldNote(Beatmap.Value.Beatmap) { Duration = 1000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableHoldNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs index bc3bdf0bcb..eac288872a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { protected override DrawableManiaHitObject CreateHitObject() { - var note = new Note(); + var note = new Note(Beatmap.Value.Beatmap); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index cffec3dfd5..b2b9d607c6 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -141,8 +141,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 2000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -168,8 +168,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 3000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index d9b1ad22fa..59da73a540 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableNote(obj)); @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 668487f673..c92fe03483 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -249,21 +249,6 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { - HitObjects = - { - new HoldNote - { - StartTime = 1000, - Duration = 500, - Column = 0, - }, - new HoldNote - { - StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, - Duration = 500, - Column = 0, - }, - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty @@ -274,6 +259,20 @@ namespace osu.Game.Rulesets.Mania.Tests Ruleset = new ManiaRuleset().RulesetInfo }, }; + beatmap.HitObjects = new List { + new HoldNote(beatmap) + { + StartTime = 1000, + Duration = 500, + Column = 0, + }, + new HoldNote(beatmap) + { + StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, + Duration = 500, + Column = 0, + }, + }; performTest(new List { @@ -297,21 +296,21 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { - HitObjects = - { - new HoldNote - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, Ruleset = new ManiaRuleset().RulesetInfo }, }; + beatmap.HitObjects = new List + { + new HoldNote(beatmap) + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }; performTest(new List { @@ -329,17 +328,17 @@ namespace osu.Game.Rulesets.Mania.Tests { var beatmap = new Beatmap { - HitObjects = + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + }; + beatmap.HitObjects = new List { - new HoldNote + new HoldNote(beatmap) { StartTime = 1000, Duration = 0, Column = 0, }, - }, - BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, - }; + }; performTest(new List { @@ -374,21 +373,21 @@ namespace osu.Game.Rulesets.Mania.Tests { beatmap = new Beatmap { - HitObjects = - { - new HoldNote - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, Ruleset = new ManiaRuleset().RulesetInfo }, }; + beatmap.HitObjects = new List + { + new HoldNote(beatmap) + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }; beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 706268e478..3660895347 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,8 +15,10 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; @@ -29,6 +31,10 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { + + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + [Test] public void TestVariousNotes() { @@ -63,9 +69,16 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("hold note 2 facing upwards", () => verifyAnchors(holdNote2, Anchor.y0)); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject) { - var note = new Note { StartTime = 0 }; + var note = new Note(null) { StartTime = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) @@ -80,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject) { - var note = new HoldNote { StartTime = 0, Duration = 5000 }; + var note = new HoldNote(null) { StartTime = 0, Duration = 5000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index 18891f8c58..65195fdd3f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests { double time = 1000 + i * 100; - objects.Add(new Note { StartTime = time }); + objects.Add(new Note(Beatmap.Value.Beatmap) { StartTime = time }); // don't hit the first note if (i > 0) @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 1010, }, - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1020, EndTime = 1030 @@ -83,12 +83,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 1010, }, - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1020, EndTime = 1030 diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 7376a90f17..777163cca7 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableNote(obj)); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 26393c8edb..5f03c93d55 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (HitObject is IHasDuration endTimeData) { - pattern.Add(new HoldNote + pattern.Add(new HoldNote(Beatmap) { StartTime = HitObject.StartTime, Duration = endTimeData.Duration, @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else if (HitObject is IHasXPosition) { - pattern.Add(new Note + pattern.Add(new Note(Beatmap) { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 26e5d381e2..a936878a38 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -512,7 +512,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (startTime == endTime) { - newObject = new Note + newObject = new Note(Beatmap) { StartTime = startTime, Samples = sampleInfoListAt(startTime), @@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new HoldNote + newObject = new HoldNote(Beatmap) { StartTime = startTime, Duration = endTime - startTime, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index f816a70ab3..9422146803 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (holdNote) { - newObject = new HoldNote + newObject = new HoldNote(Beatmap) { StartTime = HitObject.StartTime, Duration = endTime - HitObject.StartTime, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new Note + newObject = new Note(Beatmap) { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 54c37e9742..bbb4d4b466 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -441,7 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column to add the note to. private void addToPattern(Pattern pattern, int column) { - pattern.Add(new Note + pattern.Add(new Note(Beatmap) { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 39d0f4bae4..292d494d88 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); + SetDefault(ManiaRulesetSetting.ColourCode, ManiaColourCode.Off); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings @@ -34,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Configuration public enum ManiaRulesetSetting { ScrollTime, - ScrollDirection + ScrollDirection, + ColourCode } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 093a8da24f..b3902524a0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -23,8 +24,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - public HoldNotePlacementBlueprint() - : base(new HoldNote()) + public HoldNotePlacementBlueprint(IBeatmap beatmap) + : base(new HoldNote(beatmap)) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 3db89c8ae6..cc0ef5181a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -14,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { private readonly EditNotePiece piece; - public NotePlacementBlueprint() - : base(new Note()) + public NotePlacementBlueprint(IBeatmap beatmap) + : base(new Note(beatmap)) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index a5f10ed436..696278de4d 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -5,19 +5,22 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; namespace osu.Game.Rulesets.Mania.Edit { public class HoldNoteCompositionTool : HitObjectCompositionTool { - public HoldNoteCompositionTool() + private ManiaBeatmap Beatmap; + public HoldNoteCompositionTool(ManiaBeatmap beatmap) : base("Hold") { + Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(Beatmap); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d9570bf8be..0defeb026d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new NoteCompositionTool(), - new HoldNoteCompositionTool() + new NoteCompositionTool(drawableRuleset.Beatmap), + new HoldNoteCompositionTool(drawableRuleset.Beatmap) }; protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 9f54152596..c77c3722c6 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; @@ -12,13 +13,16 @@ namespace osu.Game.Rulesets.Mania.Edit { public class NoteCompositionTool : HitObjectCompositionTool { - public NoteCompositionTool() + private ManiaBeatmap Beatmap; + + public NoteCompositionTool(ManiaBeatmap beatmap) : base(nameof(Note)) { + Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(Beatmap); } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index de77af8306..d46076d3bc 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Mania Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), KeyboardStep = 5 }, + new SettingsEnumDropdown + { + LabelText = "Colour-coded notes", + Current = config.GetBindable(ManiaRulesetSetting.ColourCode), + } }; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..11310c6642 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes. duration = Math.Max(duration / 2, duration - beatLength / 4); - newColumnObjects.Add(new HoldNote + newColumnObjects.Add(new HoldNote(maniaBeatmap) { Column = column.Key, StartTime = locations[i].startTime, diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index b512986ccb..436e0923c4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -17,6 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private Bindable configColourCode { get; set; } + protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; private readonly Drawable headPiece; @@ -34,6 +44,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + HitObject.SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); + configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, HitObject.Snap)); + } + + private void UpdateSnapColour(ManiaColourCode colourCode, int snap) + { + if (colourCode == ManiaColourCode.On) + { + Colour = BindableBeatDivisor.GetColourFor(HitObject.Snap, colours); + } + else + { + Colour = Colour4.White; + } + } + protected override void OnDirectionChanged(ValueChangedEvent e) { base.OnDirectionChanged(e); diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 6cc7ff92d3..bf0631170a 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNote : ManiaHitObject, IHasDuration { + public IBeatmap Beatmap; + public double EndTime { get => StartTime + Duration; @@ -84,6 +86,11 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; + public HoldNote(IBeatmap beatmap) : base() + { + Beatmap = beatmap; + } + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -98,14 +105,14 @@ namespace osu.Game.Rulesets.Mania.Objects createTicks(cancellationToken); - AddNested(Head = new Note + AddNested(Head = new Note(Beatmap) { StartTime = StartTime, Column = Column, Samples = GetNodeSamples(0), }); - AddNested(Tail = new TailNote + AddNested(Tail = new TailNote(Beatmap) { StartTime = EndTime, Column = Column, diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 0035960c63..6ee0232c5b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,6 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Bindables; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -12,5 +17,79 @@ namespace osu.Game.Rulesets.Mania.Objects public class Note : ManiaHitObject { public override Judgement CreateJudgement() => new ManiaJudgement(); + + private IBeatmap Beatmap; + + public readonly Bindable SnapBindable = new Bindable(); + + public int Snap + { + get => SnapBindable.Value; + set => SnapBindable.Value = value; + } + + public Note(IBeatmap beatmap) : base() + { + Beatmap = beatmap; + this.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); + } + + private void SnapToBeatmap() + { + if (Beatmap != null) + { + TimingControlPoint currentTimingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); + int timeSignature = (int)currentTimingPoint.TimeSignature; + double startTime = currentTimingPoint.Time; + double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + + double offset = startTime % secondsPerFourCounts; + double snapResult = StartTime % secondsPerFourCounts - offset; + + if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + { + Snap = 1; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + { + Snap = 2; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + { + Snap = 3; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + { + Snap = 4; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + { + Snap = 6; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + { + Snap = 8; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + { + Snap = 12; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + { + Snap = 16; + } + else + { + Snap = 0; + } + } + } + + private const double LENIENCY_MS = 1.0; + private static bool AlmostDivisibleBy(double dividend, double divisor) + { + double remainder = Math.Abs(dividend) % divisor; + return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index 5a30fd6a12..bc56408691 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.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 osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -8,6 +9,10 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { + public TailNote(IBeatmap beatmap) : base(beatmap) + { + } + public override Judgement CreateJudgement() => new ManiaJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 4ee060e91e..dc9c525cb0 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + public ScrollVisualisationMethod ScrollMethod { get => scrollMethod; @@ -104,6 +107,8 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); + + Config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); } protected override void AdjustScrollSpeed(int amount) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs b/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs new file mode 100644 index 0000000000..91b637b71d --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mania.UI +{ + public enum ManiaColourCode + { + Off, + On + } +} diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index eca857f9e5..d8d419bceb 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -140,12 +140,12 @@ namespace osu.Game.Tests.Visual.Collections AddStep("add dropdown", () => { Add(new CollectionFilterDropdown - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - Width = 0.4f, - } + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } ); }); AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] From f9905ebe68352f19f6fafa981369da2a4a4faeba Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 19:30:16 +0800 Subject: [PATCH 015/161] Remove beatmap argument in Note --- .../ManiaPlacementBlueprintTestScene.cs | 12 --- .../ManiaSelectionBlueprintTestScene.cs | 12 --- .../TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../Editor/TestSceneManiaHitObjectComposer.cs | 7 +- .../Editor/TestSceneNotePlacementBlueprint.cs | 2 +- .../Editor/TestSceneNoteSelectionBlueprint.cs | 2 +- .../ManiaInputTestScene.cs | 13 ---- .../Mods/TestSceneManiaModPerfect.cs | 4 +- .../Skinning/ManiaSkinnableTestScene.cs | 12 --- .../Skinning/TestSceneHoldNote.cs | 2 +- .../Skinning/TestSceneNote.cs | 2 +- .../TestSceneAutoGeneration.cs | 24 +++--- .../TestSceneColumn.cs | 4 +- .../TestSceneHoldNoteInput.cs | 75 ++++++++++--------- .../TestSceneNotes.cs | 17 +---- .../TestSceneOutOfOrderHits.cs | 10 +-- .../TestSceneStage.cs | 4 +- .../Beatmaps/ManiaBeatmapConverter.cs | 4 +- .../Legacy/DistanceObjectPatternGenerator.cs | 4 +- .../Legacy/EndTimeObjectPatternGenerator.cs | 4 +- .../Legacy/HitObjectPatternGenerator.cs | 2 +- .../Blueprints/HoldNotePlacementBlueprint.cs | 5 +- .../Edit/Blueprints/NotePlacementBlueprint.cs | 5 +- .../Edit/HoldNoteCompositionTool.cs | 7 +- .../Edit/ManiaHitObjectComposer.cs | 4 +- .../Edit/NoteCompositionTool.cs | 8 +- .../Mods/ManiaModInvert.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 11 +-- osu.Game.Rulesets.Mania/Objects/Note.cs | 3 +- osu.Game.Rulesets.Mania/Objects/TailNote.cs | 5 -- .../TestSceneManageCollectionsDialog.cs | 12 +-- 32 files changed, 99 insertions(+), 183 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 8e94d6bbb6..ece523e84c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -30,9 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -46,13 +41,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 453f8e36e6..176fbba921 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,10 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -17,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -31,13 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index eb36e19048..87c74a12cf 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(null); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 9674985d09..24f4c6858e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneHoldNoteSelectionBlueprint() { - var holdNote = new HoldNote(null) { Column = 0, Duration = 1000 }; + var holdNote = new HoldNote { Column = 0, Duration = 1000 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 1f3f4842c7..aaf96c63a6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("setup beatmap", () => { composer.EditorBeatmap.Clear(); - composer.EditorBeatmap.Add(new HoldNote(Beatmap.Value.Beatmap) + composer.EditorBeatmap.Add(new HoldNote { Column = 1, EndTime = 200 @@ -201,10 +201,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestComposer() { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 }); InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(beatmap) + EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } }, @@ -212,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }; for (int i = 0; i < 10; i++) - EditorBeatmap.Add(new Note(beatmap) { StartTime = 125 * i }); + EditorBeatmap.Add(new Note { StartTime = 125 * i }); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 08a22ea28e..36c34a8fb9 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -55,6 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(null); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index caf89599be..0e47a12a8e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneNoteSelectionBlueprint() { - var note = new Note(null) { Column = 0 }; + var note = new Note { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); DrawableNote drawableObject; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index ea7432f3a7..9049bb3a82 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,21 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { public abstract class ManiaInputTestScene : OsuTestScene { - [Cached] - protected readonly Bindable configColourCode = new Bindable(); private readonly Container content; protected override Container Content => content ?? base.Content; @@ -24,13 +18,6 @@ namespace osu.Game.Rulesets.Mania.Tests base.Content.Add(content = new LocalInputManager(keys)); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 23d0e82439..2e3b21aed7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] - public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note(Beatmap.Value.Beatmap) { StartTime = 1000 }), shouldMiss); + public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note { StartTime = 1000 }), shouldMiss); [TestCase(false)] [TestCase(true)] - public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 3000 }), shouldMiss); + public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 3b19386da6..1d84a2dfcb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,8 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -26,9 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -43,13 +38,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index e13d0bbd7f..e88ff8e2ac 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning protected override DrawableManiaHitObject CreateHitObject() { - var note = new HoldNote(Beatmap.Value.Beatmap) { Duration = 1000 }; + var note = new HoldNote { Duration = 1000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableHoldNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs index eac288872a..bc3bdf0bcb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { protected override DrawableManiaHitObject CreateHitObject() { - var note = new Note(Beatmap.Value.Beatmap); + var note = new Note(); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index b2b9d607c6..cffec3dfd5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000, Column = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 2000, Column = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -141,8 +141,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 2000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -168,8 +168,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 3000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 59da73a540..d9b1ad22fa 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableNote(obj)); @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index c92fe03483..668487f673 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -249,6 +249,21 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { + HitObjects = + { + new HoldNote + { + StartTime = 1000, + Duration = 500, + Column = 0, + }, + new HoldNote + { + StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, + Duration = 500, + Column = 0, + }, + }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty @@ -259,20 +274,6 @@ namespace osu.Game.Rulesets.Mania.Tests Ruleset = new ManiaRuleset().RulesetInfo }, }; - beatmap.HitObjects = new List { - new HoldNote(beatmap) - { - StartTime = 1000, - Duration = 500, - Column = 0, - }, - new HoldNote(beatmap) - { - StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, - Duration = 500, - Column = 0, - }, - }; performTest(new List { @@ -296,21 +297,21 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, Ruleset = new ManiaRuleset().RulesetInfo }, }; - beatmap.HitObjects = new List - { - new HoldNote(beatmap) - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }; performTest(new List { @@ -328,17 +329,17 @@ namespace osu.Game.Rulesets.Mania.Tests { var beatmap = new Beatmap { - BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, - }; - beatmap.HitObjects = new List + HitObjects = { - new HoldNote(beatmap) + new HoldNote { StartTime = 1000, Duration = 0, Column = 0, }, - }; + }, + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + }; performTest(new List { @@ -373,21 +374,21 @@ namespace osu.Game.Rulesets.Mania.Tests { beatmap = new Beatmap { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, Ruleset = new ManiaRuleset().RulesetInfo }, }; - beatmap.HitObjects = new List - { - new HoldNote(beatmap) - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }; beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 3660895347..706268e478 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,10 +15,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; @@ -31,10 +29,6 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - [Test] public void TestVariousNotes() { @@ -69,16 +63,9 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("hold note 2 facing upwards", () => verifyAnchors(holdNote2, Anchor.y0)); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject) { - var note = new Note(null) { StartTime = 0 }; + var note = new Note { StartTime = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) @@ -93,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject) { - var note = new HoldNote(null) { StartTime = 0, Duration = 5000 }; + var note = new HoldNote { StartTime = 0, Duration = 5000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index 65195fdd3f..18891f8c58 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests { double time = 1000 + i * 100; - objects.Add(new Note(Beatmap.Value.Beatmap) { StartTime = time }); + objects.Add(new Note { StartTime = time }); // don't hit the first note if (i > 0) @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1000, EndTime = 1010, }, - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1020, EndTime = 1030 @@ -83,12 +83,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1000, EndTime = 1010, }, - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1020, EndTime = 1030 diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 777163cca7..7376a90f17 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableNote(obj)); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 5f03c93d55..26393c8edb 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (HitObject is IHasDuration endTimeData) { - pattern.Add(new HoldNote(Beatmap) + pattern.Add(new HoldNote { StartTime = HitObject.StartTime, Duration = endTimeData.Duration, @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else if (HitObject is IHasXPosition) { - pattern.Add(new Note(Beatmap) + pattern.Add(new Note { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index a936878a38..26e5d381e2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -512,7 +512,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (startTime == endTime) { - newObject = new Note(Beatmap) + newObject = new Note { StartTime = startTime, Samples = sampleInfoListAt(startTime), @@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new HoldNote(Beatmap) + newObject = new HoldNote { StartTime = startTime, Duration = endTime - startTime, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 9422146803..f816a70ab3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (holdNote) { - newObject = new HoldNote(Beatmap) + newObject = new HoldNote { StartTime = HitObject.StartTime, Duration = endTime - HitObject.StartTime, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new Note(Beatmap) + newObject = new Note { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index bbb4d4b466..54c37e9742 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -441,7 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column to add the note to. private void addToPattern(Pattern pattern, int column) { - pattern.Add(new Note(Beatmap) + pattern.Add(new Note { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index b3902524a0..093a8da24f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - public HoldNotePlacementBlueprint(IBeatmap beatmap) - : base(new HoldNote(beatmap)) + public HoldNotePlacementBlueprint() + : base(new HoldNote()) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index cc0ef5181a..3db89c8ae6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -15,8 +14,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { private readonly EditNotePiece piece; - public NotePlacementBlueprint(IBeatmap beatmap) - : base(new Note(beatmap)) + public NotePlacementBlueprint() + : base(new Note()) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 696278de4d..a5f10ed436 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -5,22 +5,19 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; namespace osu.Game.Rulesets.Mania.Edit { public class HoldNoteCompositionTool : HitObjectCompositionTool { - private ManiaBeatmap Beatmap; - public HoldNoteCompositionTool(ManiaBeatmap beatmap) + public HoldNoteCompositionTool() : base("Hold") { - Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(Beatmap); + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 0defeb026d..d9570bf8be 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new NoteCompositionTool(drawableRuleset.Beatmap), - new HoldNoteCompositionTool(drawableRuleset.Beatmap) + new NoteCompositionTool(), + new HoldNoteCompositionTool() }; protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index c77c3722c6..9f54152596 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; @@ -13,16 +12,13 @@ namespace osu.Game.Rulesets.Mania.Edit { public class NoteCompositionTool : HitObjectCompositionTool { - private ManiaBeatmap Beatmap; - - public NoteCompositionTool(ManiaBeatmap beatmap) + public NoteCompositionTool() : base(nameof(Note)) { - Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(Beatmap); + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 11310c6642..1ea45c295c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes. duration = Math.Max(duration / 2, duration - beatLength / 4); - newColumnObjects.Add(new HoldNote(maniaBeatmap) + newColumnObjects.Add(new HoldNote { Column = column.Key, StartTime = locations[i].startTime, diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index bf0631170a..6cc7ff92d3 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNote : ManiaHitObject, IHasDuration { - public IBeatmap Beatmap; - public double EndTime { get => StartTime + Duration; @@ -86,11 +84,6 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; - public HoldNote(IBeatmap beatmap) : base() - { - Beatmap = beatmap; - } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -105,14 +98,14 @@ namespace osu.Game.Rulesets.Mania.Objects createTicks(cancellationToken); - AddNested(Head = new Note(Beatmap) + AddNested(Head = new Note { StartTime = StartTime, Column = Column, Samples = GetNodeSamples(0), }); - AddNested(Tail = new TailNote(Beatmap) + AddNested(Tail = new TailNote { StartTime = EndTime, Column = Column, diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 6ee0232c5b..36f32c78af 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -28,9 +28,8 @@ namespace osu.Game.Rulesets.Mania.Objects set => SnapBindable.Value = value; } - public Note(IBeatmap beatmap) : base() + public Note() { - Beatmap = beatmap; this.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); } diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index bc56408691..5a30fd6a12 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -9,10 +8,6 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { - public TailNote(IBeatmap beatmap) : base(beatmap) - { - } - public override Judgement CreateJudgement() => new ManiaJudgement(); } } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index d8d419bceb..eca857f9e5 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -140,12 +140,12 @@ namespace osu.Game.Tests.Visual.Collections AddStep("add dropdown", () => { Add(new CollectionFilterDropdown - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - Width = 0.4f, - } + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } ); }); AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] From d6d81fb8e5a55db4f58aa4cc41d729f3e0ec3f27 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 19:53:21 +0800 Subject: [PATCH 016/161] Move color snap logic from Note to DrawableNote --- .../Objects/Drawables/DrawableNote.cs | 102 +++++++++++++++--- osu.Game.Rulesets.Mania/Objects/Note.cs | 78 -------------- .../UI/DrawableManiaRuleset.cs | 5 + 3 files changed, 93 insertions(+), 92 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 436e0923c4..3b2a9feee2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; @@ -27,10 +31,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private Bindable configColourCode { get; set; } + [Resolved(canBeNull: true)] + private ManiaBeatmap beatmap { get; set; } + protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; private readonly Drawable headPiece; + public readonly Bindable SnapBindable = new Bindable(); + + public int Snap + { + get => SnapBindable.Value; + set => SnapBindable.Value = value; + } + public DrawableNote(Note hitObject) : base(hitObject) { @@ -48,20 +63,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.LoadComplete(); - HitObject.SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); - configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, HitObject.Snap)); - } + HitObject.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); - private void UpdateSnapColour(ManiaColourCode colourCode, int snap) - { - if (colourCode == ManiaColourCode.On) - { - Colour = BindableBeatDivisor.GetColourFor(HitObject.Snap, colours); - } - else - { - Colour = Colour4.White; - } + SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); + configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -103,5 +108,74 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { } + private void SnapToBeatmap() + { + if (beatmap != null) + { + TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + int timeSignature = (int)currentTimingPoint.TimeSignature; + double startTime = currentTimingPoint.Time; + double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + + double offset = startTime % secondsPerFourCounts; + double snapResult = HitObject.StartTime % secondsPerFourCounts - offset; + + if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + { + Snap = 1; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + { + Snap = 2; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + { + Snap = 3; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + { + Snap = 4; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + { + Snap = 6; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + { + Snap = 8; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + { + Snap = 12; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + { + Snap = 16; + } + else + { + Snap = 0; + } + } + } + + private const double LENIENCY_MS = 1.0; + private static bool AlmostDivisibleBy(double dividend, double divisor) + { + double remainder = Math.Abs(dividend) % divisor; + return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + } + + private void UpdateSnapColour(ManiaColourCode colourCode, int snap) + { + if (colourCode == ManiaColourCode.On) + { + Colour = BindableBeatDivisor.GetColourFor(Snap, colours); + } + else + { + Colour = Colour4.White; + } + } } -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 36f32c78af..0035960c63 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,11 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Bindables; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -17,78 +12,5 @@ namespace osu.Game.Rulesets.Mania.Objects public class Note : ManiaHitObject { public override Judgement CreateJudgement() => new ManiaJudgement(); - - private IBeatmap Beatmap; - - public readonly Bindable SnapBindable = new Bindable(); - - public int Snap - { - get => SnapBindable.Value; - set => SnapBindable.Value = value; - } - - public Note() - { - this.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); - } - - private void SnapToBeatmap() - { - if (Beatmap != null) - { - TimingControlPoint currentTimingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); - int timeSignature = (int)currentTimingPoint.TimeSignature; - double startTime = currentTimingPoint.Time; - double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; - - double offset = startTime % secondsPerFourCounts; - double snapResult = StartTime % secondsPerFourCounts - offset; - - if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) - { - Snap = 1; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) - { - Snap = 2; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) - { - Snap = 3; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) - { - Snap = 4; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) - { - Snap = 6; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) - { - Snap = 8; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) - { - Snap = 12; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) - { - Snap = 16; - } - else - { - Snap = 0; - } - } - } - - private const double LENIENCY_MS = 1.0; - private static bool AlmostDivisibleBy(double dividend, double divisor) - { - double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); - } } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index dc9c525cb0..850d4800cd 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; + [Cached(typeof(ManiaBeatmap))] + private ManiaBeatmap CachedBeatmap { get; } + public IEnumerable BarLines; protected override bool RelativeScaleBeatLengths => true; @@ -80,6 +83,8 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; + + CachedBeatmap = Beatmap; } [BackgroundDependencyLoader] From e937b778f6a5ccb4840e9561467c81edfbfb4028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Apr 2021 14:19:39 +0200 Subject: [PATCH 017/161] Fix potential failure in `ensureSourceClockSet()` `ensureSourceClockSet()` was intended to only run when the adjustable source hasn't been set at all yet. As it turns out permitting it to run unconditionally can break the state of the underlying interpolated clock. This is caused by the following factors: * While the decoupleable clock is running, its `CurrentTime` does not come from either the source clock, or the internal stopwatch; it is instead calculated using the base `InterpolatingFramedClock` logic. * A source change of a decoupleable clock seeks the provided source clock to the decoupleable's current time. * When an interpolating clock is seeked (decoupleable clock is also an interpolating one), its interpolation state (`{Last,Current}InterpolatedTime`) are reset to 0. * If the interpolating clock determines that its current time is too far away from the source's time (which was set when the source is changed), it will ignore the source and instead continue to use its current time until the source clock has caught up. Overall, the source change is not really necessary if a source is already there. The only reason to ensure it was set was to make sure the first seek of the gameplay clock wasn't performed in decoupled mode. Therefore, add a guard to make sure the source is only set if there isn't one already. --- osu.Game/Screens/Play/GameplayClockContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6d60c09521..1c8a3e51ac 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -116,13 +116,17 @@ namespace osu.Game.Screens.Play protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); /// - /// Ensures that the is set to . + /// Ensures that the is set to , if it hasn't been given a source yet. /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, /// but not the actual source clock. /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, /// but it is not yet set on the adjustable source there. /// - private void ensureSourceClockSet() => ChangeSource(SourceClock); + private void ensureSourceClockSet() + { + if (AdjustableSource.Source == null) + ChangeSource(SourceClock); + } protected override void Update() { From a8b401522bf561d5a2d49567ec99a2048e56586a Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:39:22 +0800 Subject: [PATCH 018/161] Remove ManiaColourCode in favor for boolean --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 4 ++-- .../Objects/Drawables/DrawableNote.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs | 11 ----------- 5 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 292d494d88..f37937a736 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); - SetDefault(ManiaRulesetSetting.ColourCode, ManiaColourCode.Off); + SetDefault(ManiaRulesetSetting.ColourCode, false); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index d46076d3bc..c0c587f67c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -37,10 +37,10 @@ namespace osu.Game.Rulesets.Mania Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), KeyboardStep = 5 }, - new SettingsEnumDropdown + new SettingsCheckbox { LabelText = "Colour-coded notes", - Current = config.GetBindable(ManiaRulesetSetting.ColourCode), + Current = config.GetBindable(ManiaRulesetSetting.ColourCode), } }; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 3b2a9feee2..7cda70f2af 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private OsuColour colours { get; set; } [Resolved] - private Bindable configColourCode { get; set; } + private Bindable configColourCode { get; set; } [Resolved(canBeNull: true)] private ManiaBeatmap beatmap { get; set; } @@ -166,9 +166,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); } - private void UpdateSnapColour(ManiaColourCode colourCode, int snap) + private void UpdateSnapColour(bool colourCode, int snap) { - if (colourCode == ManiaColourCode.On) + if (colourCode) { Colour = BindableBeatDivisor.GetColourFor(Snap, colours); } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 850d4800cd..94830adc33 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; [Cached] - protected readonly Bindable configColourCode = new Bindable(); + protected readonly Bindable configColourCode = new Bindable(); public ScrollVisualisationMethod ScrollMethod { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs b/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs deleted file mode 100644 index 91b637b71d..0000000000 --- a/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Mania.UI -{ - public enum ManiaColourCode - { - Off, - On - } -} From 91bf0d422d88fe519834cbe01771a20cc5a379d8 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:40:30 +0800 Subject: [PATCH 019/161] Rename ColourCode to ColourCodedNotes --- .../Configuration/ManiaRulesetConfigManager.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index f37937a736..87a4689689 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); - SetDefault(ManiaRulesetSetting.ColourCode, false); + SetDefault(ManiaRulesetSetting.ColourCodedNotes, false); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings @@ -36,6 +36,6 @@ namespace osu.Game.Rulesets.Mania.Configuration { ScrollTime, ScrollDirection, - ColourCode + ColourCodedNotes } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index c0c587f67c..552c793096 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania new SettingsCheckbox { LabelText = "Colour-coded notes", - Current = config.GetBindable(ManiaRulesetSetting.ColourCode), + Current = config.GetBindable(ManiaRulesetSetting.ColourCodedNotes), } }; } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 94830adc33..60e9613b71 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - Config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCode); } protected override void AdjustScrollSpeed(int amount) From 3103fd8343efa3d811605ffae382da0734097dd2 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:54:05 +0800 Subject: [PATCH 020/161] Move snapping logic into SnapFinder --- .../Objects/Drawables/DrawableNote.cs | 68 +---------------- .../UI/DrawableManiaRuleset.cs | 7 +- osu.Game.Rulesets.Mania/Utils/SnapFinder.cs | 75 +++++++++++++++++++ 3 files changed, 83 insertions(+), 67 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Utils/SnapFinder.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 7cda70f2af..998d9e625e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -7,12 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; -using osu.Framework.Utils; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mania.Utils; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; @@ -31,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private Bindable configColourCode { get; set; } - [Resolved(canBeNull: true)] - private ManiaBeatmap beatmap { get; set; } + [Resolved] + private SnapFinder snapFinder { get; set; } protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -63,7 +60,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.LoadComplete(); - HitObject.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); + HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); @@ -108,63 +105,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { } - private void SnapToBeatmap() - { - if (beatmap != null) - { - TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); - int timeSignature = (int)currentTimingPoint.TimeSignature; - double startTime = currentTimingPoint.Time; - double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; - - double offset = startTime % secondsPerFourCounts; - double snapResult = HitObject.StartTime % secondsPerFourCounts - offset; - - if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) - { - Snap = 1; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) - { - Snap = 2; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) - { - Snap = 3; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) - { - Snap = 4; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) - { - Snap = 6; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) - { - Snap = 8; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) - { - Snap = 12; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) - { - Snap = 16; - } - else - { - Snap = 0; - } - } - } - - private const double LENIENCY_MS = 1.0; - private static bool AlmostDivisibleBy(double dividend, double divisor) - { - double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); - } private void UpdateSnapColour(bool colourCode, int snap) { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 60e9613b71..99fb32f81f 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -45,8 +46,8 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - [Cached(typeof(ManiaBeatmap))] - private ManiaBeatmap CachedBeatmap { get; } + [Cached] + private SnapFinder snapFinder { get; set; } public IEnumerable BarLines; @@ -84,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.UI { BarLines = new BarLineGenerator(Beatmap).BarLines; - CachedBeatmap = Beatmap; + snapFinder = new SnapFinder(Beatmap); } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs new file mode 100644 index 0000000000..01a9339552 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Utils +{ + public class SnapFinder + { + private ManiaBeatmap beatmap; + public SnapFinder(ManiaBeatmap beatmap) + { + this.beatmap = beatmap; + } + + public int FindSnap(HitObject hitObject) + { + TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); + int timeSignature = (int)currentTimingPoint.TimeSignature; + double startTime = currentTimingPoint.Time; + double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + + double offset = startTime % secondsPerFourCounts; + double snapResult = hitObject.StartTime % secondsPerFourCounts - offset; + + if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + { + return 1; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + { + return 2; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + { + return 3; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + { + return 4; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + { + return 6; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + { + return 8; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + { + return 12; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + { + return 16; + } + else + { + return 0; + } + } + + private const double LENIENCY_MS = 1.0; + private static bool AlmostDivisibleBy(double dividend, double divisor) + { + double remainder = Math.Abs(dividend) % divisor; + return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + } + } +} From 8b01082cbb3c257bd69ba6fb95cd173bce79f854 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 21:07:17 +0800 Subject: [PATCH 021/161] Fix visual tests missing dependency for ColourCodedNotes --- .../Editor/ManiaPlacementBlueprintTestScene.cs | 12 ++++++++++++ .../Editor/ManiaSelectionBlueprintTestScene.cs | 12 ++++++++++++ .../ManiaInputTestScene.cs | 13 +++++++++++++ .../Skinning/ManiaSkinnableTestScene.cs | 11 +++++++++++ osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 11 +++++++++++ .../Objects/Drawables/DrawableNote.cs | 13 ++++++++----- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index ece523e84c..8a605a7ca3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -28,6 +30,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -41,6 +46,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 176fbba921..854d0fb14e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -15,6 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -26,6 +31,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 9049bb3a82..f54d90016c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests @@ -13,11 +16,21 @@ namespace osu.Game.Rulesets.Mania.Tests private readonly Container content; protected override Container Content => content ?? base.Content; + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected ManiaInputTestScene(int keys) { base.Content.Add(content = new LocalInputManager(keys)); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 1d84a2dfcb..05f07bad12 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -38,6 +42,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 706268e478..9828db7687 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -29,6 +30,16 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + [Test] public void TestVariousNotes() { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 998d9e625e..cfae01f5f2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private OsuColour colours { get; set; } [Resolved] - private Bindable configColourCode { get; set; } + private Bindable configColourCodedNotes { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private SnapFinder snapFinder { get; set; } protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -60,10 +60,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.LoadComplete(); - HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); + if (snapFinder != null) + { + HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); - SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); - configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); + SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); + configColourCodedNotes.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); + } } protected override void OnDirectionChanged(ValueChangedEvent e) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 99fb32f81f..818d6429e4 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; [Cached] - protected readonly Bindable configColourCode = new Bindable(); + protected readonly Bindable configColourCodedNotes = new Bindable(); public ScrollVisualisationMethod ScrollMethod { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCode); + Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); } protected override void AdjustScrollSpeed(int amount) From bedabc1ddf673aa94d7a38414cb0c710a4f9d4bb Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 22:12:07 +0800 Subject: [PATCH 022/161] Fix cake errors --- .../ManiaPlacementBlueprintTestScene.cs | 4 +-- .../ManiaSelectionBlueprintTestScene.cs | 4 +-- .../ManiaInputTestScene.cs | 4 +-- .../Skinning/ManiaSkinnableTestScene.cs | 4 +-- .../TestSceneNotes.cs | 4 +-- .../Objects/Drawables/DrawableNote.cs | 7 +++-- .../UI/DrawableManiaRuleset.cs | 4 +-- osu.Game.Rulesets.Mania/Utils/SnapFinder.cs | 27 ++++++++++--------- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 8a605a7ca3..c04b875245 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private IScrollingInfo scrollingInfo; [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected ManiaPlacementBlueprintTestScene() { @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 854d0fb14e..1c11dc397e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private readonly IAdjustableClock clock = new StopwatchClock(); [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected ManiaSelectionBlueprintTestScene() { @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } public ManiaPlayfield Playfield => null; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index f54d90016c..2b2db8aa96 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests protected override Container Content => content ?? base.Content; [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected ManiaInputTestScene(int keys) { @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } private class LocalInputManager : ManiaInputManager diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 05f07bad12..941d01a1d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } [Test] diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 9828db7687..1d053688a2 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -31,13 +31,13 @@ namespace osu.Game.Rulesets.Mania.Tests public class TestSceneNotes : OsuTestScene { [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } [Test] diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index cfae01f5f2..04c4b06c88 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -64,8 +63,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); - SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); - configColourCodedNotes.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); + SnapBindable.BindValueChanged(snap => updateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); + configColourCodedNotes.BindValueChanged(colourCode => updateSnapColour(colourCode.NewValue, Snap)); } } @@ -109,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { } - private void UpdateSnapColour(bool colourCode, int snap) + private void updateSnapColour(bool colourCode, int snap) { if (colourCode) { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 818d6429e4..17535c9bc9 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); public ScrollVisualisationMethod ScrollMethod { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } protected override void AdjustScrollSpeed(int amount) diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs index 01a9339552..83df0eb756 100644 --- a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs +++ b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs @@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Mania.Utils { public class SnapFinder { - private ManiaBeatmap beatmap; + private readonly ManiaBeatmap beatmap; + public SnapFinder(ManiaBeatmap beatmap) { this.beatmap = beatmap; @@ -20,42 +21,41 @@ namespace osu.Game.Rulesets.Mania.Utils public int FindSnap(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - int timeSignature = (int)currentTimingPoint.TimeSignature; double startTime = currentTimingPoint.Time; double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; double offset = startTime % secondsPerFourCounts; double snapResult = hitObject.StartTime % secondsPerFourCounts - offset; - if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + if (almostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) { return 1; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) { return 2; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) { return 3; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) { return 4; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) { return 6; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) { return 8; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) { return 12; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) { return 16; } @@ -65,11 +65,12 @@ namespace osu.Game.Rulesets.Mania.Utils } } - private const double LENIENCY_MS = 1.0; - private static bool AlmostDivisibleBy(double dividend, double divisor) + private const double leniency_ms = 1.0; + + private static bool almostDivisibleBy(double dividend, double divisor) { double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + return Precision.AlmostEquals(remainder, 0, leniency_ms) || Precision.AlmostEquals(remainder - divisor, 0, leniency_ms); } } } From 46c44c576d2c27218da69f4493d61ebffd8ab895 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 18 Apr 2021 19:16:20 -0700 Subject: [PATCH 023/161] Fix beatmap info download button content not scaling on mouse down --- .../Buttons/HeaderDownloadButton.cs | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index cffff86a64..6d27342049 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -47,52 +47,44 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { FillFlowContainer textSprites; - AddRangeInternal(new Drawable[] + AddInternal(shakeContainer = new ShakeContainer { - shakeContainer = new ShakeContainer + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Child = button = new HeaderButton { RelativeSizeAxes = Axes.Both }, + }); + + button.AddRange(new Drawable[] + { + new Container { - Depth = -1, + Padding = new MarginPadding { Horizontal = 10 }, RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5, Children = new Drawable[] { - button = new HeaderButton { RelativeSizeAxes = Axes.Both }, - new Container + textSprites = new FillFlowContainer { - // cannot nest inside here due to the structure of button (putting things in its own content). - // requires framework fix. - Padding = new MarginPadding { Horizontal = 10 }, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - textSprites = new FillFlowContainer - { - Depth = -1, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - AutoSizeDuration = 500, - AutoSizeEasing = Easing.OutQuint, - Direction = FillDirection.Vertical, - }, - new SpriteIcon - { - Depth = -1, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Icon = FontAwesome.Solid.Download, - Size = new Vector2(18), - }, - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Direction = FillDirection.Vertical, }, - new DownloadProgressBar(BeatmapSet.Value) + new SpriteIcon { - Depth = -2, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Icon = FontAwesome.Solid.Download, + Size = new Vector2(18), }, - }, + } + }, + new DownloadProgressBar(BeatmapSet.Value) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, }, }); From c9967f7b7486d5744aba911a144110811b76ef04 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 08:37:37 -0700 Subject: [PATCH 024/161] Fix button being recreated on importing state --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index a61640a02e..85ed3f8767 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -268,11 +268,13 @@ namespace osu.Game.Overlays.BeatmapSet break; case DownloadState.Downloading: - case DownloadState.Importing: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); break; + case DownloadState.Importing: + break; + default: downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) From eaac4fe6c7b76bfef30d37bd7f15b6eb8d0cdccc Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 06:38:15 +0800 Subject: [PATCH 025/161] Simplify FindSnap method --- osu.Game.Rulesets.Mania/Utils/SnapFinder.cs | 49 ++++----------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs index 83df0eb756..ed3bd4af05 100644 --- a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs +++ b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs @@ -18,51 +18,20 @@ namespace osu.Game.Rulesets.Mania.Utils this.beatmap = beatmap; } + private readonly static int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; + public int FindSnap(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - double startTime = currentTimingPoint.Time; - double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); - double offset = startTime % secondsPerFourCounts; - double snapResult = hitObject.StartTime % secondsPerFourCounts - offset; + foreach (var snap in snaps) + { + if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / (double)snap)) + return snap; + } - if (almostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) - { - return 1; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) - { - return 2; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) - { - return 3; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) - { - return 4; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) - { - return 6; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) - { - return 8; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) - { - return 12; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) - { - return 16; - } - else - { - return 0; - } + return 0; } private const double leniency_ms = 1.0; From e0ca44c908754b2766ac5dccce2d4d93ae2dacb0 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 07:34:17 +0800 Subject: [PATCH 026/161] Move SnapFinder from mania ruleset to osu.Game --- .../Objects/Drawables/DrawableNote.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 -- .../Utils => osu.Game/Rulesets/Objects}/SnapFinder.cs | 9 ++++----- 3 files changed, 5 insertions(+), 8 deletions(-) rename {osu.Game.Rulesets.Mania/Utils => osu.Game/Rulesets/Objects}/SnapFinder.cs (85%) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 04c4b06c88..2a363606ab 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Mania.Utils; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 17535c9bc9..c1b61aaf31 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mania.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -84,7 +83,6 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - snapFinder = new SnapFinder(Beatmap); } diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game/Rulesets/Objects/SnapFinder.cs similarity index 85% rename from osu.Game.Rulesets.Mania/Utils/SnapFinder.cs rename to osu.Game/Rulesets/Objects/SnapFinder.cs index ed3bd4af05..eb8f4110a2 100644 --- a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/SnapFinder.cs @@ -3,17 +3,16 @@ using System; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Objects; -namespace osu.Game.Rulesets.Mania.Utils +namespace osu.Game.Rulesets.Objects { public class SnapFinder { - private readonly ManiaBeatmap beatmap; + private readonly IBeatmap beatmap; - public SnapFinder(ManiaBeatmap beatmap) + public SnapFinder(IBeatmap beatmap) { this.beatmap = beatmap; } From d3db19c3ce9e0b8992be15684d9ffe3214965246 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 07:44:26 +0800 Subject: [PATCH 027/161] Simplify DrawableNote --- .../Objects/Drawables/DrawableNote.cs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 2a363606ab..c364b395e1 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; @@ -34,13 +35,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Drawable headPiece; - public readonly Bindable SnapBindable = new Bindable(); - - public int Snap - { - get => SnapBindable.Value; - set => SnapBindable.Value = value; - } + private readonly Bindable Snap = new Bindable(); public DrawableNote(Note hitObject) : base(hitObject) @@ -61,10 +56,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (snapFinder != null) { - HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(_ => Snap.Value = snapFinder.FindSnap(HitObject), true); - SnapBindable.BindValueChanged(snap => updateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); - configColourCodedNotes.BindValueChanged(colourCode => updateSnapColour(colourCode.NewValue, Snap)); + Snap.BindValueChanged(_ => updateSnapColour(), true); + configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); } } @@ -106,18 +101,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { - } + } - private void updateSnapColour(bool colourCode, int snap) + private void updateSnapColour() { - if (colourCode) - { - Colour = BindableBeatDivisor.GetColourFor(Snap, colours); - } - else - { - Colour = Colour4.White; - } + Colour = configColourCodedNotes.Value + ? (ColourInfo)BindableBeatDivisor.GetColourFor(Snap.Value, colours) + : (ColourInfo)Colour4.White; } } } \ No newline at end of file From 8b9d2a6cff41601d51f4a184ee08a5869479ffb7 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 08:32:21 +0800 Subject: [PATCH 028/161] Remove caching for ConfigColourCodedNotes --- .../Editor/ManiaPlacementBlueprintTestScene.cs | 12 ------------ .../Editor/ManiaSelectionBlueprintTestScene.cs | 12 ------------ .../ManiaInputTestScene.cs | 13 ------------- .../Skinning/ManiaSkinnableTestScene.cs | 11 ----------- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 11 ----------- .../Objects/Drawables/DrawableNote.cs | 9 +++++++-- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 5 ----- 7 files changed, 7 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index c04b875245..ece523e84c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -30,9 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -46,13 +41,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 1c11dc397e..176fbba921 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,10 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -17,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -31,13 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 2b2db8aa96..9049bb3a82 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,12 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests @@ -16,21 +13,11 @@ namespace osu.Game.Rulesets.Mania.Tests private readonly Container content; protected override Container Content => content ?? base.Content; - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected ManiaInputTestScene(int keys) { base.Content.Add(content = new LocalInputManager(keys)); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 941d01a1d6..1d84a2dfcb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -25,9 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -42,13 +38,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 1d053688a2..706268e478 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,7 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -30,16 +29,6 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - [Test] public void TestVariousNotes() { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index c364b395e1..09dd0da5ac 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Input.Bindings; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -25,8 +26,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } - [Resolved] - private Bindable configColourCodedNotes { get; set; } + [Resolved(canBeNull: true)] + private ManiaRulesetConfigManager config { get; set; } + + private readonly Bindable configColourCodedNotes = new Bindable(); [Resolved(canBeNull: true)] private SnapFinder snapFinder { get; set; } @@ -56,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (snapFinder != null) { + config?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + HitObject.StartTimeBindable.BindValueChanged(_ => Snap.Value = snapFinder.FindSnap(HitObject), true); Snap.BindValueChanged(_ => updateSnapColour(), true); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c1b61aaf31..174727cc2d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -54,9 +54,6 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - public ScrollVisualisationMethod ScrollMethod { get => scrollMethod; @@ -111,8 +108,6 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - - Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } protected override void AdjustScrollSpeed(int amount) From 1f48378ce72b4036601eb09d0a6090ba51434d38 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 08:53:45 +0800 Subject: [PATCH 029/161] Add xmldoc to SnapFinder --- osu.Game/Rulesets/Objects/SnapFinder.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SnapFinder.cs b/osu.Game/Rulesets/Objects/SnapFinder.cs index eb8f4110a2..f915d41305 100644 --- a/osu.Game/Rulesets/Objects/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/SnapFinder.cs @@ -8,10 +8,17 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects { + /// + /// Used to find the lowest beat divisor that a aligns to in an + /// public class SnapFinder { private readonly IBeatmap beatmap; + /// + /// Creates a new SnapFinder instance. + /// + /// The beatmap to align to when evaulating. public SnapFinder(IBeatmap beatmap) { this.beatmap = beatmap; @@ -19,6 +26,10 @@ namespace osu.Game.Rulesets.Objects private readonly static int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; + /// + /// Finds the lowest beat divisor that the given HitObject aligns to. + /// + /// The to evaluate. public int FindSnap(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); From 211bff6a8f09d8379b1574dfb80d97b76e318ae3 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 09:21:25 +0800 Subject: [PATCH 030/161] Fix cake errors --- .../Objects/Drawables/DrawableNote.cs | 10 +++++----- osu.Game/Rulesets/Objects/SnapFinder.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 09dd0da5ac..9f30221fde 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Drawable headPiece; - private readonly Bindable Snap = new Bindable(); + private readonly Bindable snap = new Bindable(); public DrawableNote(Note hitObject) : base(hitObject) @@ -61,9 +61,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { config?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); - HitObject.StartTimeBindable.BindValueChanged(_ => Snap.Value = snapFinder.FindSnap(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = snapFinder.FindSnap(HitObject), true); - Snap.BindValueChanged(_ => updateSnapColour(), true); + snap.BindValueChanged(_ => updateSnapColour(), true); configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); } } @@ -106,12 +106,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { - } + } private void updateSnapColour() { Colour = configColourCodedNotes.Value - ? (ColourInfo)BindableBeatDivisor.GetColourFor(Snap.Value, colours) + ? (ColourInfo)BindableBeatDivisor.GetColourFor(snap.Value, colours) : (ColourInfo)Colour4.White; } } diff --git a/osu.Game/Rulesets/Objects/SnapFinder.cs b/osu.Game/Rulesets/Objects/SnapFinder.cs index f915d41305..3bc1d5cfb7 100644 --- a/osu.Game/Rulesets/Objects/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/SnapFinder.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects this.beatmap = beatmap; } - private readonly static int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; + private static readonly int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; /// /// Finds the lowest beat divisor that the given HitObject aligns to. @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects foreach (var snap in snaps) { - if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / (double)snap)) + if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / snap)) return snap; } From 6fd77e536df6bfcab9a3c2ab8d8c19fdc822fb8b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 25 Apr 2021 05:34:54 +0200 Subject: [PATCH 031/161] Add unsnap check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 5 +- osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 119 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index f33feac971..aa3459a01a 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -22,7 +22,10 @@ namespace osu.Game.Rulesets.Edit // Audio new CheckAudioPresence(), - new CheckAudioQuality() + new CheckAudioQuality(), + + // Compose + new CheckUnsnaps() }; public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs new file mode 100644 index 0000000000..835c4bdb69 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -0,0 +1,119 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckUnsnaps : ICheck + { + private const double unsnap_ms_threshold = 2; + + private static readonly int[] greatest_common_divisors = { 16, 12, 9, 7, 5 }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplate2MsOrMore(this), + new IssueTemplate1MsOrMore(this) + }; + + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + { + foreach (var hitobject in playableBeatmap.HitObjects) + { + double startUnsnap = hitobject.StartTime - closestSnapTime(playableBeatmap, hitobject.StartTime); + string startPostfix = hitobject is IHasDuration ? "start" : ""; + foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) + yield return issue; + + if (hitobject is IHasRepeats hasRepeats) + { + for (int repeatIndex = 0; repeatIndex < hasRepeats.RepeatCount; ++repeatIndex) + { + double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); + double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); + double repeatUnsnap = repeatTime - closestSnapTime(playableBeatmap, repeatTime); + foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) + yield return issue; + } + } + + if (hitobject is IHasDuration hasDuration) + { + double endUnsnap = hasDuration.EndTime - closestSnapTime(playableBeatmap, hasDuration.EndTime); + foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) + yield return issue; + } + } + } + + private IEnumerable getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "") + { + if (Math.Abs(unsnap) >= unsnap_ms_threshold) + yield return new IssueTemplate2MsOrMore(this).Create(hitobject, unsnap, time, postfix); + else if (Math.Abs(unsnap) >= 1) + yield return new IssueTemplate1MsOrMore(this).Create(hitobject, unsnap, time, postfix); + + // We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works. + } + + private int closestSnapTime(IBeatmap playableBeatmap, double time) + { + var timingPoint = playableBeatmap.ControlPointInfo.TimingPointAt(time); + double smallestUnsnap = greatest_common_divisors.Select(divisor => Math.Abs(time - snapTime(timingPoint, time, divisor))).Min(); + + return (int)Math.Round(time + smallestUnsnap); + } + + private int snapTime(TimingControlPoint timingPoint, double time, int beatDivisor) + { + double beatLength = timingPoint.BeatLength / beatDivisor; + int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + // Casting to int matches the editor in both stable and lazer. + return (int)(timingPoint.Time + beatLengths * beatLength); + } + + public abstract class IssueTemplateUnsnap : IssueTemplate + { + protected IssueTemplateUnsnap(ICheck check, IssueType type) + : base(check, type, "{0:0.##} is unsnapped by {1:0.##} ms.") + { + } + + public Issue Create(HitObject hitobject, double unsnap, double time, string postfix = "") + { + string objectName = hitobject.GetType().Name; + if (!string.IsNullOrEmpty(postfix)) + objectName += " " + postfix; + + return new Issue(hitobject, this, objectName, unsnap) { Time = time }; + } + } + + public class IssueTemplate2MsOrMore : IssueTemplateUnsnap + { + public IssueTemplate2MsOrMore(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplate1MsOrMore : IssueTemplateUnsnap + { + public IssueTemplate1MsOrMore(ICheck check) + : base(check, IssueType.Negligible) + { + } + } + } +} From fa8e8ed36f3b410fc91eb07a49bcb4413e52fbce Mon Sep 17 00:00:00 2001 From: plan-do-break-fix Date: Sat, 24 Apr 2021 22:57:18 -0500 Subject: [PATCH 032/161] fix(docs): corrects typo in project README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c539f9f4d8..eb790ca18f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commo This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. -**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. +**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: From f9e228d6bfb4e94ded823da5ceb6434c09db6b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:40:23 +0200 Subject: [PATCH 033/161] Use null-permitting BDL to reduce number of fields --- .../Objects/Drawables/DrawableNote.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 9f30221fde..52e343ba25 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,14 +26,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } - [Resolved(canBeNull: true)] - private ManiaRulesetConfigManager config { get; set; } - private readonly Bindable configColourCodedNotes = new Bindable(); - [Resolved(canBeNull: true)] - private SnapFinder snapFinder { get; set; } - protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; private readonly Drawable headPiece; @@ -53,13 +47,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - protected override void LoadComplete() + [BackgroundDependencyLoader(true)] + private void load(ManiaRulesetConfigManager rulesetConfig, SnapFinder snapFinder) { - base.LoadComplete(); - if (snapFinder != null) { - config?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = snapFinder.FindSnap(HitObject), true); @@ -115,4 +108,4 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : (ColourInfo)Colour4.White; } } -} \ No newline at end of file +} From afb67726f0057525b975bbd7b51d6e156e3316a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:40:49 +0200 Subject: [PATCH 034/161] Reduce casting --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 52e343ba25..a5749408af 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Configuration; @@ -15,6 +14,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { Colour = configColourCodedNotes.Value - ? (ColourInfo)BindableBeatDivisor.GetColourFor(snap.Value, colours) - : (ColourInfo)Colour4.White; + ? BindableBeatDivisor.GetColourFor(snap.Value, colours) + : Color4.White; } } } From e14255f39548b250d5c276663f4765cbc41d2596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:42:56 +0200 Subject: [PATCH 035/161] Rename {Snap -> BeatDivisor}Finder --- .../Objects/Drawables/DrawableNote.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- .../{SnapFinder.cs => BeatDivisorFinder.cs} | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Rulesets/Objects/{SnapFinder.cs => BeatDivisorFinder.cs} (77%) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index a5749408af..2d2fba0ad5 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -48,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } [BackgroundDependencyLoader(true)] - private void load(ManiaRulesetConfigManager rulesetConfig, SnapFinder snapFinder) + private void load(ManiaRulesetConfigManager rulesetConfig, BeatDivisorFinder beatDivisorFinder) { - if (snapFinder != null) + if (beatDivisorFinder != null) { rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); - HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = snapFinder.FindSnap(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = beatDivisorFinder.FindDivisor(HitObject), true); snap.BindValueChanged(_ => updateSnapColour(), true); configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 174727cc2d..0177c01240 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; [Cached] - private SnapFinder snapFinder { get; set; } + private BeatDivisorFinder beatDivisorFinder { get; set; } public IEnumerable BarLines; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - snapFinder = new SnapFinder(Beatmap); + beatDivisorFinder = new BeatDivisorFinder(Beatmap); } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Objects/SnapFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs similarity index 77% rename from osu.Game/Rulesets/Objects/SnapFinder.cs rename to osu.Game/Rulesets/Objects/BeatDivisorFinder.cs index 3bc1d5cfb7..29b271a7e7 100644 --- a/osu.Game/Rulesets/Objects/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs @@ -9,17 +9,17 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects { /// - /// Used to find the lowest beat divisor that a aligns to in an + /// Used to find the lowest beat divisor that a aligns to in an . /// - public class SnapFinder + public class BeatDivisorFinder { private readonly IBeatmap beatmap; /// - /// Creates a new SnapFinder instance. + /// Creates a new instance. /// - /// The beatmap to align to when evaulating. - public SnapFinder(IBeatmap beatmap) + /// The beatmap to use when calculating beat divisor alignment. + public BeatDivisorFinder(IBeatmap beatmap) { this.beatmap = beatmap; } @@ -27,10 +27,10 @@ namespace osu.Game.Rulesets.Objects private static readonly int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; /// - /// Finds the lowest beat divisor that the given HitObject aligns to. + /// Finds the lowest beat divisor that the given aligns to. /// /// The to evaluate. - public int FindSnap(HitObject hitObject) + public int FindDivisor(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); From 33a5c156a1e79e3ffe22f7199e9348f8f42b2464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:52:11 +0200 Subject: [PATCH 036/161] Use existing snap list from BindableBeatDivisor --- osu.Game/Rulesets/Objects/BeatDivisorFinder.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs index 29b271a7e7..63a4ff2d34 100644 --- a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs +++ b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Objects { @@ -24,8 +25,6 @@ namespace osu.Game.Rulesets.Objects this.beatmap = beatmap; } - private static readonly int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; - /// /// Finds the lowest beat divisor that the given aligns to. /// @@ -35,10 +34,10 @@ namespace osu.Game.Rulesets.Objects TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); - foreach (var snap in snaps) + foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) { - if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / snap)) - return snap; + if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / divisor)) + return divisor; } return 0; From 8bb1fcd39b29b55b693d9540777f96ecb0d8d3c6 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 07:07:32 +0800 Subject: [PATCH 037/161] Add tests for BeatDivisorFinder --- osu.Game.Tests/NonVisual/BeatDivisorFinder.cs | 116 ++++++++++++++++++ .../Rulesets/Objects/BeatDivisorFinder.cs | 3 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/NonVisual/BeatDivisorFinder.cs diff --git a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs new file mode 100644 index 0000000000..742f5790d6 --- /dev/null +++ b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.NonVisual +{ + public class BeatDivisorFinderTest + { + [Test] + public void TestFindDivisor() + { + const int beatLength = 1000; + + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitObject { StartTime = -beatLength / 3 }, + new HitObject { StartTime = 0 }, + new HitObject { StartTime = beatLength / 16 }, + new HitObject { StartTime = beatLength / 12 }, + new HitObject { StartTime = beatLength / 8 }, + new HitObject { StartTime = beatLength / 6 }, + new HitObject { StartTime = beatLength / 4 }, + new HitObject { StartTime = beatLength / 3 }, + new HitObject { StartTime = beatLength / 2 }, + new HitObject { StartTime = beatLength }, + new HitObject { StartTime = beatLength + beatLength / 7 } + }, + ControlPointInfo = new ControlPointInfo() + }; + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = beatLength + }); + + var beatDivisorFinder = new BeatDivisorFinder(beatmap); + + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 3); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 16); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 12); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 8); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 6); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[6]), 4); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[7]), 3); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[8]), 2); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[9]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[10]), 0); + } + + [Test] + public void TestFindDivisorWithTempoChanges() + { + const int firstBeatLength = 1000; + const int secondBeatLength = 700; + const int thirdBeatLength = 200; + + const int firstBeatLengthStart = 0; + const int secondBeatLengthStart = 1000; + const int thirdBeatLengthStart = 2000; + + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitObject { StartTime = firstBeatLengthStart }, + new HitObject { StartTime = firstBeatLengthStart + firstBeatLength / 2 }, + new HitObject { StartTime = secondBeatLengthStart }, + new HitObject { StartTime = secondBeatLengthStart + secondBeatLength / 2 }, + new HitObject { StartTime = thirdBeatLengthStart }, + new HitObject { StartTime = thirdBeatLengthStart + thirdBeatLength / 2 }, + }, + ControlPointInfo = new ControlPointInfo() + }; + + var firstTimingControlPoint = new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = firstBeatLength + }; + + var secondTimingControlPoint = new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = secondBeatLength + }; + + var thirdTimingControlPoint = new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = thirdBeatLength + }; + + beatmap.ControlPointInfo.Add(firstBeatLengthStart, firstTimingControlPoint); + beatmap.ControlPointInfo.Add(secondBeatLengthStart, secondTimingControlPoint); + beatmap.ControlPointInfo.Add(thirdBeatLengthStart, thirdTimingControlPoint); + + var beatDivisorFinder = new BeatDivisorFinder(beatmap); + + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 2); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 2); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 2); + } + } +} diff --git a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs index 63a4ff2d34..1479b22942 100644 --- a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs +++ b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs @@ -27,12 +27,13 @@ namespace osu.Game.Rulesets.Objects /// /// Finds the lowest beat divisor that the given aligns to. + /// Returns 0 if it does not align to any divisor. /// /// The to evaluate. public int FindDivisor(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); + double snapResult = hitObject.StartTime - currentTimingPoint.Time; foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) { From 0b9172a1dc08dfa3e432b93aa48d0a3b7862fd31 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Apr 2021 02:39:18 +0300 Subject: [PATCH 038/161] Animate back slider repeat and tail circle pieces --- .../Objects/Drawables/DrawableSliderRepeat.cs | 7 +++++-- .../Objects/Drawables/DrawableSliderTail.cs | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 76490e0de1..5af2a38559 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -26,7 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - public Drawable CirclePiece { get; private set; } + public SkinnableDrawable CirclePiece { get; private set; } + private Drawable scaleContainer; private ReverseArrowPiece arrow; @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new[] + Children = new Drawable[] { // no default for this; only visible in legacy skins. CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), @@ -91,6 +92,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 87f098dd29..84b9b881a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -84,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); + (circlePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: From e6474e6ff73b968970dc0c9e943130a6f865af06 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 11:47:38 +0900 Subject: [PATCH 039/161] Remove redundant statement (lifetime is set in base) --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 312ed93e45..6ab6a4b984 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -212,10 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. if (entry is SyntheticHitObjectEntry) - entry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; - - LifetimeStart = entry.LifetimeStart; - LifetimeEnd = entry.LifetimeEnd; + LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; ensureEntryHasResult(); From 9178aa1d7d745f3057f0080ec704e86ced17f534 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 04:48:56 +0200 Subject: [PATCH 040/161] Add unsnap check tests --- .../Editing/Checks/CheckUnsnapsTest.cs | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs new file mode 100644 index 0000000000..88939c43ce --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs @@ -0,0 +1,160 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckUnsnapsTest + { + private CheckUnsnaps check; + private ControlPointInfo cpi; + + [SetUp] + public void Setup() + { + check = new CheckUnsnaps(); + + cpi = new ControlPointInfo(); + cpi.Add(100, new TimingControlPoint { BeatLength = 100 }); + } + + [Test] + public void TestCircleSnapped() + { + assertOk(new List + { + new HitCircle { StartTime = 100 } + }); + } + + [Test] + public void TestCircleUnsnapped1Ms() + { + assert1Ms(new List + { + new HitCircle { StartTime = 101 } + }); + + assert1Ms(new List + { + new HitCircle { StartTime = 99 } + }); + } + + [Test] + public void TestCircleUnsnapped2Ms() + { + assert2Ms(new List + { + new HitCircle { StartTime = 102 } + }); + + assert2Ms(new List + { + new HitCircle { StartTime = 98 } + }); + } + + [Test] + public void TestSliderSnapped() + { + // Slider ends are naturally < 1 ms unsnapped because of how SV works. + var mockSlider = new Mock(); + mockSlider.SetupGet(s => s.StartTime).Returns(100); + mockSlider.As().Setup(r => r.RepeatCount).Returns(0); + mockSlider.As().Setup(d => d.Duration).Returns(400.75d); + + assertOk(new List + { + mockSlider.Object + }); + } + + [Test] + public void TestSliderUnsnapped1Ms() + { + assert1Ms(new List + { + getSliderMock(startTime: 101, endTime: 401.75d).Object + }, count: 2); + + // End is only off by 0.25 ms, hence count 1. + assert1Ms(new List + { + getSliderMock(startTime: 99, endTime: 399.75d).Object + }, count: 1); + } + + [Test] + public void TestSliderUnsnapped2Ms() + { + assert2Ms(new List + { + getSliderMock(startTime: 102, endTime: 402.75d).Object + }, count: 2); + + // Start and end are 2 ms and 1.25 ms off respectively, hence two different issues in one object. + var hitobjects = new List + { + getSliderMock(startTime: 98, endTime: 398.75d).Object + }; + + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(2)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + } + + private Mock getSliderMock(double startTime, double endTime, int repeats = 0) + { + var mockSlider = new Mock(); + mockSlider.SetupGet(s => s.StartTime).Returns(startTime); + mockSlider.As().Setup(r => r.RepeatCount).Returns(repeats); + mockSlider.As().Setup(d => d.EndTime).Returns(endTime); + + return mockSlider; + } + + private void assertOk(List hitobjects) + { + Assert.That(check.Run(getPlayableBeatmap(hitobjects), null), Is.Empty); + } + + private void assert1Ms(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); + } + + private void assert2Ms(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + } + + private IBeatmap getPlayableBeatmap(List hitobjects) + { + return new Beatmap + { + ControlPointInfo = cpi, + HitObjects = hitobjects + }; + } + } +} From 20e3cadd30e6c2e9e49b0f880915646e7caa9624 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 12:04:59 +0900 Subject: [PATCH 041/161] freeIfInUse -> free, and add comments --- .../Rulesets/Objects/Pooling/DrawableObject.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs index b29e6a6c3c..27ed4c04f2 100644 --- a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs +++ b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.LoadAsyncComplete(); + // Apply the initial entry given in the constructor. if (Entry != null && !HasEntryApplied) Apply(Entry); } @@ -60,7 +61,8 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public void Apply(TEntry entry) { - freeIfInUse(); + if (HasEntryApplied) + free(); setLifetime(entry.LifetimeStart, entry.LifetimeEnd); Entry = entry; @@ -74,8 +76,9 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.FreeAfterUse(); - if (IsInPool) - freeIfInUse(); + // We preserve the existing entry in case we want to move a non-pooled drawable between different parent drawables. + if (HasEntryApplied && IsInPool) + free(); } /// @@ -104,11 +107,9 @@ namespace osu.Game.Rulesets.Objects.Pooling } } - private void freeIfInUse() + private void free() { - if (!HasEntryApplied) return; - - Debug.Assert(Entry != null); + Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); From 6561a7c7d697dafd15d3f35b82d7ccd86552ca0b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 12:06:21 +0900 Subject: [PATCH 042/161] Rename DrawableObject -> PoolableDrawableWithLifetime --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- ...{DrawableObject.cs => PoolableDrawableWithLifetime.cs} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Rulesets/Objects/Pooling/{DrawableObject.cs => PoolableDrawableWithLifetime.cs} (92%) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6ab6a4b984..7739994527 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -26,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : DrawableObject + public abstract class DrawableHitObject : PoolableDrawableWithLifetime { /// /// Invoked after this 's applied has had its defaults applied. @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs similarity index 92% rename from osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs rename to osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 27ed4c04f2..93e476be76 100644 --- a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -13,15 +13,15 @@ namespace osu.Game.Rulesets.Objects.Pooling /// A that is controlled by to implement drawable pooling and replay rewinding. /// /// The type storing state and controlling this drawable. - public abstract class DrawableObject : PoolableDrawable where TEntry : LifetimeEntry + public abstract class PoolableDrawableWithLifetime : PoolableDrawable where TEntry : LifetimeEntry { /// - /// The entry holding essential state of this . + /// The entry holding essential state of this . /// protected TEntry? Entry { get; private set; } /// - /// Whether is applied to this . + /// Whether is applied to this . /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. /// protected bool HasEntryApplied { get; private set; } @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Pooling public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; - protected DrawableObject(TEntry? initialEntry = null) + protected PoolableDrawableWithLifetime(TEntry? initialEntry = null) { Entry = initialEntry; } From 049e42fa854f2ec8bb0493246d34a8d43cb9209a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 05:07:24 +0200 Subject: [PATCH 043/161] Move snapping responsibility to `IBeatmap` Seems `EditorBeatmap` already implements a different kind of `SnapTime` from `IBeatSnapProvider`, so method names here aren't great. This is very similar to what https://github.com/ppy/osu/pull/12558 is doing, so may need to do some duplicate resolution later, especially surrounding `ClosestBeatSnapDivisor`. Worth noting that this change makes 1/7, 1/5, etc unsupported for now, as we now rely on `BindableBeatDivisor.VALID_DIVISORS`. --- osu.Game/Beatmaps/Beatmap.cs | 26 ++++++++++++++++++ osu.Game/Beatmaps/IBeatmap.cs | 22 +++++++++++++++ osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 27 +++---------------- osu.Game/Screens/Edit/EditorBeatmap.cs | 13 +++++---- osu.Game/Screens/Play/GameplayBeatmap.cs | 9 +++++++ 5 files changed, 68 insertions(+), 29 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index e5b6a4bc44..1ce01aee24 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; +using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps { @@ -74,6 +75,31 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } + public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + { + var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); + var beatLength = timingPoint.BeatLength / beatDivisor; + var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + return (int)(timingPoint.Time + beatLengths * beatLength); + } + + public int SnapTimeAnyDivisor(double time, double? referenceTime = null) + { + return SnapTimeForDivisor(time, ClosestBeatSnapDivisor(time, referenceTime), referenceTime); + } + + public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) + { + double getUnsnap(int divisor) => Math.Abs(time - SnapTimeForDivisor(time, divisor, referenceTime)); + + int[] divisors = BindableBeatDivisor.VALID_DIVISORS; + double smallestUnsnap = divisors.Min(getUnsnap); + int closestDivisor = divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); + + return closestDivisor; + } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 769b33009a..3b043cb59b 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -51,6 +51,28 @@ namespace osu.Game.Beatmaps /// double GetMostCommonBeatLength(); + /// + /// Returns the time on the given beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// The beat divisor to snap to. + /// The time at which the timing point is retrieved, by default same as time. + int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null); + + /// + /// Returns the time on any valid beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// The time at which the timing point is retrieved, by default same as time. + int SnapTimeAnyDivisor(double time, double? referenceTime = null); + + /// + /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. + /// + /// The time to find the closest beat snap divisor to. + /// The time at which the timing point is retrieved, by default same as time. + int ClosestBeatSnapDivisor(double time, double? referenceTime = null); + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index 835c4bdb69..d15dc6f179 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -16,8 +14,6 @@ namespace osu.Game.Rulesets.Edit.Checks { private const double unsnap_ms_threshold = 2; - private static readonly int[] greatest_common_divisors = { 16, 12, 9, 7, 5 }; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -30,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Checks { foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - closestSnapTime(playableBeatmap, hitobject.StartTime); + double startUnsnap = hitobject.StartTime - playableBeatmap.SnapTimeAnyDivisor(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -41,7 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - closestSnapTime(playableBeatmap, repeatTime); + double repeatUnsnap = repeatTime - playableBeatmap.SnapTimeAnyDivisor(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -49,7 +45,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - closestSnapTime(playableBeatmap, hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - playableBeatmap.SnapTimeAnyDivisor(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } @@ -66,23 +62,6 @@ namespace osu.Game.Rulesets.Edit.Checks // We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works. } - private int closestSnapTime(IBeatmap playableBeatmap, double time) - { - var timingPoint = playableBeatmap.ControlPointInfo.TimingPointAt(time); - double smallestUnsnap = greatest_common_divisors.Select(divisor => Math.Abs(time - snapTime(timingPoint, time, divisor))).Min(); - - return (int)Math.Round(time + smallestUnsnap); - } - - private int snapTime(TimingControlPoint timingPoint, double time, int beatDivisor) - { - double beatLength = timingPoint.BeatLength / beatDivisor; - int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - - // Casting to int matches the editor in both stable and lazer. - return (int)(timingPoint.Time + beatLengths * beatLength); - } - public abstract class IssueTemplateUnsnap : IssueTemplate { protected IssueTemplateUnsnap(ICheck check, IssueType type) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 4bf4a3b8f3..9334814e2a 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,14 +301,17 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public double SnapTime(double time, double? referenceTime) + public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) { - var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); - var beatLength = timingPoint.BeatLength / BeatDivisor; - - return timingPoint.Time + (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero) * beatLength; + return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); } + public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + + public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + + public double SnapTime(double time, double? referenceTime) => SnapTimeForDivisor(time, BeatDivisor, referenceTime); + public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; public int BeatDivisor => beatDivisor?.Value ?? 1; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 74fbe540fa..a3a2bbd41b 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -45,6 +45,15 @@ namespace osu.Game.Screens.Play public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); + public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + { + return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); + } + + public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + + public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + public IBeatmap Clone() => PlayableBeatmap.Clone(); private readonly Bindable lastJudgementResult = new Bindable(); From e8d83f2f99887423d9be29ac5eea0cdb673e24b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 14:33:30 +0900 Subject: [PATCH 044/161] Rename "EditRuleset" and "EditPlayfield" to use full "Editor" keyword --- ...eManiaEditRuleset.cs => DrawableManiaEditorRuleset.cs} | 6 +++--- .../{ManiaEditPlayfield.cs => ManiaEditorPlayfield.cs} | 4 ++-- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 ++-- ...wableOsuEditRuleset.cs => DrawableOsuEditorRuleset.cs} | 8 ++++---- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game.Rulesets.Mania/Edit/{DrawableManiaEditRuleset.cs => DrawableManiaEditorRuleset.cs} (78%) rename osu.Game.Rulesets.Mania/Edit/{ManiaEditPlayfield.cs => ManiaEditorPlayfield.cs} (75%) rename osu.Game.Rulesets.Osu/Edit/{DrawableOsuEditRuleset.cs => DrawableOsuEditorRuleset.cs} (93%) diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs similarity index 78% rename from osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs index 445df79f6f..b0af8c503b 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs @@ -12,16 +12,16 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class DrawableManiaEditRuleset : DrawableManiaRuleset + public class DrawableManiaEditorRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new ManiaEditPlayfield(Beatmap.Stages) + protected override Playfield CreatePlayfield() => new ManiaEditorPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs similarity index 75% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs rename to osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs index a42f793a77..186d50716e 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditPlayfield : ManiaPlayfield + public class ManiaEditorPlayfield : ManiaPlayfield { - public ManiaEditPlayfield(List stages) + public ManiaEditorPlayfield(List stages) : base(stages) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d9570bf8be..2baec95c94 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaHitObjectComposer : HitObjectComposer { - private DrawableManiaEditRuleset drawableRuleset; + private DrawableManiaEditorRuleset drawableRuleset; private ManiaBeatSnapGrid beatSnapGrid; private InputManager inputManager; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { - drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); + drawableRuleset = new DrawableManiaEditorRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it dependencies.CacheAs(drawableRuleset.ScrollingInfo); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs similarity index 93% rename from osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index b8d0637e90..7227e2b511 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -17,18 +17,18 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class DrawableOsuEditRuleset : DrawableOsuRuleset + public class DrawableOsuEditorRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + protected override Playfield CreatePlayfield() => new OsuEditorPlayfield(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; - private class OsuEditPlayfield : OsuPlayfield + private class OsuEditorPlayfield : OsuPlayfield { private Bindable hitAnimations; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 396fd41377..7b67d7aaf1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) - => new DrawableOsuEditRuleset(ruleset, beatmap, mods); + => new DrawableOsuEditorRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { From bda8f68da45a8645a07abd6c5fc1d7e5a2478156 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:03:43 +0900 Subject: [PATCH 045/161] Add failing test --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2cc031405e..590d159300 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -34,6 +34,18 @@ namespace osu.Game.Rulesets.Osu.Tests private List judgementResults; + [Test] + public void TestPressBothKeysSimultaneouslyAndReleaseOne() + { + performTest(new List + { + new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, + }); + + AddAssert("Tracking retained", assertMaxJudge); + } + /// /// Scenario: /// - Press a key before a slider starts From 6182181ea1d3f1a6809d8631341ef2addbfbd3d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:20:29 +0900 Subject: [PATCH 046/161] Fix simultaneous slider input not allowing both keys --- .../Skinning/Default/SliderBall.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 82b677e12c..b85610491c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -134,6 +135,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// private double? timeToAcceptAnyKeyAfter; + /// + /// The actions that were pressed in the previous frame. + /// + private readonly List lastPressedActions = new List(); + protected override void Update() { base.Update(); @@ -152,8 +158,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - // we can return to accepting all keys if the initial head circle key is the *only* key pressed, or all keys have been released. - if (actions?.Contains(otherKey) != true) + // we can return to accepting all other keys if they were released in the previous frame. + if (!lastPressedActions.Contains(otherKey)) timeToAcceptAnyKeyAfter = Time.Current; } @@ -164,6 +170,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); + + lastPressedActions.Clear(); + lastPressedActions.AddRange(actions); } /// From aa7ade8186df4033412bf93db2b970c5ae18a8fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:22:17 +0900 Subject: [PATCH 047/161] Expose presence of `MainCirclePiece` via an interface --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- .../Objects/Drawables/DrawableSliderTail.cs | 11 ++++++----- .../Skinning/Default/IHasMainCirclePiece.cs | 12 ++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index fb6c110b3c..1bf9e76d7d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableHitCircle : DrawableOsuHitObject + public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece { public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5af2a38559..cc7d9d1b23 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking + public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking, IHasMainCirclePiece { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 84b9b881a2..d81af053d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece { public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; @@ -35,7 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } - private SkinnableDrawable circlePiece; + public SkinnableDrawable CirclePiece { get; private set; } + private Container scaleContainer; public DrawableSliderTail() @@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - circlePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) } }, }; @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - circlePiece.FadeInFromZero(HitObject.TimeFadeIn); + CirclePiece.FadeInFromZero(HitObject.TimeFadeIn); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -85,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); - (circlePiece.Drawable as IMainCirclePiece)?.Animate(state); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); switch (state) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs new file mode 100644 index 0000000000..8bb7629542 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public interface IHasMainCirclePiece + { + SkinnableDrawable CirclePiece { get; } + } +} From 4da964c3f3235661b8f4b9c9d7c32cb60f3e711b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:22:42 +0900 Subject: [PATCH 048/161] Expose `DrawableSliderRepeat`'s arrow and move transforms to children --- .../Objects/Drawables/DrawableSliderRepeat.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index cc7d9d1b23..5db012c4f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -28,8 +28,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SkinnableDrawable CirclePiece { get; private set; } + public ReverseArrowPiece Arrow { get; private set; } + private Drawable scaleContainer; - private ReverseArrowPiece arrow; public override bool DisplayResult => false; @@ -57,8 +58,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), - arrow = new ReverseArrowPiece(), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + Arrow = new ReverseArrowPiece(), } }; @@ -105,8 +110,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; case ArmedState.Hit: - this.FadeOut(animDuration, Easing.Out) - .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + this.FadeOut(animDuration, Easing.Out); + + Arrow.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + CirclePiece.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); break; } } @@ -142,18 +149,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); - while (Math.Abs(aimRotation - arrow.Rotation) > 180) - aimRotation += aimRotation < arrow.Rotation ? 360 : -360; + while (Math.Abs(aimRotation - Arrow.Rotation) > 180) + aimRotation += aimRotation < Arrow.Rotation ? 360 : -360; if (!hasRotation) { - arrow.Rotation = aimRotation; + Arrow.Rotation = aimRotation; hasRotation = true; } else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); + Arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } From 8795c5f0824e1f1cd9b1aae00d429ac2968713f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:23:21 +0900 Subject: [PATCH 049/161] Update osu! editor transform logic to allow adjustments to `DrawableSliderRepeat` and `DrawableSliderTail` --- .../Edit/DrawableOsuEditorRuleset.cs | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 7227e2b511..aeeae84d14 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -11,6 +11,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -56,43 +57,45 @@ namespace osu.Game.Rulesets.Osu.Edit if (state == ArmedState.Idle || hitAnimations.Value) return; - // adjust the visuals of certain object types to make them stay on screen for longer than usual. - switch (hitObject) + if (hitObject is DrawableHitCircle circle) { - default: - // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.) - return; + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) + .Expire(); - case DrawableSlider _: - // no specifics to sliders but let them fade slower below. - break; - - case DrawableHitCircle circle: // also handles slider heads - circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) - .Expire(); - - circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); - - var circlePieceDrawable = circle.CirclePiece.Drawable; - - // clear any explode animation logic. - circlePieceDrawable.ApplyTransformsAt(circle.HitStateUpdateTime, true); - circlePieceDrawable.ClearTransformsAfter(circle.HitStateUpdateTime, true); - - break; + circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); } - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + if (hitObject is IHasMainCirclePiece mainPieceContainer) + { + // clear any explode animation logic. + mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + } - if (existing == null) - return; + if (hitObject is DrawableSliderRepeat repeat) + { + repeat.Arrow.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + repeat.Arrow.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + } - hitObject.RemoveTransform(existing); + // adjust the visuals of top-level object types to make them stay on screen for longer than usual. + switch (hitObject) + { + case DrawableSlider _: + case DrawableHitCircle _: + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + break; + } } } } From d10aac851debb1a7b185a855f76c491833e193ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:30:22 +0900 Subject: [PATCH 050/161] Extract scale constant --- .../Objects/Drawables/DrawableSliderRepeat.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5db012c4f8..7b4188edab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -112,8 +112,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out); - Arrow.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - CirclePiece.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + const float final_scale = 1.5f; + + Arrow.ScaleTo(Scale * final_scale, animDuration, Easing.Out); + CirclePiece.ScaleTo(Scale * final_scale, animDuration, Easing.Out); break; } } From f70e45b1998a97dd8421f0f046c7efcada54d40b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:35:08 +0900 Subject: [PATCH 051/161] Prevent adding null enumerable --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index b85610491c..c5c95557e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -172,7 +172,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default (actions?.Any(isValidTrackingAction) ?? false); lastPressedActions.Clear(); - lastPressedActions.AddRange(actions); + if (actions != null) + lastPressedActions.AddRange(actions); } /// From fd5fbaf0dbc5dcf7d5fe475f4ac7c88fe80edbb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:37:42 +0900 Subject: [PATCH 052/161] Rename ruleset wrapper class --- ...eEditRulesetWrapper.cs => DrawableEditorRulesetWrapper.cs} | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Rulesets/Edit/{DrawableEditRulesetWrapper.cs => DrawableEditorRulesetWrapper.cs} (94%) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs similarity index 94% rename from osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs rename to osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index c60d4c7834..62e2539c2a 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Edit /// /// A wrapper for a . Handles adding visual representations of s to the underlying . /// - internal class DrawableEditRulesetWrapper : CompositeDrawable + internal class DrawableEditorRulesetWrapper : CompositeDrawable where TObject : HitObject { public Playfield Playfield => drawableRuleset.Playfield; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } - public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset) + public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 736fc47dee..35896d4982 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - private DrawableEditRulesetWrapper drawableRulesetWrapper; + private DrawableEditorRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) + drawableRulesetWrapper = new DrawableEditorRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) { Clock = EditorClock, ProcessCustomClock = false From 0d0b4ea78a8a07be1ac472641f8123e26f1a4bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:47:37 +0900 Subject: [PATCH 053/161] Rewrite comment to hopefully be more readable --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index c5c95557e6..8feeca56e8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - // we can return to accepting all other keys if they were released in the previous frame. + // we can start accepting any key once all other keys have been released in the previous frame. if (!lastPressedActions.Contains(otherKey)) timeToAcceptAnyKeyAfter = Time.Current; } From 58ebec48037499b162fb5045c767812a22f82fb5 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 19:00:40 +0800 Subject: [PATCH 054/161] Move BindValueChanged hooks to LoadComplete() --- .../Objects/Drawables/DrawableNote.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 2d2fba0ad5..49536080ab 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } + [Resolved] + private BeatDivisorFinder beatDivisorFinder { get; set; } + private readonly Bindable configColourCodedNotes = new Bindable(); protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -48,17 +51,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } [BackgroundDependencyLoader(true)] - private void load(ManiaRulesetConfigManager rulesetConfig, BeatDivisorFinder beatDivisorFinder) + private void load(ManiaRulesetConfigManager rulesetConfig) + { + rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + + protected override void LoadComplete() { if (beatDivisorFinder != null) { - rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); - HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = beatDivisorFinder.FindDivisor(HitObject), true); - - snap.BindValueChanged(_ => updateSnapColour(), true); - configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); } + + snap.BindValueChanged(_ => updateSnapColour()); + configColourCodedNotes.BindValueChanged(_ => updateSnapColour(), true); } protected override void OnDirectionChanged(ValueChangedEvent e) From 559d403abec8046cd8331fbc45523676ae2452f0 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 19:05:12 +0800 Subject: [PATCH 055/161] Rename ColourCodedNotes to TimingBasedNoteColouring --- .../Configuration/ManiaRulesetConfigManager.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 4 ++-- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 87a4689689..ac8168dfc9 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); - SetDefault(ManiaRulesetSetting.ColourCodedNotes, false); + SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings @@ -36,6 +36,6 @@ namespace osu.Game.Rulesets.Mania.Configuration { ScrollTime, ScrollDirection, - ColourCodedNotes + TimingBasedNoteColouring } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 552c793096..1c89d9cd00 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Rulesets.Mania }, new SettingsCheckbox { - LabelText = "Colour-coded notes", - Current = config.GetBindable(ManiaRulesetSetting.ColourCodedNotes), + LabelText = "Timing-based note colouring", + Current = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring), } }; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 49536080ab..102cd485dc 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private BeatDivisorFinder beatDivisorFinder { get; set; } - private readonly Bindable configColourCodedNotes = new Bindable(); + private readonly Bindable configTimingBasedNoteColouring = new Bindable(); protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [BackgroundDependencyLoader(true)] private void load(ManiaRulesetConfigManager rulesetConfig) { - rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); } protected override void LoadComplete() @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } snap.BindValueChanged(_ => updateSnapColour()); - configColourCodedNotes.BindValueChanged(_ => updateSnapColour(), true); + configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { - Colour = configColourCodedNotes.Value + Colour = configTimingBasedNoteColouring.Value ? BindableBeatDivisor.GetColourFor(snap.Value, colours) : Color4.White; } From 6560dc2d1f579947218d1fa88853775efb0d1792 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 20:46:44 +0900 Subject: [PATCH 056/161] Fix exported replays being wrapped in zip packages --- osu.Game/Database/ArchiveModelManager.cs | 20 +++++++++++++++----- osu.Game/Scoring/ScoreManager.cs | 10 ++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index d809dbcb01..6719351530 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -422,15 +422,25 @@ namespace osu.Game.Database if (retrievedItem == null) throw new ArgumentException("Specified model could not be found", nameof(item)); + using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) + ExportModelTo(retrievedItem, outputStream); + + exportStorage.OpenInNativeExplorer(); + } + + /// + /// Exports an item to the given output stream. + /// + /// The item to export. + /// The output stream to export to. + protected virtual void ExportModelTo(TModel model, Stream outputStream) + { using (var archive = ZipArchive.Create()) { - foreach (var file in retrievedItem.Files) + foreach (var file in model.Files) archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); - using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) - archive.SaveTo(outputStream); - - exportStorage.OpenInNativeExplorer(); + archive.SaveTo(outputStream); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index c7ee26c248..9d3b952ada 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -72,6 +72,16 @@ namespace osu.Game.Scoring } } + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) + { + var file = model.Files.SingleOrDefault(); + if (file == null) + return; + + using (var inputStream = Files.Storage.GetStream(file.FileInfo.StoragePath)) + inputStream.CopyTo(outputStream); + } + protected override IEnumerable GetStableImportPaths(Storage storage) => storage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) .Select(path => storage.GetFullPath(path)); From 213ac88a8b34b7d1caa57183945b501b3c828e1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 20:52:20 +0900 Subject: [PATCH 057/161] Fix exported scores not being compatible with osu-stable --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index db7e51e833..56c4e75864 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{(int)Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; } } From 7b9ed924be4ab084101d24fa551ddaee8297f410 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:07:30 +0200 Subject: [PATCH 058/161] Rename snapping methods Further separates them from `IBeatSnapProvider`'s `SnapTime`, and groups them together more, to prevent confusion between the two interfaces. Also changes the xmldoc of the reference time to that of `IBeatSnapProvider` for consistency. --- osu.Game/Beatmaps/Beatmap.cs | 10 +++++----- osu.Game/Beatmaps/IBeatmap.cs | 12 ++++++------ osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 10 +++++----- osu.Game/Screens/Play/GameplayBeatmap.cs | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 1ce01aee24..66b8f169ef 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -75,7 +75,7 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } - public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); var beatLength = timingPoint.BeatLength / beatDivisor; @@ -84,14 +84,14 @@ namespace osu.Game.Beatmaps return (int)(timingPoint.Time + beatLengths * beatLength); } - public int SnapTimeAnyDivisor(double time, double? referenceTime = null) + public int ClosestSnapTime(double time, double? referenceTime = null) { - return SnapTimeForDivisor(time, ClosestBeatSnapDivisor(time, referenceTime), referenceTime); + return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); } - public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) + public int ClosestBeatDivisor(double time, double? referenceTime = null) { - double getUnsnap(int divisor) => Math.Abs(time - SnapTimeForDivisor(time, divisor, referenceTime)); + double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); int[] divisors = BindableBeatDivisor.VALID_DIVISORS; double smallestUnsnap = divisors.Min(getUnsnap); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3b043cb59b..679d639fd1 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -56,22 +56,22 @@ namespace osu.Game.Beatmaps /// /// The time to find the closest snapped time to. /// The beat divisor to snap to. - /// The time at which the timing point is retrieved, by default same as time. - int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null); + /// An optional reference point to use for timing point lookup. + int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null); /// /// Returns the time on any valid beat divisor closest to the given time. /// /// The time to find the closest snapped time to. - /// The time at which the timing point is retrieved, by default same as time. - int SnapTimeAnyDivisor(double time, double? referenceTime = null); + /// An optional reference point to use for timing point lookup. + int ClosestSnapTime(double time, double? referenceTime = null); /// /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. /// /// The time to find the closest beat snap divisor to. - /// The time at which the timing point is retrieved, by default same as time. - int ClosestBeatSnapDivisor(double time, double? referenceTime = null); + /// An optional reference point to use for timing point lookup. + int ClosestBeatDivisor(double time, double? referenceTime = null); /// /// Creates a shallow-clone of this beatmap and returns it. diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index d15dc6f179..ca268652a9 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Checks { foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - playableBeatmap.SnapTimeAnyDivisor(hitobject.StartTime); + double startUnsnap = hitobject.StartTime - playableBeatmap.ClosestSnapTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - playableBeatmap.SnapTimeAnyDivisor(repeatTime); + double repeatUnsnap = repeatTime - playableBeatmap.ClosestSnapTime(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - playableBeatmap.SnapTimeAnyDivisor(hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - playableBeatmap.ClosestSnapTime(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 9334814e2a..72fb0ac9e9 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,16 +301,16 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) { - return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); + return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); } - public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); - public double SnapTime(double time, double? referenceTime) => SnapTimeForDivisor(time, BeatDivisor, referenceTime); + public double SnapTime(double time, double? referenceTime) => ClosestSnapTime(time, BeatDivisor, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index a3a2bbd41b..92f58c8759 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -45,14 +45,14 @@ namespace osu.Game.Screens.Play public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); - public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) { - return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); + return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); } - public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); public IBeatmap Clone() => PlayableBeatmap.Clone(); From 9b9c473616c6d29a550881e17b9f94cc6271ade8 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:17:38 +0200 Subject: [PATCH 059/161] Remove redundant string formatting --- osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index ca268652a9..ff270b6d60 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit.Checks public abstract class IssueTemplateUnsnap : IssueTemplate { protected IssueTemplateUnsnap(ICheck check, IssueType type) - : base(check, type, "{0:0.##} is unsnapped by {1:0.##} ms.") + : base(check, type, "{0} is unsnapped by {1:0.##} ms.") { } From 71f880aa94eaf3f9defd3b943c1f949293c32631 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 17:44:46 +0200 Subject: [PATCH 060/161] Fix duplicate code in unsnap test --- osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs index 88939c43ce..bac3c41cb0 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs @@ -70,14 +70,9 @@ namespace osu.Game.Tests.Editing.Checks public void TestSliderSnapped() { // Slider ends are naturally < 1 ms unsnapped because of how SV works. - var mockSlider = new Mock(); - mockSlider.SetupGet(s => s.StartTime).Returns(100); - mockSlider.As().Setup(r => r.RepeatCount).Returns(0); - mockSlider.As().Setup(d => d.Duration).Returns(400.75d); - assertOk(new List { - mockSlider.Object + getSliderMock(startTime: 100, endTime: 400.75d).Object }); } From 08a232f7fad662384426f21b3131e36828b9de62 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 26 Apr 2021 20:08:40 +0200 Subject: [PATCH 061/161] Add method to safely refresh DrawableHitObject transforms --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7739994527..679c86072b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -14,13 +14,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using osu.Game.Configuration; -using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -429,6 +429,13 @@ namespace osu.Game.Rulesets.Objects.Drawables base.ClearTransformsAfter(double.MinValue, true); } + /// + /// Removes all previously applied transforms, then reapplies a new set of transforms with potentially different parameters. + /// The transforms will use the current , and they will use the appropriate start times. + /// This also takes in account potential overrides defined in . + /// + protected void RefreshStateTransforms() => updateState(State.Value, true); + /// /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. From a3570e18dd3c860b0a3942d5c0ba3cf8593471c8 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:17:18 +0200 Subject: [PATCH 062/161] Add concurrent objects check Here we use `IHasColumn` to support rulesets with columns, and so I moved that interface out into `osu.Game` from `osu.Game.Rulesets.Mania`. We also use the same threshold as the unsnap check to ensure that no problems slip through. Specifically where an object is simultaneously not concurrent and not unsnapped but still on the same tick. --- .../Objects/ManiaHitObject.cs | 1 - .../Edit/Checks/CheckConcurrentObjects.cs | 88 +++++++++++++++++++ osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 4 +- .../Rulesets}/Objects/Types/IHasColumn.cs | 2 +- 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs rename {osu.Game.Rulesets.Mania => osu.Game/Rulesets}/Objects/Types/IHasColumn.cs (90%) diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 27bf50493d..6289744df1 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs new file mode 100644 index 0000000000..7c41569fab --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -0,0 +1,88 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckConcurrentObjects : ICheck + { + // We guarantee that the objects are either treated as concurrent or unsnapped when near the same beat divisor. + private const double ms_leniency = CheckUnsnaps.UNSNAP_MS_THRESHOLD; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects"); + + public virtual IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateConcurrentSame(this), + new IssueTemplateConcurrentDifferent(this) + }; + + public virtual IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + { + for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i) + { + var hitobject = playableBeatmap.HitObjects[i]; + + for (int j = i + 1; j < playableBeatmap.HitObjects.Count; ++j) + { + var nextHitobject = playableBeatmap.HitObjects[j]; + + // Accounts for rulesets with hitobjects separated by columns, such as Mania. + // In these cases we only care about concurrent objects within the same column. + if ((hitobject as IHasColumn)?.Column != (nextHitobject as IHasColumn)?.Column) + continue; + + // Two hitobjects cannot be concurrent without also being concurrent with all objects in between. + // So if the next object is not concurrent, then we know no future objects will be either. + if (!areConcurrent(hitobject, nextHitobject)) + break; + + if (hitobject.GetType() == nextHitobject.GetType()) + yield return new IssueTemplateConcurrentSame(this).Create(hitobject, nextHitobject); + else + yield return new IssueTemplateConcurrentDifferent(this).Create(hitobject, nextHitobject); + } + } + } + + protected bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency; + + public abstract class IssueTemplateConcurrent : IssueTemplate + { + protected IssueTemplateConcurrent(ICheck check, string unformattedMessage) + : base(check, IssueType.Problem, unformattedMessage) + { + } + + public Issue Create(HitObject hitobject, HitObject nextHitobject) + { + var hitobjects = new List { hitobject, nextHitobject }; + return new Issue(hitobjects, this, hitobject.GetType().Name, nextHitobject.GetType().Name) + { + Time = nextHitobject.StartTime + }; + } + } + + public class IssueTemplateConcurrentSame : IssueTemplateConcurrent + { + public IssueTemplateConcurrentSame(ICheck check) + : base(check, "{0}s are concurrent here.") + { + } + } + + public class IssueTemplateConcurrentDifferent : IssueTemplateConcurrent + { + public IssueTemplateConcurrentDifferent(ICheck check) + : base(check, "{0} and {1} are concurrent here.") + { + } + } + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index ff270b6d60..564ef13d8f 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Checks { public class CheckUnsnaps : ICheck { - private const double unsnap_ms_threshold = 2; + public const double UNSNAP_MS_THRESHOLD = 2; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "") { - if (Math.Abs(unsnap) >= unsnap_ms_threshold) + if (Math.Abs(unsnap) >= UNSNAP_MS_THRESHOLD) yield return new IssueTemplate2MsOrMore(this).Create(hitobject, unsnap, time, postfix); else if (Math.Abs(unsnap) >= 1) yield return new IssueTemplate1MsOrMore(this).Create(hitobject, unsnap, time, postfix); diff --git a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs b/osu.Game/Rulesets/Objects/Types/IHasColumn.cs similarity index 90% rename from osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs rename to osu.Game/Rulesets/Objects/Types/IHasColumn.cs index 1ea3138828..dc07cfbb6a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasColumn.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -namespace osu.Game.Rulesets.Mania.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A type of hit object which lies in one of a number of predetermined columns. From b8cdcf56c03ef5f4dc27e204c7efece3ffd4572f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:22:24 +0200 Subject: [PATCH 063/161] Add concurrent object check tests --- .../Checks/CheckConcurrentObjectsTest.cs | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs new file mode 100644 index 0000000000..0f771427ee --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs @@ -0,0 +1,194 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckConcurrentObjectsTest + { + private CheckConcurrentObjects check; + + [SetUp] + public void Setup() + { + check = new CheckConcurrentObjects(); + } + + [Test] + public void TestCirclesSeparate() + { + assertOk(new List + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 150 } + }); + } + + [Test] + public void TestCirclesConcurrent() + { + assertConcurrentSame(new List + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 100 } + }); + } + + [Test] + public void TestCirclesAlmostConcurrent() + { + assertConcurrentSame(new List + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 101 } + }); + } + + [Test] + public void TestSlidersSeparate() + { + assertOk(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + getSliderMock(startTime: 500, endTime: 900.75d).Object + }); + } + + [Test] + public void TestSlidersConcurrent() + { + assertConcurrentSame(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + getSliderMock(startTime: 300, endTime: 700.75d).Object + }); + } + + [Test] + public void TestSlidersAlmostConcurrent() + { + assertConcurrentSame(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + getSliderMock(startTime: 402, endTime: 902.75d).Object + }); + } + + [Test] + public void TestSliderAndCircleConcurrent() + { + assertConcurrentDifferent(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + new HitCircle { StartTime = 300 } + }); + } + + [Test] + public void TestManyObjectsConcurrent() + { + var hitobjects = new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + new HitCircle { StartTime = 300 }, + getSliderMock(startTime: 200, endTime: 500.75d).Object + }; + + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(3)); + Assert.That(issues.Where(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent).ToList(), Has.Count.EqualTo(2)); + Assert.That(issues.Any(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame)); + } + + [Test] + public void TestHoldNotesSeparateOnSameColumn() + { + assertOk(new List + { + getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object, + getHoldNoteMock(startTime: 500, endTime: 900.75d, column: 1).Object + }); + } + + [Test] + public void TestHoldNotesConcurrentOnDifferentColumns() + { + assertOk(new List + { + getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object, + getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 2).Object + }); + } + + [Test] + public void TestHoldNotesConcurrentOnSameColumn() + { + assertConcurrentSame(new List + { + getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object, + getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 1).Object + }); + } + + private Mock getSliderMock(double startTime, double endTime, int repeats = 0) + { + var mock = new Mock(); + mock.SetupGet(s => s.StartTime).Returns(startTime); + mock.As().Setup(r => r.RepeatCount).Returns(repeats); + mock.As().Setup(d => d.EndTime).Returns(endTime); + + return mock; + } + + private Mock getHoldNoteMock(double startTime, double endTime, int column) + { + var mock = new Mock(); + mock.SetupGet(s => s.StartTime).Returns(startTime); + mock.As().Setup(d => d.EndTime).Returns(endTime); + mock.As().Setup(c => c.Column).Returns(column); + + return mock; + } + + private void assertOk(List hitobjects) + { + Assert.That(check.Run(getPlayableBeatmap(hitobjects), null), Is.Empty); + } + + private void assertConcurrentSame(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame)); + } + + private void assertConcurrentDifferent(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent)); + } + + private IBeatmap getPlayableBeatmap(List hitobjects) + { + return new Beatmap + { + HitObjects = hitobjects + }; + } + } +} From b9e4f73f78fa5bf3cdacefdd10d3a186079913be Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:28:59 +0200 Subject: [PATCH 064/161] Add concurrent objects check to `BeatmapVerifier` --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index aa3459a01a..6754d62a11 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -25,7 +25,8 @@ namespace osu.Game.Rulesets.Edit new CheckAudioQuality(), // Compose - new CheckUnsnaps() + new CheckUnsnaps(), + new CheckConcurrentObjects() }; public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) From ce258febf6de3ec3e3e1b53cdd240107ca46028c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:32:44 +0200 Subject: [PATCH 065/161] Rename `CheckUnsnaps` -> `CheckUnsnappedObjects` Will potentially have `CheckUnsnappedKiai` or similar later, so this is worth specifying. Also consistent with `CheckConcurrentObjects`, which will likely have a `CheckConcurrentLines` later. --- ...UnsnapsTest.cs => CheckUnsnappedObjectsTest.cs} | 14 +++++++------- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 +- .../Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 2 +- .../{CheckUnsnaps.cs => CheckUnsnappedObjects.cs} | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game.Tests/Editing/Checks/{CheckUnsnapsTest.cs => CheckUnsnappedObjectsTest.cs} (93%) rename osu.Game/Rulesets/Edit/Checks/{CheckUnsnaps.cs => CheckUnsnappedObjects.cs} (98%) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs similarity index 93% rename from osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs rename to osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index bac3c41cb0..f8cac331bc 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -15,15 +15,15 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Tests.Editing.Checks { [TestFixture] - public class CheckUnsnapsTest + public class CheckUnsnappedObjectsTest { - private CheckUnsnaps check; + private CheckUnsnappedObjects check; private ControlPointInfo cpi; [SetUp] public void Setup() { - check = new CheckUnsnaps(); + check = new CheckUnsnappedObjects(); cpi = new ControlPointInfo(); cpi.Add(100, new TimingControlPoint { BeatLength = 100 }); @@ -108,8 +108,8 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); } private Mock getSliderMock(double startTime, double endTime, int repeats = 0) @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); } private void assert2Ms(List hitobjects, int count = 1) @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); } private IBeatmap getPlayableBeatmap(List hitobjects) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 6754d62a11..2f7b7b0ab8 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit new CheckAudioQuality(), // Compose - new CheckUnsnaps(), + new CheckUnsnappedObjects(), new CheckConcurrentObjects() }; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index 7c41569fab..bcc8fead18 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckConcurrentObjects : ICheck { // We guarantee that the objects are either treated as concurrent or unsnapped when near the same beat divisor. - private const double ms_leniency = CheckUnsnaps.UNSNAP_MS_THRESHOLD; + private const double ms_leniency = CheckUnsnappedObjects.UNSNAP_MS_THRESHOLD; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects"); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs similarity index 98% rename from osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs rename to osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index 564ef13d8f..cdcf8a6b80 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckUnsnaps : ICheck + public class CheckUnsnappedObjects : ICheck { public const double UNSNAP_MS_THRESHOLD = 2; From 9ad30da72943d4bae44dc82e7438d8bb84e14ce0 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 16:41:26 -0400 Subject: [PATCH 066/161] Show a notification if game is run as administrator --- osu.Desktop/OsuGameDesktop.cs | 9 ++++++ osu.Desktop/osu.Desktop.csproj | 1 + osu.Game/Admin/AdminChecker.cs | 57 ++++++++++++++++++++++++++++++++++ osu.Game/OsuGame.cs | 5 +++ 4 files changed, 72 insertions(+) create mode 100644 osu.Game/Admin/AdminChecker.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 0c21c75290..1ce79b3b49 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Win32; using osu.Desktop.Overlays; @@ -20,6 +21,7 @@ using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; +using osu.Game.Admin; using osu.Game.IO; namespace osu.Desktop @@ -102,6 +104,8 @@ namespace osu.Desktop } } + protected override AdminChecker CreateAdminChecker() => new DesktopAdminChecker(); + protected override void LoadComplete() { base.LoadComplete(); @@ -180,5 +184,10 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } + + private class DesktopAdminChecker : AdminChecker + { + protected override bool IsAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; + } } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3e0f0cb7f6..6bd0f64218 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,6 +25,7 @@ + diff --git a/osu.Game/Admin/AdminChecker.cs b/osu.Game/Admin/AdminChecker.cs new file mode 100644 index 0000000000..b81e34653e --- /dev/null +++ b/osu.Game/Admin/AdminChecker.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Admin +{ + /// + /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. + /// + public class AdminChecker : CompositeDrawable + { + [Resolved] + protected NotificationOverlay Notifications { get; private set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + if (IsAdmin()) + Notifications.Post(new AdminNotification()); + } + + protected virtual bool IsAdmin() => false; + + private class AdminNotification : SimpleNotification + { + public override bool IsImportant => true; + + public AdminNotification() + { + bool isUnix = RuntimeInfo.IsUnix; + Text = $"Running osu! as {(isUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.ShieldAlt; + IconBackgound.Colour = colours.YellowDark; + + Activated = delegate + { + notificationOverlay.Hide(); + return true; + }; + } + } + } + + +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 28f32ba455..358e85b247 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,6 +28,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Threading; +using osu.Game.Admin; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -450,6 +451,8 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected virtual AdminChecker CreateAdminChecker() => new AdminChecker(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -673,6 +676,8 @@ namespace osu.Game // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); + loadComponentSingleFile(CreateAdminChecker(), Add, false); + // overlay elements loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); From 0f0870c8b875451c2f7ce3e724fb63ee05b776ec Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 00:36:26 +0200 Subject: [PATCH 067/161] Sort objects by time in concurrent check test --- osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs index 0f771427ee..ffe5d34e67 100644 --- a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs @@ -101,8 +101,8 @@ namespace osu.Game.Tests.Editing.Checks var hitobjects = new List { getSliderMock(startTime: 100, endTime: 400.75d).Object, - new HitCircle { StartTime = 300 }, - getSliderMock(startTime: 200, endTime: 500.75d).Object + getSliderMock(startTime: 200, endTime: 500.75d).Object, + new HitCircle { StartTime = 300 } }; var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); From 6d5883abcb6293c54a362c7e765e07a8441b70fd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 01:19:38 +0200 Subject: [PATCH 068/161] Return result of local variable instead --- osu.Game/Beatmaps/Beatmap.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 66b8f169ef..e3a11e2326 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -95,9 +95,8 @@ namespace osu.Game.Beatmaps int[] divisors = BindableBeatDivisor.VALID_DIVISORS; double smallestUnsnap = divisors.Min(getUnsnap); - int closestDivisor = divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); - return closestDivisor; + return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); } IBeatmap IBeatmap.Clone() => Clone(); From 217ff8238ea50abeea0ccb62319cd785a91d39f3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 01:23:03 +0200 Subject: [PATCH 069/161] Add snapping time comment --- osu.Game/Beatmaps/Beatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index e3a11e2326..6515540527 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -81,6 +81,7 @@ namespace osu.Game.Beatmaps var beatLength = timingPoint.BeatLength / beatDivisor; var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + // Casting to int matches stable. return (int)(timingPoint.Time + beatLengths * beatLength); } From a3c1b1fd52d1a74df5df536d05df6550852c9ab5 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 01:24:38 +0200 Subject: [PATCH 070/161] Fix accessibility of `areConcurrent` --- osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index bcc8fead18..6e8355024e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Edit.Checks } } - protected bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency; + private bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency; public abstract class IssueTemplateConcurrent : IssueTemplate { From 260dd06f4707f378ae2fd7a6fda4852d6160105a Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 17:41:04 -0400 Subject: [PATCH 071/161] Move AdminChecker to osu.Desktop.Admin --- {osu.Game => osu.Desktop}/Admin/AdminChecker.cs | 13 ++++++------- osu.Desktop/OsuGameDesktop.cs | 12 +++--------- osu.Game/OsuGame.cs | 5 ----- 3 files changed, 9 insertions(+), 21 deletions(-) rename {osu.Game => osu.Desktop}/Admin/AdminChecker.cs (75%) diff --git a/osu.Game/Admin/AdminChecker.cs b/osu.Desktop/Admin/AdminChecker.cs similarity index 75% rename from osu.Game/Admin/AdminChecker.cs rename to osu.Desktop/Admin/AdminChecker.cs index b81e34653e..b9f0d68694 100644 --- a/osu.Game/Admin/AdminChecker.cs +++ b/osu.Desktop/Admin/AdminChecker.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Security.Principal; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; @@ -9,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Game.Admin +namespace osu.Desktop.Admin { /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. @@ -22,11 +24,11 @@ namespace osu.Game.Admin protected override void LoadComplete() { base.LoadComplete(); - if (IsAdmin()) + if (isAdmin()) Notifications.Post(new AdminNotification()); } - protected virtual bool IsAdmin() => false; + private bool isAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; private class AdminNotification : SimpleNotification { @@ -34,8 +36,7 @@ namespace osu.Game.Admin public AdminNotification() { - bool isUnix = RuntimeInfo.IsUnix; - Text = $"Running osu! as {(isUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; } [BackgroundDependencyLoader] @@ -52,6 +53,4 @@ namespace osu.Game.Admin } } } - - } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 1ce79b3b49..77f968e1b6 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,9 +7,9 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; -using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Win32; +using osu.Desktop.Admin; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; @@ -21,7 +21,6 @@ using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; -using osu.Game.Admin; using osu.Game.IO; namespace osu.Desktop @@ -104,8 +103,6 @@ namespace osu.Desktop } } - protected override AdminChecker CreateAdminChecker() => new DesktopAdminChecker(); - protected override void LoadComplete() { base.LoadComplete(); @@ -117,6 +114,8 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); + + LoadComponentAsync(new AdminChecker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) @@ -184,10 +183,5 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } - - private class DesktopAdminChecker : AdminChecker - { - protected override bool IsAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; - } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 358e85b247..28f32ba455 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,7 +28,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Threading; -using osu.Game.Admin; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -451,8 +450,6 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); - protected virtual AdminChecker CreateAdminChecker() => new AdminChecker(); - protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -676,8 +673,6 @@ namespace osu.Game // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); - loadComponentSingleFile(CreateAdminChecker(), Add, false); - // overlay elements loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); From 9e49ecb57311b3d8e83c9c8655cca8e13e0342bb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 02:23:06 +0200 Subject: [PATCH 072/161] Remove unused `virtual` keywords Added these in a previous iteration, where I had the mania variant inherit this class. No longer necessary as `IHasColumn` was used to make this check more generic. --- osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index 6e8355024e..ddebe2923a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -16,13 +16,13 @@ namespace osu.Game.Rulesets.Edit.Checks public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects"); - public virtual IEnumerable PossibleTemplates => new IssueTemplate[] + public IEnumerable PossibleTemplates => new IssueTemplate[] { new IssueTemplateConcurrentSame(this), new IssueTemplateConcurrentDifferent(this) }; - public virtual IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i) { From 7a6e9e5070b63d7b1563108f170ca676994e8b5f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 02:32:57 +0200 Subject: [PATCH 073/161] Change category of unsnap check to timing Makes more sense, as this is typically the result of timing changes. --- osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index cdcf8a6b80..74a2ce2fd7 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit.Checks { public const double UNSNAP_MS_THRESHOLD = 2; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Timing, "Unsnapped hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] { From c3bad1d4c599e43846c8f0e596b370bc082ef5ed Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 21:05:18 -0400 Subject: [PATCH 074/161] Rename AdminChecker to ElevatedPrivilegesChecker, refactor elevated check --- ...hecker.cs => ElevatedPrivilegesChecker.cs} | 30 ++++++++++++++----- osu.Desktop/OsuGameDesktop.cs | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) rename osu.Desktop/Admin/{AdminChecker.cs => ElevatedPrivilegesChecker.cs} (64%) diff --git a/osu.Desktop/Admin/AdminChecker.cs b/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs similarity index 64% rename from osu.Desktop/Admin/AdminChecker.cs rename to osu.Desktop/Admin/ElevatedPrivilegesChecker.cs index b9f0d68694..ea4aab0f85 100644 --- a/osu.Desktop/Admin/AdminChecker.cs +++ b/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs @@ -5,7 +5,7 @@ using System; using System.Security.Principal; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays; @@ -16,7 +16,7 @@ namespace osu.Desktop.Admin /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. /// - public class AdminChecker : CompositeDrawable + public class ElevatedPrivilegesChecker : Component { [Resolved] protected NotificationOverlay Notifications { get; private set; } @@ -24,17 +24,31 @@ namespace osu.Desktop.Admin protected override void LoadComplete() { base.LoadComplete(); - if (isAdmin()) - Notifications.Post(new AdminNotification()); + + bool elevated = false; + + if (OperatingSystem.IsWindows()) + { + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + elevated = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + else if (RuntimeInfo.IsUnix) + { + elevated = Mono.Unix.Native.Syscall.geteuid() == 0; + } + + if (!elevated) + return; + + Notifications.Post(new ElevatedPrivilegesNotification()); } - private bool isAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; - - private class AdminNotification : SimpleNotification + private class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; - public AdminNotification() + public ElevatedPrivilegesNotification() { Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 77f968e1b6..9f7854779e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -115,7 +115,7 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); - LoadComponentAsync(new AdminChecker(), Add); + LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) From a2723f3f579565b2aca0e1c6159362838c9c559b Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 22:37:08 -0400 Subject: [PATCH 075/161] Perform elevated check asynchronously, use a separate function w/ switch statement --- osu.Desktop/OsuGameDesktop.cs | 2 +- .../ElevatedPrivilegesChecker.cs | 48 +++++++++++++------ osu.Desktop/osu.Desktop.csproj | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) rename osu.Desktop/{Admin => Security}/ElevatedPrivilegesChecker.cs (66%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 9f7854779e..4a28ab3722 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -9,7 +9,7 @@ using System.Reflection; using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.Win32; -using osu.Desktop.Admin; +using osu.Desktop.Security; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; diff --git a/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs similarity index 66% rename from osu.Desktop/Admin/ElevatedPrivilegesChecker.cs rename to osu.Desktop/Security/ElevatedPrivilegesChecker.cs index ea4aab0f85..a719dbf952 100644 --- a/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Desktop.Admin +namespace osu.Desktop.Security { /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. @@ -21,36 +21,54 @@ namespace osu.Desktop.Admin [Resolved] protected NotificationOverlay Notifications { get; private set; } + private bool elevated; + protected override void LoadComplete() { base.LoadComplete(); - bool elevated = false; - - if (OperatingSystem.IsWindows()) - { - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - elevated = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); - } - else if (RuntimeInfo.IsUnix) - { - elevated = Mono.Unix.Native.Syscall.geteuid() == 0; - } - if (!elevated) return; Notifications.Post(new ElevatedPrivilegesNotification()); } + [BackgroundDependencyLoader] + private void load() + { + elevated = isElevated(); + } + + private bool isElevated() + { + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + { + if (!OperatingSystem.IsWindows()) return false; + + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.Linux: + return Mono.Unix.Native.Syscall.geteuid() == 0; + + default: + return false; + } + } + private class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; public ElevatedPrivilegesNotification() { - Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game as a normal user."; } [BackgroundDependencyLoader] diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 6bd0f64218..ad5c323e9b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,7 +25,7 @@ - + From e0f54f58424f6fc77d79fe12ef6f6a511e4b6f71 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 22:51:03 -0400 Subject: [PATCH 076/161] Move load() before LoadComplete() --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index a719dbf952..5fb8263516 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -23,6 +23,12 @@ namespace osu.Desktop.Security private bool elevated; + [BackgroundDependencyLoader] + private void load() + { + elevated = isElevated(); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -33,12 +39,6 @@ namespace osu.Desktop.Security Notifications.Post(new ElevatedPrivilegesNotification()); } - [BackgroundDependencyLoader] - private void load() - { - elevated = isElevated(); - } - private bool isElevated() { switch (RuntimeInfo.OS) From 5a3fbef5ace66e56b2f5312aacf74a4e2ff26793 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Tue, 27 Apr 2021 00:23:08 -0400 Subject: [PATCH 077/161] Use a try-catch, notification activation does nothing --- .../Security/ElevatedPrivilegesChecker.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 5fb8263516..47705eb929 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -19,7 +19,7 @@ namespace osu.Desktop.Security public class ElevatedPrivilegesChecker : Component { [Resolved] - protected NotificationOverlay Notifications { get; private set; } + private NotificationOverlay notifications { get; set; } private bool elevated; @@ -33,32 +33,35 @@ namespace osu.Desktop.Security { base.LoadComplete(); - if (!elevated) - return; - - Notifications.Post(new ElevatedPrivilegesNotification()); + if (elevated) + notifications.Post(new ElevatedPrivilegesNotification()); } private bool isElevated() { - switch (RuntimeInfo.OS) + try { - case RuntimeInfo.Platform.Windows: + switch (RuntimeInfo.OS) { - if (!OperatingSystem.IsWindows()) return false; + case RuntimeInfo.Platform.Windows: + if (!OperatingSystem.IsWindows()) return false; - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.Linux: + return Mono.Unix.Native.Syscall.geteuid() == 0; + + default: + return false; } - - case RuntimeInfo.Platform.macOS: - case RuntimeInfo.Platform.Linux: - return Mono.Unix.Native.Syscall.geteuid() == 0; - - default: - return false; + } + catch + { + return false; } } @@ -77,11 +80,7 @@ namespace osu.Desktop.Security Icon = FontAwesome.Solid.ShieldAlt; IconBackgound.Colour = colours.YellowDark; - Activated = delegate - { - notificationOverlay.Hide(); - return true; - }; + Activated = () => true; } } } From ec1c336b0aca43376e853e0a4d0826368924a95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 13:23:14 +0900 Subject: [PATCH 078/161] Fix a couple of inspections --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 77ea3b05dc..8b20df9a68 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last); lastPoint = last; - return lastPiece?.IsHovered != true; + return lastPiece.IsHovered != true; } private void updateSlider() diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 99cdca045b..337a806b6e 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,7 +3,6 @@ using System; using osu.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -35,9 +34,6 @@ namespace osu.Game.Rulesets.Edit public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } - protected SelectionBlueprint(HitObject hitObject) { HitObject = hitObject; From 7980d16b4c202bcb063c0569112ad76463e8a9c6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 14:26:12 +0900 Subject: [PATCH 079/161] Add failing test showing the issue of DHO lifetime --- .../Gameplay/TestSceneDrawableHitObject.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs new file mode 100644 index 0000000000..d42802db91 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneDrawableHitObject : OsuTestScene + { + [Test] + public void TestEntryLifetime() + { + TestDrawableHitObject dho = null; + var initialHitObject = new HitObject + { + StartTime = 1000 + }; + var entry = new TestLifetimeEntry(new HitObject + { + StartTime = 2000 + }); + + AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject(initialHitObject)); + + AddAssert("Correct initial lifetime", () => dho.LifetimeStart == initialHitObject.StartTime - TestDrawableHitObject.INITIAL_LIFETIME_OFFSET); + + AddStep("Apply entry", () => dho.Apply(entry)); + + AddAssert("Correct initial lifetime", () => dho.LifetimeStart == entry.HitObject.StartTime - TestLifetimeEntry.INITIAL_LIFETIME_OFFSET); + + AddStep("Set lifetime", () => dho.LifetimeEnd = 3000); + AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public const double INITIAL_LIFETIME_OFFSET = 100; + protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + + public TestDrawableHitObject(HitObject hitObject) + : base(hitObject) + { + } + } + + private class TestLifetimeEntry : HitObjectLifetimeEntry + { + public const double INITIAL_LIFETIME_OFFSET = 200; + protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + + public TestLifetimeEntry(HitObject hitObject) + : base(hitObject) + { + } + } + } +} From 2303d108bb56b8214c788c18b59424d3fe69858e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:35:14 +0900 Subject: [PATCH 080/161] Simplify false return path --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 47705eb929..92885ad855 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -54,15 +54,13 @@ namespace osu.Desktop.Security case RuntimeInfo.Platform.macOS: case RuntimeInfo.Platform.Linux: return Mono.Unix.Native.Syscall.geteuid() == 0; - - default: - return false; } } catch { - return false; } + + return false; } private class ElevatedPrivilegesNotification : SimpleNotification From 13de571b3c690b2def91c4331b3b70bff00c4719 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:35:57 +0900 Subject: [PATCH 081/161] Rename private method --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 92885ad855..82e7d2a141 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -26,7 +26,7 @@ namespace osu.Desktop.Security [BackgroundDependencyLoader] private void load() { - elevated = isElevated(); + elevated = checkElevated(); } protected override void LoadComplete() @@ -37,7 +37,7 @@ namespace osu.Desktop.Security notifications.Post(new ElevatedPrivilegesNotification()); } - private bool isElevated() + private bool checkElevated() { try { From 2673cd3d9935025232453d9b371d516bc6b7de8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:36:11 +0900 Subject: [PATCH 082/161] Remove unnecessary noop action --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 82e7d2a141..edd4906421 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -77,8 +77,6 @@ namespace osu.Desktop.Security { Icon = FontAwesome.Solid.ShieldAlt; IconBackgound.Colour = colours.YellowDark; - - Activated = () => true; } } } From dbcb1259e2c07a726b8be0b1d12aca362c43df7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:38:19 +0900 Subject: [PATCH 083/161] Add a note about elevated privileges also breaking integrations --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index edd4906421..01458b4c37 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -69,7 +69,7 @@ namespace osu.Desktop.Security public ElevatedPrivilegesNotification() { - Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game as a normal user."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user."; } [BackgroundDependencyLoader] From a2c0951d94102cee2660e65412ffb4c4850114f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 15:23:33 +0900 Subject: [PATCH 084/161] Use overriding instead of hiding in HitObjectLifetimeEntry Hidden properties are used when the type is the base class. It caused issues when `DrawableHitObject` logic is factored out to `PoolableDrawableWithLifetime` because it is using the base `LifetimeEntry`, not `HitObjectLifetimeEntry`. --- .../Objects/Drawables/DrawableHitObject.cs | 5 ++- .../Objects/HitObjectLifetimeEntry.cs | 41 +++++-------------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7739994527..6431ec8980 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -431,7 +432,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. + /// The local drawable hierarchy is recursively delayed to for convenience. /// /// By default this will fade in the object from zero with no duration. /// @@ -613,7 +614,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// /// Only has an effect if this is not being pooled. /// For pooled s, use instead. diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 1954d7e6d2..23e991d00e 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -38,40 +38,19 @@ namespace osu.Game.Rulesets.Objects startTimeBindable.BindValueChanged(onStartTimeChanged, true); } - // The lifetime start, as set by the hitobject. + // The lifetime, as set by the hitobject. private double realLifetimeStart = double.MinValue; - - /// - /// The time at which the should become alive. - /// - public new double LifetimeStart - { - get => realLifetimeStart; - set => setLifetime(realLifetimeStart = value, LifetimeEnd); - } - - // The lifetime end, as set by the hitobject. private double realLifetimeEnd = double.MaxValue; - /// - /// The time at which the should become dead. - /// - public new double LifetimeEnd + public override void SetLifetime(double start, double end) { - get => realLifetimeEnd; - set => setLifetime(LifetimeStart, realLifetimeEnd = value); - } + realLifetimeStart = start; + realLifetimeEnd = end; - private void setLifetime(double start, double end) - { if (keepAlive) - { - start = double.MinValue; - end = double.MaxValue; - } - - base.LifetimeStart = start; - base.LifetimeEnd = end; + base.SetLifetime(double.MinValue, double.MaxValue); + else + base.SetLifetime(start, end); } private bool keepAlive; @@ -87,7 +66,7 @@ namespace osu.Game.Rulesets.Objects return; keepAlive = value; - setLifetime(realLifetimeStart, realLifetimeEnd); + SetLifetime(realLifetimeStart, realLifetimeEnd); } } @@ -98,12 +77,12 @@ namespace osu.Game.Rulesets.Objects /// /// This is only used as an optimisation to delay the initial update of the and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// protected virtual double InitialLifetimeOffset => 10000; /// - /// Resets according to the change in start time of the . + /// Resets according to the change in start time of the . /// private void onStartTimeChanged(ValueChangedEvent startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; } From c9e6ca537896fd7a099d2b4a4338b1ecfcda9001 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 15:24:52 +0900 Subject: [PATCH 085/161] Use now-public Entry.SetLifetime method --- .../Objects/Pooling/PoolableDrawableWithLifetime.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 93e476be76..049ff9d446 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -100,11 +100,7 @@ namespace osu.Game.Rulesets.Objects.Pooling base.LifetimeStart = start; base.LifetimeEnd = end; - if (Entry != null) - { - Entry.LifetimeStart = start; - Entry.LifetimeEnd = end; - } + Entry?.SetLifetime(start, end); } private void free() From 3899e500d3edf2b15622ed4e5ace339ab68fbdc0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 17:54:18 +0900 Subject: [PATCH 086/161] Adopt framework change of LifetimeEntry Override SetLifetimeStart/SetLifetimeEnd separately to track individual assignment. It is necessary to ensure real lifetime is not lost when lifetime is partially updated. --- .../Rulesets/Objects/HitObjectLifetimeEntry.cs | 15 +++++++++++++-- .../Pooling/PoolableDrawableWithLifetime.cs | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 23e991d00e..0181d6241b 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -42,11 +42,22 @@ namespace osu.Game.Rulesets.Objects private double realLifetimeStart = double.MinValue; private double realLifetimeEnd = double.MaxValue; - public override void SetLifetime(double start, double end) + // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`). + protected override void SetLifetimeStart(double start) { + // This assignment cannot be done in `SetLifetime` because otherwise setting only `LifetimeStart` will make `realLifetimeEnd` to be lost. realLifetimeStart = start; - realLifetimeEnd = end; + base.SetLifetimeStart(start); + } + protected override void SetLifetimeEnd(double end) + { + realLifetimeEnd = end; + base.SetLifetimeEnd(end); + } + + protected override void SetLifetime(double start, double end) + { if (keepAlive) base.SetLifetime(double.MinValue, double.MaxValue); else diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 049ff9d446..93e476be76 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -100,7 +100,11 @@ namespace osu.Game.Rulesets.Objects.Pooling base.LifetimeStart = start; base.LifetimeEnd = end; - Entry?.SetLifetime(start, end); + if (Entry != null) + { + Entry.LifetimeStart = start; + Entry.LifetimeEnd = end; + } } private void free() From 003553aba32d5af6a9decf2a7f2b8c307857ac9b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 18:10:44 +0900 Subject: [PATCH 087/161] Add test of HitObjectLifetimeEntry.KeepAlive behavior --- .../Gameplay/TestSceneDrawableHitObject.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index d42802db91..2e3f192f1b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -37,6 +37,38 @@ namespace osu.Game.Tests.Gameplay AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000); } + [Test] + public void TestKeepAlive() + { + TestDrawableHitObject dho = null; + TestLifetimeEntry entry = null; + AddStep("Create DHO", () => + { + dho = new TestDrawableHitObject(null); + dho.Apply(entry = new TestLifetimeEntry(new HitObject()) + { + LifetimeStart = 0, + LifetimeEnd = 1000, + }); + Child = dho; + }); + + AddStep("KeepAlive = true", () => entry.KeepAlive = true); + AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue); + + AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500); + AddStep("KeepAlive = false", () => entry.KeepAlive = false); + AddAssert("Lifetime is correct", () => entry.LifetimeStart == 500 && entry.LifetimeEnd == 1000); + + AddStep("Set LifetimeStart while KeepAlive", () => + { + entry.KeepAlive = true; + dho.LifetimeStart = double.MinValue; + entry.KeepAlive = false; + }); + AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000); + } + private class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; From 3ea55314f2f11c53dc2232e4152beacf7dc7f779 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 27 Apr 2021 11:29:16 +0200 Subject: [PATCH 088/161] Update osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs Co-authored-by: Dan Balasescu --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 679c86072b..e715e4aac6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -430,9 +430,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Removes all previously applied transforms, then reapplies a new set of transforms with potentially different parameters. - /// The transforms will use the current , and they will use the appropriate start times. - /// This also takes in account potential overrides defined in . + /// Reapplies the current . /// protected void RefreshStateTransforms() => updateState(State.Value, true); From f2e56bd3060438b70105e0cdb4a630f43ea31151 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 15:40:35 +0900 Subject: [PATCH 089/161] Refactor editor selection/blueprint components to be generic --- .../Edit/ManiaBlueprintContainer.cs | 3 +- .../Edit/ManiaSelectionHandler.cs | 7 +- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 2 +- .../Edit/OsuBlueprintContainer.cs | 3 +- .../Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSelectionHandler.cs | 4 +- .../Edit/TaikoBlueprintContainer.cs | 3 +- .../Edit/TaikoSelectionHandler.cs | 11 +- .../Editing/TestSceneBlueprintSelection.cs | 4 +- .../Editing/TestSceneEditorClipboard.cs | 8 +- .../Editing/TestSceneEditorSelection.cs | 6 +- .../Edit/OverlaySelectionBlueprint.cs | 3 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 +- .../Compose/Components/BlueprintContainer.cs | 211 +++++---------- .../Components/ComposeBlueprintContainer.cs | 12 +- .../Components/EditorBlueprintContainer.cs | 176 +++++++++++++ .../Components/EditorSelectionHandler.cs | 233 +++++++++++++++++ .../HitObjectOrderedSelectionContainer.cs | 24 +- .../Compose/Components/MoveSelectionEvent.cs | 8 +- .../Compose/Components/SelectionHandler.cs | 247 ++---------------- .../Timeline/TimelineBlueprintContainer.cs | 31 +-- .../Timeline/TimelineHitObjectBlueprint.cs | 22 +- 22 files changed, 588 insertions(+), 449 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index 2fa3f378ff..c4429176d1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -4,6 +4,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Edit.Compose.Components; @@ -30,6 +31,6 @@ namespace osu.Game.Rulesets.Mania.Edit return base.CreateBlueprintFor(hitObject); } - protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 2689ed4112..dd059c967c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -7,12 +7,13 @@ using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaSelectionHandler : SelectionHandler + public class ManiaSelectionHandler : EditorSelectionHandler { [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private HitObjectComposer composer { get; set; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Edit return true; } - private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) + private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 8dd550bb96..299f8fc43a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint where T : OsuHitObject { - protected new T HitObject => (T)DrawableObject.HitObject; + protected T HitObject => (T)DrawableObject.HitObject; protected override bool AlwaysShowWhenSelected => true; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index a68ed34e6b..abac5eb56e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7b67d7aaf1..806b7e6051 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (b.IsSelected) continue; - var hitObject = (OsuHitObject)b.HitObject; + var hitObject = (OsuHitObject)b.Item; Vector2? snap = checkSnap(hitObject.Position); if (snap == null && hitObject.Position != hitObject.EndPosition) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index de0a4682a3..92f5254182 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuSelectionHandler : SelectionHandler + public class OsuSelectionHandler : EditorSelectionHandler { protected override void OnSelectionChanged() { @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit referencePathTypes = null; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var hitObjects = selectedMovableObjects; diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index 8b41448c9d..b1b08a9461 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Screens.Edit.Compose.Components; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { } - protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => new TaikoSelectionBlueprint(hitObject); diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index ac2dd4bdb6..20366e5b3a 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -8,12 +8,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Taiko.Edit { - public class TaikoSelectionHandler : SelectionHandler + public class TaikoSelectionHandler : EditorSelectionHandler { private readonly Bindable selectionRimState = new Bindable(); private readonly Bindable selectionStrongState = new Bindable(); @@ -72,16 +73,16 @@ namespace osu.Game.Rulesets.Taiko.Edit }); } - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - if (selection.All(s => s.HitObject is Hit)) + if (selection.All(s => s.Item is Hit)) yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } }; - if (selection.All(s => s.HitObject is TaikoHitObject)) + if (selection.All(s => s.Item is TaikoHitObject)) yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; protected override void UpdateTernaryStates() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs index fd9c09fd5f..976bf93c15 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs @@ -23,8 +23,8 @@ namespace osu.Game.Tests.Visual.Editing protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private BlueprintContainer blueprintContainer - => Editor.ChildrenOfType().First(); + private EditorBlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); [Test] public void TestSelectedObjectHasPriorityWhenOverlapping() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 01d9966736..3a063af843 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -132,8 +132,8 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); - AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); - AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); } AddStep("paste hitobject", () => Editor.Paste()); @@ -142,8 +142,8 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000); - AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); - AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); + AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); + AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index b82842e30d..0758d73c2a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -26,15 +26,15 @@ namespace osu.Game.Tests.Visual.Editing protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private BlueprintContainer blueprintContainer - => Editor.ChildrenOfType().First(); + private EditorBlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); private void moveMouseToObject(Func targetFunc) { AddStep("move mouse to object", () => { var pos = blueprintContainer.SelectionBlueprints - .First(s => s.HitObject == targetFunc()) + .First(s => s.Item == targetFunc()) .ChildrenOfType() .First().ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 75200e3027..6369112d80 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -3,12 +3,13 @@ using osu.Framework.Graphics.Primitives; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Edit { - public abstract class OverlaySelectionBlueprint : SelectionBlueprint + public abstract class OverlaySelectionBlueprint : SelectionBlueprint { /// /// The which this applies to. diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 337a806b6e..905e433731 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -17,26 +16,26 @@ namespace osu.Game.Rulesets.Edit /// /// A blueprint placed above a adding editing functionality. /// - public abstract class SelectionBlueprint : CompositeDrawable, IStateful + public abstract class SelectionBlueprint : CompositeDrawable, IStateful { - public readonly HitObject HitObject; + public readonly T Item; /// - /// Invoked when this has been selected. + /// Invoked when this has been selected. /// - public event Action Selected; + public event Action> Selected; /// - /// Invoked when this has been deselected. + /// Invoked when this has been deselected. /// - public event Action Deselected; + public event Action> Deselected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - protected SelectionBlueprint(HitObject hitObject) + protected SelectionBlueprint(T item) { - HitObject = hitObject; + Item = item; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index f70e063ba9..a0bb4feadc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -25,37 +23,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { /// /// A container which provides a "blueprint" display of hitobjects. - /// Includes selection and manipulation support via a . + /// Includes selection and manipulation support via a . /// - public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler + public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { protected DragBox DragBox { get; private set; } - public Container SelectionBlueprints { get; private set; } + public Container> SelectionBlueprints { get; private set; } - protected SelectionHandler SelectionHandler { get; private set; } + protected SelectionHandler SelectionHandler { get; private set; } - protected readonly HitObjectComposer Composer; - - [Resolved(CanBeNull = true)] - private IEditorChangeHandler changeHandler { get; set; } - - [Resolved] - protected EditorClock EditorClock { get; private set; } - - [Resolved] - protected EditorBeatmap Beatmap { get; private set; } - - private readonly BindableList selectedHitObjects = new BindableList(); - private readonly Dictionary blueprintMap = new Dictionary(); + private readonly Dictionary> blueprintMap = new Dictionary>(); [Resolved(canBeNull: true)] private IPositionSnapProvider snapProvider { get; set; } - protected BlueprintContainer(HitObjectComposer composer) - { - Composer = composer; + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + protected BlueprintContainer() + { RelativeSizeAxes = Axes.Both; } @@ -73,66 +60,28 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionHandler.CreateProxy(), DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); - - // For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context. - if (Composer != null) - { - foreach (var obj in Composer.HitObjects) - addBlueprintFor(obj.HitObject); - } - - selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); - selectedHitObjects.CollectionChanged += (selectedObjects, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (var o in args.NewItems) - SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Select(); - break; - - case NotifyCollectionChangedAction.Remove: - foreach (var o in args.OldItems) - SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); - - break; - } - }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.HitObjectAdded += addBlueprintFor; - Beatmap.HitObjectRemoved += removeBlueprintFor; - - if (Composer != null) - { - // For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above. - foreach (var obj in Composer.HitObjects) - addBlueprintFor(obj.HitObject); - - Composer.Playfield.HitObjectUsageBegan += addBlueprintFor; - Composer.Playfield.HitObjectUsageFinished += removeBlueprintFor; - } - } - - protected virtual Container CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + protected virtual Container> CreateSelectionBlueprintContainer() => new Container> { RelativeSizeAxes = Axes.Both }; /// - /// Creates a which outlines s and handles movement of selections. + /// Creates a which outlines s and handles movement of selections. /// - protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); /// - /// Creates a for a specific . + /// Creates a for a specific . /// /// The to create the overlay for. - protected virtual SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => null; + protected virtual SelectionBlueprint CreateBlueprintFor(T hitObject) => null; protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); + /// + /// Whether this component is in a state where deselection should be allowed. If false, selection will only be added to. + /// + protected virtual bool AllowDeselection => true; + protected override bool OnMouseDown(MouseDownEvent e) { bool selectionPerformed = performMouseDownActions(e); @@ -143,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return selectionPerformed || e.Button == MouseButton.Left; } - private SelectionBlueprint clickedBlueprint; + protected SelectionBlueprint ClickedBlueprint { get; private set; } protected override bool OnClick(ClickEvent e) { @@ -151,11 +100,11 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // store for double-click handling - clickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); + ClickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); // Deselection should only occur if no selected blueprints are hovered // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection - if (endClickSelection(e) || clickedBlueprint != null) + if (endClickSelection(e) || ClickedBlueprint != null) return true; deselectAll(); @@ -168,10 +117,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // ensure the blueprint which was hovered for the first click is still the hovered blueprint. - if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) + if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint) return false; - EditorClock?.SeekSmoothlyTo(clickedBlueprint.HitObject.StartTime); return true; } @@ -227,9 +175,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { - // handle positional change etc. - foreach (var obj in selectedHitObjects) - Beatmap.Update(obj); + UpdateSelection(); changeHandler?.EndChange(); } @@ -238,6 +184,10 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.Hide(); } + protected virtual void UpdateSelection() + { + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) @@ -258,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (action.ActionType) { case PlatformActionType.SelectAll: - selectAll(); + SelectAll(); return true; } @@ -271,61 +221,55 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Blueprint Addition/Removal - private void addBlueprintFor(HitObject hitObject) + protected virtual void AddBlueprintFor(T item) { - if (hitObject is IBarLine) + if (blueprintMap.ContainsKey(item)) return; - if (blueprintMap.ContainsKey(hitObject)) - return; - - var blueprint = CreateBlueprintFor(hitObject); + var blueprint = CreateBlueprintFor(item); if (blueprint == null) return; - blueprintMap[hitObject] = blueprint; + blueprintMap[item] = blueprint; - blueprint.Selected += onBlueprintSelected; - blueprint.Deselected += onBlueprintDeselected; - - if (Beatmap.SelectedHitObjects.Contains(hitObject)) - blueprint.Select(); + blueprint.Selected += OnBlueprintSelected; + blueprint.Deselected += OnBlueprintDeselected; SelectionBlueprints.Add(blueprint); - OnBlueprintAdded(hitObject); + OnBlueprintAdded(blueprint); } - private void removeBlueprintFor(HitObject hitObject) + protected void RemoveBlueprintFor(T item) { - if (!blueprintMap.Remove(hitObject, out var blueprint)) + if (!blueprintMap.Remove(item, out var blueprint)) return; blueprint.Deselect(); - blueprint.Selected -= onBlueprintSelected; - blueprint.Deselected -= onBlueprintDeselected; + blueprint.Selected -= OnBlueprintSelected; + blueprint.Deselected -= OnBlueprintDeselected; SelectionBlueprints.Remove(blueprint); if (movementBlueprints?.Contains(blueprint) == true) finishSelectionMovement(); - OnBlueprintRemoved(hitObject); + OnBlueprintRemoved(blueprint); } /// /// Called after a blueprint has been added. /// - /// The for which the blueprint has been added. - protected virtual void OnBlueprintAdded(HitObject hitObject) + /// The for which the blueprint has been added. + protected virtual void OnBlueprintAdded(SelectionBlueprint blueprint) { } /// /// Called after a blueprint has been removed. /// - /// The for which the blueprint has been removed. - protected virtual void OnBlueprintRemoved(HitObject hitObject) + /// The for which the blueprint has been removed. + protected virtual void OnBlueprintRemoved(SelectionBlueprint item) { } @@ -347,7 +291,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) { if (!blueprint.IsHovered) continue; @@ -371,7 +315,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) { if (!blueprint.IsHovered) continue; @@ -405,7 +349,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case SelectionState.Selected: // if the editor is playing, we generally don't want to deselect objects even if outside the selection area. - if (!EditorClock.IsRunning && !isValidForSelection()) + if (AllowDeselection && !isValidForSelection()) blueprint.Deselect(); break; } @@ -413,35 +357,29 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Selects all s. + /// Selects all s. /// - private void selectAll() + protected virtual void SelectAll() { - Composer.Playfield.KeepAllAlive(); - // Scheduled to allow the change in lifetime to take place. Schedule(() => SelectionBlueprints.ToList().ForEach(m => m.Select())); } /// - /// Deselects all selected s. + /// Deselects all selected s. /// private void deselectAll() => SelectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); - private void onBlueprintSelected(SelectionBlueprint blueprint) + protected virtual void OnBlueprintSelected(SelectionBlueprint blueprint) { SelectionHandler.HandleSelected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 1); - - Composer.Playfield.SetKeepAlive(blueprint.HitObject, true); } - private void onBlueprintDeselected(SelectionBlueprint blueprint) + protected virtual void OnBlueprintDeselected(SelectionBlueprint blueprint) { SelectionBlueprints.ChangeChildDepth(blueprint, 0); SelectionHandler.HandleDeselected(blueprint); - - Composer.Playfield.SetKeepAlive(blueprint.HitObject, false); } #endregion @@ -449,7 +387,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement private Vector2[] movementBlueprintOriginalPositions; - private SelectionBlueprint[] movementBlueprints; + private SelectionBlueprint[] movementBlueprints; private bool isDraggingBlueprint; /// @@ -466,10 +404,12 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject - movementBlueprints = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).ToArray(); + movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); } + protected virtual IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints; + /// /// Moves the current selected blueprints. /// @@ -497,7 +437,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (positionalResult.ScreenSpacePosition == testPosition) continue; // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) return true; } @@ -510,20 +450,12 @@ namespace osu.Game.Screens.Edit.Compose.Components // Retrieve a snapped position. var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); - // Move the hitobjects. - if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), result.ScreenSpacePosition))) - return true; + return ApplySnapResult(movementBlueprints, result); + } - if (result.Time.HasValue) - { - // Apply the start time at the newly snapped-to position - double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime; - - if (offset != 0) - Beatmap.PerformOnSelection(obj => obj.StartTime += offset); - } - - return true; + protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) + { + return !SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); } /// @@ -542,22 +474,5 @@ namespace osu.Game.Screens.Edit.Compose.Components } #endregion - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (Beatmap != null) - { - Beatmap.HitObjectAdded -= addBlueprintFor; - Beatmap.HitObjectRemoved -= removeBlueprintFor; - } - - if (Composer != null) - { - Composer.Playfield.HitObjectUsageBegan -= addBlueprintFor; - Composer.Playfield.HitObjectUsageFinished -= removeBlueprintFor; - } - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b0a6a091f0..994e862946 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -27,12 +27,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A blueprint container generally displayed as an overlay to a ruleset's playfield. /// - public class ComposeBlueprintContainer : BlueprintContainer + public class ComposeBlueprintContainer : EditorBlueprintContainer { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private readonly Container placementBlueprintContainer; + protected new EditorSelectionHandler SelectionHandler => (EditorSelectionHandler)base.SelectionHandler; + private PlacementBlueprint currentPlacement; private InputManager inputManager; @@ -113,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // convert to game space coordinates delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); - SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); } private void updatePlacementNewCombo() @@ -237,7 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components updatePlacementPosition(); } - protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); @@ -249,9 +251,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - protected override void OnBlueprintAdded(HitObject hitObject) + protected override void OnBlueprintAdded(SelectionBlueprint item) { - base.OnBlueprintAdded(hitObject); + base.OnBlueprintAdded(item); refreshTool(); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs new file mode 100644 index 0000000000..aef02d5ffd --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -0,0 +1,176 @@ +// 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.Collections.Specialized; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class EditorBlueprintContainer : BlueprintContainer + { + [Resolved] + protected EditorClock EditorClock { get; private set; } + + [Resolved] + protected EditorBeatmap Beatmap { get; private set; } + + protected readonly HitObjectComposer Composer; + + private readonly BindableList selectedHitObjects = new BindableList(); + + protected EditorBlueprintContainer(HitObjectComposer composer) + { + Composer = composer; + } + + [BackgroundDependencyLoader] + private void load() + { + // For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context. + if (Composer != null) + { + foreach (var obj in Composer.HitObjects) + AddBlueprintFor(obj.HitObject); + } + + selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); + selectedHitObjects.CollectionChanged += (selectedObjects, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var o in args.NewItems) + SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select(); + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var o in args.OldItems) + SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect(); + + break; + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.HitObjectAdded += AddBlueprintFor; + Beatmap.HitObjectRemoved += RemoveBlueprintFor; + + if (Composer != null) + { + // For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above. + foreach (var obj in Composer.HitObjects) + AddBlueprintFor(obj.HitObject); + + Composer.Playfield.HitObjectUsageBegan += AddBlueprintFor; + Composer.Playfield.HitObjectUsageFinished += RemoveBlueprintFor; + } + } + + protected override IEnumerable> SortForMovement(IReadOnlyList> blueprints) + => blueprints.OrderBy(b => b.Item.StartTime); + + protected override bool AllowDeselection => !EditorClock.IsRunning; + + protected override bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) + { + if (!base.ApplySnapResult(blueprints, result)) + return false; + + if (result.Time.HasValue) + { + // Apply the start time at the newly snapped-to position + double offset = result.Time.Value - blueprints.First().Item.StartTime; + + if (offset != 0) + Beatmap.PerformOnSelection(obj => obj.StartTime += offset); + } + + return true; + } + + protected override void AddBlueprintFor(HitObject item) + { + if (item is IBarLine) + return; + + base.AddBlueprintFor(item); + } + + protected override void OnBlueprintAdded(SelectionBlueprint blueprint) + { + base.OnBlueprintAdded(blueprint); + if (Beatmap.SelectedHitObjects.Contains(blueprint.Item)) + blueprint.Select(); + } + + protected override void UpdateSelection() + { + base.UpdateSelection(); + + // handle positional change etc. + foreach (var blueprint in SelectionBlueprints) + Beatmap.Update(blueprint.Item); + } + + protected override bool OnDoubleClick(DoubleClickEvent e) + { + if (!base.OnDoubleClick(e)) + return false; + + EditorClock?.SeekSmoothlyTo(ClickedBlueprint.Item.StartTime); + return true; + } + + protected override Container> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + + protected override void SelectAll() + { + Composer.Playfield.KeepAllAlive(); + + base.SelectAll(); + } + + protected override void OnBlueprintSelected(SelectionBlueprint blueprint) + { + base.OnBlueprintSelected(blueprint); + + Composer.Playfield.SetKeepAlive(blueprint.Item, true); + } + + protected override void OnBlueprintDeselected(SelectionBlueprint blueprint) + { + base.OnBlueprintDeselected(blueprint); + + Composer.Playfield.SetKeepAlive(blueprint.Item, false); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (Beatmap != null) + { + Beatmap.HitObjectAdded -= AddBlueprintFor; + Beatmap.HitObjectRemoved -= RemoveBlueprintFor; + } + + if (Composer != null) + { + Composer.Playfield.HitObjectUsageBegan -= AddBlueprintFor; + Composer.Playfield.HitObjectUsageFinished -= RemoveBlueprintFor; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs new file mode 100644 index 0000000000..a117d42574 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -0,0 +1,233 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Audio; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class EditorSelectionHandler : SelectionHandler, IHasContextMenu + { + [Resolved] + protected EditorBeatmap EditorBeatmap { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + createStateBindables(); + + // bring in updates from selection changes + EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); + EditorBeatmap.SelectedHitObjects.BindTo(SelectedItems); + + SelectedItems.CollectionChanged += (sender, args) => + { + Scheduler.AddOnce(UpdateTernaryStates); + }; + } + + internal override void HandleSelected(SelectionBlueprint blueprint) + { + base.HandleSelected(blueprint); + + // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. + if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.Item)) + EditorBeatmap.SelectedHitObjects.Add(blueprint.Item); + } + + internal override void HandleDeselected(SelectionBlueprint blueprint) + { + base.HandleDeselected(blueprint); + + EditorBeatmap.SelectedHitObjects.Remove(blueprint.Item); + } + + protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); + + #region Selection State + + /// + /// The state of "new combo" for all selected hitobjects. + /// + public readonly Bindable SelectionNewComboState = new Bindable(); + + /// + /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. + /// + public readonly Dictionary> SelectionSampleStates = new Dictionary>(); + + /// + /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) + /// + private void createStateBindables() + { + foreach (var sampleName in HitSampleInfo.AllAdditions) + { + var bindable = new Bindable + { + Description = sampleName.Replace("hit", string.Empty).Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + RemoveHitSample(sampleName); + break; + + case TernaryState.True: + AddHitSample(sampleName); + break; + } + }; + + SelectionSampleStates[sampleName] = bindable; + } + + // new combo + SelectionNewComboState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetNewCombo(false); + break; + + case TernaryState.True: + SetNewCombo(true); + break; + } + }; + } + + /// + /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). + /// + protected virtual void UpdateTernaryStates() + { + SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); + + foreach (var (sampleName, bindable) in SelectionSampleStates) + { + bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); + } + } + + /// + /// Given a selection target and a function of truth, retrieve the correct ternary state for display. + /// + protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + + #endregion + + #region Sample Changes + + /// + /// Adds a hit sample to all selected s. + /// + /// The name of the hit sample. + public void AddHitSample(string sampleName) + { + EditorBeatmap.PerformOnSelection(h => + { + // Make sure there isn't already an existing sample + if (h.Samples.Any(s => s.Name == sampleName)) + return; + + h.Samples.Add(new HitSampleInfo(sampleName)); + }); + } + + /// + /// Set the new combo state of all selected s. + /// + /// Whether to set or unset. + /// Throws if any selected object doesn't implement + public void SetNewCombo(bool state) + { + EditorBeatmap.PerformOnSelection(h => + { + var comboInfo = h as IHasComboInformation; + + if (comboInfo == null || comboInfo.NewCombo == state) return; + + comboInfo.NewCombo = state; + EditorBeatmap.Update(h); + }); + } + + /// + /// Removes a hit sample from all selected s. + /// + /// The name of the hit sample. + public void RemoveHitSample(string sampleName) + { + EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); + } + + #endregion + + #region Context Menu + + public MenuItem[] ContextMenuItems + { + get + { + if (!SelectedBlueprints.Any(b => b.IsHovered)) + return Array.Empty(); + + var items = new List(); + + items.AddRange(GetContextMenuItemsForSelection(SelectedBlueprints)); + + if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) + { + items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); + } + + if (SelectedBlueprints.Count == 1) + items.AddRange(SelectedBlueprints[0].ContextMenuItems); + + items.AddRange(new[] + { + new OsuMenuItem("Sound") + { + Items = SelectionSampleStates.Select(kvp => + new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + }, + new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected), + }); + + return items.ToArray(); + } + } + + /// + /// Provide context menu items relevant to current selection. Calling base is not required. + /// + /// The current selection. + /// The relevant menu items. + protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + => Enumerable.Empty(); + + #endregion + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index d612cf3fe0..4078661a26 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -11,17 +11,17 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A container for ordered by their start times. + /// A container for ordered by their start times. /// - public sealed class HitObjectOrderedSelectionContainer : Container + public sealed class HitObjectOrderedSelectionContainer : Container> { - public override void Add(SelectionBlueprint drawable) + public override void Add(SelectionBlueprint drawable) { base.Add(drawable); bindStartTime(drawable); } - public override bool Remove(SelectionBlueprint drawable) + public override bool Remove(SelectionBlueprint drawable) { if (!base.Remove(drawable)) return false; @@ -36,11 +36,11 @@ namespace osu.Game.Screens.Edit.Compose.Components unbindAllStartTimes(); } - private readonly Dictionary startTimeMap = new Dictionary(); + private readonly Dictionary, IBindable> startTimeMap = new Dictionary, IBindable>(); - private void bindStartTime(SelectionBlueprint blueprint) + private void bindStartTime(SelectionBlueprint blueprint) { - var bindable = blueprint.HitObject.StartTimeBindable.GetBoundCopy(); + var bindable = blueprint.Item.StartTimeBindable.GetBoundCopy(); bindable.BindValueChanged(_ => { @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components startTimeMap[blueprint] = bindable; } - private void unbindStartTime(SelectionBlueprint blueprint) + private void unbindStartTime(SelectionBlueprint blueprint) { startTimeMap[blueprint].UnbindAll(); startTimeMap.Remove(blueprint); @@ -66,16 +66,16 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override int Compare(Drawable x, Drawable y) { - var xObj = (SelectionBlueprint)x; - var yObj = (SelectionBlueprint)y; + var xObj = (SelectionBlueprint)x; + var yObj = (SelectionBlueprint)y; // Put earlier blueprints towards the end of the list, so they handle input first - int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + int i = yObj.Item.StartTime.CompareTo(xObj.Item.StartTime); if (i != 0) return i; // Fall back to end time if the start time is equal. - i = yObj.HitObject.GetEndTime().CompareTo(xObj.HitObject.GetEndTime()); + i = yObj.Item.GetEndTime().CompareTo(xObj.Item.GetEndTime()); return i == 0 ? CompareReverseChildID(y, x) : i; } diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 0792d0f80e..a07b434011 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -9,12 +9,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// An event which occurs when a is moved. /// - public class MoveSelectionEvent + public class MoveSelectionEvent { /// - /// The that triggered this . + /// The that triggered this . /// - public readonly SelectionBlueprint Blueprint; + public readonly SelectionBlueprint Blueprint; /// /// The expected screen-space position of the hitobject at the current cursor position. @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly Vector2 InstantDelta; - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) { Blueprint = blueprint; ScreenSpacePosition = screenSpacePosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index b06e982859..1ee1de7d43 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -4,25 +4,19 @@ using System; using System.Collections.Generic; using System.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Input; @@ -31,16 +25,18 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public class SelectionHandler : CompositeDrawable, IKeyBindingHandler { /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. /// For more general selection operations, use instead. /// - public IEnumerable SelectedBlueprints => selectedBlueprints; + public IReadOnlyList> SelectedBlueprints => selectedBlueprints; - private readonly List selectedBlueprints; + protected BindableList SelectedItems = new BindableList(); + + private readonly List> selectedBlueprints; private Drawable content; @@ -48,15 +44,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected SelectionBox SelectionBox { get; private set; } - [Resolved] - protected EditorBeatmap EditorBeatmap { get; private set; } - [Resolved(CanBeNull = true)] protected IEditorChangeHandler ChangeHandler { get; private set; } public SelectionHandler() { - selectedBlueprints = new List(); + selectedBlueprints = new List>(); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -66,8 +59,6 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - createStateBindables(); - InternalChild = content = new Container { Children = new Drawable[] @@ -95,6 +86,11 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBox = CreateSelectionBox(), } }; + + SelectedItems.CollectionChanged += (sender, args) => + { + Scheduler.AddOnce(updateVisibility); + }; } public SelectionBox CreateSelectionBox() @@ -139,7 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be moved. /// Returning true will also propagate StartTime changes provided by the closest . /// - public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; /// /// Handles the selected s being rotated. @@ -174,7 +170,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (action.ActionMethod) { case PlatformActionMethod.Delete: - deleteSelected(); + DeleteSelected(); return true; } @@ -198,24 +194,18 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint becoming selected. /// /// The blueprint. - internal void HandleSelected(SelectionBlueprint blueprint) + internal virtual void HandleSelected(SelectionBlueprint blueprint) { selectedBlueprints.Add(blueprint); - - // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. - if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) - EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); } /// /// Handle a blueprint becoming deselected. /// /// The blueprint. - internal void HandleDeselected(SelectionBlueprint blueprint) + internal virtual void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); - - EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); } /// @@ -224,7 +214,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for selection. /// Whether a selection was performed. - internal bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (e.ShiftPressed && e.Button == MouseButton.Right) { @@ -248,7 +238,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for deselection. /// Whether a deselection was performed. - internal bool MouseUpSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal bool MouseUpSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (blueprint.IsSelected) { @@ -259,15 +249,19 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - private void handleQuickDeletion(SelectionBlueprint blueprint) + private void handleQuickDeletion(SelectionBlueprint blueprint) { if (blueprint.HandleQuickDeletion()) return; if (!blueprint.IsSelected) - EditorBeatmap.Remove(blueprint.HitObject); + DeleteItems(new[] { blueprint.Item }); else - deleteSelected(); + DeleteSelected(); + } + + protected virtual void DeleteItems(IEnumerable items) + { } /// @@ -275,7 +269,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The blueprint to select. /// Whether selection state was changed. - private bool ensureSelected(SelectionBlueprint blueprint) + private bool ensureSelected(SelectionBlueprint blueprint) { if (blueprint.IsSelected) return false; @@ -285,9 +279,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - private void deleteSelected() + protected void DeleteSelected() { - EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); + DeleteItems(selectedBlueprints.Select(b => b.Item)); } #endregion @@ -295,11 +289,11 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Outline Display /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// private void updateVisibility() { - int count = EditorBeatmap.SelectedHitObjects.Count; + int count = SelectedItems.Count; selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; @@ -340,188 +334,5 @@ namespace osu.Game.Screens.Edit.Compose.Components } #endregion - - #region Sample Changes - - /// - /// Adds a hit sample to all selected s. - /// - /// The name of the hit sample. - public void AddHitSample(string sampleName) - { - EditorBeatmap.PerformOnSelection(h => - { - // Make sure there isn't already an existing sample - if (h.Samples.Any(s => s.Name == sampleName)) - return; - - h.Samples.Add(new HitSampleInfo(sampleName)); - }); - } - - /// - /// Set the new combo state of all selected s. - /// - /// Whether to set or unset. - /// Throws if any selected object doesn't implement - public void SetNewCombo(bool state) - { - EditorBeatmap.PerformOnSelection(h => - { - var comboInfo = h as IHasComboInformation; - - if (comboInfo == null || comboInfo.NewCombo == state) return; - - comboInfo.NewCombo = state; - EditorBeatmap.Update(h); - }); - } - - /// - /// Removes a hit sample from all selected s. - /// - /// The name of the hit sample. - public void RemoveHitSample(string sampleName) - { - EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); - } - - #endregion - - #region Selection State - - /// - /// The state of "new combo" for all selected hitobjects. - /// - public readonly Bindable SelectionNewComboState = new Bindable(); - - /// - /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. - /// - public readonly Dictionary> SelectionSampleStates = new Dictionary>(); - - /// - /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) - /// - private void createStateBindables() - { - foreach (var sampleName in HitSampleInfo.AllAdditions) - { - var bindable = new Bindable - { - Description = sampleName.Replace("hit", string.Empty).Titleize() - }; - - bindable.ValueChanged += state => - { - switch (state.NewValue) - { - case TernaryState.False: - RemoveHitSample(sampleName); - break; - - case TernaryState.True: - AddHitSample(sampleName); - break; - } - }; - - SelectionSampleStates[sampleName] = bindable; - } - - // new combo - SelectionNewComboState.ValueChanged += state => - { - switch (state.NewValue) - { - case TernaryState.False: - SetNewCombo(false); - break; - - case TernaryState.True: - SetNewCombo(true); - break; - } - }; - - // bring in updates from selection changes - EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => - { - Scheduler.AddOnce(updateVisibility); - Scheduler.AddOnce(UpdateTernaryStates); - }; - } - - /// - /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). - /// - protected virtual void UpdateTernaryStates() - { - SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); - - foreach (var (sampleName, bindable) in SelectionSampleStates) - { - bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); - } - } - - /// - /// Given a selection target and a function of truth, retrieve the correct ternary state for display. - /// - protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) - { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - - return TernaryState.False; - } - - #endregion - - #region Context Menu - - public MenuItem[] ContextMenuItems - { - get - { - if (!selectedBlueprints.Any(b => b.IsHovered)) - return Array.Empty(); - - var items = new List(); - - items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints)); - - if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) - { - items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); - } - - if (selectedBlueprints.Count == 1) - items.AddRange(selectedBlueprints[0].ContextMenuItems); - - items.AddRange(new[] - { - new OsuMenuItem("Sound") - { - Items = SelectionSampleStates.Select(kvp => - new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() - }, - new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), - }); - - return items.ToArray(); - } - } - - /// - /// Provide context menu items relevant to current selection. Calling base is not required. - /// - /// The current selection. - /// The relevant menu items. - protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) - => Enumerable.Empty(); - - #endregion } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 3555bc2800..2bb80bc27b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -25,20 +25,17 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - internal class TimelineBlueprintContainer : BlueprintContainer + internal class TimelineBlueprintContainer : EditorBlueprintContainer { [Resolved(CanBeNull = true)] private Timeline timeline { get; set; } - [Resolved] - private EditorBeatmap beatmap { get; set; } - [Resolved] private OsuColour colours { get; set; } private DragEvent lastDragEvent; private Bindable placement; - private SelectionBlueprint placementBlueprint; + private SelectionBlueprint placementBlueprint; private SelectableAreaBackground backgroundBox; @@ -76,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.LoadComplete(); DragBox.Alpha = 0; - placement = beatmap.PlacementObject.GetBoundCopy(); + placement = Beatmap.PlacementObject.GetBoundCopy(); placement.ValueChanged += placementChanged; } @@ -100,7 +97,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override bool OnHover(HoverEvent e) { @@ -160,7 +157,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // remove objects from the stack as long as their end time is in the past. while (currentConcurrentObjects.TryPeek(out HitObject hitObject)) { - if (Precision.AlmostBigger(hitObject.GetEndTime(), b.HitObject.StartTime, 1)) + if (Precision.AlmostBigger(hitObject.GetEndTime(), b.Item.StartTime, 1)) break; currentConcurrentObjects.Pop(); @@ -168,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the stack gets too high, we should have space below it to display the next batch of objects. // importantly, we only do this if time has incremented, else a stack of hitobjects all at the same time value would start to overlap themselves. - if (currentConcurrentObjects.TryPeek(out HitObject h) && !Precision.AlmostEquals(h.StartTime, b.HitObject.StartTime, 1)) + if (currentConcurrentObjects.TryPeek(out HitObject h) && !Precision.AlmostEquals(h.StartTime, b.Item.StartTime, 1)) { if (currentConcurrentObjects.Count >= stack_reset_count) currentConcurrentObjects.Clear(); @@ -176,13 +173,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline b.Y = -(stack_offset * currentConcurrentObjects.Count); - currentConcurrentObjects.Push(b.HitObject); + currentConcurrentObjects.Push(b.Item); } } - protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { return new TimelineHitObjectBlueprint(hitObject) { @@ -239,10 +236,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - internal class TimelineSelectionHandler : SelectionHandler, IKeyBindingHandler + internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation - public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; public bool OnPressed(GlobalAction action) { @@ -344,13 +341,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected class TimelineSelectionBlueprintContainer : Container + protected class TimelineSelectionBlueprintContainer : Container> { - protected override Container Content { get; } + protected override Container> Content { get; } public TimelineSelectionBlueprintContainer() { - AddInternal(new TimelinePart(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + AddInternal(new TimelinePart>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 0425370ae5..dbe689be2f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -26,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineHitObjectBlueprint : SelectionBlueprint + public class TimelineHitObjectBlueprint : SelectionBlueprint { private const float circle_size = 38; @@ -49,13 +49,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private ISkinSource skin { get; set; } - public TimelineHitObjectBlueprint(HitObject hitObject) - : base(hitObject) + public TimelineHitObjectBlueprint(HitObject item) + : base(item) { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; - startTime = hitObject.StartTimeBindable.GetBoundCopy(); + startTime = item.StartTimeBindable.GetBoundCopy(); startTime.BindValueChanged(time => X = (float)time.NewValue, true); RelativePositionAxes = Axes.X; @@ -95,9 +95,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, }); - if (hitObject is IHasDuration) + if (item is IHasDuration) { - colouredComponents.Add(new DragArea(hitObject) + colouredComponents.Add(new DragArea(item) { OnDragHandled = e => OnDragHandled?.Invoke(e) }); @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - if (HitObject is IHasComboInformation comboInfo) + if (Item is IHasComboInformation comboInfo) { indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateComboColour() { - if (!(HitObject is IHasComboInformation combo)) + if (!(Item is IHasComboInformation combo)) return; var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline border.Hide(); } - if (HitObject is IHasDuration duration && duration.Duration > 0) + if (Item is IHasDuration duration && duration.Duration > 0) circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); else circle.Colour = comboColour; @@ -166,14 +166,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.Update(); // no bindable so we perform this every update - float duration = (float)(HitObject.GetEndTime() - HitObject.StartTime); + float duration = (float)(Item.GetEndTime() - Item.StartTime); if (Width != duration) { Width = duration; // kind of haphazard but yeah, no bindables. - if (HitObject is IHasRepeats repeats) + if (Item is IHasRepeats repeats) updateRepeats(repeats); } } From eac139ca0e0a1747199c3ac1110a434d25fa5429 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 17:41:46 +0900 Subject: [PATCH 090/161] Allow BlueprintContainer to perform movement without an `ISnapProvider` --- .../Compose/Components/BlueprintContainer.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a0bb4feadc..bd22c7329a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -420,25 +420,25 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprints == null) return false; - if (snapProvider == null) - return true; - Debug.Assert(movementBlueprintOriginalPositions != null); Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; - // check for positional snap for every object in selection (for things like object-object snapping) - for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) + if (snapProvider != null) { - var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; + // check for positional snap for every object in selection (for things like object-object snapping) + for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) + { + var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; - var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); + var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); - if (positionalResult.ScreenSpacePosition == testPosition) continue; + if (positionalResult.ScreenSpacePosition == testPosition) continue; - // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) - return true; + // attempt to move the objects, and abort any time based snapping if we can. + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) + return true; + } } // if no positional snapping could be performed, try unrestricted snapping from the earliest @@ -448,15 +448,18 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; // Retrieve a snapped position. - var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); + var result = snapProvider?.SnapScreenSpacePositionToValidTime(movePosition); + + if (result == null) + { + return SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), movePosition)); + } return ApplySnapResult(movementBlueprints, result); } - protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) - { - return !SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); - } + protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) => + SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); /// /// Finishes the current movement of selected blueprints. From 32416e4e313eb818464355124415d661e23b8b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 17:42:10 +0900 Subject: [PATCH 091/161] Move model selection handling to base `SelectionHandler` class --- .../Compose/Components/EditorSelectionHandler.cs | 16 ---------------- .../Edit/Compose/Components/SelectionHandler.cs | 5 +++++ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a117d42574..4c6833cc4a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -37,22 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - internal override void HandleSelected(SelectionBlueprint blueprint) - { - base.HandleSelected(blueprint); - - // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. - if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.Item)) - EditorBeatmap.SelectedHitObjects.Add(blueprint.Item); - } - - internal override void HandleDeselected(SelectionBlueprint blueprint) - { - base.HandleDeselected(blueprint); - - EditorBeatmap.SelectedHitObjects.Remove(blueprint.Item); - } - protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); #region Selection State diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1ee1de7d43..59ade746a9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -196,6 +196,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal virtual void HandleSelected(SelectionBlueprint blueprint) { + // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. + if (!SelectedItems.Contains(blueprint.Item)) + SelectedItems.Add(blueprint.Item); + selectedBlueprints.Add(blueprint); } @@ -205,6 +209,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal virtual void HandleDeselected(SelectionBlueprint blueprint) { + SelectedItems.Remove(blueprint.Item); selectedBlueprints.Remove(blueprint); } From f586bc46e624257e8e8c8438c644d4dfdcb240d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:03:34 +0900 Subject: [PATCH 092/161] Avoid using `EditorBeatmap.SelectedHitObjects` --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 92f5254182..5164c7f204 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -374,8 +374,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// All osu! hitobjects which can be moved/rotated/scaled. /// - private OsuHitObject[] selectedMovableObjects => EditorBeatmap.SelectedHitObjects - .OfType() + private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType() .Where(h => !(h is Spinner)) .ToArray(); From dd3d8e5d0388974191d4907e449814ec25b1576c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:06:27 +0900 Subject: [PATCH 093/161] Make `SelectionHandler` abstract to ensure things get implemented --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +-- .../Edit/Compose/Components/BlueprintContainer.cs | 2 +- .../Compose/Components/EditorBlueprintContainer.cs | 2 ++ .../Compose/Components/EditorSelectionHandler.cs | 4 ++-- .../Edit/Compose/Components/SelectionHandler.cs | 12 +++++++----- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 35896d4982..b47cf97a4d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -182,8 +182,7 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected virtual ComposeBlueprintContainer CreateBlueprintContainer() - => new ComposeBlueprintContainer(this); + protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this); /// /// Construct a drawable ruleset for the provided ruleset. diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index bd22c7329a..7f81629f80 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Creates a which outlines s and handles movement of selections. /// - protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + protected abstract SelectionHandler CreateSelectionHandler(); /// /// Creates a for a specific . diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index aef02d5ffd..0cb9cf930c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -135,6 +135,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override Container> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + protected override SelectionHandler CreateSelectionHandler() => new EditorSelectionHandler(); + protected override void SelectAll() { Composer.Playfield.KeepAllAlive(); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 4c6833cc4a..4b85b5cc0e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -101,11 +101,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); + SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); foreach (var (sampleName, bindable) in SelectionSampleStates) { - bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); + bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName)); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 59ade746a9..4b99aa5da7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable, IKeyBindingHandler + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler { /// /// The currently selected blueprints. @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved(CanBeNull = true)] protected IEditorChangeHandler ChangeHandler { get; private set; } - public SelectionHandler() + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -265,9 +265,11 @@ namespace osu.Game.Screens.Edit.Compose.Components DeleteSelected(); } - protected virtual void DeleteItems(IEnumerable items) - { - } + /// + /// Called whenever the deletion of items has been requested. + /// + /// The items to be deleted. + protected abstract void DeleteItems(IEnumerable items); /// /// Ensure the blueprint is in a selected state. From f97b14a20aeb786a03fe200e78a61376a5a23b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:15:24 +0900 Subject: [PATCH 094/161] Fix binding direction of selected items --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 4b85b5cc0e..d26a2946c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -29,8 +29,8 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - EditorBeatmap.SelectedHitObjects.BindTo(SelectedItems); + SelectedItems.BindTo(EditorBeatmap.SelectedHitObjects); SelectedItems.CollectionChanged += (sender, args) => { Scheduler.AddOnce(UpdateTernaryStates); From ff06a27a1237bc71febd6b4027449346c7c07ccd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:33:39 +0900 Subject: [PATCH 095/161] Revert changes to `OnBlueprint` methods and handle select-on-addition locally --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 7 ------- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 994e862946..f22404e77d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - protected override void OnBlueprintAdded(SelectionBlueprint item) + protected override void OnBlueprintAdded(HitObject item) { base.OnBlueprintAdded(item); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 0cb9cf930c..d17892d6ba 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -108,13 +108,6 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(item); } - protected override void OnBlueprintAdded(SelectionBlueprint blueprint) - { - base.OnBlueprintAdded(blueprint); - if (Beatmap.SelectedHitObjects.Contains(blueprint.Item)) - blueprint.Select(); - } - protected override void UpdateSelection() { base.UpdateSelection(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 4b99aa5da7..52c148f852 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public IReadOnlyList> SelectedBlueprints => selectedBlueprints; - protected BindableList SelectedItems = new BindableList(); + public BindableList SelectedItems = new BindableList(); private readonly List> selectedBlueprints; From 7ec5ea1eb5a761760da2133dc03a4b9bce1882aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:33:47 +0900 Subject: [PATCH 096/161] Remove hitobject terminology from base classes --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 +- .../Compose/Components/BlueprintContainer.cs | 46 +++++++++++-------- .../Components/ComposeBlueprintContainer.cs | 4 +- .../Compose/Components/SelectionHandler.cs | 37 +++++++-------- .../Timeline/TimelineBlueprintContainer.cs | 4 +- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 905e433731..b176ecb148 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,13 +8,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Edit { /// - /// A blueprint placed above a adding editing functionality. + /// A blueprint placed above a displaying item adding editing functionality. /// public abstract class SelectionBlueprint : CompositeDrawable, IStateful { @@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Edit protected virtual void OnDeselected() { - // selection blueprints are AlwaysPresent while the related DrawableHitObject is visible + // selection blueprints are AlwaysPresent while the related item is visible // set the body piece's alpha directly to avoid arbitrarily rendering frame buffers etc. of children. foreach (var d in InternalChildren) d.Hide(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7f81629f80..c2ba627df4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -14,15 +14,13 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A container which provides a "blueprint" display of hitobjects. + /// A container which provides a "blueprint" display of items. /// Includes selection and manipulation support via a . /// public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler @@ -65,15 +63,15 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual Container> CreateSelectionBlueprintContainer() => new Container> { RelativeSizeAxes = Axes.Both }; /// - /// Creates a which outlines s and handles movement of selections. + /// Creates a which outlines items and handles movement of selections. /// protected abstract SelectionHandler CreateSelectionHandler(); /// - /// Creates a for a specific . + /// Creates a for a specific item. /// - /// The to create the overlay for. - protected virtual SelectionBlueprint CreateBlueprintFor(T hitObject) => null; + /// The item to create the overlay for. + protected virtual SelectionBlueprint CreateBlueprintFor(T item) => null; protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); @@ -103,7 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ClickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); // Deselection should only occur if no selected blueprints are hovered - // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection + // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the item and should not trigger deselection if (endClickSelection(e) || ClickedBlueprint != null) return true; @@ -237,7 +235,10 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBlueprints.Add(blueprint); - OnBlueprintAdded(blueprint); + if (SelectionHandler.SelectedItems.Contains(item)) + blueprint.Select(); + + OnBlueprintAdded(blueprint.Item); } protected void RemoveBlueprintFor(T item) @@ -254,22 +255,24 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprints?.Contains(blueprint) == true) finishSelectionMovement(); - OnBlueprintRemoved(blueprint); + OnBlueprintRemoved(blueprint.Item); } /// - /// Called after a blueprint has been added. + /// Called after an item's blueprint has been added. /// - /// The for which the blueprint has been added. - protected virtual void OnBlueprintAdded(SelectionBlueprint blueprint) + /// The item for which the blueprint has been added. + protected virtual void OnBlueprintAdded(T item) { + if (SelectionHandler.SelectedItems.Contains(item)) + blueprintMap[item].Select(); } /// - /// Called after a blueprint has been removed. + /// Called after an item's blueprint has been removed. /// - /// The for which the blueprint has been removed. - protected virtual void OnBlueprintRemoved(SelectionBlueprint item) + /// The item for which the blueprint has been removed. + protected virtual void OnBlueprintRemoved(T item) { } @@ -398,16 +401,21 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!SelectionHandler.SelectedBlueprints.Any()) return; - // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement + // Any selected blueprint that is hovered can begin the movement of the group, however only the first item (according to SortForMovement) is used for movement. // A special case is added for when a click selection occurred before the drag if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return; - // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject + // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); } + /// + /// Apply sorting of selected blueprints before performing movement. Generally used to surface the "main" item to the beginning of the collection. + /// + /// The blueprints to be moved. + /// Sorted blueprints. protected virtual IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints; /// @@ -442,7 +450,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } // if no positional snapping could be performed, try unrestricted snapping from the earliest - // hitobject in the selection. + // item in the selection. // The final movement position, relative to movementBlueprintOriginalPosition. Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f22404e77d..f8ac0552ae 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -239,9 +239,9 @@ namespace osu.Game.Screens.Edit.Compose.Components updatePlacementPosition(); } - protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject item) { - var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); + var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == item); if (drawable == null) return null; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 52c148f852..8f2f1d65b8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -15,26 +15,27 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A component which outlines s and handles movement of selections. + /// A component which outlines items and handles movement of selections. /// public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler { /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. - /// For more general selection operations, use instead. + /// For more general selection operations, use instead. /// public IReadOnlyList> SelectedBlueprints => selectedBlueprints; - public BindableList SelectedItems = new BindableList(); + /// + /// The currently selected items. + /// + public readonly BindableList SelectedItems = new BindableList(); private readonly List> selectedBlueprints; @@ -124,45 +125,45 @@ namespace osu.Game.Screens.Edit.Compose.Components #region User Input Handling /// - /// Handles the selected s being moved. + /// Handles the selected items being moved. /// /// - /// Just returning true is enough to allow updates to take place. + /// Just returning true is enough to allow default movement to take place. /// Custom implementation is only required if other attributes are to be considered, like changing columns. /// /// The move event. /// - /// Whether any s could be moved. + /// Whether any items could be moved. /// Returning true will also propagate StartTime changes provided by the closest . /// public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; /// - /// Handles the selected s being rotated. + /// Handles the selected items being rotated. /// /// The delta angle to apply to the selection. - /// Whether any s could be rotated. + /// Whether any items could be rotated. public virtual bool HandleRotation(float angle) => false; /// - /// Handles the selected s being scaled. + /// Handles the selected items being scaled. /// /// The delta scale to apply, in playfield local coordinates. /// The point of reference where the scale is originating from. - /// Whether any s could be scaled. + /// Whether any items could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; /// - /// Handles the selected s being flipped. + /// Handles the selected items being flipped. /// /// The direction to flip - /// Whether any s could be flipped. + /// Whether any items could be flipped. public virtual bool HandleFlip(Direction direction) => false; /// - /// Handles the selected s being reversed pattern-wise. + /// Handles the selected items being reversed pattern-wise. /// - /// Whether any s could be reversed. + /// Whether any items could be reversed. public virtual bool HandleReverse() => false; public bool OnPressed(PlatformAction action) @@ -196,7 +197,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal virtual void HandleSelected(SelectionBlueprint blueprint) { - // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. + // there are potentially multiple SelectionHandlers active, but we only want to add items to the selected list once. if (!SelectedItems.Contains(blueprint.Item)) SelectedItems.Add(blueprint.Item); @@ -323,7 +324,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (selectedBlueprints.Count == 0) return; - // Move the rectangle to cover the hitobjects + // Move the rectangle to cover the items var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 2bb80bc27b..7c1bbd65f9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -179,9 +179,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected override SelectionBlueprint CreateBlueprintFor(HitObject item) { - return new TimelineHitObjectBlueprint(hitObject) + return new TimelineHitObjectBlueprint(item) { OnDragHandled = handleScrollViaDrag }; From 42255f8d3350673f085835da8eca68a6a3df382c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:57:51 +0900 Subject: [PATCH 097/161] Rename and xmldoc selection completed method --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 7 +++++-- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c2ba627df4..392b375d12 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { - UpdateSelection(); + DragOperationCompleted(); changeHandler?.EndChange(); } @@ -182,7 +182,10 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.Hide(); } - protected virtual void UpdateSelection() + /// + /// Called whenever a drag operation completes, before any change transaction is committed. + /// + protected virtual void DragOperationCompleted() { } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index d17892d6ba..9eee680bda 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -108,9 +108,9 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(item); } - protected override void UpdateSelection() + protected override void DragOperationCompleted() { - base.UpdateSelection(); + base.DragOperationCompleted(); // handle positional change etc. foreach (var blueprint in SelectionBlueprints) From b87446a5778ee76c96f02cd163beebdc44476e24 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 19:37:01 +0900 Subject: [PATCH 098/161] Simplify HitObjectLifetimeEntry logic a bit --- .../Objects/HitObjectLifetimeEntry.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 0181d6241b..0d1eb68f07 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -45,23 +45,16 @@ namespace osu.Game.Rulesets.Objects // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`). protected override void SetLifetimeStart(double start) { - // This assignment cannot be done in `SetLifetime` because otherwise setting only `LifetimeStart` will make `realLifetimeEnd` to be lost. realLifetimeStart = start; - base.SetLifetimeStart(start); + if (!keepAlive) + base.SetLifetimeStart(start); } protected override void SetLifetimeEnd(double end) { realLifetimeEnd = end; - base.SetLifetimeEnd(end); - } - - protected override void SetLifetime(double start, double end) - { - if (keepAlive) - base.SetLifetime(double.MinValue, double.MaxValue); - else - base.SetLifetime(start, end); + if (!keepAlive) + base.SetLifetimeEnd(end); } private bool keepAlive; @@ -77,7 +70,10 @@ namespace osu.Game.Rulesets.Objects return; keepAlive = value; - SetLifetime(realLifetimeStart, realLifetimeEnd); + if (keepAlive) + SetLifetime(double.MinValue, double.MaxValue); + else + SetLifetime(realLifetimeStart, realLifetimeEnd); } } From c4d28110d641c9d121d88e4559c783cfc2094218 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Tue, 27 Apr 2021 19:02:57 +0800 Subject: [PATCH 099/161] Add visual tests for timing based note coloring --- .../TestSceneTimingBasedNoteColouring.cs | 88 +++++++++++++++++++ .../Objects/Drawables/DrawableNote.cs | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs new file mode 100644 index 0000000000..3a9ddb6a9d --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Tests.Visual; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestSceneTimingBasedNoteColouring : OsuTestScene + { + [Resolved] + private RulesetConfigCache configCache { get; set; } + private readonly Bindable configTimingBasedNoteColouring = new Bindable(); + + protected override void LoadComplete() + { + const int beatLength = 500; + + var ruleset = new ManiaRuleset(); + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) + { + HitObjects = { + new Note { StartTime = 0 }, + new Note { StartTime = beatLength / 16 }, + new Note { StartTime = beatLength / 12 }, + new Note { StartTime = beatLength / 8 }, + new Note { StartTime = beatLength / 6 }, + new Note { StartTime = beatLength / 4 }, + new Note { StartTime = beatLength / 3 }, + new Note { StartTime = beatLength / 2 }, + new Note { StartTime = beatLength } + }, + ControlPointInfo = new ControlPointInfo(), + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty + { + SliderTickRate = 4, + OverallDifficulty = 10, + }, + Ruleset = ruleset.RulesetInfo + }, + }; + + foreach (var note in beatmap.HitObjects) + { + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = beatLength + } + ); + + Child = new Container + { + Clock = new FramedClock(new ManualClock()), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] + { + ruleset.CreateDrawableRulesetWith(beatmap) + } + }; + + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); + + AddStep("Enable", () => configTimingBasedNoteColouring.Value = true); + AddStep("Disable", () => configTimingBasedNoteColouring.Value = false); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 102cd485dc..942a32936c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private BeatDivisorFinder beatDivisorFinder { get; set; } private readonly Bindable configTimingBasedNoteColouring = new Bindable(); From 4752a0201ad2373c2bfefb9ae8328e5de8609298 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Tue, 27 Apr 2021 19:25:46 +0800 Subject: [PATCH 100/161] Fix cake errors --- .../TestSceneTimingBasedNoteColouring.cs | 26 +++--- osu.Game.Tests/NonVisual/BeatDivisorFinder.cs | 90 +++++++++---------- 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index 3a9ddb6a9d..5cfd7ff389 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -21,26 +21,28 @@ namespace osu.Game.Rulesets.Mania.Tests { [Resolved] private RulesetConfigCache configCache { get; set; } + private readonly Bindable configTimingBasedNoteColouring = new Bindable(); protected override void LoadComplete() { - const int beatLength = 500; + const double beat_length = 500; var ruleset = new ManiaRuleset(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) { - HitObjects = { + HitObjects = + { new Note { StartTime = 0 }, - new Note { StartTime = beatLength / 16 }, - new Note { StartTime = beatLength / 12 }, - new Note { StartTime = beatLength / 8 }, - new Note { StartTime = beatLength / 6 }, - new Note { StartTime = beatLength / 4 }, - new Note { StartTime = beatLength / 3 }, - new Note { StartTime = beatLength / 2 }, - new Note { StartTime = beatLength } + new Note { StartTime = beat_length / 16 }, + new Note { StartTime = beat_length / 12 }, + new Note { StartTime = beat_length / 8 }, + new Note { StartTime = beat_length / 6 }, + new Note { StartTime = beat_length / 4 }, + new Note { StartTime = beat_length / 3 }, + new Note { StartTime = beat_length / 2 }, + new Note { StartTime = beat_length } }, ControlPointInfo = new ControlPointInfo(), BeatmapInfo = @@ -59,10 +61,10 @@ namespace osu.Game.Rulesets.Mania.Tests note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } - beatmap.ControlPointInfo.Add(0, new TimingControlPoint() + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beatLength + BeatLength = beat_length } ); diff --git a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs index 742f5790d6..720d8e8ecd 100644 --- a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs +++ b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs @@ -14,32 +14,32 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestFindDivisor() { - const int beatLength = 1000; + const double beat_length = 1000; var beatmap = new Beatmap { HitObjects = new List { - new HitObject { StartTime = -beatLength / 3 }, + new HitObject { StartTime = -beat_length / 3 }, new HitObject { StartTime = 0 }, - new HitObject { StartTime = beatLength / 16 }, - new HitObject { StartTime = beatLength / 12 }, - new HitObject { StartTime = beatLength / 8 }, - new HitObject { StartTime = beatLength / 6 }, - new HitObject { StartTime = beatLength / 4 }, - new HitObject { StartTime = beatLength / 3 }, - new HitObject { StartTime = beatLength / 2 }, - new HitObject { StartTime = beatLength }, - new HitObject { StartTime = beatLength + beatLength / 7 } + new HitObject { StartTime = beat_length / 16 }, + new HitObject { StartTime = beat_length / 12 }, + new HitObject { StartTime = beat_length / 8 }, + new HitObject { StartTime = beat_length / 6 }, + new HitObject { StartTime = beat_length / 4 }, + new HitObject { StartTime = beat_length / 3 }, + new HitObject { StartTime = beat_length / 2 }, + new HitObject { StartTime = beat_length }, + new HitObject { StartTime = beat_length + beat_length / 7 } }, ControlPointInfo = new ControlPointInfo() }; - beatmap.ControlPointInfo.Add(0, new TimingControlPoint() - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beatLength - }); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = beat_length + }); var beatDivisorFinder = new BeatDivisorFinder(beatmap); @@ -59,49 +59,49 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestFindDivisorWithTempoChanges() { - const int firstBeatLength = 1000; - const int secondBeatLength = 700; - const int thirdBeatLength = 200; + const double first_beat_length = 1000; + const double second_beat_length = 700; + const double third_beat_length = 200; - const int firstBeatLengthStart = 0; - const int secondBeatLengthStart = 1000; - const int thirdBeatLengthStart = 2000; + const double first_beat_length_start = 0; + const double second_beat_length_start = 1000; + const double third_beat_length_start = 2000; var beatmap = new Beatmap { HitObjects = new List { - new HitObject { StartTime = firstBeatLengthStart }, - new HitObject { StartTime = firstBeatLengthStart + firstBeatLength / 2 }, - new HitObject { StartTime = secondBeatLengthStart }, - new HitObject { StartTime = secondBeatLengthStart + secondBeatLength / 2 }, - new HitObject { StartTime = thirdBeatLengthStart }, - new HitObject { StartTime = thirdBeatLengthStart + thirdBeatLength / 2 }, + new HitObject { StartTime = first_beat_length_start }, + new HitObject { StartTime = first_beat_length_start + first_beat_length / 2 }, + new HitObject { StartTime = second_beat_length_start }, + new HitObject { StartTime = second_beat_length_start + second_beat_length / 2 }, + new HitObject { StartTime = third_beat_length_start }, + new HitObject { StartTime = third_beat_length_start + third_beat_length / 2 }, }, ControlPointInfo = new ControlPointInfo() }; - var firstTimingControlPoint = new TimingControlPoint() + var firstTimingControlPoint = new TimingControlPoint { TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = firstBeatLength - }; - - var secondTimingControlPoint = new TimingControlPoint() - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = secondBeatLength - }; - - var thirdTimingControlPoint = new TimingControlPoint() - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = thirdBeatLength + BeatLength = first_beat_length }; - beatmap.ControlPointInfo.Add(firstBeatLengthStart, firstTimingControlPoint); - beatmap.ControlPointInfo.Add(secondBeatLengthStart, secondTimingControlPoint); - beatmap.ControlPointInfo.Add(thirdBeatLengthStart, thirdTimingControlPoint); + var secondTimingControlPoint = new TimingControlPoint + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = second_beat_length + }; + + var thirdTimingControlPoint = new TimingControlPoint + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = third_beat_length + }; + + beatmap.ControlPointInfo.Add(first_beat_length_start, firstTimingControlPoint); + beatmap.ControlPointInfo.Add(second_beat_length_start, secondTimingControlPoint); + beatmap.ControlPointInfo.Add(third_beat_length_start, thirdTimingControlPoint); var beatDivisorFinder = new BeatDivisorFinder(beatmap); From 200352b7507cb234f6fce5f287e06492caf87606 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 13:56:05 +0200 Subject: [PATCH 101/161] Rename unsnap check templates --- .../Editing/Checks/CheckUnsnappedObjectsTest.cs | 8 ++++---- .../Edit/Checks/CheckUnsnappedObjects.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index f8cac331bc..5e65b263f2 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -108,8 +108,8 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap)); } private Mock getSliderMock(double startTime, double endTime, int repeats = 0) @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); } private void assert2Ms(List hitobjects, int count = 1) @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap)); } private IBeatmap getPlayableBeatmap(List hitobjects) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index 74a2ce2fd7..8b6bb7d461 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable PossibleTemplates => new IssueTemplate[] { - new IssueTemplate2MsOrMore(this), - new IssueTemplate1MsOrMore(this) + new IssueTemplateLargeUnsnap(this), + new IssueTemplateSmallUnsnap(this) }; public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) @@ -55,9 +55,9 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "") { if (Math.Abs(unsnap) >= UNSNAP_MS_THRESHOLD) - yield return new IssueTemplate2MsOrMore(this).Create(hitobject, unsnap, time, postfix); + yield return new IssueTemplateLargeUnsnap(this).Create(hitobject, unsnap, time, postfix); else if (Math.Abs(unsnap) >= 1) - yield return new IssueTemplate1MsOrMore(this).Create(hitobject, unsnap, time, postfix); + yield return new IssueTemplateSmallUnsnap(this).Create(hitobject, unsnap, time, postfix); // We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works. } @@ -79,17 +79,17 @@ namespace osu.Game.Rulesets.Edit.Checks } } - public class IssueTemplate2MsOrMore : IssueTemplateUnsnap + public class IssueTemplateLargeUnsnap : IssueTemplateUnsnap { - public IssueTemplate2MsOrMore(ICheck check) + public IssueTemplateLargeUnsnap(ICheck check) : base(check, IssueType.Problem) { } } - public class IssueTemplate1MsOrMore : IssueTemplateUnsnap + public class IssueTemplateSmallUnsnap : IssueTemplateUnsnap { - public IssueTemplate1MsOrMore(ICheck check) + public IssueTemplateSmallUnsnap(ICheck check) : base(check, IssueType.Negligible) { } From 5dadfd04e7c11f0e69982401fe5647ba67621067 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 22:36:25 +0900 Subject: [PATCH 102/161] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e0b07549f4..5aee9e15cc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9a43d5f031..986bd8e7ba 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2c41b3ef26..c32109d6db 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 3b04aed491abef2278c34f497ead995a839c6f52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:31:23 +0900 Subject: [PATCH 103/161] Add failing test --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 20fa0732b9..2c862fe8a8 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -287,6 +287,23 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.AreEqual(expectedReturnValue, hitResult.IsScorable()); } + [TestCase(HitResult.Perfect, 1_000_000)] + [TestCase(HitResult.SmallTickHit, 1_000_000)] + [TestCase(HitResult.LargeTickHit, 1_000_000)] + [TestCase(HitResult.SmallBonus, 700_000 + Judgement.SMALL_BONUS_SCORE)] + [TestCase(HitResult.LargeBonus, 700_000 + Judgement.LARGE_BONUS_SCORE)] + public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore) + { + var statistic = new Dictionary { { result, 1 } }; + + scoreProcessor.ApplyBeatmap(new Beatmap + { + HitObjects = { new TestHitObject(result) } + }); + + Assert.That(scoreProcessor.GetImmediateScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } From 1281993f1f9b106c6e77c05cf59b96e85590feda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:35:31 +0900 Subject: [PATCH 104/161] Fix bonus score not calculated from the correct statistics --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fb5c2f4b5..a16a6a6c9b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), scoreResultCounts); + return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// From baa6e845aa812d91a15c655b459de4fbaa203c09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:38:37 +0900 Subject: [PATCH 105/161] Change to fluent assertions --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2c862fe8a8..ac26f4723e 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; @@ -51,7 +50,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(judgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } /// @@ -118,7 +117,7 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.ApplyResult(judgementResult); } - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } /// @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(lastJudgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } [Test] @@ -169,7 +168,7 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.Mode.Value = scoringMode; scoreProcessor.ApplyBeatmap(new TestBeatmap(new RulesetInfo())); - Assert.IsTrue(Precision.AlmostEquals(0, scoreProcessor.TotalScore.Value)); + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); } [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] From b8b6d0e861bd0269b841ceacfaab3c361128bc3b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 16:54:47 +0200 Subject: [PATCH 106/161] Add tests for `ClosestBeatDivisor` Used https://github.com/ppy/osu/pull/12558/files#diff-5c1f04c5b262ca3abbaf867aa91b62a60b66691323c286ad5aa0b75c153cc6ca as reference. --- .../NonVisual/ClosestBeatDivisorTest.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs new file mode 100644 index 0000000000..4d6986f5d2 --- /dev/null +++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.NonVisual +{ + public class ClosestBeatDivisorTest + { + [Test] + public void TestExactDivisors() + { + var cpi = new ControlPointInfo(); + cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 }); + + double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 }; + + assertClosestDivisors(divisors, divisors, cpi); + } + + [Test] + public void TestExactDivisorWithTempoChanges() + { + int offset = 0; + int[] beatLengths = { 1000, 200, 100, 50 }; + + var cpi = new ControlPointInfo(); + + foreach (int beatLength in beatLengths) + { + cpi.Add(offset, new TimingControlPoint { BeatLength = beatLength }); + offset += beatLength * 2; + } + + double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3 }; + + assertClosestDivisors(divisors, divisors, cpi); + } + + [Test] + public void TestExactDivisorsHighBPMStream() + { + var cpi = new ControlPointInfo(); + cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing) + + // A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors. + double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 }; + double[] closestDivisors = { 4, 2, 4, 1, 4, 2, 4, 1 }; + + assertClosestDivisors(divisors, closestDivisors, cpi, step: 1 / 4d); + } + + [Test] + public void TestApproximateDivisors() + { + var cpi = new ControlPointInfo(); + cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 }); + + double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 }; + double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 }; + + assertClosestDivisors(divisors, closestDivisors, cpi); + } + + private void assertClosestDivisors(IReadOnlyList divisors, IReadOnlyList closestDivisors, ControlPointInfo cpi, double step = 1) + { + List hitobjects = new List(); + double offset = cpi.TimingPoints[0].Time; + + for (int i = 0; i < divisors.Count; ++i) + { + double beatLength = cpi.TimingPointAt(offset).BeatLength; + hitobjects.Add(new HitObject { StartTime = offset + beatLength / divisors[i] }); + offset += beatLength * step; + } + + var beatmap = new Beatmap + { + HitObjects = hitobjects, + ControlPointInfo = cpi + }; + + for (int i = 0; i < divisors.Count; ++i) + Assert.AreEqual(closestDivisors[i], beatmap.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); + } + } +} From 4e3ee773968e52788ccf73bca74fe883d72da113 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 02:46:34 +0900 Subject: [PATCH 107/161] Add support for custom controls to SettingSourceAttribute --- .../Mods/SettingsSourceAttributeTest.cs | 29 +++++++++++++++++++ .../Configuration/SettingSourceAttribute.cs | 29 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index 883c9d1ac2..dd105787fa 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -4,7 +4,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; namespace osu.Game.Tests.Mods { @@ -26,6 +29,16 @@ namespace osu.Game.Tests.Mods Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting))); } + [Test] + public void TestCustomControl() + { + var objectWithCustomSettingControl = new ClassWithCustomSettingControl(); + var settings = objectWithCustomSettingControl.CreateSettingsControls().ToArray(); + + Assert.That(settings, Has.Length.EqualTo(1)); + Assert.That(settings[0], Is.TypeOf()); + } + private class ClassWithSettings { [SettingSource("Unordered setting", "Should be last")] @@ -40,5 +53,21 @@ namespace osu.Game.Tests.Mods [SettingSource("Third setting", "Yet another description", 3)] public BindableInt ThirdSetting { get; set; } = new BindableInt(); } + + private class ClassWithCustomSettingControl + { + [SettingSource("Custom setting", "Should be a custom control", SettingControlType = typeof(CustomSettingsControl))] + public BindableInt UnorderedSetting { get; set; } = new BindableInt(); + } + + private class CustomSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new CustomControl(); + + private class CustomControl : Drawable, IHasCurrentValue + { + public Bindable Current { get; set; } = new Bindable(); + } + } } } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cfce615130..13cf0a9f0c 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Overlays.Settings; @@ -31,7 +34,15 @@ namespace osu.Game.Configuration public int? OrderPosition { get; } - public SettingSourceAttribute(string label, string description = null) + /// + /// The type of the settings control which handles this setting source. + /// + /// + /// Must be a type deriving with a public constructor. + /// + public Type? SettingControlType { get; set; } + + public SettingSourceAttribute(string? label, string? description = null) { Label = label ?? string.Empty; Description = description ?? string.Empty; @@ -67,6 +78,22 @@ namespace osu.Game.Configuration { object value = property.GetValue(obj); + if (attr.SettingControlType != null) + { + var controlType = attr.SettingControlType; + if (controlType.EnumerateBaseTypes().All(t => !t.IsGenericType || t.GetGenericTypeDefinition() != typeof(SettingsItem<>))) + throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} had an unsupported custom control type ({controlType.ReadableName()})"); + + var control = (Drawable)Activator.CreateInstance(controlType); + controlType.GetProperty(nameof(SettingsItem.LabelText))?.SetValue(control, attr.Label); + controlType.GetProperty(nameof(SettingsItem.TooltipText))?.SetValue(control, attr.Description); + controlType.GetProperty(nameof(SettingsItem.Current))?.SetValue(control, value); + + yield return control; + + continue; + } + switch (value) { case BindableNumber bNumber: From 61b7dc1e0601feea555c2841249714a9bc684f68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:42:29 +0900 Subject: [PATCH 108/161] Fix bonus-only maps having 700K base score --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 12 ++++++------ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index ac26f4723e..baa10663d5 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -289,8 +289,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.Perfect, 1_000_000)] [TestCase(HitResult.SmallTickHit, 1_000_000)] [TestCase(HitResult.LargeTickHit, 1_000_000)] - [TestCase(HitResult.SmallBonus, 700_000 + Judgement.SMALL_BONUS_SCORE)] - [TestCase(HitResult.LargeBonus, 700_000 + Judgement.LARGE_BONUS_SCORE)] + [TestCase(HitResult.SmallBonus, 1_000_000 + Judgement.SMALL_BONUS_SCORE)] + [TestCase(HitResult.LargeBonus, 1_000_000 + Judgement.LARGE_BONUS_SCORE)] public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore) { var statistic = new Dictionary { { result, 1 } }; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a16a6a6c9b..f32f70d4ba 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring if (preferRolling && rollingMaxBaseScore != 0) return baseScore / rollingMaxBaseScore; - return maxBaseScore > 0 ? baseScore / maxBaseScore : 0; + return maxBaseScore > 0 ? baseScore / maxBaseScore : 1; } private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; From 04454062b7dad5cd47b6bd066c23a018e5290f06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:52:59 +0900 Subject: [PATCH 109/161] Fix up xmldoc --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 13cf0a9f0c..a9436aac89 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -38,7 +38,7 @@ namespace osu.Game.Configuration /// The type of the settings control which handles this setting source. /// /// - /// Must be a type deriving with a public constructor. + /// Must be a type deriving . /// public Type? SettingControlType { get; set; } From 05bd6ee50cc20a014c958c86752152ca80e3e553 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:54:42 +0900 Subject: [PATCH 110/161] Add back ctor doc --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index a9436aac89..3e50613093 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -38,7 +38,7 @@ namespace osu.Game.Configuration /// The type of the settings control which handles this setting source. /// /// - /// Must be a type deriving . + /// Must be a type deriving with a public parameterless constructor. /// public Type? SettingControlType { get; set; } From aa1cb65eaad96ebdc07d1a5e626284b91957af33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:42:10 +0900 Subject: [PATCH 111/161] Rename region to be more inclusive --- .../Components/EditorSelectionHandler.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index d26a2946c1..bb7abd8d8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion - #region Sample Changes + #region Ternary state changes /// /// Adds a hit sample to all selected s. @@ -140,6 +140,15 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } + /// + /// Removes a hit sample from all selected s. + /// + /// The name of the hit sample. + public void RemoveHitSample(string sampleName) + { + EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); + } + /// /// Set the new combo state of all selected s. /// @@ -158,15 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } - /// - /// Removes a hit sample from all selected s. - /// - /// The name of the hit sample. - public void RemoveHitSample(string sampleName) - { - EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); - } - #endregion #region Context Menu From 61d4eb177710e44f3e17586f01da6f23048b360c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:44:19 +0900 Subject: [PATCH 112/161] Remove unnecessary and out-of-place xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8f2f1d65b8..0a1f313407 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -134,7 +134,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The move event. /// /// Whether any items could be moved. - /// Returning true will also propagate StartTime changes provided by the closest . /// public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; From bc455005a5ef1dcd739518ec4e1dec86f3838457 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:44:50 +0900 Subject: [PATCH 113/161] Fix incorrect coordinate space mention in xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 0a1f313407..a19798ed9a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Handles the selected items being scaled. /// - /// The delta scale to apply, in playfield local coordinates. + /// The delta scale to apply, in local coordinates. /// The point of reference where the scale is originating from. /// Whether any items could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; From d0be8f9fb31e6794e68523f2be42c8cfe70223c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:45:36 +0900 Subject: [PATCH 114/161] Remove one more out-of-date comment --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 392b375d12..7c1b488163 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -354,7 +354,6 @@ namespace osu.Game.Screens.Edit.Compose.Components break; case SelectionState.Selected: - // if the editor is playing, we generally don't want to deselect objects even if outside the selection area. if (AllowDeselection && !isValidForSelection()) blueprint.Deselect(); break; From a9a5809e940f2f424958533d6f6e46e133b985f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:46:52 +0900 Subject: [PATCH 115/161] Fix incorrect xmldoc in `MoveSelectionEvent` --- .../Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index a07b434011..a32c30b579 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -17,12 +17,12 @@ namespace osu.Game.Screens.Edit.Compose.Components public readonly SelectionBlueprint Blueprint; /// - /// The expected screen-space position of the hitobject at the current cursor position. + /// The expected screen-space position of the blueprint's item at the current cursor position. /// public readonly Vector2 ScreenSpacePosition; /// - /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. + /// The distance between and the blueprint's current position, in the coordinate-space of the blueprint item's parent. /// public readonly Vector2 InstantDelta; From e4f2e0131c94afbfb95f8b680dfb8c8d37144a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 12:02:55 +0900 Subject: [PATCH 116/161] Rename `AllowDeselection` to better match use case --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 +++--- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7c1b488163..7f31afb9ba 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -76,9 +76,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); /// - /// Whether this component is in a state where deselection should be allowed. If false, selection will only be added to. + /// Whether this component is in a state where items outside a drag selection should be deselected. If false, selection will only be added to. /// - protected virtual bool AllowDeselection => true; + protected virtual bool AllowDeselectionDuringDrag => true; protected override bool OnMouseDown(MouseDownEvent e) { @@ -354,7 +354,7 @@ namespace osu.Game.Screens.Edit.Compose.Components break; case SelectionState.Selected: - if (AllowDeselection && !isValidForSelection()) + if (AllowDeselectionDuringDrag && !isValidForSelection()) blueprint.Deselect(); break; } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 9eee680bda..2a605f75d8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints.OrderBy(b => b.Item.StartTime); - protected override bool AllowDeselection => !EditorClock.IsRunning; + protected override bool AllowDeselectionDuringDrag => !EditorClock.IsRunning; protected override bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) { From 43772f4303aedcec12b075899b09873db3f766f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 12:03:41 +0900 Subject: [PATCH 117/161] Remove duplicated call to initially select blueprint --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7f31afb9ba..a7076bf40e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -267,8 +267,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The item for which the blueprint has been added. protected virtual void OnBlueprintAdded(T item) { - if (SelectionHandler.SelectedItems.Contains(item)) - blueprintMap[item].Select(); } /// From 532ec403951c0efdce9abdd4c29c085133079baf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 12:04:48 +0900 Subject: [PATCH 118/161] Remove unnecessary newline --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a7076bf40e..1f9cd0258e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -174,7 +174,6 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { DragOperationCompleted(); - changeHandler?.EndChange(); } From 4c9e94da2b05b5c5679848258223767b7927b8a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 13:43:16 +0900 Subject: [PATCH 119/161] Move context menu logic to base class --- .../Edit/TaikoSelectionHandler.cs | 3 ++ .../Components/EditorSelectionHandler.cs | 51 +++++-------------- .../Compose/Components/SelectionHandler.cs | 37 +++++++++++++- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 20366e5b3a..48ee0d4cf4 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -80,6 +80,9 @@ namespace osu.Game.Rulesets.Taiko.Edit if (selection.All(s => s.Item is TaikoHitObject)) yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; + + foreach (var item in base.GetContextMenuItemsForSelection(selection)) + yield return item; } public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index bb7abd8d8f..0fc305dcc4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -7,7 +7,6 @@ using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; @@ -17,7 +16,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Compose.Components { - public class EditorSelectionHandler : SelectionHandler, IHasContextMenu + public class EditorSelectionHandler : SelectionHandler { [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } @@ -171,46 +170,24 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Context Menu - public MenuItem[] ContextMenuItems - { - get - { - if (!SelectedBlueprints.Any(b => b.IsHovered)) - return Array.Empty(); - - var items = new List(); - - items.AddRange(GetContextMenuItemsForSelection(SelectedBlueprints)); - - if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) - { - items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); - } - - if (SelectedBlueprints.Count == 1) - items.AddRange(SelectedBlueprints[0].ContextMenuItems); - - items.AddRange(new[] - { - new OsuMenuItem("Sound") - { - Items = SelectionSampleStates.Select(kvp => - new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() - }, - new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected), - }); - - return items.ToArray(); - } - } - /// /// Provide context menu items relevant to current selection. Calling base is not required. /// /// The current selection. /// The relevant menu items. - protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) - => Enumerable.Empty(); + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + { + if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) + { + yield return new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; + } + + yield return new OsuMenuItem("Sound") + { + Items = SelectionSampleStates.Select(kvp => + new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + }; + } #endregion } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a19798ed9a..02df004129 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -8,12 +8,15 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -23,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines items and handles movement of selections. /// - public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { /// /// The currently selected blueprints. @@ -341,5 +344,37 @@ namespace osu.Game.Screens.Edit.Compose.Components } #endregion + + #region Context Menu + + public MenuItem[] ContextMenuItems + { + get + { + if (!SelectedBlueprints.Any(b => b.IsHovered)) + return Array.Empty(); + + var items = new List(); + + items.AddRange(GetContextMenuItemsForSelection(SelectedBlueprints)); + + if (SelectedBlueprints.Count == 1) + items.AddRange(SelectedBlueprints[0].ContextMenuItems); + + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected)); + + return items.ToArray(); + } + } + + /// + /// Provide context menu items relevant to current selection. Calling base is not required. + /// + /// The current selection. + /// The relevant menu items. + protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + => Enumerable.Empty(); + + #endregion } } From e0906daebf9f17dfca21fa87f776df29e9d568bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 13:49:41 +0900 Subject: [PATCH 120/161] Change one remaining instance of incorrect terminology in xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 02df004129..cb3424a250 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -312,7 +312,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Triggered whenever the set of selected objects changes. + /// Triggered whenever the set of selected items changes. /// Should update the selection box's state to match supported operations. /// protected virtual void OnSelectionChanged() From ac1534cda2fb51afad43bcc4e4f4fe945a232cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:54:40 +0900 Subject: [PATCH 121/161] Add test covering existing button actually changing to `LocallyAvailable` state --- osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 0c199bfb62..3fc894da0d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -43,7 +43,10 @@ namespace osu.Game.Tests.Visual.Online createButtonWithBeatmap(createSoleily()); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); + createButtonWithBeatmap(createSoleily()); AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); ensureSoleilyRemoved(); From 05e3a73a7d24acf00431405b96234498f830ddcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:54:58 +0900 Subject: [PATCH 122/161] Fix import cancellation not correctly being forwarded to import notification --- osu.Game/Database/ArchiveModelManager.cs | 49 +++++++++++++++--------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6719351530..dbeaebb1cd 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -156,33 +156,44 @@ namespace osu.Game.Database bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; - await Task.WhenAll(tasks.Select(async task => + try { - notification.CancellationToken.ThrowIfCancellationRequested(); - - try + await Task.WhenAll(tasks.Select(async task => { - var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); + notification.CancellationToken.ThrowIfCancellationRequested(); - lock (imported) + try { - if (model != null) - imported.Add(model); - current++; + var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; + lock (imported) + { + if (model != null) + imported.Add(model); + current++; + + notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / tasks.Length; + } } - } - catch (TaskCanceledException) + catch (TaskCanceledException) + { + throw; + } + catch (Exception e) + { + Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); + } + })).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (imported.Count == 0) { - throw; + notification.State = ProgressNotificationState.Cancelled; + return imported; } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - })).ConfigureAwait(false); + } if (imported.Count == 0) { From 8598a0968f17c0733ec38e030abceeddc8dca37a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:29:23 +0900 Subject: [PATCH 123/161] Update calculations in comments to match new logic Mostly look to be errors that existed before this PR. Co-authored-by: Endrik --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index baa10663d5..41e16ebeaf 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; From e71dbfd730df6a2909deb9b3b53ba027f9f45e29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:37:48 +0900 Subject: [PATCH 124/161] Add inline comment regarding remaining issues with classic scoring --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 41e16ebeaf..184a94912a 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -95,6 +95,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) + // TODO: The following two cases don't match expectations currently (a single hit is registered in acc portion when it shouldn't be). See https://github.com/ppy/osu/issues/12604. [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) From 48d6c9ac4bee70169ac982611545c9d276126988 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:47:30 +0900 Subject: [PATCH 125/161] Move snap/divisor helper methods to inside `ControlPointInfo` --- .../NonVisual/ClosestBeatDivisorTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 26 ------------ .../ControlPoints/ControlPointInfo.cs | 42 +++++++++++++++++++ osu.Game/Beatmaps/IBeatmap.cs | 22 ---------- .../Edit/Checks/CheckUnsnappedObjects.cs | 8 ++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 11 +---- osu.Game/Screens/Play/GameplayBeatmap.cs | 9 ---- 7 files changed, 49 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs index 4d6986f5d2..5ac121f5bc 100644 --- a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs +++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.NonVisual }; for (int i = 0; i < divisors.Count; ++i) - Assert.AreEqual(closestDivisors[i], beatmap.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); + Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); } } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 6515540527..e5b6a4bc44 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,7 +9,6 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; -using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps { @@ -75,31 +74,6 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) - { - var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); - var beatLength = timingPoint.BeatLength / beatDivisor; - var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - - // Casting to int matches stable. - return (int)(timingPoint.Time + beatLengths * beatLength); - } - - public int ClosestSnapTime(double time, double? referenceTime = null) - { - return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); - } - - public int ClosestBeatDivisor(double time, double? referenceTime = null) - { - double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); - - int[] divisors = BindableBeatDivisor.VALID_DIVISORS; - double smallestUnsnap = divisors.Min(getUnsnap); - - return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); - } - IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 5cc60a5758..d1a04061b9 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -7,6 +7,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; +using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps.ControlPoints { @@ -160,6 +161,47 @@ namespace osu.Game.Beatmaps.ControlPoints groups.Remove(group); } + /// + /// Returns the time on the given beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// The beat divisor to snap to. + /// An optional reference point to use for timing point lookup. + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) + { + var timingPoint = TimingPointAt(referenceTime ?? time); + var beatLength = timingPoint.BeatLength / beatDivisor; + var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + // Casting to int matches stable. + return (int)(timingPoint.Time + beatLengths * beatLength); + } + + /// + /// Returns the time on any valid beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// An optional reference point to use for timing point lookup. + public int ClosestSnapTime(double time, double? referenceTime = null) + { + return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); + } + + /// + /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. + /// + /// The time to find the closest beat snap divisor to. + /// An optional reference point to use for timing point lookup. + public int ClosestBeatDivisor(double time, double? referenceTime = null) + { + double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); + + int[] divisors = BindableBeatDivisor.VALID_DIVISORS; + double smallestUnsnap = divisors.Min(getUnsnap); + + return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); + } + /// /// Binary searches one of the control point lists to find the active control point at . /// Includes logic for returning a specific point when no matching point is found. diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 679d639fd1..769b33009a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -51,28 +51,6 @@ namespace osu.Game.Beatmaps /// double GetMostCommonBeatLength(); - /// - /// Returns the time on the given beat divisor closest to the given time. - /// - /// The time to find the closest snapped time to. - /// The beat divisor to snap to. - /// An optional reference point to use for timing point lookup. - int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null); - - /// - /// Returns the time on any valid beat divisor closest to the given time. - /// - /// The time to find the closest snapped time to. - /// An optional reference point to use for timing point lookup. - int ClosestSnapTime(double time, double? referenceTime = null); - - /// - /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. - /// - /// The time to find the closest beat snap divisor to. - /// An optional reference point to use for timing point lookup. - int ClosestBeatDivisor(double time, double? referenceTime = null); - /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index 8b6bb7d461..cc5ea2a988 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -24,9 +24,11 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { + var controlPointInfo = playableBeatmap.ControlPointInfo; + foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - playableBeatmap.ClosestSnapTime(hitobject.StartTime); + double startUnsnap = hitobject.StartTime - controlPointInfo.ClosestSnapTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - playableBeatmap.ClosestSnapTime(repeatTime); + double repeatUnsnap = repeatTime - controlPointInfo.ClosestSnapTime(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - playableBeatmap.ClosestSnapTime(hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - controlPointInfo.ClosestSnapTime(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 72fb0ac9e9..f1262daab3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,16 +301,7 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) - { - return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); - } - - public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - - public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); - - public double SnapTime(double time, double? referenceTime) => ClosestSnapTime(time, BeatDivisor, referenceTime); + public double SnapTime(double time, double? referenceTime) => ControlPointInfo.ClosestSnapTime(time, BeatDivisor, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 92f58c8759..74fbe540fa 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -45,15 +45,6 @@ namespace osu.Game.Screens.Play public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) - { - return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); - } - - public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - - public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); - public IBeatmap Clone() => PlayableBeatmap.Clone(); private readonly Bindable lastJudgementResult = new Bindable(); From f3c7694eeb8f78cd6368fd5701e22ed291e000ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:57:52 +0900 Subject: [PATCH 126/161] Rename methods to match generally how these find-methods are named elsewhere --- osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs | 2 +- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 10 +++++----- osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs index 5ac121f5bc..08cd80dcfa 100644 --- a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs +++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.NonVisual }; for (int i = 0; i < divisors.Count; ++i) - Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); + Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.GetClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); } } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d1a04061b9..60a8a40f06 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -167,7 +167,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the closest snapped time to. /// The beat divisor to snap to. /// An optional reference point to use for timing point lookup. - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) + public int GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); var beatLength = timingPoint.BeatLength / beatDivisor; @@ -182,9 +182,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the closest snapped time to. /// An optional reference point to use for timing point lookup. - public int ClosestSnapTime(double time, double? referenceTime = null) + public int GetClosestSnappedTime(double time, double? referenceTime = null) { - return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); + return GetClosestSnappedTime(time, GetClosestBeatDivisor(time, referenceTime), referenceTime); } /// @@ -192,9 +192,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the closest beat snap divisor to. /// An optional reference point to use for timing point lookup. - public int ClosestBeatDivisor(double time, double? referenceTime = null) + public int GetClosestBeatDivisor(double time, double? referenceTime = null) { - double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); + double getUnsnap(int divisor) => Math.Abs(time - GetClosestSnappedTime(time, divisor, referenceTime)); int[] divisors = BindableBeatDivisor.VALID_DIVISORS; double smallestUnsnap = divisors.Min(getUnsnap); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index cc5ea2a988..cdf3f05465 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit.Checks foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - controlPointInfo.ClosestSnapTime(hitobject.StartTime); + double startUnsnap = hitobject.StartTime - controlPointInfo.GetClosestSnappedTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - controlPointInfo.ClosestSnapTime(repeatTime); + double repeatUnsnap = repeatTime - controlPointInfo.GetClosestSnappedTime(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - controlPointInfo.ClosestSnapTime(hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - controlPointInfo.GetClosestSnappedTime(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f1262daab3..be53abbd55 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public double SnapTime(double time, double? referenceTime) => ControlPointInfo.ClosestSnapTime(time, BeatDivisor, referenceTime); + public double SnapTime(double time, double? referenceTime) => ControlPointInfo.GetClosestSnappedTime(time, BeatDivisor, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; From c5186b6a693e7ff3ff1b7737a5b4f81ba0cf1124 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:59:49 +0900 Subject: [PATCH 127/161] Revert return values to non-rounded doubles --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 60a8a40f06..fa1c59bb08 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -167,14 +167,13 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the closest snapped time to. /// The beat divisor to snap to. /// An optional reference point to use for timing point lookup. - public int GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) + public double GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); var beatLength = timingPoint.BeatLength / beatDivisor; var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - // Casting to int matches stable. - return (int)(timingPoint.Time + beatLengths * beatLength); + return timingPoint.Time + beatLengths * beatLength; } /// @@ -182,7 +181,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the closest snapped time to. /// An optional reference point to use for timing point lookup. - public int GetClosestSnappedTime(double time, double? referenceTime = null) + public double GetClosestSnappedTime(double time, double? referenceTime = null) { return GetClosestSnappedTime(time, GetClosestBeatDivisor(time, referenceTime), referenceTime); } From 859898d98f09b9f10e928eca51206be5519b3fbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 17:16:05 +0900 Subject: [PATCH 128/161] Refactor lookup methods to avoid linq and reduce `TimingPointAt` calls --- .../ControlPoints/ControlPointInfo.cs | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index fa1c59bb08..e47d48edcf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -170,35 +170,47 @@ namespace osu.Game.Beatmaps.ControlPoints public double GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); - var beatLength = timingPoint.BeatLength / beatDivisor; - var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - - return timingPoint.Time + beatLengths * beatLength; + return getClosestSnappedTime(timingPoint, time, beatDivisor); } /// - /// Returns the time on any valid beat divisor closest to the given time. + /// Returns the time on *ANY* valid beat divisor, favouring the divisor closest to the given time. /// /// The time to find the closest snapped time to. - /// An optional reference point to use for timing point lookup. - public double GetClosestSnappedTime(double time, double? referenceTime = null) - { - return GetClosestSnappedTime(time, GetClosestBeatDivisor(time, referenceTime), referenceTime); - } + public double GetClosestSnappedTime(double time) => GetClosestSnappedTime(time, GetClosestBeatDivisor(time)); /// - /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. + /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest divisor is returned. /// /// The time to find the closest beat snap divisor to. /// An optional reference point to use for timing point lookup. public int GetClosestBeatDivisor(double time, double? referenceTime = null) { - double getUnsnap(int divisor) => Math.Abs(time - GetClosestSnappedTime(time, divisor, referenceTime)); + TimingControlPoint timingPoint = TimingPointAt(referenceTime ?? time); - int[] divisors = BindableBeatDivisor.VALID_DIVISORS; - double smallestUnsnap = divisors.Min(getUnsnap); + int closestDivisor = 0; + double closestTime = double.MaxValue; - return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); + foreach (int divisor in BindableBeatDivisor.VALID_DIVISORS) + { + double distanceFromSnap = Math.Abs(time - getClosestSnappedTime(timingPoint, time, divisor)); + + if (distanceFromSnap < closestTime) + { + closestDivisor = divisor; + closestTime = distanceFromSnap; + } + } + + return closestDivisor; + } + + private static double getClosestSnappedTime(TimingControlPoint timingPoint, double time, int beatDivisor) + { + var beatLength = timingPoint.BeatLength / beatDivisor; + var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + return timingPoint.Time + beatLengths * beatLength; } /// From 126056c43626cfbe5fcb9906ceb8d8e8425029a9 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Wed, 28 Apr 2021 19:27:18 +0800 Subject: [PATCH 129/161] Fix precision loss on exporting legacy replays --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 56c4e75864..144aeeebce 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,8 +95,9 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{(int)Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; + lastF.Time = Math.Round(f.Time); } } From 4fe1497f63ec6f65badc27505821a311690226db Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:23:56 +0800 Subject: [PATCH 130/161] Add comment & remove lastF --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 144aeeebce..13876f1648 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -91,13 +91,13 @@ namespace osu.Game.Scoring.Legacy if (score.Replay != null) { - LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None); - + int lastTimeRounded = 0; foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); - lastF = f; - lastF.Time = Math.Round(f.Time); + // Rounding because stable could only parse integral values + int timeRounded = (int)Math.Round(f.Time); + replayData.Append(FormattableString.Invariant($"{timeRounded - lastTimeRounded}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + lastTimeRounded = timeRounded; } } From 6d7eef3f5a437e1886908f3440138700e0dcd9e2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 16:45:57 +0000 Subject: [PATCH 131/161] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9e9af23b27 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/" + schedule: + interval: monthly + time: "17:00" + open-pull-requests-limit: 99 + ignore: + - dependency-name: Microsoft.EntityFrameworkCore.Design + versions: + - "> 2.2.6" + - dependency-name: Microsoft.EntityFrameworkCore.Sqlite + versions: + - "> 2.2.6" + - dependency-name: Microsoft.EntityFrameworkCore.Sqlite.Core + versions: + - "> 2.2.6" + - dependency-name: Microsoft.Extensions.DependencyInjection + versions: + - ">= 5.a, < 6" + - dependency-name: NUnit3TestAdapter + versions: + - ">= 3.16.a, < 3.17" + - dependency-name: Microsoft.NET.Test.Sdk + versions: + - 16.9.1 + - dependency-name: Microsoft.Extensions.DependencyInjection + versions: + - 3.1.11 + - 3.1.12 + - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson + versions: + - 3.1.11 + - dependency-name: Microsoft.NETCore.Targets + versions: + - 5.0.0 + - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack + versions: + - 5.0.2 + - dependency-name: NUnit + versions: + - 3.13.1 + - dependency-name: Microsoft.AspNetCore.SignalR.Client + versions: + - 3.1.11 From 243605728d5f167680caa5bbb0dee1225960075d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Apr 2021 11:44:05 -0700 Subject: [PATCH 132/161] Fix approved maps not displaying pp column on score table --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index b598b7d97f..3e95d125de 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -59,8 +59,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); + var status = topScore.Beatmap?.Status; + var showPerformanceColumn = status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked); + scoreTable.DisplayScores(scoreInfos, showPerformanceColumn); scoreTable.Show(); var userScore = value.UserScore; From 921d4510478943f5b6c195044d30102f2a9f2fe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:49 +0000 Subject: [PATCH 133/161] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.4 to 5.0.5 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..c61c59e589 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + From 5720e255c9b3ed3c1d466db57ca4eac65fae0a09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:53 +0000 Subject: [PATCH 134/161] Bump Sentry from 3.2.0 to 3.3.4 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.2.0 to 3.3.4. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.2.0...3.3.4) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..abb1e40b64 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + From 6c51bf523ca0740feae42ade648b7bbffa49192d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:56 +0000 Subject: [PATCH 135/161] Bump SharpCompress from 0.28.1 to 0.28.2 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.28.1 to 0.28.2. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.28.1...0.28.2) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..189bc724fc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -32,7 +32,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..1a9f945ec8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -94,7 +94,7 @@ - + From 437e9201ba844fb6d7baac65ea679647d439b370 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:00 +0000 Subject: [PATCH 136/161] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..e2c8e2cf1b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + From 5b071c68e7ab85f7e7faab2e2ae79762140ba6ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:04 +0000 Subject: [PATCH 137/161] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..c9de4dd28a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From 1e7feff49d57fc2f0575a8a423083e0ff9a44d65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:08 +0000 Subject: [PATCH 138/161] Bump Humanizer from 2.8.26 to 2.9.9 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.8.26 to 2.9.9. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/main/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.8.26...v2.9.9) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..aa1599e69a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..7061bb5e84 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From 1b3b07d6a9f0ccb9e595a1c6ae69456254356b96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:12 +0000 Subject: [PATCH 139/161] Bump NUnit from 3.13.1 to 3.13.2 Bumps [NUnit](https://github.com/nunit/nunit) from 3.13.1 to 3.13.2. - [Release notes](https://github.com/nunit/nunit/releases) - [Changelog](https://github.com/nunit/nunit/blob/v3.13.2/CHANGES.md) - [Commits](https://github.com/nunit/nunit/compare/v3.13.1...v3.13.2) Signed-off-by: dependabot[bot] --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 98a32f9b3a..992f954a3a 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index afa7b03536..7571d1827a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index c9f87a8551..1c8ed54440 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index afa7b03536..7571d1827a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index ea43d9a54c..bfcf4ef35e 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -8,7 +8,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index c2d9a923d9..77e9d672e3 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 64e934efd2..8f8b99b092 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f743d65db3..e01e858873 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index eab144592f..2dfa1dfbb7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index df6d17f615..895518e1b9 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index a4e52f8cd4..d5dda39aa5 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..b242811939 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -33,7 +33,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..423f87923b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -95,7 +95,7 @@ - + From 9c62c90cfc441a2688afbefb636f430aeea534b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 15:29:25 +0900 Subject: [PATCH 140/161] Refactor `SelectionBlueprint` and `MoveSelectionEvent` to work in screen-space coordinates Until now, the implementation of the overrides in `SelectionBlueprint` have been confusing to the point where I would just implement by trial-and-error (or copying from an existing implementation). This was due to a combination of using "object" space coordinates (ie. the thing the `Blueprint` is operating on) and screen-space coordinates. This change switches all event related coordinates to screen-space, which is how we already handle rotation/scale operations. With the introduction of other editor types where the related objects are drawables, this also makes a lot more sense. --- .../Edit/ManiaSelectionHandler.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 ++- osu.Game/Extensions/DrawableExtensions.cs | 11 +++++++++++ .../Rulesets/Edit/OverlaySelectionBlueprint.cs | 2 -- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 4 +--- .../Edit/Compose/Components/BlueprintContainer.cs | 11 +++++++---- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Edit/Compose/Components/MoveSelectionEvent.cs | 15 ++++----------- 8 files changed, 27 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index dd059c967c..7042110423 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Edit { var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; - var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); + var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.Blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); if (currentColumn == null) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 5164c7f204..c2c1f6d602 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; +using osu.Game.Extensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit // this will potentially move the selection out of bounds... foreach (var h in hitObjects) - h.Position += moveEvent.InstantDelta; + h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); // but this will be corrected. moveSelectionInBounds(); diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 67b9e727a5..a8de3f6407 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Threading; +using osuTK; namespace osu.Game.Extensions { @@ -32,5 +34,14 @@ namespace osu.Game.Extensions scheduler.Add(repeatDelegate); return repeatDelegate; } + + /// + /// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position. + /// + /// The drawable. + /// A delta in screen-space coordinates. + /// The delta vector in Parent's coordinates. + public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => + drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); } } diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 6369112d80..7911cf874b 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -34,7 +34,5 @@ namespace osu.Game.Rulesets.Edit public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; - - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => DrawableObject.Parent.ToLocalSpace(screenSpacePosition) - DrawableObject.Position; } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index b176ecb148..55703a2cd3 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Edit public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected. + /// The screen-space point that causes this to be selected via a drag. /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; @@ -136,8 +136,6 @@ namespace osu.Game.Rulesets.Edit /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; - public virtual Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; - /// /// Handle to perform a partial deletion when the user requests a quick delete (Shift+Right Click). /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 1f9cd0258e..361e98e0dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -436,14 +436,17 @@ namespace osu.Game.Screens.Edit.Compose.Components // check for positional snap for every object in selection (for things like object-object snapping) for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) { - var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; + Vector2 originalPosition = movementBlueprintOriginalPositions[i]; + var testPosition = originalPosition + distanceTravelled; var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); if (positionalResult.ScreenSpacePosition == testPosition) continue; + var delta = positionalResult.ScreenSpacePosition - movementBlueprints[i].ScreenSpaceSelectionPoint; + // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) return true; } } @@ -459,14 +462,14 @@ namespace osu.Game.Screens.Edit.Compose.Components if (result == null) { - return SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), movePosition)); + return SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), movePosition - movementBlueprints.First().ScreenSpaceSelectionPoint)); } return ApplySnapResult(movementBlueprints, result); } protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) => - SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition - blueprints.First().ScreenSpaceSelectionPoint)); /// /// Finishes the current movement of selected blueprints. diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f8ac0552ae..6c174e563e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // convert to game space coordinates delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); - SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); } private void updatePlacementNewCombo() diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index a32c30b579..4d4f4b76c6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -17,21 +17,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public readonly SelectionBlueprint Blueprint; /// - /// The expected screen-space position of the blueprint's item at the current cursor position. + /// The screen-space delta of this move event. /// - public readonly Vector2 ScreenSpacePosition; + public readonly Vector2 ScreenSpaceDelta; - /// - /// The distance between and the blueprint's current position, in the coordinate-space of the blueprint item's parent. - /// - public readonly Vector2 InstantDelta; - - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceDelta) { Blueprint = blueprint; - ScreenSpacePosition = screenSpacePosition; - - InstantDelta = Blueprint.GetInstantDelta(ScreenSpacePosition); + ScreenSpaceDelta = screenSpaceDelta; } } } From e716162ac242a5a36f2b435339a54472707e9ffd Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:55:20 +0800 Subject: [PATCH 141/161] Fix formatting --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 13876f1648..f8dd6953ad 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -8,7 +8,6 @@ using System.Text; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; -using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -91,13 +90,14 @@ namespace osu.Game.Scoring.Legacy if (score.Replay != null) { - int lastTimeRounded = 0; + int lastTime = 0; + foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { // Rounding because stable could only parse integral values - int timeRounded = (int)Math.Round(f.Time); - replayData.Append(FormattableString.Invariant($"{timeRounded - lastTimeRounded}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); - lastTimeRounded = timeRounded; + int time = (int)Math.Round(f.Time); + replayData.Append(FormattableString.Invariant($"{time - lastTime}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + lastTime = time; } } From cfbf95b4331c777a5dbd109fb57b3e5f76e2fc93 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Apr 2021 14:11:35 -0700 Subject: [PATCH 142/161] Add HasPerformancePoints extension method --- ...tOnlineStatus.cs => BeatmapSetOnlineStatusExtensions.cs} | 6 ++++++ osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 4 +--- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) rename osu.Game/Beatmaps/{BeatmapSetOnlineStatus.cs => BeatmapSetOnlineStatusExtensions.cs} (61%) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs b/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs similarity index 61% rename from osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs rename to osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs index 5864975a2e..1de641f4f1 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs @@ -3,6 +3,12 @@ namespace osu.Game.Beatmaps { + public static class BeatmapSetOnlineStatusExtensions + { + public static bool HasPerformancePoints(this BeatmapSetOnlineStatus status) + => status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; + } + public enum BeatmapSetOnlineStatus { None = -3, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3e95d125de..8786cf0e63 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -59,10 +59,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); - var status = topScore.Beatmap?.Status; - var showPerformanceColumn = status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; - scoreTable.DisplayScores(scoreInfos, showPerformanceColumn); + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.HasPerformancePoints() ?? false); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 5cb834b510..57df54d851 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -111,7 +111,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; + + ppColumn.Alpha = value.Beatmap?.Status.HasPerformancePoints() ?? false ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); From 9952a5bfdb25c5130d1fe1462b5578a860d1880c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Apr 2021 14:24:29 -0700 Subject: [PATCH 143/161] Revert "Fix button being recreated on importing state" This reverts commit c9967f7b7486d5744aba911a144110811b76ef04. --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 85ed3f8767..a61640a02e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -268,13 +268,11 @@ namespace osu.Game.Overlays.BeatmapSet break; case DownloadState.Downloading: + case DownloadState.Importing: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); break; - case DownloadState.Importing: - break; - default: downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) From 25e0fb1cf9de0e01d8225f97f737cadf43390572 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Apr 2021 01:59:59 +0300 Subject: [PATCH 144/161] Refactor OsuModBarrelRoll to allow it's usage by other rulesets --- .../Mods/OsuModBarrelRoll.cs | 40 ++------------ osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModBarrelRoll.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 37ba401d42..f63edbd99f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -2,52 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToDrawableHitObjects + public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObjects { - private float currentRotation; - - [SettingSource("Roll speed", "Rotations per minute")] - public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) - { - MinValue = 0.02, - MaxValue = 12, - Precision = 0.01, - }; - - [SettingSource("Direction", "The direction of rotation")] - public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); - - public override string Name => "Barrel Roll"; - public override string Acronym => "BR"; - public override string Description => "The whole playfield is on a wheel!"; - public override double ScoreMultiplier => 1; - - public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; - - public void Update(Playfield playfield) - { - playfield.Rotation = currentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); - } - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - // scale the playfield to allow all hitobjects to stay within the visible region. - drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); - } + protected override Vector2 PlayfieldScale => new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); public void ApplyToDrawableHitObjects(IEnumerable drawables) { @@ -58,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (d) { case DrawableHitCircle circle: - circle.CirclePiece.Rotation = -currentRotation; + circle.CirclePiece.Rotation = -CurrentRotation; break; } }; diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs new file mode 100644 index 0000000000..d9424563c5 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + where TObject : HitObject + { + protected float CurrentRotation { get; private set; } + + [SettingSource("Roll speed", "Rotations per minute")] + public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) + { + MinValue = 0.02, + MaxValue = 12, + Precision = 0.01, + }; + + [SettingSource("Direction", "The direction of rotation")] + public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); + + public override string Name => "Barrel Roll"; + public override string Acronym => "BR"; + public override string Description => "The whole playfield is on a wheel!"; + public override double ScoreMultiplier => 1; + + public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; + + /// + /// Used to allow all hitobjects to stay within the visible region. + /// + protected abstract Vector2 PlayfieldScale { get; } + + public void Update(Playfield playfield) + { + playfield.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Playfield.Scale = PlayfieldScale; + } + } +} From 7bf3498e2add11f5b3590b2d76df409efce1e06d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Apr 2021 02:49:19 +0300 Subject: [PATCH 145/161] Calculate playfield scale locally --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 4 ---- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 13 +++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index f63edbd99f..9ae9653e9b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -6,15 +6,11 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osuTK; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObjects { - protected override Vector2 PlayfieldScale => new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); - public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var d in drawables) diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index d9424563c5..4c28c730ec 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -34,11 +35,6 @@ namespace osu.Game.Rulesets.Mods public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; - /// - /// Used to allow all hitobjects to stay within the visible region. - /// - protected abstract Vector2 PlayfieldScale { get; } - public void Update(Playfield playfield) { playfield.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); @@ -46,7 +42,12 @@ namespace osu.Game.Rulesets.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Playfield.Scale = PlayfieldScale; + // scale the playfield to allow all hitobjects to stay within the visible region. + + var playfieldSize = drawableRuleset.Playfield.DrawSize; + var minSide = MathF.Min(playfieldSize.X, playfieldSize.Y); + var maxSide = MathF.Max(playfieldSize.X, playfieldSize.Y); + drawableRuleset.Playfield.Scale = new Vector2(minSide / maxSide); } } } From e69ec91c072c7f4edac21eb944307a1999c8c485 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 11:25:39 +0900 Subject: [PATCH 146/161] Add xmldoc for `CurrentRotation` --- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 4c28c730ec..0d344b5269 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -15,6 +15,10 @@ namespace osu.Game.Rulesets.Mods public abstract class ModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset where TObject : HitObject { + /// + /// The current angle of rotation being applied by this mod. + /// Generally should be used to apply inverse rotation to elements which should not be rotated. + /// protected float CurrentRotation { get; private set; } [SettingSource("Roll speed", "Rotations per minute")] From e4f895b49046948816dc3f260ba399aad816f153 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 14:48:37 +0900 Subject: [PATCH 147/161] Fix editor buttons inheriting from `TriangleButton` when they have no need to --- .../Edit/Components/RadioButtons/DrawableRadioButton.cs | 4 +--- .../Edit/Components/TernaryButtons/DrawableTernaryButton.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 0cf7b83f3b..1f608d28fd 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class DrawableRadioButton : TriangleButton + public class DrawableRadioButton : OsuButton { /// /// Invoked when this has been selected. @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons selectedBackgroundColour = colours.BlueDark; selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); - Triangles.Alpha = 0; - Content.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index c72fff5c91..c43561eaa7 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.TernaryButtons { - internal class DrawableTernaryButton : TriangleButton + internal class DrawableTernaryButton : OsuButton { private Color4 defaultBackgroundColour; private Color4 defaultBubbleColour; @@ -43,8 +43,6 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons selectedBackgroundColour = colours.BlueDark; selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); - Triangles.Alpha = 0; - Content.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, From 786ab163f622c0770c2b63a27e54db958881f499 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 30 Apr 2021 12:40:16 -0700 Subject: [PATCH 148/161] Rename extension and move to bottom of file --- ...StatusExtensions.cs => BeatmapSetOnlineStatus.cs} | 12 ++++++------ .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Beatmaps/{BeatmapSetOnlineStatusExtensions.cs => BeatmapSetOnlineStatus.cs} (86%) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs similarity index 86% rename from osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs rename to osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs index 1de641f4f1..ae5a44cfcd 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs @@ -3,12 +3,6 @@ namespace osu.Game.Beatmaps { - public static class BeatmapSetOnlineStatusExtensions - { - public static bool HasPerformancePoints(this BeatmapSetOnlineStatus status) - => status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; - } - public enum BeatmapSetOnlineStatus { None = -3, @@ -20,4 +14,10 @@ namespace osu.Game.Beatmaps Qualified = 3, Loved = 4, } + + public static class BeatmapSetOnlineStatusExtensions + { + public static bool GrantsPerformancePoints(this BeatmapSetOnlineStatus status) + => status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; + } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 8786cf0e63..e9733bad20 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.HasPerformancePoints() ?? false); + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() ?? false); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 57df54d851..a4b58e74a6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Alpha = value.Beatmap?.Status.HasPerformancePoints() ?? false ? 1 : 0; + ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() ?? false ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); From fdf8c129474893b46e7e76b6cc1c19f835c82bcc Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 1 May 2021 11:57:47 +0800 Subject: [PATCH 149/161] Replace BeatDivisorFinder with GetClosestBeatDivisor --- .../Objects/Drawables/DrawableNote.cs | 13 +- .../UI/DrawableManiaRuleset.cs | 6 +- osu.Game.Tests/NonVisual/BeatDivisorFinder.cs | 116 ------------------ .../Rulesets/Objects/BeatDivisorFinder.cs | 55 --------- 4 files changed, 12 insertions(+), 178 deletions(-) delete mode 100644 osu.Game.Tests/NonVisual/BeatDivisorFinder.cs delete mode 100644 osu.Game/Rulesets/Objects/BeatDivisorFinder.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 942a32936c..d67c360301 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -6,10 +6,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private OsuColour colours { get; set; } [Resolved(canBeNull: true)] - private BeatDivisorFinder beatDivisorFinder { get; set; } + private IBeatmap beatmap { get; set; } private readonly Bindable configTimingBasedNoteColouring = new Bindable(); @@ -58,9 +58,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void LoadComplete() { - if (beatDivisorFinder != null) + if (beatmap != null) { - HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = beatDivisorFinder.FindDivisor(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(startTime => + { + snap.Value = beatmap.ControlPointInfo.GetClosestBeatDivisor(startTime.NewValue); + }, + true + ); } snap.BindValueChanged(_ => updateSnapColour()); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 0177c01240..536d7b39b7 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -45,8 +45,8 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - [Cached] - private BeatDivisorFinder beatDivisorFinder { get; set; } + [Cached(typeof(IBeatmap))] + private ManiaBeatmap cachedBeatmap { get; set; } public IEnumerable BarLines; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - beatDivisorFinder = new BeatDivisorFinder(Beatmap); + cachedBeatmap = Beatmap; } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs deleted file mode 100644 index 720d8e8ecd..0000000000 --- a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Tests.NonVisual -{ - public class BeatDivisorFinderTest - { - [Test] - public void TestFindDivisor() - { - const double beat_length = 1000; - - var beatmap = new Beatmap - { - HitObjects = new List - { - new HitObject { StartTime = -beat_length / 3 }, - new HitObject { StartTime = 0 }, - new HitObject { StartTime = beat_length / 16 }, - new HitObject { StartTime = beat_length / 12 }, - new HitObject { StartTime = beat_length / 8 }, - new HitObject { StartTime = beat_length / 6 }, - new HitObject { StartTime = beat_length / 4 }, - new HitObject { StartTime = beat_length / 3 }, - new HitObject { StartTime = beat_length / 2 }, - new HitObject { StartTime = beat_length }, - new HitObject { StartTime = beat_length + beat_length / 7 } - }, - ControlPointInfo = new ControlPointInfo() - }; - - beatmap.ControlPointInfo.Add(0, new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beat_length - }); - - var beatDivisorFinder = new BeatDivisorFinder(beatmap); - - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 3); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 16); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 12); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 8); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 6); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[6]), 4); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[7]), 3); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[8]), 2); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[9]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[10]), 0); - } - - [Test] - public void TestFindDivisorWithTempoChanges() - { - const double first_beat_length = 1000; - const double second_beat_length = 700; - const double third_beat_length = 200; - - const double first_beat_length_start = 0; - const double second_beat_length_start = 1000; - const double third_beat_length_start = 2000; - - var beatmap = new Beatmap - { - HitObjects = new List - { - new HitObject { StartTime = first_beat_length_start }, - new HitObject { StartTime = first_beat_length_start + first_beat_length / 2 }, - new HitObject { StartTime = second_beat_length_start }, - new HitObject { StartTime = second_beat_length_start + second_beat_length / 2 }, - new HitObject { StartTime = third_beat_length_start }, - new HitObject { StartTime = third_beat_length_start + third_beat_length / 2 }, - }, - ControlPointInfo = new ControlPointInfo() - }; - - var firstTimingControlPoint = new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = first_beat_length - }; - - var secondTimingControlPoint = new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = second_beat_length - }; - - var thirdTimingControlPoint = new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = third_beat_length - }; - - beatmap.ControlPointInfo.Add(first_beat_length_start, firstTimingControlPoint); - beatmap.ControlPointInfo.Add(second_beat_length_start, secondTimingControlPoint); - beatmap.ControlPointInfo.Add(third_beat_length_start, thirdTimingControlPoint); - - var beatDivisorFinder = new BeatDivisorFinder(beatmap); - - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 2); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 2); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 2); - } - } -} diff --git a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs deleted file mode 100644 index 1479b22942..0000000000 --- a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs +++ /dev/null @@ -1,55 +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 osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit; - -namespace osu.Game.Rulesets.Objects -{ - /// - /// Used to find the lowest beat divisor that a aligns to in an . - /// - public class BeatDivisorFinder - { - private readonly IBeatmap beatmap; - - /// - /// Creates a new instance. - /// - /// The beatmap to use when calculating beat divisor alignment. - public BeatDivisorFinder(IBeatmap beatmap) - { - this.beatmap = beatmap; - } - - /// - /// Finds the lowest beat divisor that the given aligns to. - /// Returns 0 if it does not align to any divisor. - /// - /// The to evaluate. - public int FindDivisor(HitObject hitObject) - { - TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - double snapResult = hitObject.StartTime - currentTimingPoint.Time; - - foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) - { - if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / divisor)) - return divisor; - } - - return 0; - } - - private const double leniency_ms = 1.0; - - private static bool almostDivisibleBy(double dividend, double divisor) - { - double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, leniency_ms) || Precision.AlmostEquals(remainder - divisor, 0, leniency_ms); - } - } -} From 0d077b7a5deb9a5ec4f44c5a8335cb063080062b Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 1 May 2021 14:13:42 +0800 Subject: [PATCH 150/161] Fix GetClosestBeatDivisor returning the wrong divisor --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e47d48edcf..d3a4b635f5 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -7,6 +7,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; +using osu.Framework.Utils; using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps.ControlPoints @@ -195,7 +196,7 @@ namespace osu.Game.Beatmaps.ControlPoints { double distanceFromSnap = Math.Abs(time - getClosestSnappedTime(timingPoint, time, divisor)); - if (distanceFromSnap < closestTime) + if (Precision.DefinitelyBigger(closestTime, distanceFromSnap)) { closestDivisor = divisor; closestTime = distanceFromSnap; From 0b06c5bcb198aec5a587fa45e035ef76d0665116 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 1 May 2021 15:00:18 +0800 Subject: [PATCH 151/161] Remove unneeded test data --- .../TestSceneTimingBasedNoteColouring.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index 5cfd7ff389..e14ad92842 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -45,15 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests new Note { StartTime = beat_length } }, ControlPointInfo = new ControlPointInfo(), - BeatmapInfo = - { - BaseDifficulty = new BeatmapDifficulty - { - SliderTickRate = 4, - OverallDifficulty = 10, - }, - Ruleset = ruleset.RulesetInfo - }, + BeatmapInfo = { Ruleset = ruleset.RulesetInfo }, }; foreach (var note in beatmap.HitObjects) @@ -62,11 +54,9 @@ namespace osu.Game.Rulesets.Mania.Tests } beatmap.ControlPointInfo.Add(0, new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beat_length - } - ); + { + BeatLength = beat_length + }); Child = new Container { From db815f793038ca0c0f58a7a70d831a4ff21d8b63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 May 2021 20:39:10 +0900 Subject: [PATCH 152/161] Tidy up implementation in `DrawableNote` --- .../Objects/Drawables/DrawableNote.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index d67c360301..36565e14aa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Drawable headPiece; - private readonly Bindable snap = new Bindable(); - public DrawableNote(Note hitObject) : base(hitObject) { @@ -58,17 +56,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void LoadComplete() { - if (beatmap != null) - { - HitObject.StartTimeBindable.BindValueChanged(startTime => - { - snap.Value = beatmap.ControlPointInfo.GetClosestBeatDivisor(startTime.NewValue); - }, - true - ); - } - - snap.BindValueChanged(_ => updateSnapColour()); + HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour()); configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true); } @@ -114,9 +102,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { - Colour = configTimingBasedNoteColouring.Value - ? BindableBeatDivisor.GetColourFor(snap.Value, colours) - : Color4.White; + if (beatmap == null) return; + + int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime); + + Colour = configTimingBasedNoteColouring.Value ? BindableBeatDivisor.GetColourFor(snapDivisor, colours) : Color4.White; } } } From a551958eeb413e7e4a4b34b72cc8ed53b14f6d5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 May 2021 21:32:45 +0900 Subject: [PATCH 153/161] Move caching of `IBeatmap` to base `DrawableRuleset` --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ---- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 536d7b39b7..4ee060e91e 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -45,9 +45,6 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - [Cached(typeof(IBeatmap))] - private ManiaBeatmap cachedBeatmap { get; set; } - public IEnumerable BarLines; protected override bool RelativeScaleBeatLengths => true; @@ -80,7 +77,6 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - cachedBeatmap = Beatmap; } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ca27e6b21a..a2dade2627 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -85,6 +85,7 @@ namespace osu.Game.Rulesets.UI /// /// The beatmap. /// + [Cached(typeof(IBeatmap))] public readonly Beatmap Beatmap; public override IEnumerable Objects => Beatmap.HitObjects; From 137be5dc971b6f6fddc300ac9dafc0512a38295f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 1 May 2021 14:14:07 -0700 Subject: [PATCH 154/161] Use equality operator instead of null coalescing Co-Authored-By: Salman Ahmed --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index e9733bad20..aff48919b4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() ?? false); + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index a4b58e74a6..262f321598 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() ?? false ? 1 : 0; + ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); From 07fe99025f9e2493e86a91ac078c509675b010d2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 1 May 2021 15:05:12 +0300 Subject: [PATCH 155/161] Use bounding box of blueprint for computing selection box area --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index cb3424a250..c11c20df2a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; @@ -332,8 +333,9 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var blueprint in selectedBlueprints) { - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprint.SelectionQuad.BottomRight)); + var blueprintRect = blueprint.SelectionQuad.AABBFloat; + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprintRect.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprintRect.BottomRight)); } topLeft -= new Vector2(5); From 0aa17e7c955b93e4b0b2283279ebdd16fcf9e7c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 May 2021 02:51:06 +0300 Subject: [PATCH 156/161] Rewrite selection box computation logic with `RectangleF`'s helper methods --- .../Compose/Components/SelectionHandler.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c11c20df2a..8335ece236 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -328,21 +328,15 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Move the rectangle to cover the items - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); + RectangleF selectionRect = ToLocalSpace(selectedBlueprints.First().SelectionQuad).AABBFloat; - foreach (var blueprint in selectedBlueprints) - { - var blueprintRect = blueprint.SelectionQuad.AABBFloat; - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprintRect.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprintRect.BottomRight)); - } + foreach (var blueprint in selectedBlueprints.Skip(1)) + selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(blueprint.SelectionQuad).AABBFloat); - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); + selectionRect = selectionRect.Inflate(5f); - content.Size = bottomRight - topLeft; - content.Position = topLeft; + content.Position = selectionRect.Location; + content.Size = selectionRect.Size; } #endregion From b83aa0bd76e59007e7e8063aefe34bc16ed58841 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 May 2021 06:21:14 +0300 Subject: [PATCH 157/161] Avoid LINQ in update --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8335ece236..917cbca4e1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -328,10 +328,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Move the rectangle to cover the items - RectangleF selectionRect = ToLocalSpace(selectedBlueprints.First().SelectionQuad).AABBFloat; + RectangleF selectionRect = ToLocalSpace(selectedBlueprints[0].SelectionQuad).AABBFloat; - foreach (var blueprint in selectedBlueprints.Skip(1)) - selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(blueprint.SelectionQuad).AABBFloat); + for (int i = 1; i < selectedBlueprints.Count; i++) + selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(selectedBlueprints[i].SelectionQuad).AABBFloat); selectionRect = selectionRect.Inflate(5f); From 3aa18e19c93272515a293946b4b468deea3fb1e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 May 2021 11:30:44 +0300 Subject: [PATCH 158/161] Revert "Bump Humanizer from 2.8.26 to 2.9.9" This reverts commit 1e7feff49d57fc2f0575a8a423083e0ff9a44d65. --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b25e462453..1e0eabfff7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index bbf0f6046c..e26e727e69 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From 59cb5f4679aab9b861fa3ca0b2c6ed473b22d330 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 09:26:43 -0700 Subject: [PATCH 159/161] Get recent count from api instead --- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 6 +++--- osu.Game/Users/User.cs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 53f6d375ca..9ac9040a98 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -39,6 +39,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks case ScoreType.Firsts: return user.ScoresFirstCount; + case ScoreType.Recent: + return user.ScoresRecentCount; + default: return 0; } @@ -50,9 +53,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks drawableItemIndex = 0; base.OnItemsReceived(items); - - if (type == ScoreType.Recent) - SetCount(items.Count); } protected override APIRequest> CreateRequest() => diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 74ffb7c457..beb41c3b06 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -147,6 +147,9 @@ namespace osu.Game.Users [JsonProperty(@"scores_first_count")] public int ScoresFirstCount; + [JsonProperty(@"scores_recent_count")] + public int ScoresRecentCount; + [JsonProperty(@"beatmap_playcounts_count")] public int BeatmapPlaycountsCount; From 3e74d61dab44ea25341aebd79cd02fe6792c1a05 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 09:27:32 -0700 Subject: [PATCH 160/161] Add best count from api --- .../Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs | 3 +++ osu.Game/Users/User.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 9ac9040a98..d06e0c87fc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -36,6 +36,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { switch (type) { + case ScoreType.Best: + return user.ScoresBestCount; + case ScoreType.Firsts: return user.ScoresFirstCount; diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index beb41c3b06..2e04693e82 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -144,6 +144,9 @@ namespace osu.Game.Users [JsonProperty(@"unranked_beatmapset_count")] public int UnrankedBeatmapsetCount; + [JsonProperty(@"scores_best_count")] + public int ScoresBestCount; + [JsonProperty(@"scores_first_count")] public int ScoresFirstCount; From cc056088bdf0cbf988f7b8185addbedd669ebb95 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 2 May 2021 14:31:06 -0700 Subject: [PATCH 161/161] Update profile subsections to use counters instead of missing text in line with web --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../Historical/PaginatedMostPlayedBeatmapContainer.cs | 2 +- osu.Game/Overlays/Profile/Sections/HistoricalSection.cs | 2 +- .../Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 4 ++-- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 780d7ea986..fe9c710bcc 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private readonly BeatmapSetType type; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string headerText) - : base(user, headerText, "", CounterVisibilityState.AlwaysVisible) + : base(user, headerText) { this.type = type; ItemsPerPage = 6; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index e5bb1f8008..eeb14e5e4f 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible) + : base(user, "Most Played Beatmaps") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 6e2b9873cf..4fbb7fc7d7 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Sections { new PlayHistorySubsection(User), new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", CounterVisibilityState.VisibleWhenZero), + new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)"), new ReplaysSubsection(User) }; } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 51e5622f68..e237b43b2e 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -38,8 +38,8 @@ namespace osu.Game.Overlays.Profile.Sections private OsuSpriteText missing; private readonly string missingText; - protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) - : base(user, headerText, counterVisibilityState) + protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "") + : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index d06e0c87fc..720cd4a3db 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missingText = "") - : base(user, headerText, missingText, counterVisibilityState) + public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText) + : base(user, headerText) { this.type = type; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index e41e414893..33f7c2f71a 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", CounterVisibilityState.AlwaysHidden, "No performance records. :("), - new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", CounterVisibilityState.AlwaysVisible) + new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance"), + new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks") }; } }