mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 15:33:05 +08:00
Merge branch 'master' into grid-momentary-shortcuts
This commit is contained in:
commit
48057412f5
@ -0,0 +1,22 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModFreezeFrame : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestFreezeFrame()
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModFreezeFrame(),
|
||||||
|
PassCondition = () => true,
|
||||||
|
Autoplay = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
|
||||||
|
|
||||||
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
||||||
public BindableFloat Scale { get; } = new BindableFloat(4)
|
public BindableFloat Scale { get; } = new BindableFloat(4)
|
||||||
|
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// 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;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModFreezeFrame : Mod, IApplicableToDrawableHitObject, IApplicableToBeatmap
|
||||||
|
{
|
||||||
|
public override string Name => "Freeze Frame";
|
||||||
|
|
||||||
|
public override string Acronym => "FR";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||||
|
|
||||||
|
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModApproachDifferent) };
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
|
||||||
|
//mod breaks normal approach circle preempt
|
||||||
|
private double originalPreempt;
|
||||||
|
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var firstHitObject = beatmap.HitObjects.OfType<OsuHitObject>().FirstOrDefault();
|
||||||
|
if (firstHitObject == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double lastNewComboTime = 0;
|
||||||
|
|
||||||
|
originalPreempt = firstHitObject.TimePreempt;
|
||||||
|
|
||||||
|
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||||
|
{
|
||||||
|
if (obj.NewCombo) { lastNewComboTime = obj.StartTime; }
|
||||||
|
|
||||||
|
applyFadeInAdjustment(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||||
|
{
|
||||||
|
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||||
|
|
||||||
|
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||||
|
{
|
||||||
|
switch (nested)
|
||||||
|
{
|
||||||
|
//SliderRepeat wont layer correctly if preempt is changed.
|
||||||
|
case SliderRepeat:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
applyFadeInAdjustment(nested);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
drawableObject.ApplyCustomUpdateState += (drawableHitObject, _) =>
|
||||||
|
{
|
||||||
|
if (drawableHitObject is not DrawableHitCircle drawableHitCircle) return;
|
||||||
|
|
||||||
|
var hitCircle = drawableHitCircle.HitObject;
|
||||||
|
var approachCircle = drawableHitCircle.ApproachCircle;
|
||||||
|
|
||||||
|
// Reapply scale, ensuring the AR isn't changed due to the new preempt.
|
||||||
|
approachCircle.ClearTransforms(targetMember: nameof(approachCircle.Scale));
|
||||||
|
approachCircle.ScaleTo(4 * (float)(hitCircle.TimePreempt / originalPreempt));
|
||||||
|
|
||||||
|
using (drawableHitCircle.ApproachCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
|
||||||
|
approachCircle.ScaleTo(1, hitCircle.TimePreempt).Then().Expire();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -201,7 +201,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
new OsuModNoScope(),
|
new OsuModNoScope(),
|
||||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||||
new ModAdaptiveSpeed()
|
new ModAdaptiveSpeed(),
|
||||||
|
new OsuModFreezeFrame()
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
@ -106,6 +106,49 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
assertBeatSnap(16);
|
assertBeatSnap(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardNavigation()
|
||||||
|
{
|
||||||
|
pressKey(1);
|
||||||
|
assertBeatSnap(1);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
pressKey(2);
|
||||||
|
assertBeatSnap(2);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
pressKey(3);
|
||||||
|
assertBeatSnap(3);
|
||||||
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
|
|
||||||
|
pressKey(4);
|
||||||
|
assertBeatSnap(4);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
pressKey(5);
|
||||||
|
assertBeatSnap(5);
|
||||||
|
assertPreset(BeatDivisorType.Custom, 5);
|
||||||
|
|
||||||
|
pressKey(6);
|
||||||
|
assertBeatSnap(6);
|
||||||
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
|
|
||||||
|
pressKey(7);
|
||||||
|
assertBeatSnap(7);
|
||||||
|
assertPreset(BeatDivisorType.Custom, 7);
|
||||||
|
|
||||||
|
pressKey(8);
|
||||||
|
assertBeatSnap(8);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
void pressKey(int key) => AddStep($"press shift+{key}", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.Key(Key.Number0 + key);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBeatPresetNavigation()
|
public void TestBeatPresetNavigation()
|
||||||
{
|
{
|
||||||
|
32
osu.Game.Tests/Visual/Editing/TestSceneEditorBindings.cs
Normal file
32
osu.Game.Tests/Visual/Editing/TestSceneEditorBindings.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test editor hotkeys at a high level to ensure they all work well together.
|
||||||
|
/// </summary>
|
||||||
|
public class TestSceneEditorBindings : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatDivisorChangeHotkeys()
|
||||||
|
{
|
||||||
|
AddStep("hold shift", () => InputManager.PressKey(Key.LShift));
|
||||||
|
|
||||||
|
AddStep("press 4", () => InputManager.Key(Key.Number4));
|
||||||
|
AddAssert("snap updated to 4", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(4));
|
||||||
|
|
||||||
|
AddStep("press 6", () => InputManager.Key(Key.Number6));
|
||||||
|
AddAssert("snap updated to 6", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(6));
|
||||||
|
|
||||||
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -155,6 +155,20 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClone()
|
||||||
|
{
|
||||||
|
var addedObject = new HitCircle { StartTime = 1000 };
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||||
|
|
||||||
|
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
AddStep("clone", () => Editor.Clone());
|
||||||
|
AddAssert("is two objects", () => EditorBeatmap.HitObjects.Count == 2);
|
||||||
|
AddStep("clone", () => Editor.Clone());
|
||||||
|
AddAssert("is three objects", () => EditorBeatmap.HitObjects.Count == 3);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCutNothing()
|
public void TestCutNothing()
|
||||||
{
|
{
|
||||||
@ -175,5 +189,22 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("paste hitobject", () => Editor.Paste());
|
AddStep("paste hitobject", () => Editor.Paste());
|
||||||
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCloneNothing()
|
||||||
|
{
|
||||||
|
// Add arbitrary object and copy to clipboard.
|
||||||
|
// This is tested to ensure that clone doesn't incorrectly read from the clipboard when no selection is made.
|
||||||
|
var addedObject = new HitCircle { StartTime = 1000 };
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||||
|
AddStep("copy hitobject", () => Editor.Copy());
|
||||||
|
|
||||||
|
AddStep("deselect all objects", () => EditorBeatmap.SelectedHitObjects.Clear());
|
||||||
|
|
||||||
|
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
AddStep("clone", () => Editor.Clone());
|
||||||
|
AddAssert("still one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,14 +31,17 @@ namespace osu.Game.Input.Bindings
|
|||||||
parentInputManager = GetContainingInputManager();
|
parentInputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: Do not change the order of key bindings in this list.
|
// IMPORTANT: Take care when changing order of the items in the enumerable.
|
||||||
// It is used to decide the order of precedence (see note in DatabasedKeyBindingContainer).
|
// It is used to decide the order of precedence, with the earlier items having higher precedence.
|
||||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||||
.Concat(OverlayKeyBindings)
|
|
||||||
.Concat(EditorKeyBindings)
|
.Concat(EditorKeyBindings)
|
||||||
.Concat(InGameKeyBindings)
|
.Concat(InGameKeyBindings)
|
||||||
.Concat(SongSelectKeyBindings)
|
.Concat(SongSelectKeyBindings)
|
||||||
.Concat(AudioControlKeyBindings);
|
.Concat(AudioControlKeyBindings)
|
||||||
|
// Overlay bindings may conflict with more local cases like the editor so they are checked last.
|
||||||
|
// It has generally been agreed on that local screens like the editor should have priority,
|
||||||
|
// based on such usages potentially requiring a lot more key bindings that may be "shared" with global ones.
|
||||||
|
.Concat(OverlayKeyBindings);
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||||
{
|
{
|
||||||
@ -87,6 +90,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
||||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.EditorCloneSelection),
|
||||||
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
||||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||||
@ -343,5 +347,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleProfile))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleProfile))]
|
||||||
ToggleProfile,
|
ToggleProfile,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCloneSelection))]
|
||||||
|
EditorCloneSelection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Clone selection"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorCloneSelection => new TranslatableString(getKey(@"editor_clone_selection"), @"Clone selection");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Cycle grid display mode"
|
/// "Cycle grid display mode"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
|
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
|
||||||
|
@ -199,8 +199,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
if (stringAddBank == @"none")
|
if (stringAddBank == @"none")
|
||||||
stringAddBank = null;
|
stringAddBank = null;
|
||||||
|
|
||||||
bankInfo.Normal = stringBank;
|
bankInfo.BankForNormal = stringBank;
|
||||||
bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||||
|
|
||||||
if (split.Length > 2)
|
if (split.Length > 2)
|
||||||
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
||||||
@ -447,32 +447,54 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
var soundTypes = new List<HitSampleInfo>
|
var soundTypes = new List<HitSampleInfo>
|
||||||
{
|
{
|
||||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||||
|
|
||||||
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
|
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||||
|
|
||||||
if (type.HasFlagFast(LegacyHitSoundType.Clap))
|
if (type.HasFlagFast(LegacyHitSoundType.Clap))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||||
|
|
||||||
return soundTypes;
|
return soundTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SampleBankInfo
|
private class SampleBankInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An optional overriding filename which causes all bank/sample specifications to be ignored.
|
||||||
|
/// </summary>
|
||||||
public string Filename;
|
public string Filename;
|
||||||
|
|
||||||
public string Normal;
|
/// <summary>
|
||||||
public string Add;
|
/// The bank identifier to use for the base ("hitnormal") sample.
|
||||||
|
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||||
|
/// </summary>
|
||||||
|
public string BankForNormal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
|
||||||
|
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||||
|
/// </summary>
|
||||||
|
public string BankForAdditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hit sample volume (0-100).
|
||||||
|
/// See <see cref="HitSampleInfo.Volume"/>.
|
||||||
|
/// </summary>
|
||||||
public int Volume;
|
public int Volume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index of the custom sample bank. Is only used if 2 or above for "reasons".
|
||||||
|
/// This will add a suffix to lookups, allowing extended bank lookups (ie. "normal-hitnormal-2").
|
||||||
|
/// See <see cref="HitSampleInfo.Suffix"/>.
|
||||||
|
/// </summary>
|
||||||
public int CustomSampleBank;
|
public int CustomSampleBank;
|
||||||
|
|
||||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
||||||
@ -503,7 +525,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||||
=> With(newName, newBank, newVolume);
|
=> With(newName, newBank, newVolume);
|
||||||
|
|
||||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default, Optional<int> newCustomSampleBank = default,
|
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
||||||
|
Optional<int> newCustomSampleBank = default,
|
||||||
Optional<bool> newIsLayered = default)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
||||||
|
|
||||||
@ -537,7 +560,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
Path.ChangeExtension(Filename, null)
|
Path.ChangeExtension(Filename, null)
|
||||||
};
|
};
|
||||||
|
|
||||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default, Optional<int> newCustomSampleBank = default,
|
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
||||||
|
Optional<int> newCustomSampleBank = default,
|
||||||
Optional<bool> newIsLayered = default)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||||
|
|
||||||
|
@ -25,6 +25,26 @@ namespace osu.Game.Screens.Edit
|
|||||||
BindValueChanged(_ => ensureValidDivisor());
|
BindValueChanged(_ => ensureValidDivisor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a divisor, updating the valid divisor range appropriately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="divisor">The intended divisor.</param>
|
||||||
|
public void SetArbitraryDivisor(int divisor)
|
||||||
|
{
|
||||||
|
// If the current valid divisor range doesn't contain the proposed value, attempt to find one which does.
|
||||||
|
if (!ValidDivisors.Value.Presets.Contains(divisor))
|
||||||
|
{
|
||||||
|
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
||||||
|
ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
||||||
|
else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor))
|
||||||
|
ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
||||||
|
else
|
||||||
|
ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value = divisor;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateBindableProperties()
|
private void updateBindableProperties()
|
||||||
{
|
{
|
||||||
ensureValidDivisor();
|
ensureValidDivisor();
|
||||||
|
@ -209,6 +209,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.ShiftPressed && e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
||||||
|
{
|
||||||
|
beatDivisor.SetArbitraryDivisor(e.Key - Key.Number0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
internal class DivisorDisplay : OsuAnimatedButton, IHasPopover
|
internal class DivisorDisplay : OsuAnimatedButton, IHasPopover
|
||||||
{
|
{
|
||||||
public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor();
|
public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor();
|
||||||
@ -306,17 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BeatDivisor.ValidDivisors.Value.Presets.Contains(divisor))
|
BeatDivisor.SetArbitraryDivisor(divisor);
|
||||||
{
|
|
||||||
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
|
||||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
|
||||||
else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor))
|
|
||||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
|
||||||
else
|
|
||||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor);
|
|
||||||
}
|
|
||||||
|
|
||||||
BeatDivisor.Value = divisor;
|
|
||||||
|
|
||||||
this.HidePopover();
|
this.HidePopover();
|
||||||
}
|
}
|
||||||
|
@ -304,6 +304,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut),
|
cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut),
|
||||||
copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy),
|
copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy),
|
||||||
pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste),
|
pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste),
|
||||||
|
cloneMenuItem = new EditorMenuItem("Clone", MenuItemType.Standard, Clone),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuItem("View")
|
new MenuItem("View")
|
||||||
@ -575,6 +576,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
this.Exit();
|
this.Exit();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorCloneSelection:
|
||||||
|
Clone();
|
||||||
|
return true;
|
||||||
|
|
||||||
case GlobalAction.EditorComposeMode:
|
case GlobalAction.EditorComposeMode:
|
||||||
Mode.Value = EditorScreenMode.Compose;
|
Mode.Value = EditorScreenMode.Compose;
|
||||||
return true;
|
return true;
|
||||||
@ -741,6 +746,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private EditorMenuItem cutMenuItem;
|
private EditorMenuItem cutMenuItem;
|
||||||
private EditorMenuItem copyMenuItem;
|
private EditorMenuItem copyMenuItem;
|
||||||
|
private EditorMenuItem cloneMenuItem;
|
||||||
private EditorMenuItem pasteMenuItem;
|
private EditorMenuItem pasteMenuItem;
|
||||||
|
|
||||||
private readonly BindableWithCurrent<bool> canCut = new BindableWithCurrent<bool>();
|
private readonly BindableWithCurrent<bool> canCut = new BindableWithCurrent<bool>();
|
||||||
@ -750,7 +756,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
private void setUpClipboardActionAvailability()
|
private void setUpClipboardActionAvailability()
|
||||||
{
|
{
|
||||||
canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true);
|
canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true);
|
||||||
canCopy.Current.BindValueChanged(copy => copyMenuItem.Action.Disabled = !copy.NewValue, true);
|
canCopy.Current.BindValueChanged(copy =>
|
||||||
|
{
|
||||||
|
copyMenuItem.Action.Disabled = !copy.NewValue;
|
||||||
|
cloneMenuItem.Action.Disabled = !copy.NewValue;
|
||||||
|
}, true);
|
||||||
canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true);
|
canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -765,6 +775,21 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
protected void Copy() => currentScreen?.Copy();
|
protected void Copy() => currentScreen?.Copy();
|
||||||
|
|
||||||
|
protected void Clone()
|
||||||
|
{
|
||||||
|
// Avoid attempting to clone if copying is not available (as it may result in pasting something unexpected).
|
||||||
|
if (!canCopy.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// This is an initial implementation just to get an idea of how people used this function.
|
||||||
|
// There are a couple of differences from osu!stable's implementation which will require more work to match:
|
||||||
|
// - The "clipboard" is not populated during the duplication process.
|
||||||
|
// - The duplicated hitobjects are inserted after the original pattern (add one beat_length and then quantize using beat snap).
|
||||||
|
// - The duplicated hitobjects are selected (but this is also applied for all paste operations so should be changed there).
|
||||||
|
Copy();
|
||||||
|
Paste();
|
||||||
|
}
|
||||||
|
|
||||||
protected void Paste() => currentScreen?.Paste();
|
protected void Paste() => currentScreen?.Paste();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -110,6 +110,8 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
public new void Paste() => base.Paste();
|
public new void Paste() => base.Paste();
|
||||||
|
|
||||||
|
public new void Clone() => base.Clone();
|
||||||
|
|
||||||
public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo);
|
public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo);
|
||||||
|
|
||||||
public new void CreateNewDifficulty(RulesetInfo rulesetInfo) => base.CreateNewDifficulty(rulesetInfo);
|
public new void CreateNewDifficulty(RulesetInfo rulesetInfo) => base.CreateNewDifficulty(rulesetInfo);
|
||||||
|
Loading…
Reference in New Issue
Block a user