// 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 { /// /// A which applies visibility adjustments to s /// with an optional increased visibility adjustment depending on the user's "increase first object visibility" setting. /// public abstract class ModWithVisibilityAdjustment : Mod, IReadFromConfig, IApplicableToBeatmap, IApplicableToDrawableHitObject { /// /// 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 hitobject. protected abstract 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 hitobject. protected abstract 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 ApplyToDrawableHitObject(DrawableHitObject dho) { 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; } } }