// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; namespace osu.Game.Rulesets.Edit { /// /// A blueprint which governs the creation of a new to actualisation. /// public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint { /// /// Whether the sample bank should be taken from the previous hit object. /// public bool AutomaticBankAssignment { get; set; } /// /// Whether the sample addition bank should be taken from the previous hit objects. /// public bool AutomaticAdditionBankAssignment { get; set; } /// /// The that is being placed. /// public readonly HitObject HitObject; [Resolved] protected EditorClock EditorClock { get; private set; } = null!; [Resolved] private EditorBeatmap beatmap { get; set; } = null!; private Bindable startTimeBindable = null!; private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault(); [Resolved] private IPlacementHandler placementHandler { get; set; } = null!; protected HitObjectPlacementBlueprint(HitObject hitObject) { HitObject = hitObject; // adding the default hit sample should be the case regardless of the ruleset. HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); } [BackgroundDependencyLoader] private void load() { startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } private bool placementBegun; protected override void BeginPlacement(bool commitStart = false) { base.BeginPlacement(commitStart); if (State.Value == Visibility.Visible) placementHandler.ShowPlacement(HitObject); placementBegun = true; } public override void EndPlacement(bool commit) { base.EndPlacement(commit); if (IsValidForPlacement && commit) placementHandler.CommitPlacement(HitObject); else placementHandler.HidePlacement(); } /// /// Updates the time and position of this based on the provided snap information. /// /// The snap result information. public override void UpdateTimeAndPosition(SnapResult result) { if (PlacementActive == PlacementState.Waiting) { HitObject.StartTime = result.Time ?? EditorClock.CurrentTime; if (HitObject is IHasComboInformation comboInformation) comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); } var lastHitObject = getPreviousHitObject(); var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); if (AutomaticAdditionBankAssignment) { // Inherit the addition bank from the previous hit object // If there is no previous addition, inherit from the normal sample var lastAddition = lastHitObject?.Samples?.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL) ?? lastHitNormal; if (lastAddition != null) HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastAddition.Bank) : s).ToList(); } if (lastHitNormal != null) { if (AutomaticBankAssignment) // Inherit the bank from the previous hit object HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList(); // Inherit the volume from the previous hit object HitObject.Samples = HitObject.Samples.Select(s => s.With(newVolume: lastHitNormal.Volume)).ToList(); } if (HitObject is IHasRepeats hasRepeats) { // Make sure all the node samples are identical to the hit object's samples for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); } } /// /// Invokes , /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); protected override void PopIn() { base.PopIn(); if (placementBegun) placementHandler.ShowPlacement(HitObject); } protected override void PopOut() { base.PopOut(); placementHandler.HidePlacement(); } } }