diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 8ebc09bf25..515fa4c0b5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -595,6 +595,172 @@ namespace osu.Game.Tests.Visual.Editing () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); } + [Test] + public void TestNonAutoBankHotkeysDuringPlacementPersistAfterPlacement() + { + AddStep("Clear all objects", () => EditorBeatmap.Clear()); + AddStep("Enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + + AddStep("Move to 3000", () => EditorClock.Seek(3000)); + + AddStep("Press drum bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddAssert($"Placement sample is {HitSampleInfo.BANK_DRUM}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM)); + + AddStep("Press normal addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddStep("Press finish sample shortcut", () => + { + InputManager.Key(Key.E); + }); + + AddAssert($"Placement sample addition is {HitSampleInfo.BANK_NORMAL}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL)); + + AddStep("Finish placement", () => InputManager.Click(MouseButton.Left)); + + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasAutoNormalBankFlag(0, false); + hitObjectHasAutoAdditionBankFlag(0, false); + + clickSamplePiece(0); + samplePopoverIsOpen(); + samplePopoverHasSingleAdditionBank(HitSampleInfo.BANK_NORMAL); + } + + [Test] + public void TestAutoAdditionBankHotkeyDuringPlacementPersistsAfterPlacement() + { + AddStep("Clear all objects", () => EditorBeatmap.Clear()); + AddStep("Enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + + AddStep("Move to 3000", () => EditorClock.Seek(3000)); + + AddStep("Press drum bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddAssert($"Placement sample is {HitSampleInfo.BANK_DRUM}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM)); + + AddStep("Press normal addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddStep("Press finish sample shortcut", () => + { + InputManager.Key(Key.E); + }); + + AddStep("Press auto addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddAssert($"Placement sample addition is {HitSampleInfo.BANK_DRUM}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM)); + + AddStep("Finish placement", () => InputManager.Click(MouseButton.Left)); + + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasAutoNormalBankFlag(0, false); + hitObjectHasAutoAdditionBankFlag(0, true); + + clickSamplePiece(0); + samplePopoverIsOpen(); + samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO); + } + + [Test] + public void TestFullAutoBankHotkeyDuringPlacementPersistsAfterPlacement() + { + AddStep("Clear all objects", () => EditorBeatmap.Clear()); + AddStep("Enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + + AddStep("Move to 3000", () => EditorClock.Seek(3000)); + + AddStep("Press auto normal bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddAssert($"Placement sample is {HitSampleInfo.BANK_NORMAL}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL)); + + AddStep("Press finish sample shortcut", () => + { + InputManager.Key(Key.E); + }); + + AddStep("Press auto addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddAssert($"Placement sample addition is {HitSampleInfo.BANK_NORMAL}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL)); + + AddStep("Finish placement", () => InputManager.Click(MouseButton.Left)); + + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasAutoNormalBankFlag(0, false); // it's the first object - nothing to inherit bank from + hitObjectHasAutoAdditionBankFlag(0, true); + + clickSamplePiece(0); + samplePopoverIsOpen(); + samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL); + samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO); + dismissPopover(); + + AddStep("Move to 5000", () => EditorClock.Seek(5000)); + AddStep("Enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + AddStep("Finish placement", () => InputManager.Click(MouseButton.Left)); + + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); // finish is still implied, continuing from first placement + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL); + hitObjectHasAutoNormalBankFlag(1, true); + hitObjectHasAutoAdditionBankFlag(1, true); + + clickSamplePiece(1); + samplePopoverIsOpen(); + samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL); + samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO); + } + [Test] public void PopoverForMultipleSelectionChangesAllSamples() { @@ -952,6 +1118,14 @@ namespace osu.Game.Tests.Visual.Editing return dropdown?.Current.Value == "(multiple)"; }); + private void samplePopoverHasSingleAdditionBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var dropdown = popover?.ChildrenOfType>().ElementAt(1); + + return dropdown?.Current.Value == bank; + }); + private void dismissPopover() { AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); @@ -1025,6 +1199,18 @@ namespace osu.Game.Tests.Visual.Editing return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); }); + private void hitObjectHasAutoNormalBankFlag(int objectIndex, bool autoBank) => AddAssert($"{objectIndex.ToOrdinalWords()} has auto normal bank {(autoBank ? "on" : "off")}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.EditorAutoBank == autoBank); + }); + + private void hitObjectHasAutoAdditionBankFlag(int objectIndex, bool autoBank) => AddAssert($"{objectIndex.ToOrdinalWords()} has auto addition bank {(autoBank ? "on" : "off")}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.EditorAutoBank == autoBank); + }); + private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert( $"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () => { diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 675de2f57a..31e2e5b302 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -272,8 +272,8 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[1].Samples, () => Has.Count.EqualTo(2)); AddAssert("normal sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_SOFT)); - AddAssert("clap sample has drum bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank, - () => Is.EqualTo(HitSampleInfo.BANK_DRUM)); + AddAssert("clap sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank, + () => Is.EqualTo(HitSampleInfo.BANK_SOFT)); AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); AddStep("seek to 1000", () => EditorClock.Seek(1000)); // previous object is the one at time 500, which has no additions diff --git a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs index 7b378edda6..a24249d42c 100644 --- a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs @@ -113,22 +113,14 @@ namespace osu.Game.Rulesets.Edit 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 && 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) { - 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 and sample set info from the previous hit object HitObject.Samples = HitObject.Samples.Select(s => s.With( newVolume: lastHitNormal.Volume, @@ -136,6 +128,14 @@ namespace osu.Game.Rulesets.Edit 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 diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 374ceaccc6..074d94eb26 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -297,22 +297,25 @@ namespace osu.Game.Screens.Edit.Compose.Components var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray(); - foreach ((string sampleName, var bindable) in SelectionSampleStates) + if (samplesInSelection.Length > 0) { - bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Name == sampleName)); - } + foreach ((string sampleName, var bindable) in SelectionSampleStates) + { + bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Name == sampleName)); + } - foreach ((string bankName, var bindable) in SelectionBankStates) - { - bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); - } + foreach ((string bankName, var bindable) in SelectionBankStates) + { + 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); + SelectionAdditionBanksEnabled.Value = samplesInSelection.SelectMany(s => s).Any(o => o.Name != HitSampleInfo.HIT_NORMAL); - 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)); + 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)); + } } }