mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 19:04:00 +08:00
7e2771c3f0
- [x] Depends on https://github.com/ppy/osu/pull/36741 for merge conflict avoidance RFC, cc @OliBomby ## [Adjust behaviour of automatic bank assignment during placement](https://github.com/ppy/osu/commit/547f55e9b3ded668fe6e1c8865a2d625e64a2f45) Diatribe time! This is fallout of the discussion about auto bank in https://github.com/ppy/osu/issues/36705. Auto bank in lazer as written before this commit is confused. On stable, auto bank is closer to "no bank", as in "go look up the current sample timing point, get the bank of that, and use that". lazer has no timing points anymore, but people still want auto bank. So what do? Auto bank for normal samples is somewhat sane still. It only works during placement, and will just copy the normal bank of the previous object - if one exists. That said, one *might not* exist, but the resulting object will still have its normal sample created with `editorAutoBank: true`. That is largely cosmetic and without consequences, but this commit fixes that. Auto bank for *addition* samples, however... Hoo boy. - For placed objects, auto bank means "take the normal sample, read its bank, and use that". Simple enough, right? - Hoooooowever. During placement, auto bank before this commit used to mean "look at the *previous object*, check if it has an addition sound and then use its bank, if not use *the previous object's* normal sample and then use its bank" which is a completely different thing with its own implications. Like, say, what happens if the previous object uses the auto addition bank too? What should be copied over? Should it be the notion of "auto bank" in that the addition bank should match the normal bank, or should it be the literal bank that the previous object is using? This change attempts to define this unambiguously. "Auto additions bank" means "the same bank as the normal bank of this object", full stop. ## [Do not touch sample toggle state if there are no selected objects](https://github.com/ppy/osu/commit/052cde5987e48800ec68ab2528c7e0ce3140e6e0) Fixes issue described in https://github.com/ppy/osu/issues/36705#issuecomment-3953917163 wherein opening a sample popover will disable addition bank toggles and toggle off all addition samples. --------- Co-authored-by: Dean Herbert <pe@ppy.sh>
170 lines
6.5 KiB
C#
170 lines
6.5 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
|
|
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;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.Edit
|
|
{
|
|
/// <summary>
|
|
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
|
|
/// </summary>
|
|
public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint
|
|
{
|
|
/// <summary>
|
|
/// Whether the sample bank should be taken from the previous hit object.
|
|
/// </summary>
|
|
public bool AutomaticBankAssignment { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the sample addition bank should be taken from the previous hit objects.
|
|
/// </summary>
|
|
public bool AutomaticAdditionBankAssignment { get; set; }
|
|
|
|
/// <summary>
|
|
/// The <see cref="HitObject"/> that is being placed.
|
|
/// </summary>
|
|
public readonly HitObject HitObject;
|
|
|
|
[Resolved]
|
|
protected EditorClock EditorClock { get; private set; } = null!;
|
|
|
|
[Resolved]
|
|
private EditorBeatmap beatmap { get; set; } = null!;
|
|
|
|
private Bindable<double> startTimeBindable = null!;
|
|
|
|
private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
|
|
|
|
protected override bool IsValidForPlacement => HitObject.StartTime >= beatmap.ControlPointInfo.TimingPoints.FirstOrDefault()?.Time;
|
|
|
|
[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();
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
|
|
Colour = IsValidForPlacement ? Colour4.White : Colour4.Red;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the time and position of this <see cref="PlacementBlueprint"/>.
|
|
/// </summary>
|
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double time)
|
|
{
|
|
if (PlacementActive == PlacementState.Waiting)
|
|
{
|
|
HitObject.StartTime = time;
|
|
|
|
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 (lastHitNormal != null && 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, newEditorAutoBank: true) : s).ToList();
|
|
else
|
|
HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newEditorAutoBank: false) : s).ToList();
|
|
|
|
if (lastHitNormal != null)
|
|
{
|
|
// Inherit the volume and sample set info from the previous hit object
|
|
HitObject.Samples = HitObject.Samples.Select(s => s.With(
|
|
newVolume: lastHitNormal.Volume,
|
|
newSuffix: lastHitNormal.Suffix,
|
|
newUseBeatmapSamples: lastHitNormal.UseBeatmapSamples)).ToList();
|
|
}
|
|
|
|
if (AutomaticAdditionBankAssignment)
|
|
{
|
|
string bank = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
|
|
HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bank, newEditorAutoBank: true) : s).ToList();
|
|
}
|
|
else
|
|
HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newEditorAutoBank: false) : s).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();
|
|
}
|
|
|
|
return new SnapResult(screenSpacePosition, time);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,IBeatmapDifficultyInfo,CancellationToken)"/>,
|
|
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|