From 7f432665e52774a5bef21cf930ff2ac1ac0527f6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 14 Jul 2021 14:38:38 +0900 Subject: [PATCH 01/10] Preserve Y position of hit objects in osu!catch --- .../Beatmaps/CatchBeatmapConverter.cs | 11 +++++---- .../Objects/CatchHitObject.cs | 24 ++++++++++++++++--- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 +--- .../Objects/Legacy/Catch/ConvertHit.cs | 9 +++++-- .../Legacy/Catch/ConvertHitObjectParser.cs | 4 ++-- .../Objects/Legacy/Catch/ConvertSlider.cs | 9 +++++-- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 34964fc4ae..7774a7da09 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken) { - var positionData = obj as IHasXPosition; + var xPositionData = obj as IHasXPosition; + var yPositionData = obj as IHasYPosition; var comboData = obj as IHasCombo; switch (obj) @@ -36,10 +37,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Path = curveData.Path, NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, - X = positionData?.X ?? 0, + X = xPositionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 + LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0, + LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y }.Yield(); case IHasDuration endTime: @@ -59,7 +61,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - X = positionData?.X ?? 0 + X = xPositionData?.X ?? 0, + LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y }.Yield(); } } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 0b8c0e28a7..f979e3e0ca 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -9,10 +9,11 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Catch.Objects { - public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation + public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation { public const float OBJECT_RADIUS = 64; @@ -31,8 +32,6 @@ namespace osu.Game.Rulesets.Catch.Objects set => OriginalXBindable.Value = value; } - float IHasXPosition.X => OriginalXBindable.Value; - public readonly Bindable XOffsetBindable = new Bindable(); /// @@ -131,5 +130,24 @@ namespace osu.Game.Rulesets.Catch.Objects } protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + #region Hit object conversion + + // The half of the height of the osu! playfield. + public const float DEFAULT_LEGACY_CONVERT_Y = 192; + + /// + /// The Y position of the hit object is not used in the normal osu!catch gameplay. + /// It is preserved to maximize the backward compatibility with the legacy editor, in which the mappers use the Y position to organize the patterns. + /// + public float LegacyConvertedY { get; set; } = DEFAULT_LEGACY_CONVERT_Y; + + float IHasXPosition.X => OriginalX; + + float IHasYPosition.Y => LegacyConvertedY; + + Vector2 IHasPosition.Position => new Vector2(OriginalX, LegacyConvertedY); + + #endregion } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index acbf57d25f..f14f6ec10c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -251,11 +251,8 @@ namespace osu.Game.Beatmaps.Formats switch (beatmap.BeatmapInfo.RulesetID) { case 0: - position = ((IHasPosition)hitObject).Position; - break; - case 2: - position.X = ((IHasXPosition)hitObject).X; + position = ((IHasPosition)hitObject).Position; break; case 3: diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 19722fb796..12b4812824 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -2,15 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Catch { /// /// Legacy osu!catch Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasCombo, IHasXPosition + internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo { - public float X { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + + public Vector2 Position { get; set; } public bool NewCombo { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index c10c8dc30f..c29179f749 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertHit { - X = position.X, + Position = position, NewCombo = newCombo, ComboOffset = comboOffset }; @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertSlider { - X = position.X, + Position = position, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index 56790629b4..fb1afed3b4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -2,15 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Catch { /// /// Legacy osu!catch Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo { - public float X { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + + public Vector2 Position { get; set; } public bool NewCombo { get; set; } From 7a671754f2c067b38ab8b4515fbd718cbcf8228e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:29:31 +0900 Subject: [PATCH 02/10] Change `RadioButton`'s `object` to a `string` --- .../Edit/Components/RadioButtons/RadioButton.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index dcf5f8a788..c3ffd8add0 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// /// The item related to this button. /// - public object Item; + public string Item; /// /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private readonly Action action; - public RadioButton(object item, Action action, Func createIcon = null) + public RadioButton(string item, Action action, Func createIcon = null) { Item = item; CreateIcon = createIcon; @@ -34,13 +34,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Selected = new BindableBool(); } - public RadioButton(string item) - : this(item, null) - { - Item = item; - action = null; - } - /// /// Selects this . /// From 3ae5f6707abe50ffa9c4ba9bae2a359a0dc49468 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:30:00 +0900 Subject: [PATCH 03/10] Expose whether an `EditorBeatmap` has timing present or not via bindable --- osu.Game/Screens/Edit/EditorBeatmap.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index be53abbd55..7de98e5e85 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -46,12 +46,22 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; + /// + /// Whether at least one timing control point is present and providing timing information. + /// + public IBindable HasTiming => hasTiming; + + private readonly Bindable hasTiming = new Bindable(); + [CanBeNull] public readonly ISkin BeatmapSkin; [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + [Resolved] + private EditorClock editorClock { get; set; } + private readonly IBeatmapProcessor beatmapProcessor; private readonly Dictionary> startTimeBindables = new Dictionary>(); @@ -238,6 +248,8 @@ namespace osu.Game.Screens.Edit if (batchPendingUpdates.Count > 0) UpdateState(); + + hasTiming.Value = !ReferenceEquals(ControlPointInfo.TimingPointAt(editorClock.CurrentTime), TimingControlPoint.DEFAULT); } protected override void UpdateState() From eac9b1ec7e32eee5998cf4ff95cc464e04375eb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:30:13 +0900 Subject: [PATCH 04/10] Disable toolbox composition buttons when beatmap is not timed --- .../Editing/TestSceneHitObjectComposer.cs | 105 ++++++++++++++---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +- .../RadioButtons/DrawableRadioButton.cs | 27 +++-- 3 files changed, 103 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 7ca24346aa..f75c976f56 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -2,17 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Timing; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Tests.Visual.Editing @@ -20,37 +27,89 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneHitObjectComposer : EditorClockTestScene { - [BackgroundDependencyLoader] - private void load() + private OsuHitObjectComposer hitObjectComposer; + private EditorBeatmapContainer editorBeatmapContainer; + + private EditorBeatmap editorBeatmap => editorBeatmapContainer.EditorBeatmap; + + [SetUpSteps] + public void SetUpSteps() { - Beatmap.Value = CreateWorkingBeatmap(new Beatmap + AddStep("create beatmap", () => { - HitObjects = new List + Beatmap.Value = CreateWorkingBeatmap(new Beatmap { - new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, - new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, - new Slider + HitObjects = new List { - Position = new Vector2(128, 256), - Path = new SliderPath(PathType.Linear, new[] + new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, + new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, + new Slider { - Vector2.Zero, - new Vector2(216, 0), - }), - Scale = 0.5f, - } - }, + Position = new Vector2(128, 256), + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(216, 0), + }), + Scale = 0.5f, + } + }, + }); }); - var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + AddStep("Create composer", () => + { + Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) + { + Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) + }; + }); + } - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - Dependencies.CacheAs(clock); - Dependencies.CacheAs(clock); - Dependencies.CacheAs(editorBeatmap); - Dependencies.CacheAs(editorBeatmap); + [Test] + public void TestPlacementOnlyWorksWithTiming() + { + AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear()); - Child = new OsuHitObjectComposer(new OsuRuleset()); + AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is SelectTool); + AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); + AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); + AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Click()); + AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); + } + + public class EditorBeatmapContainer : Container + { + private readonly WorkingBeatmap working; + + public EditorBeatmap EditorBeatmap { get; private set; } + + public EditorBeatmapContainer(WorkingBeatmap working) + { + this.working = working; + + RelativeSizeAxes = Axes.Both; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + EditorBeatmap = new EditorBeatmap(working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + + dependencies.CacheAs(EditorBeatmap); + dependencies.CacheAs(EditorBeatmap); + + return dependencies; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(EditorBeatmap); + } } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a7005954b2..c6c112bec8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -219,7 +219,8 @@ namespace osu.Game.Rulesets.Edit if (item != null) { - item.Select(); + if (!item.Selected.Disabled) + item.Select(); return true; } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 1f608d28fd..79468f0ab0 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -5,9 +5,11 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -16,24 +18,28 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class DrawableRadioButton : OsuButton + public class DrawableRadioButton : OsuButton, IHasTooltip { /// /// Invoked when this has been selected. /// public Action Selected; + public readonly RadioButton Button; + private Color4 defaultBackgroundColour; private Color4 defaultBubbleColour; private Color4 selectedBackgroundColour; private Color4 selectedBubbleColour; private Drawable icon; - private readonly RadioButton button; + + [Resolved(canBeNull: true)] + private EditorBeatmap editorBeatmap { get; set; } public DrawableRadioButton(RadioButton button) { - this.button = button; + this.Button = button; Text = button.Item.ToString(); Action = button.Select; @@ -57,7 +63,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Colour = Color4.Black.Opacity(0.5f) }; - Add(icon = (button.CreateIcon?.Invoke() ?? new Circle()).With(b => + Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => { b.Blending = BlendingParameters.Additive; b.Anchor = Anchor.CentreLeft; @@ -71,13 +77,16 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons { base.LoadComplete(); - button.Selected.ValueChanged += selected => + Button.Selected.ValueChanged += selected => { updateSelectionState(); if (selected.NewValue) - Selected?.Invoke(button); + Selected?.Invoke(Button); }; + editorBeatmap?.HasTiming.BindValueChanged(hasTiming => Button.Selected.Disabled = !hasTiming.NewValue, true); + + Button.Selected.BindDisabledChanged(disabled => Enabled.Value = !disabled, true); updateSelectionState(); } @@ -86,8 +95,8 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons if (!IsLoaded) return; - BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; - icon.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; + BackgroundColour = Button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; + icon.Colour = Button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; } protected override SpriteText CreateText() => new OsuSpriteText @@ -97,5 +106,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Anchor = Anchor.CentreLeft, X = 40f }; + + public LocalisableString TooltipText => Enabled.Value ? string.Empty : "Add at least one timing point first!"; } } From 50eed26bd1addd0f929db1b0a55609e7e7cc8b97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:30:49 +0900 Subject: [PATCH 05/10] Rename radio button `item` to `label` --- osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | 6 +++--- .../Edit/Components/RadioButtons/DrawableRadioButton.cs | 4 ++-- .../Screens/Edit/Components/RadioButtons/RadioButton.cs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f75c976f56..67c37413ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear()); AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is SelectTool); - AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); + AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); - AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); - AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Click()); + AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); + AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Click()); AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 79468f0ab0..bbe77eaa07 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -39,9 +39,9 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons public DrawableRadioButton(RadioButton button) { - this.Button = button; + Button = button; - Text = button.Item.ToString(); + Text = button.Label; Action = button.Select; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index c3ffd8add0..ca79dd15d7 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// /// The item related to this button. /// - public string Item; + public string Label; /// /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. @@ -26,9 +26,9 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private readonly Action action; - public RadioButton(string item, Action action, Func createIcon = null) + public RadioButton(string label, Action action, Func createIcon = null) { - Item = item; + Label = label; CreateIcon = createIcon; this.action = action; Selected = new BindableBool(); From d7f997a7f3f221279ee4b1497129655950adb525 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 12:36:13 +0900 Subject: [PATCH 06/10] Set score's rank on a failed submission As we don't have a `RankInfo.F`, this is the next best choice. I am also adding a check osu-web side for this - this is just to make sure we aren't sending scores with SS when they are not actually completed. I'm working on a separate PR to ensure this does not get mutated during the player exit process. --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 97854ee12f..dc7ac1a48b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -953,7 +953,11 @@ namespace osu.Game.Screens.Play // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. if (prepareScoreForDisplayTask == null) + { Score.ScoreInfo.Passed = false; + // potentially should be ScoreRank.F instead? this is the best alternative for now. + Score.ScoreInfo.Rank = ScoreRank.D; + } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. From 70c9d7105feb5162661379f427562de6cba17f91 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 13:33:22 +0900 Subject: [PATCH 07/10] Add a function to compute hit object position in catch editor --- .../Edit/CatchHitObjectUtils.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs new file mode 100644 index 0000000000..beffdf0362 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit +{ + /// + /// Utility functions used by the editor. + /// + public static class CatchHitObjectUtils + { + /// + /// Get the position of the hit object in the playfield based on and . + /// + public static Vector2 GetStartPosition(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + return new Vector2(hitObject.OriginalX, hitObjectContainer.PositionAtTime(hitObject.StartTime)); + } + } +} From c34758485125a3d0ad2f41d28c4c60e1771b2fc1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 13:33:46 +0900 Subject: [PATCH 08/10] Use added utility function --- .../Edit/Blueprints/CatchSelectionBlueprint.cs | 5 ++--- .../Edit/Blueprints/Components/FruitOutline.cs | 6 +----- .../Edit/Blueprints/Components/NestedOutlineContainer.cs | 9 ++------- .../Edit/Blueprints/Components/ScrollingPath.cs | 6 ------ .../Edit/Blueprints/FruitPlacementBlueprint.cs | 3 ++- .../Edit/Blueprints/FruitSelectionBlueprint.cs | 6 ++++-- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 3 +-- 7 files changed, 12 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs index 720d730858..7e566c810c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -19,9 +19,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { get { - float x = HitObject.OriginalX; - float y = HitObjectContainer.PositionAtTime(HitObject.StartTime); - return HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight)); + Vector2 position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); + return HitObjectContainer.ToScreenSpace(position + new Vector2(0, HitObjectContainer.DrawHeight)); } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 345b59bdcd..0c03068e26 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Skinning.Default; -using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components @@ -28,10 +26,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Colour = osuColour.Yellow; } - public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null) + public void UpdateFrom(CatchHitObject hitObject) { - X = hitObject.EffectiveX - (parent?.OriginalX ?? 0); - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current); Scale = new Vector2(hitObject.Scale); } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs index 48d90e8b24..cf916b27a4 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Anchor = Anchor.BottomLeft; } - public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) - { - X = parentHitObject.OriginalX; - Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime); - } - public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) { nestedHitObjects.Clear(); @@ -43,7 +37,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { var hitObject = nestedHitObjects[i]; var outline = (FruitOutline)InternalChildren[i]; - outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + outline.Position = CatchHitObjectUtils.GetStartPosition(hitObjectContainer, hitObject) - Position; + outline.UpdateFrom(hitObject); outline.Scale *= hitObject is Droplet ? 0.5f : 1; } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs index 96111beda4..109bf61ea5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -33,12 +33,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components }; } - public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) - { - X = hitObject.OriginalX; - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); - } - public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) { double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs index 0f28cf6786..e169e3b75c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs @@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { base.Update(); - outline.UpdateFrom(HitObjectContainer, HitObject); + outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); + outline.UpdateFrom(HitObject); } protected override bool OnMouseDown(MouseDownEvent e) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs index 9665aac2fb..150297badb 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs @@ -20,8 +20,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { base.Update(); - if (IsSelected) - outline.UpdateFrom(HitObjectContainer, HitObject); + if (!IsSelected) return; + + outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); + outline.UpdateFrom(HitObject); } } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index bf7b962e0a..0614c4c24d 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -49,8 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (!IsSelected) return; - scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); - nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.Position = scrollingPath.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); if (pathCache.IsValid) return; From bde35d9f211e9cd789eb6499e7321b33f87d1f31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 16:57:12 +0900 Subject: [PATCH 09/10] Rename radio button classes to be local to editor --- .../Editing/TestSceneEditorComposeRadioButtons.cs | 4 ++-- .../Visual/Editing/TestSceneHitObjectComposer.cs | 6 +++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- .../{DrawableRadioButton.cs => EditorRadioButton.cs} | 6 +++--- ...tonCollection.cs => EditorRadioButtonCollection.cs} | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game/Screens/Edit/Components/RadioButtons/{DrawableRadioButton.cs => EditorRadioButton.cs} (94%) rename osu.Game/Screens/Edit/Components/RadioButtons/{RadioButtonCollection.cs => EditorRadioButtonCollection.cs} (85%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs index 0b52ae2b95..028509ccd4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs @@ -13,8 +13,8 @@ namespace osu.Game.Tests.Visual.Editing { public TestSceneEditorComposeRadioButtons() { - RadioButtonCollection collection; - Add(collection = new RadioButtonCollection + EditorRadioButtonCollection collection; + Add(collection = new EditorRadioButtonCollection { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 67c37413ed..550896270a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear()); AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is SelectTool); - AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); + AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); - AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); - AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Click()); + AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); + AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Click()); AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c6c112bec8..7ee631674e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Edit private InputManager inputManager; - private RadioButtonCollection toolboxCollection; + private EditorRadioButtonCollection toolboxCollection; private FillFlowContainer togglesCollection; @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Edit { new ToolboxGroup("toolbox (1-9)") { - Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } + Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X } }, new ToolboxGroup("toggles (Q~P)") { diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs similarity index 94% rename from osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs index bbe77eaa07..d66856ebd8 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs @@ -18,10 +18,10 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class DrawableRadioButton : OsuButton, IHasTooltip + public class EditorRadioButton : OsuButton, IHasTooltip { /// - /// Invoked when this has been selected. + /// Invoked when this has been selected. /// public Action Selected; @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons [Resolved(canBeNull: true)] private EditorBeatmap editorBeatmap { get; set; } - public DrawableRadioButton(RadioButton button) + public EditorRadioButton(RadioButton button) { Button = button; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButtonCollection.cs similarity index 85% rename from osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButtonCollection.cs index 16574c0baf..6a7b0c9ef7 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButtonCollection.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class RadioButtonCollection : CompositeDrawable + public class EditorRadioButtonCollection : CompositeDrawable { private IReadOnlyList items; @@ -28,13 +28,13 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons } } - private readonly FlowContainer buttonContainer; + private readonly FlowContainer buttonContainer; - public RadioButtonCollection() + public EditorRadioButtonCollection() { AutoSizeAxes = Axes.Y; - InternalChild = buttonContainer = new FillFlowContainer + InternalChild = buttonContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons currentlySelected = null; }; - buttonContainer.Add(new DrawableRadioButton(button)); + buttonContainer.Add(new EditorRadioButton(button)); } } } From b29209d13f6bd66b52f5d2c122001392691dd3c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 17:08:40 +0900 Subject: [PATCH 10/10] Ensure tool is always set back to select tool when beatmap becomes untimed --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 7ee631674e..8090fcbd32 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -67,6 +68,8 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; + private IBindable hasTiming; + protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; @@ -160,6 +163,14 @@ namespace osu.Game.Rulesets.Edit base.LoadComplete(); inputManager = GetContainingInputManager(); + + hasTiming = EditorBeatmap.HasTiming.GetBoundCopy(); + hasTiming.BindValueChanged(timing => + { + // it's important this is performed before the similar code in EditorRadioButton disables the button. + if (!timing.NewValue) + setSelectTool(); + }); } public override Playfield Playfield => drawableRulesetWrapper.Playfield;