1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 12:27:26 +08:00

Merge pull request #29986 from OliBomby/refactor-placement-tool

Refactor placement blueprints to not be limited to hit objects
This commit is contained in:
Dan Balasescu 2024-09-25 17:59:43 +09:00 committed by GitHub
commit 3111d6a74d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 209 additions and 171 deletions

View File

@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
contentContainer.Playfield.HitObjectContainer.Add(hitObject); contentContainer.Playfield.HitObjectContainer.Add(hitObject);
} }
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
{ {
var result = base.SnapForBlueprint(blueprint); var result = base.SnapForBlueprint(blueprint);
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP; result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
{ {
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
protected override void AddHitObject(DrawableHitObject hitObject) protected override void AddHitObject(DrawableHitObject hitObject)
{ {

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
{ {
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
[Test] [Test]
public void TestFruitPlacementPosition() public void TestFruitPlacementPosition()

View File

@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
private void addMoveAndClickSteps(double time, float position, bool end = false) private void addMoveAndClickSteps(double time, float position, bool end = false)
{ {

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
namespace osu.Game.Rulesets.Catch.Edit namespace osu.Game.Rulesets.Catch.Edit
{ {
public class BananaShowerCompositionTool : HitObjectCompositionTool public class BananaShowerCompositionTool : CompositionTool
{ {
public BananaShowerCompositionTool() public BananaShowerCompositionTool()
: base(nameof(BananaShower)) : base(nameof(BananaShower))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
} }
} }

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Catch.Edit.Blueprints namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{ {
public partial class CatchPlacementBlueprint<THitObject> : PlacementBlueprint public partial class CatchPlacementBlueprint<THitObject> : HitObjectPlacementBlueprint
where THitObject : CatchHitObject, new() where THitObject : CatchHitObject, new()
{ {
protected new THitObject HitObject => (THitObject)base.HitObject; protected new THitObject HitObject => (THitObject)base.HitObject;

View File

@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Edit
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid(); protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
{ {
new FruitCompositionTool(), new FruitCompositionTool(),
new JuiceStreamCompositionTool(), new JuiceStreamCompositionTool(),

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
namespace osu.Game.Rulesets.Catch.Edit namespace osu.Game.Rulesets.Catch.Edit
{ {
public class FruitCompositionTool : HitObjectCompositionTool public class FruitCompositionTool : CompositionTool
{ {
public FruitCompositionTool() public FruitCompositionTool()
: base(nameof(Fruit)) : base(nameof(Fruit))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
namespace osu.Game.Rulesets.Catch.Edit namespace osu.Game.Rulesets.Catch.Edit
{ {
public class JuiceStreamCompositionTool : HitObjectCompositionTool public class JuiceStreamCompositionTool : CompositionTool
{ {
public JuiceStreamCompositionTool() public JuiceStreamCompositionTool()
: base(nameof(JuiceStream)) : base(nameof(JuiceStream))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
public override PlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
} }
} }

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
}); });
} }
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
{ {
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
var pos = column.ScreenSpacePositionAtTime(time); var pos = column.ScreenSpacePositionAtTime(time);

View File

@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
{ {
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
} }
} }

View File

@ -64,6 +64,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject; private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
} }
} }

View File

@ -15,7 +15,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public abstract partial class ManiaPlacementBlueprint<T> : PlacementBlueprint public abstract partial class ManiaPlacementBlueprint<T> : HitObjectPlacementBlueprint
where T : ManiaHitObject where T : ManiaHitObject
{ {
protected new T HitObject => (T)base.HitObject; protected new T HitObject => (T)base.HitObject;

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.Edit.Blueprints;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
{ {
public class HoldNoteCompositionTool : HitObjectCompositionTool public class HoldNoteCompositionTool : CompositionTool
{ {
public HoldNoteCompositionTool() public HoldNoteCompositionTool()
: base("Hold") : base("Hold")
@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
} }
} }

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit
protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid(); protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
{ {
new NoteCompositionTool(), new NoteCompositionTool(),
new HoldNoteCompositionTool() new HoldNoteCompositionTool()

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
{ {
public class NoteCompositionTool : HitObjectCompositionTool public class NoteCompositionTool : CompositionTool
{ {
public NoteCompositionTool() public NoteCompositionTool()
: base(nameof(Note)) : base(nameof(Note))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
} }
} }

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
{ {
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
} }
} }

View File

@ -514,6 +514,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
} }
} }

View File

@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
} }
} }

View File

@ -9,7 +9,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{ {
public partial class HitCirclePlacementBlueprint : PlacementBlueprint public partial class HitCirclePlacementBlueprint : HitObjectPlacementBlueprint
{ {
public new HitCircle HitObject => (HitCircle)base.HitObject; public new HitCircle HitObject => (HitCircle)base.HitObject;

View File

@ -21,7 +21,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
public partial class SliderPlacementBlueprint : PlacementBlueprint public partial class SliderPlacementBlueprint : HitObjectPlacementBlueprint
{ {
public new Slider HitObject => (Slider)base.HitObject; public new Slider HitObject => (Slider)base.HitObject;

View File

@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{ {
public partial class SpinnerPlacementBlueprint : PlacementBlueprint public partial class SpinnerPlacementBlueprint : HitObjectPlacementBlueprint
{ {
public new Spinner HitObject => (Spinner)base.HitObject; public new Spinner HitObject => (Spinner)base.HitObject;

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
{ {
public class HitCircleCompositionTool : HitObjectCompositionTool public class HitCircleCompositionTool : CompositionTool
{ {
public HitCircleCompositionTool() public HitCircleCompositionTool()
: base(nameof(HitCircle)) : base(nameof(HitCircle))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
} }
} }

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override DrawableRuleset<OsuHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) protected override DrawableRuleset<OsuHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
=> new DrawableOsuEditorRuleset(ruleset, beatmap, mods); => new DrawableOsuEditorRuleset(ruleset, beatmap, mods);
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
{ {
new HitCircleCompositionTool(), new HitCircleCompositionTool(),
new SliderCompositionTool(), new SliderCompositionTool(),

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
{ {
public class SliderCompositionTool : HitObjectCompositionTool public class SliderCompositionTool : CompositionTool
{ {
public SliderCompositionTool() public SliderCompositionTool()
: base(nameof(Slider)) : base(nameof(Slider))
@ -26,6 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
{ {
public class SpinnerCompositionTool : HitObjectCompositionTool public class SpinnerCompositionTool : CompositionTool
{ {
public SpinnerCompositionTool() public SpinnerCompositionTool()
: base(nameof(Spinner)) : base(nameof(Spinner))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
} }
} }

View File

@ -10,7 +10,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{ {
public partial class HitPlacementBlueprint : PlacementBlueprint public partial class HitPlacementBlueprint : HitObjectPlacementBlueprint
{ {
private readonly HitPiece piece; private readonly HitPiece piece;

View File

@ -17,7 +17,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{ {
public partial class TaikoSpanPlacementBlueprint : PlacementBlueprint public partial class TaikoSpanPlacementBlueprint : HitObjectPlacementBlueprint
{ {
private readonly HitPiece headPiece; private readonly HitPiece headPiece;
private readonly HitPiece tailPiece; private readonly HitPiece tailPiece;

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit namespace osu.Game.Rulesets.Taiko.Edit
{ {
public class DrumRollCompositionTool : HitObjectCompositionTool public class DrumRollCompositionTool : CompositionTool
{ {
public DrumRollCompositionTool() public DrumRollCompositionTool()
: base(nameof(DrumRoll)) : base(nameof(DrumRoll))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit namespace osu.Game.Rulesets.Taiko.Edit
{ {
public class HitCompositionTool : HitObjectCompositionTool public class HitCompositionTool : CompositionTool
{ {
public HitCompositionTool() public HitCompositionTool()
: base(nameof(Hit)) : base(nameof(Hit))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit namespace osu.Game.Rulesets.Taiko.Edit
{ {
public class SwellCompositionTool : HitObjectCompositionTool public class SwellCompositionTool : CompositionTool
{ {
public SwellCompositionTool() public SwellCompositionTool()
: base(nameof(Swell)) : base(nameof(Swell))
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
} }
} }

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
{ {
} }
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
{ {
new HitCompositionTool(), new HitCompositionTool(),
new DrumRollCompositionTool(), new DrumRollCompositionTool(),

View File

@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Edit
/// <remarks> /// <remarks>
/// A "select" tool is automatically added as the first tool. /// A "select" tool is automatically added as the first tool.
/// </remarks> /// </remarks>
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; } protected abstract IReadOnlyList<CompositionTool> CompositionTools { get; }
/// <summary> /// <summary>
/// A collection of states which will be displayed to the user in the toolbox. /// A collection of states which will be displayed to the user in the toolbox.
@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Edit
private void setSelectTool() => toolboxCollection.Items.First().Select(); private void setSelectTool() => toolboxCollection.Items.First().Select();
private void toolSelected(HitObjectCompositionTool tool) private void toolSelected(CompositionTool tool)
{ {
BlueprintContainer.CurrentTool = tool; BlueprintContainer.CurrentTool = tool;

View File

@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Edit
{ {
public class HitObjectCompositionToolButton : RadioButton public class HitObjectCompositionToolButton : RadioButton
{ {
public HitObjectCompositionTool Tool { get; } public CompositionTool Tool { get; }
public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action) public HitObjectCompositionToolButton(CompositionTool tool, Action? action)
: base(tool.Name, action, tool.CreateIcon) : base(tool.Name, action, tool.CreateIcon)
{ {
Tool = tool; Tool = tool;

View File

@ -0,0 +1,126 @@
// 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.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Rulesets.Edit
{
/// <summary>
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
/// </summary>
public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint
{
/// <summary>
/// Whether the sample bank should be taken from the previous hit object.
/// </summary>
public bool AutomaticBankAssignment { get; set; }
/// <summary>
/// The <see cref="HitObject"/> that is being placed.
/// </summary>
public readonly HitObject HitObject;
[Resolved]
protected EditorClock EditorClock { get; private set; } = null!;
[Resolved]
private EditorBeatmap beatmap { get; set; } = null!;
private Bindable<double> startTimeBindable = null!;
private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
[Resolved]
private IPlacementHandler placementHandler { get; set; } = null!;
protected HitObjectPlacementBlueprint(HitObject hitObject)
{
HitObject = hitObject;
// adding the default hit sample should be the case regardless of the ruleset.
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
}
[BackgroundDependencyLoader]
private void load()
{
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
}
protected override void BeginPlacement(bool commitStart = false)
{
base.BeginPlacement(commitStart);
placementHandler.BeginPlacement(HitObject);
}
public override void EndPlacement(bool commit)
{
base.EndPlacement(commit);
placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
}
/// <summary>
/// Updates the time and position of this <see cref="HitObjectPlacementBlueprint"/> based on the provided snap information.
/// </summary>
/// <param name="result">The snap result information.</param>
public override void UpdateTimeAndPosition(SnapResult result)
{
if (PlacementActive == PlacementState.Waiting)
{
HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
if (HitObject is IHasComboInformation comboInformation)
comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
}
var lastHitObject = getPreviousHitObject();
if (AutomaticBankAssignment)
{
// Create samples based on the sample settings of the previous hit object
if (lastHitObject != null)
{
for (int i = 0; i < HitObject.Samples.Count; i++)
HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
}
}
else
{
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
if (lastHitNormal != null)
{
// Only inherit the volume from the previous hit object
for (int i = 0; i < HitObject.Samples.Count; i++)
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
}
}
if (HitObject is IHasRepeats hasRepeats)
{
// Make sure all the node samples are identical to the hit object's samples
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList();
}
}
/// <summary>
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,IBeatmapDifficultyInfo,CancellationToken)"/>,
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
/// </summary>
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
}
}

View File

@ -1,29 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
{ {
/// <summary> /// <summary>
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation. /// A blueprint which governs the placement of something.
/// </summary> /// </summary>
public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction> public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{ {
@ -32,29 +22,6 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public PlacementState PlacementActive { get; private set; } public PlacementState PlacementActive { get; private set; }
/// <summary>
/// Whether the sample bank should be taken from the previous hit object.
/// </summary>
public bool AutomaticBankAssignment { get; set; }
/// <summary>
/// The <see cref="HitObject"/> that is being placed.
/// </summary>
public readonly HitObject HitObject;
[Resolved]
protected EditorClock EditorClock { get; private set; } = null!;
[Resolved]
private EditorBeatmap beatmap { get; set; } = null!;
private Bindable<double> startTimeBindable = null!;
private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
[Resolved]
private IPlacementHandler placementHandler { get; set; } = null!;
/// <summary> /// <summary>
/// Whether this blueprint is currently in a state that can be committed. /// Whether this blueprint is currently in a state that can be committed.
/// </summary> /// </summary>
@ -64,13 +31,8 @@ namespace osu.Game.Rulesets.Edit
/// </remarks> /// </remarks>
protected virtual bool IsValidForPlacement => true; protected virtual bool IsValidForPlacement => true;
protected PlacementBlueprint(HitObject hitObject) protected PlacementBlueprint()
{ {
HitObject = hitObject;
// adding the default hit sample should be the case regardless of the ruleset.
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
@ -78,30 +40,22 @@ namespace osu.Game.Rulesets.Edit
AlwaysPresent = true; AlwaysPresent = true;
} }
[BackgroundDependencyLoader]
private void load()
{
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
}
/// <summary> /// <summary>
/// Signals that the placement of <see cref="HitObject"/> has started. /// Signals that the placement has started.
/// </summary> /// </summary>
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param> /// <param name="commitStart">Whether this call is committing a value and continuing with further adjustments.</param>
protected void BeginPlacement(bool commitStart = false) protected virtual void BeginPlacement(bool commitStart = false)
{ {
placementHandler.BeginPlacement(HitObject);
if (commitStart) if (commitStart)
PlacementActive = PlacementState.Active; PlacementActive = PlacementState.Active;
} }
/// <summary> /// <summary>
/// Signals that the placement of <see cref="HitObject"/> has finished. /// Signals that the placement of <see cref="HitObject"/> has finished.
/// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>. /// This will destroy this <see cref="PlacementBlueprint"/>, and commit the changes.
/// </summary> /// </summary>
/// <param name="commit">Whether the object should be committed. Note that a commit may fail if <see cref="IsValidForPlacement"/> is <c>false</c>.</param> /// <param name="commit">Whether the changes should be committed. Note that a commit may fail if <see cref="IsValidForPlacement"/> is <c>false</c>.</param>
public void EndPlacement(bool commit) public virtual void EndPlacement(bool commit)
{ {
switch (PlacementActive) switch (PlacementActive)
{ {
@ -114,10 +68,17 @@ namespace osu.Game.Rulesets.Edit
break; break;
} }
placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
PlacementActive = PlacementState.Finished; PlacementActive = PlacementState.Finished;
} }
/// <summary>
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
/// </summary>
/// <param name="result">The snap result information.</param>
public virtual void UpdateTimeAndPosition(SnapResult result)
{
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (PlacementActive == PlacementState.Waiting) if (PlacementActive == PlacementState.Waiting)
@ -138,57 +99,6 @@ namespace osu.Game.Rulesets.Edit
{ {
} }
/// <summary>
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
/// </summary>
/// <param name="result">The snap result information.</param>
public virtual void UpdateTimeAndPosition(SnapResult result)
{
if (PlacementActive == PlacementState.Waiting)
{
HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
if (HitObject is IHasComboInformation comboInformation)
comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
}
var lastHitObject = getPreviousHitObject();
if (AutomaticBankAssignment)
{
// Create samples based on the sample settings of the previous hit object
if (lastHitObject != null)
{
for (int i = 0; i < HitObject.Samples.Count; i++)
HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
}
}
else
{
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
if (lastHitNormal != null)
{
// Only inherit the volume from the previous hit object
for (int i = 0; i < HitObject.Samples.Count; i++)
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
}
}
if (HitObject is IHasRepeats hasRepeats)
{
// Make sure all the node samples are identical to the hit object's samples
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList();
}
}
/// <summary>
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,IBeatmapDifficultyInfo,CancellationToken)"/>,
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
/// </summary>
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool Handle(UIEvent e) protected override bool Handle(UIEvent e)

View File

@ -6,13 +6,13 @@ using osu.Framework.Localisation;
namespace osu.Game.Rulesets.Edit.Tools namespace osu.Game.Rulesets.Edit.Tools
{ {
public abstract class HitObjectCompositionTool public abstract class CompositionTool
{ {
public readonly string Name; public readonly string Name;
public LocalisableString TooltipText { get; init; } public LocalisableString TooltipText { get; init; }
protected HitObjectCompositionTool(string name) protected CompositionTool(string name)
{ {
Name = name; Name = name;
} }

View File

@ -9,7 +9,7 @@ using osu.Game.Graphics;
namespace osu.Game.Rulesets.Edit.Tools namespace osu.Game.Rulesets.Edit.Tools
{ {
public class SelectTool : HitObjectCompositionTool public class SelectTool : CompositionTool
{ {
public SelectTool() public SelectTool()
: base("Select") : base("Select")
@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Edit.Tools
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect };
public override PlacementBlueprint CreatePlacementBlueprint() => null; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => null;
} }
} }

View File

@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
public PlacementBlueprint CurrentPlacement { get; private set; } public PlacementBlueprint CurrentPlacement { get; private set; }
public HitObjectPlacementBlueprint CurrentHitObjectPlacement => CurrentPlacement as HitObjectPlacementBlueprint;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private EditorScreenWithTimeline editorScreen { get; set; } private EditorScreenWithTimeline editorScreen { get; set; }
@ -164,13 +166,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementNewCombo() private void updatePlacementNewCombo()
{ {
if (CurrentPlacement?.HitObject is IHasComboInformation c) if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c)
c.NewCombo = NewCombo.Value == TernaryState.True; c.NewCombo = NewCombo.Value == TernaryState.True;
} }
private void updatePlacementSamples() private void updatePlacementSamples()
{ {
if (CurrentPlacement == null) return; if (CurrentHitObjectPlacement == null) return;
foreach (var kvp in SelectionHandler.SelectionSampleStates) foreach (var kvp in SelectionHandler.SelectionSampleStates)
sampleChanged(kvp.Key, kvp.Value.Value); sampleChanged(kvp.Key, kvp.Value.Value);
@ -181,9 +183,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void sampleChanged(string sampleName, TernaryState state) private void sampleChanged(string sampleName, TernaryState state)
{ {
if (CurrentPlacement == null) return; if (CurrentHitObjectPlacement == null) return;
var samples = CurrentPlacement.HitObject.Samples; var samples = CurrentHitObjectPlacement.HitObject.Samples;
var existingSample = samples.FirstOrDefault(s => s.Name == sampleName); var existingSample = samples.FirstOrDefault(s => s.Name == sampleName);
@ -196,19 +198,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
case TernaryState.True: case TernaryState.True:
if (existingSample == null) if (existingSample == null)
samples.Add(CurrentPlacement.HitObject.CreateHitSampleInfo(sampleName)); samples.Add(CurrentHitObjectPlacement.HitObject.CreateHitSampleInfo(sampleName));
break; break;
} }
} }
private void bankChanged(string bankName, TernaryState state) private void bankChanged(string bankName, TernaryState state)
{ {
if (CurrentPlacement == null) return; if (CurrentHitObjectPlacement == null) return;
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True; CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True;
else if (state == TernaryState.True) else if (state == TernaryState.True)
CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
} }
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" }; public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
@ -386,12 +388,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
CurrentPlacement = null; CurrentPlacement = null;
} }
private HitObjectCompositionTool currentTool; private CompositionTool currentTool;
/// <summary> /// <summary>
/// The current placement tool. /// The current placement tool.
/// </summary> /// </summary>
public HitObjectCompositionTool CurrentTool public CompositionTool CurrentTool
{ {
get => currentTool; get => currentTool;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual
public abstract partial class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler public abstract partial class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler
{ {
protected readonly Container HitObjectContainer; protected readonly Container HitObjectContainer;
protected PlacementBlueprint CurrentBlueprint { get; private set; } protected HitObjectPlacementBlueprint CurrentBlueprint { get; private set; }
protected PlacementBlueprintTestScene() protected PlacementBlueprintTestScene()
{ {
@ -87,14 +87,14 @@ namespace osu.Game.Tests.Visual
CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint));
} }
protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) =>
new SnapResult(InputManager.CurrentState.Mouse.Position, null); new SnapResult(InputManager.CurrentState.Mouse.Position, null);
public override void Add(Drawable drawable) public override void Add(Drawable drawable)
{ {
base.Add(drawable); base.Add(drawable);
if (drawable is PlacementBlueprint blueprint) if (drawable is HitObjectPlacementBlueprint blueprint)
{ {
blueprint.Show(); blueprint.Show();
blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint)); blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint));
@ -106,6 +106,6 @@ namespace osu.Game.Tests.Visual
protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both };
protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract DrawableHitObject CreateHitObject(HitObject hitObject);
protected abstract PlacementBlueprint CreateBlueprint(); protected abstract HitObjectPlacementBlueprint CreateBlueprint();
} }
} }