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:
parent
81caa854e6
commit
4e1e19728c
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user