1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-08 02:53:40 +08:00

Allow changing addition bank button state when objects are selected even if the selection has no addition sounds (#36808)

Before:


https://github.com/user-attachments/assets/d87bd7e3-37f8-4634-9e6a-5859d5bade57

After:


https://github.com/user-attachments/assets/4de940af-1e30-4266-9aac-5ccd12f38742

---

The title is convoluted but basically I'm angling to close
https://github.com/ppy/osu/issues/36705 with this.

The point is that on current `master`, the keyboard-hotkey-based toggles
on the left of the screen get disabled if you select a range of objects
which contains no addition samples. The report linked above finds this
annoying because it means you basically always need to add an addition
sound *first* and *then* pick a bank.

This is not necessary, and this commit changes the behaviour such that
the bank selection toggles are no longer blocked when you select a range
of objects without additions. Choosing an addition bank when there are
no additions still does nothing to the selected object, *but* adding a
sound *after* that bank preselection will use the preselected bank
rather than auto.
This commit is contained in:
Bartłomiej Dach
2026-03-06 16:47:35 +01:00
committed by GitHub
Unverified
parent 0d74983551
commit 16bc1de9fd
3 changed files with 205 additions and 43 deletions
@@ -1046,6 +1046,174 @@ namespace osu.Game.Tests.Visual.Editing
}
}
[Test]
public void TestAddSoundBeforeSettingNonAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
}
[Test]
public void TestAddSoundAfterSettingNonAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
}
[Test]
public void TestSwitchSoundAfterSettingNonAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
AddStep("remove finish sound", () => InputManager.Key(Key.E));
AddStep("add whistle sound", () => InputManager.Key(Key.W));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
}
[Test]
public void TestAddSoundBeforeSettingAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set auto addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set drum normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, true);
}
[Test]
public void TestAddSoundAfterSettingAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set auto addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set drum normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, true);
}
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
{
var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
@@ -100,7 +100,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true);
SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true);
}
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
@@ -252,17 +251,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
autoBankButton.NormalButton.TooltipText = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty;
}
private void updateAdditionBankTernaryButtonTooltips()
{
bool enabled = SelectionHandler.SelectionAdditionBanksEnabled.Value;
foreach (var ternaryButton in SampleBankTernaryStates)
{
ternaryButton.AdditionsButton.Enabled.Value = enabled;
ternaryButton.AdditionsButton.TooltipText = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty;
}
}
#region Placement
/// <summary>
@@ -85,11 +85,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary>
public readonly Bindable<bool> AutoSelectionBankEnabled = new Bindable<bool>();
/// <summary>
/// Whether the selection contains any addition samples and the <see cref="SelectionAdditionBankStates"/> can be used.
/// </summary>
public readonly Bindable<bool> SelectionAdditionBanksEnabled = new Bindable<bool>();
/// <summary>
/// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions)
/// </summary>
@@ -201,26 +196,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
break;
case TernaryState.True:
if (SelectedItems.Count == 0)
{
// Ensure the user can't stack multiple bank selections when there's no hitobject selection.
// Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects.
foreach (var other in SelectionAdditionBankStates.Values)
{
if (other != bindable)
other.Value = TernaryState.False;
}
}
else
{
// If none of the selected objects have any addition samples, we should not apply the addition bank.
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
{
bindable.Value = TernaryState.False;
break;
}
// If any of the selected objects have any addition samples, we should apply the addition bank.
if (SelectedItems.SelectMany(enumerateAllSamples).Any(h => h.Any(o => o.Name != HitSampleInfo.HIT_NORMAL)))
SetSampleAdditionBank(bankName);
// There are either no selected items, or none of the selected items have addition sounds.
// This state is basically the user pre-selecting an addition bank before actually adding an addition.
// Ensure the user can't stack multiple bank selections in this state.
// Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects.
foreach (var other in SelectionAdditionBankStates.Values)
{
if (other != bindable)
other.Value = TernaryState.False;
}
break;
@@ -279,7 +266,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
SelectionNewComboState.Value = TernaryState.False;
AutoSelectionBankEnabled.Value = true;
SelectionAdditionBanksEnabled.Value = true;
SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True;
SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True;
foreach (var (_, sampleState) in SelectionSampleStates)
@@ -309,12 +295,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName);
}
SelectionAdditionBanksEnabled.Value = samplesInSelection.SelectMany(s => s).Any(o => o.Name != HitSampleInfo.HIT_NORMAL);
foreach ((string bankName, var bindable) in SelectionAdditionBankStates)
// if there are no addition samples in the selection, do not touch the state of addition bank bindables.
// this is to reduce annoyance from the bank resetting if the user wants to e.g. remove the only addition sound on an object, but then add another addition sound
// while keeping the bank the same.
// note that deselecting all objects will still reset the addition bank selection to auto via `ResetTernaryStates()`. this may need to be reconsidered later.
if (samplesInSelection.SelectMany(s => s).Any(o => o.Name != HitSampleInfo.HIT_NORMAL))
{
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL),
h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank));
foreach ((string bankName, var bindable) in SelectionAdditionBankStates)
{
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL),
h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank));
}
}
}
}
@@ -453,9 +444,24 @@ namespace osu.Game.Screens.Edit.Compose.Components
EditorBeatmap.PerformOnSelection(h =>
{
string? forcedBank = null;
// if the selected object(s) only have normal samples, check whether the user has preselected a singular non-auto bank using `SelectionAdditionBankStates`.
// other scenarios are already handled by `CreateHitSampleInfo()`:
// - if the selected object(s) already have addition samples, `CreateHitSampleInfo()` will copy the bank from said addition samples.
// - if the selected object(s) do not have addition samples but the user has preselected auto bank, `CreateHitSampleInfo()` will use the auto bank anyway.
if (h.Samples.All(s => s.Name == HitSampleInfo.HIT_NORMAL))
forcedBank = SelectionAdditionBankStates.SingleOrDefault(kv => kv.Value.Value == TernaryState.True).Key;
// Make sure there isn't already an existing sample
if (h.Samples.All(s => s.Name != sampleName))
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
{
var hitSample = h.CreateHitSampleInfo(sampleName);
if (forcedBank != null && forcedBank != HIT_BANK_AUTO)
hitSample = hitSample.With(newBank: forcedBank, newEditorAutoBank: false);
h.Samples.Add(hitSample);
}
if (h is IHasRepeats hasRepeats)
{