// 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.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; } /// /// 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); } protected override void BeginPlacement(bool commitStart = false) { base.BeginPlacement(commitStart); placementHandler.BeginPlacement(HitObject); } public override void EndPlacement(bool commit) { base.EndPlacement(commit); placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit); } /// /// 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(); if (AutomaticBankAssignment) { // Create samples based on the sample settings of the previous hit object if (lastHitObject != null) { for (int i = 0; i < HitObject.Samples.Count; i++) HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); } } else { var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); if (lastHitNormal != null) { // Only inherit the volume from the previous hit object for (int i = 0; i < HitObject.Samples.Count; i++) HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); } } 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); } }