diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index ee88edbea1..4b008d2734 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -17,9 +17,11 @@ namespace osu.Game.Rulesets.Catch.Mods private const double fade_out_offset_multiplier = 0.6; private const double fade_out_duration_multiplier = 0.44; - protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - if (!(drawable is DrawableCatchHitObject catchDrawable)) + base.ApplyNormalVisibilityState(hitObject, state); + + if (!(hitObject is DrawableCatchHitObject catchDrawable)) return; if (catchDrawable.NestedHitObjects.Any()) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index f69cacd432..db5fbb0dd6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; - protected override bool IsFirstHideableObject(DrawableHitObject hitObject) => !(hitObject is DrawableSpinner); + protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner); public override void ApplyToDrawableHitObjects(IEnumerable drawables) { @@ -42,9 +42,17 @@ namespace osu.Game.Rulesets.Osu.Mods private double lastSliderHeadFadeOutStartTime; private double lastSliderHeadFadeOutDuration; - protected override void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, true); + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + base.ApplyIncreasedVisibilityState(hitObject, state); + applyState(hitObject, true); + } - protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, false); + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + base.ApplyNormalVisibilityState(hitObject, state); + applyState(hitObject, false); + } private void applyState(DrawableHitObject drawable, bool increaseVisibility) { diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index ad01bf036c..f35546d400 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,19 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Configuration; +using System; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects, IApplicableToScoreProcessor + public abstract class ModHidden : ModWithVisibilityAdjustment, IApplicableToScoreProcessor { public override string Name => "Hidden"; public override string Acronym => "HD"; @@ -21,37 +18,14 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; - protected Bindable IncreaseFirstObjectVisibility = new Bindable(); - /// /// Check whether the provided hitobject should be considered the "first" hideable object. /// Can be used to skip spinners, for instance. /// /// The hitobject to check. + [Obsolete("Use IsFirstAdjustableObject() instead.")] protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true; - public void ReadFromConfig(OsuConfigManager config) - { - IncreaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); - } - - public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) - { - if (IncreaseFirstObjectVisibility.Value) - { - drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)); - - var firstObject = drawables.FirstOrDefault(); - if (firstObject != null) - firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState; - - drawables = drawables.Skip(1); - } - - foreach (var dho in drawables) - dho.ApplyCustomUpdateState += ApplyHiddenState; - } - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ @@ -73,11 +47,28 @@ namespace osu.Game.Rulesets.Mods } } + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + base.ApplyIncreasedVisibilityState(hitObject, state); +#pragma warning disable 618 + ApplyFirstObjectIncreaseVisibilityState(hitObject, state); +#pragma warning restore 618 + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + base.ApplyNormalVisibilityState(hitObject, state); +#pragma warning disable 618 + ApplyHiddenState(hitObject, state); +#pragma warning restore 618 + } + /// /// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting. /// /// The hit object to apply the state change to. /// The state of the hit object. + [Obsolete("Use ApplyIncreasedVisibilityState() instead.")] protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state) { } @@ -87,6 +78,7 @@ namespace osu.Game.Rulesets.Mods /// /// The hit object to apply the state change to. /// The state of the hit object. + [Obsolete("Use ApplyNormalVisibilityState() instead.")] protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } diff --git a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs new file mode 100644 index 0000000000..89d1837348 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs @@ -0,0 +1,114 @@ +// 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.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModWithVisibilityAdjustment : Mod, IReadFromConfig, IApplicableToBeatmap, IApplicableToDrawableHitObjects + { + /// + /// The first adjustable object. + /// + protected HitObject FirstObject { get; private set; } + + /// + /// Whether the visibility of should be increased. + /// + protected readonly Bindable IncreaseFirstObjectVisibility = new Bindable(); + + /// + /// Check whether the provided hitobject should be considered the "first" adjustable object. + /// Can be used to skip spinners, for instance. + /// + /// The hitobject to check. + protected virtual bool IsFirstAdjustableObject(HitObject hitObject) => true; + + /// + /// Apply a special increased-visibility state to the first adjustable object.. + /// Only applicable if the user chooses to turn on the "increase first object visibility" setting. + /// + /// The hit object to apply the state change to. + /// The state of the hit object. + protected virtual void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + /// + /// Apply a normal visibility state adjustment to an object. + /// + /// The hit object to apply the state change to. + /// The state of the hit object. + protected virtual void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + public virtual void ReadFromConfig(OsuConfigManager config) + { + config.BindWith(OsuSetting.IncreaseFirstObjectVisibility, IncreaseFirstObjectVisibility); + } + + public virtual void ApplyToBeatmap(IBeatmap beatmap) + { + FirstObject = getFirstAdjustableObjectRecursive(beatmap.HitObjects); + + HitObject getFirstAdjustableObjectRecursive(IReadOnlyList hitObjects) + { + foreach (var h in hitObjects) + { + if (IsFirstAdjustableObject(h)) + return h; + + var nestedResult = getFirstAdjustableObjectRecursive(h.NestedHitObjects); + if (nestedResult != null) + return nestedResult; + } + + return null; + } + } + + public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var dho in drawables) + { + dho.ApplyCustomUpdateState += (o, state) => + { + // Increased visibility is applied to the entire first object, including all of its nested hitobjects. + if (IncreaseFirstObjectVisibility.Value && isObjectEqualToOrNestedIn(o.HitObject, FirstObject)) + ApplyIncreasedVisibilityState(o, state); + else + ApplyNormalVisibilityState(o, state); + }; + } + } + + /// + /// Checks whether a given object is nested within a target. + /// + /// The to check. + /// The which may be equal to or contain as a nested object. + /// Whether is equal to or nested within . + private bool isObjectEqualToOrNestedIn(HitObject toCheck, HitObject target) + { + if (target == null) + return false; + + if (toCheck == target) + return true; + + foreach (var h in target.NestedHitObjects) + { + if (isObjectEqualToOrNestedIn(toCheck, h)) + return true; + } + + return false; + } + } +}