1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

Refactor HitObject selection in Composer

This commit is contained in:
ratinfx 2023-11-11 14:02:42 +01:00
parent 81caa854e6
commit 4e1e19728c
5 changed files with 68 additions and 76 deletions

View File

@ -3,16 +3,15 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
@ -50,22 +49,44 @@ namespace osu.Game.Rulesets.Mania.Edit
new HoldNoteCompositionTool()
};
private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$");
public override string ConvertSelectionToString()
=> string.Join(ObjectSeparator, EditorBeatmap.SelectedHitObjects.Cast<ManiaHitObject>().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}"));
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<ManiaHitObject>().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}"));
public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo)
public override void SelectHitObjects(double timestamp, string objectDescription)
{
if (hitObject is not ManiaHitObject maniaHitObject)
return false;
if (!selection_regex.IsMatch(objectDescription))
return;
double[] split = objectInfo.Split('|').Select(double.Parse).ToArray();
List<ManiaHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<ManiaHitObject>().Where(h => h.StartTime >= timestamp).ToList();
string[] split = objectDescription.Split(',').ToArray();
for (int i = 0; i < split.Length; i++)
{
ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i]));
if (current == null)
continue;
EditorBeatmap.SelectedHitObjects.Add(current);
if (i < split.Length - 1)
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
}
}
private bool shouldBeSelected(ManiaHitObject hitObject, string objectInfo)
{
string[] split = objectInfo.Split('|').ToArray();
if (split.Length != 2)
return false;
double timeValue = split[0];
double columnValue = split[1];
return Math.Abs(maniaHitObject.StartTime - timeValue) < 0.5
&& Math.Abs(maniaHitObject.Column - columnValue) < 0.5;
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
return false;
return hitObject.StartTime == time
&& hitObject.Column == column;
}
}
}

View File

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
@ -103,18 +104,39 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override ComposeBlueprintContainer CreateBlueprintContainer()
=> new OsuBlueprintContainer(this);
private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$");
public override string ConvertSelectionToString()
=> string.Join(ObjectSeparator, selectedHitObjects.Cast<OsuHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
=> string.Join(',', selectedHitObjects.Cast<OsuHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo)
public override void SelectHitObjects(double timestamp, string objectDescription)
{
if (hitObject is not OsuHitObject osuHitObject)
if (!selection_regex.IsMatch(objectDescription))
return;
List<OsuHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<OsuHitObject>().Where(h => h.StartTime >= timestamp).ToList();
string[] split = objectDescription.Split(',').ToArray();
for (int i = 0; i < split.Length; i++)
{
OsuHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i]));
if (current == null)
continue;
EditorBeatmap.SelectedHitObjects.Add(current);
if (i < split.Length - 1)
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
}
}
private bool shouldBeSelected(OsuHitObject hitObject, string objectInfo)
{
if (!int.TryParse(objectInfo, out int combo) || combo < 1)
return false;
if (!int.TryParse(objectInfo, out int comboValue) || comboValue < 1)
return false;
return osuHitObject.IndexInCurrentCombo + 1 == comboValue;
return hitObject.IndexInCurrentCombo + 1 == combo;
}
private DistanceSnapGrid distanceSnapGrid;

View File

@ -2,11 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Edit
{
@ -31,43 +29,7 @@ namespace osu.Game.Rulesets.Edit
Debug.Assert(times.Length == 3);
return (times[0] * 60 + times[1]) * 1_000 + times[2];
}
public static List<HitObject> GetSelectedHitObjects(HitObjectComposer composer, IReadOnlyList<HitObject> editorHitObjects, string objectsGroup, double position)
{
List<HitObject> hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList();
List<HitObject> selectedObjects = new List<HitObject>();
string[] objectsToSelect = objectsGroup.Split(composer.ObjectSeparator).ToArray();
foreach (string objectInfo in objectsToSelect)
{
HitObject? current = hitObjects.FirstOrDefault(x => composer.HandleHitObjectSelection(x, objectInfo));
if (current == null)
continue;
selectedObjects.Add(current);
hitObjects = hitObjects.Where(x => x != current && x.StartTime >= current.StartTime).ToList();
}
// Stable behavior
// - always selects the next closest object when `objectsGroup` only has one (combo) item
if (objectsToSelect.Length != 1 || objectsGroup.Contains('|'))
return selectedObjects;
HitObject? nextClosest = editorHitObjects.FirstOrDefault(x => x.StartTime >= position);
if (nextClosest == null)
return selectedObjects;
if (nextClosest.StartTime <= (selectedObjects.FirstOrDefault()?.StartTime ?? position))
{
selectedObjects.Clear();
selectedObjects.Add(nextClosest);
}
return selectedObjects;
return (times[0] * 60 + times[1]) * 1000 + times[2];
}
}
}

View File

@ -529,17 +529,11 @@ namespace osu.Game.Rulesets.Edit
public virtual string ConvertSelectionToString() => string.Empty;
/// <summary>
/// The custom logic that decides whether a HitObject should be selected when clicking an editor timestamp link
/// Each ruleset has it's own selection method
/// </summary>
/// <param name="hitObject">The hitObject being checked</param>
/// <param name="objectInfo">A single hitObject's information created with <see cref="ConvertSelectionToString"/></param>
/// <returns>Whether a HitObject should be selected or not</returns>
public virtual bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) => false;
/// <summary>
/// A character that separates the selection in <see cref="ConvertSelectionToString"/>
/// </summary>
public virtual char ObjectSeparator => ',';
/// <param name="timestamp">The given timestamp</param>
/// <param name="objectDescription">The selected object information between the brackets</param>
public virtual void SelectHitObjects(double timestamp, string objectDescription) { }
#region IPositionSnapProvider

View File

@ -1194,15 +1194,8 @@ namespace osu.Game.Screens.Edit
if (Mode.Value != EditorScreenMode.Compose)
Mode.Value = EditorScreenMode.Compose;
List<HitObject> selected = EditorTimestampParser.GetSelectedHitObjects(
currentScreen.Dependencies.Get<HitObjectComposer>(),
editorBeatmap.HitObjects.ToList(),
objectsGroup,
position
);
if (selected.Any())
editorBeatmap.SelectedHitObjects.AddRange(selected);
// Let the Ruleset handle selection
currentScreen.Dependencies.Get<HitObjectComposer>().SelectHitObjects(position, objectsGroup);
}
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);