1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 19:54:15 +08:00

Ensure users cannot lose data when editing beatmaps above 10K (#37668)

Intends to close https://github.com/ppy/osu/issues/37395.

In the past there have been many discussions about this dual stages
wart. Whether it will continue to be "dual stages", or just a single
stage with 12/14/16/18K, whether all of the skinning weirdness related
to it (https://github.com/ppy/osu/issues/23620) will be supported, etc.

This isn't that, I don't want to get into that, and I'm not promising
anything. All I want to make sure here is that users cannot get their
data lost by editing an existing >10K beatmap, and be understandably
upset about it.

To that end this mostly apes how stable does things.
This commit is contained in:
Bartłomiej Dach
2026-05-08 08:45:06 +02:00
committed by GitHub
Unverified
parent 9e20929ad3
commit 9d31332457
2 changed files with 90 additions and 15 deletions
@@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Test]
public void TestKeyCountChange()
{
FormSliderBar<float> keyCount = null!;
FormSliderBar<int> keyCount = null!;
AddStep("go to setup screen", () => InputManager.Key(Key.F4));
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<FormSliderBar<float>>().First(), () => Is.Not.Null);
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<FormSliderBar<int>>().First(), () => Is.Not.Null);
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
AddStep("change key count to 8", () =>
{
@@ -41,5 +41,32 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
AddStep("acquiesce", () => InputManager.Key(Key.Number1));
AddUntilStep("beatmap became 8K", () => Game.Beatmap.Value.BeatmapInfo.Difficulty.CircleSize, () => Is.EqualTo(8));
}
[Test]
public void TestDualStagesChange()
{
FormCheckBox dualStages = null!;
FormSliderBar<int> keyCount = null!;
AddStep("go to setup screen", () => InputManager.Key(Key.F4));
AddUntilStep("retrieve dual stages checkbox", () => dualStages = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<FormCheckBox>().First(), () => Is.Not.Null);
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<FormSliderBar<int>>().First(), () => Is.Not.Null);
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
AddStep("set dual stages", () =>
{
dualStages.Current.Value = true;
});
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().SingleOrDefault()?.CurrentDialog, Is.InstanceOf<SaveAndReloadEditorDialog>);
AddStep("refuse", () => InputManager.Key(Key.Number2));
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
AddStep("set dual stages again", () =>
{
dualStages.Current.Value = true;
});
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().Single().CurrentDialog, Is.InstanceOf<SaveAndReloadEditorDialog>);
AddStep("acquiesce", () => InputManager.Key(Key.Number1));
AddUntilStep("beatmap became 12K", () => Game.Beatmap.Value.BeatmapInfo.Difficulty.CircleSize, () => Is.EqualTo(12));
}
}
}
@@ -7,6 +7,7 @@ using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web;
@@ -19,13 +20,22 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
{
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
private FormSliderBar<float> keyCountSlider { get; set; } = null!;
private FormSliderBar<int> keyCountSlider { get; set; } = null!;
private FormCheckBox dualStages { get; set; } = null!;
private FormCheckBox specialStyle { get; set; } = null!;
private FormSliderBar<float> healthDrainSlider { get; set; } = null!;
private FormSliderBar<float> overallDifficultySlider { get; set; } = null!;
private FormSliderBar<double> baseVelocitySlider { get; set; } = null!;
private FormSliderBar<double> tickRateSlider { get; set; } = null!;
private readonly BindableInt singleStageKeyCount = new BindableInt
{
Default = (int)BeatmapDifficulty.DEFAULT_DIFFICULTY,
Precision = 1,
};
private readonly BindableInt actualKeyCount = new BindableInt();
[Resolved]
private Editor? editor { get; set; }
@@ -37,20 +47,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
{
Children = new Drawable[]
{
keyCountSlider = new FormSliderBar<float>
keyCountSlider = new FormSliderBar<int>
{
Caption = BeatmapsetsStrings.ShowStatsCsMania,
HintText = "The number of columns in the beatmap",
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 1,
},
Current = singleStageKeyCount,
TransferValueOnCommit = true,
TabbableContentContainer = this,
},
dualStages = new FormCheckBox
{
Caption = "Dual stages",
HintText = "Doubles the number of keys by adding a second stage."
},
specialStyle = new FormCheckBox
{
Caption = "Use special (N+1) style",
@@ -117,16 +126,54 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
},
};
keyCountSlider.Current.BindValueChanged(updateKeyCount);
setStateFromActualKeyCount((int)Beatmap.Difficulty.CircleSize);
keyCountSlider.Current.BindValueChanged(_ => calculateActualKeyCount());
dualStages.Current.BindValueChanged(_ =>
{
updateSingleStageKeyCountBounds();
calculateActualKeyCount();
});
actualKeyCount.BindValueChanged(updateKeyCount);
healthDrainSlider.Current.BindValueChanged(_ => updateValues());
overallDifficultySlider.Current.BindValueChanged(_ => updateValues());
baseVelocitySlider.Current.BindValueChanged(_ => updateValues());
tickRateSlider.Current.BindValueChanged(_ => updateValues());
}
private void updateSingleStageKeyCountBounds()
{
singleStageKeyCount.MinValue = dualStages.Current.Value ? ManiaRuleset.MAX_STAGE_KEYS / 2 + 1 : 1;
singleStageKeyCount.MaxValue = dualStages.Current.Value ? LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT / 2 : ManiaRuleset.MAX_STAGE_KEYS;
}
private void setStateFromActualKeyCount(int keyCount)
{
actualKeyCount.Value = keyCount;
if (keyCount > 10)
{
dualStages.Current.Value = true;
singleStageKeyCount.Value = keyCount / 2;
}
else
{
dualStages.Current.Value = false;
singleStageKeyCount.Value = keyCount;
}
updateSingleStageKeyCountBounds();
}
private void calculateActualKeyCount()
{
actualKeyCount.Value = keyCountSlider.Current.Value * (dualStages.Current.Value ? 2 : 1);
}
private bool updatingKeyCount;
private void updateKeyCount(ValueChangedEvent<float> keyCount)
private void updateKeyCount(ValueChangedEvent<int> keyCount)
{
if (updatingKeyCount) return;
@@ -143,7 +190,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
Schedule(() =>
{
changeHandler!.RestoreState(-1);
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value = keyCount.OldValue;
Beatmap.Difficulty.CircleSize = keyCount.OldValue;
setStateFromActualKeyCount(keyCount.OldValue);
updatingKeyCount = false;
});
}
@@ -158,7 +206,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
{
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
Beatmap.Difficulty.CircleSize = actualKeyCount.Value;
Beatmap.SpecialStyle = specialStyle.Current.Value;
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;