mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 07:06:35 +09:00
Merge branch 'master' into barrel-roll
This commit is contained in:
@ -22,7 +22,11 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
// Audio
|
||||
new CheckAudioPresence(),
|
||||
new CheckAudioQuality()
|
||||
new CheckAudioQuality(),
|
||||
|
||||
// Compose
|
||||
new CheckUnsnappedObjects(),
|
||||
new CheckConcurrentObjects()
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap)
|
||||
|
88
osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs
Normal file
88
osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs
Normal file
@ -0,0 +1,88 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckConcurrentObjects : ICheck
|
||||
{
|
||||
// We guarantee that the objects are either treated as concurrent or unsnapped when near the same beat divisor.
|
||||
private const double ms_leniency = CheckUnsnappedObjects.UNSNAP_MS_THRESHOLD;
|
||||
|
||||
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateConcurrentSame(this),
|
||||
new IssueTemplateConcurrentDifferent(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
|
||||
{
|
||||
for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i)
|
||||
{
|
||||
var hitobject = playableBeatmap.HitObjects[i];
|
||||
|
||||
for (int j = i + 1; j < playableBeatmap.HitObjects.Count; ++j)
|
||||
{
|
||||
var nextHitobject = playableBeatmap.HitObjects[j];
|
||||
|
||||
// Accounts for rulesets with hitobjects separated by columns, such as Mania.
|
||||
// In these cases we only care about concurrent objects within the same column.
|
||||
if ((hitobject as IHasColumn)?.Column != (nextHitobject as IHasColumn)?.Column)
|
||||
continue;
|
||||
|
||||
// Two hitobjects cannot be concurrent without also being concurrent with all objects in between.
|
||||
// So if the next object is not concurrent, then we know no future objects will be either.
|
||||
if (!areConcurrent(hitobject, nextHitobject))
|
||||
break;
|
||||
|
||||
if (hitobject.GetType() == nextHitobject.GetType())
|
||||
yield return new IssueTemplateConcurrentSame(this).Create(hitobject, nextHitobject);
|
||||
else
|
||||
yield return new IssueTemplateConcurrentDifferent(this).Create(hitobject, nextHitobject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency;
|
||||
|
||||
public abstract class IssueTemplateConcurrent : IssueTemplate
|
||||
{
|
||||
protected IssueTemplateConcurrent(ICheck check, string unformattedMessage)
|
||||
: base(check, IssueType.Problem, unformattedMessage)
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(HitObject hitobject, HitObject nextHitobject)
|
||||
{
|
||||
var hitobjects = new List<HitObject> { hitobject, nextHitobject };
|
||||
return new Issue(hitobjects, this, hitobject.GetType().Name, nextHitobject.GetType().Name)
|
||||
{
|
||||
Time = nextHitobject.StartTime
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateConcurrentSame : IssueTemplateConcurrent
|
||||
{
|
||||
public IssueTemplateConcurrentSame(ICheck check)
|
||||
: base(check, "{0}s are concurrent here.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateConcurrentDifferent : IssueTemplateConcurrent
|
||||
{
|
||||
public IssueTemplateConcurrentDifferent(ICheck check)
|
||||
: base(check, "{0} and {1} are concurrent here.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs
Normal file
100
osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs
Normal file
@ -0,0 +1,100 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckUnsnappedObjects : ICheck
|
||||
{
|
||||
public const double UNSNAP_MS_THRESHOLD = 2;
|
||||
|
||||
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Timing, "Unsnapped hitobjects");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateLargeUnsnap(this),
|
||||
new IssueTemplateSmallUnsnap(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
|
||||
{
|
||||
var controlPointInfo = playableBeatmap.ControlPointInfo;
|
||||
|
||||
foreach (var hitobject in playableBeatmap.HitObjects)
|
||||
{
|
||||
double startUnsnap = hitobject.StartTime - controlPointInfo.GetClosestSnappedTime(hitobject.StartTime);
|
||||
string startPostfix = hitobject is IHasDuration ? "start" : "";
|
||||
foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix))
|
||||
yield return issue;
|
||||
|
||||
if (hitobject is IHasRepeats hasRepeats)
|
||||
{
|
||||
for (int repeatIndex = 0; repeatIndex < hasRepeats.RepeatCount; ++repeatIndex)
|
||||
{
|
||||
double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1);
|
||||
double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1);
|
||||
double repeatUnsnap = repeatTime - controlPointInfo.GetClosestSnappedTime(repeatTime);
|
||||
foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat"))
|
||||
yield return issue;
|
||||
}
|
||||
}
|
||||
|
||||
if (hitobject is IHasDuration hasDuration)
|
||||
{
|
||||
double endUnsnap = hasDuration.EndTime - controlPointInfo.GetClosestSnappedTime(hasDuration.EndTime);
|
||||
foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end"))
|
||||
yield return issue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Issue> getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "")
|
||||
{
|
||||
if (Math.Abs(unsnap) >= UNSNAP_MS_THRESHOLD)
|
||||
yield return new IssueTemplateLargeUnsnap(this).Create(hitobject, unsnap, time, postfix);
|
||||
else if (Math.Abs(unsnap) >= 1)
|
||||
yield return new IssueTemplateSmallUnsnap(this).Create(hitobject, unsnap, time, postfix);
|
||||
|
||||
// We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works.
|
||||
}
|
||||
|
||||
public abstract class IssueTemplateUnsnap : IssueTemplate
|
||||
{
|
||||
protected IssueTemplateUnsnap(ICheck check, IssueType type)
|
||||
: base(check, type, "{0} is unsnapped by {1:0.##} ms.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(HitObject hitobject, double unsnap, double time, string postfix = "")
|
||||
{
|
||||
string objectName = hitobject.GetType().Name;
|
||||
if (!string.IsNullOrEmpty(postfix))
|
||||
objectName += " " + postfix;
|
||||
|
||||
return new Issue(hitobject, this, objectName, unsnap) { Time = time };
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateLargeUnsnap : IssueTemplateUnsnap
|
||||
{
|
||||
public IssueTemplateLargeUnsnap(ICheck check)
|
||||
: base(check, IssueType.Problem)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateSmallUnsnap : IssueTemplateUnsnap
|
||||
{
|
||||
public IssueTemplateSmallUnsnap(ICheck check)
|
||||
: base(check, IssueType.Negligible)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -182,8 +182,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// <summary>
|
||||
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
|
||||
/// </summary>
|
||||
protected virtual ComposeBlueprintContainer CreateBlueprintContainer()
|
||||
=> new ComposeBlueprintContainer(this);
|
||||
protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a drawable ruleset for the provided ruleset.
|
||||
|
@ -3,12 +3,13 @@
|
||||
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
public abstract class OverlaySelectionBlueprint : SelectionBlueprint
|
||||
public abstract class OverlaySelectionBlueprint : SelectionBlueprint<HitObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="DrawableHitObject"/> which this <see cref="OverlaySelectionBlueprint"/> applies to.
|
||||
@ -33,7 +34,5 @@ namespace osu.Game.Rulesets.Edit
|
||||
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
|
||||
|
||||
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
||||
|
||||
public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => DrawableObject.Parent.ToLocalSpace(screenSpacePosition) - DrawableObject.Position;
|
||||
}
|
||||
}
|
||||
|
@ -8,35 +8,33 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
/// <summary>
|
||||
/// A blueprint placed above a <see cref="DrawableHitObject"/> adding editing functionality.
|
||||
/// A blueprint placed above a displaying item adding editing functionality.
|
||||
/// </summary>
|
||||
public abstract class SelectionBlueprint : CompositeDrawable, IStateful<SelectionState>
|
||||
public abstract class SelectionBlueprint<T> : CompositeDrawable, IStateful<SelectionState>
|
||||
{
|
||||
public readonly HitObject HitObject;
|
||||
public readonly T Item;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this <see cref="SelectionBlueprint"/> has been selected.
|
||||
/// Invoked when this <see cref="SelectionBlueprint{T}"/> has been selected.
|
||||
/// </summary>
|
||||
public event Action<SelectionBlueprint> Selected;
|
||||
public event Action<SelectionBlueprint<T>> Selected;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this <see cref="SelectionBlueprint"/> has been deselected.
|
||||
/// Invoked when this <see cref="SelectionBlueprint{T}"/> has been deselected.
|
||||
/// </summary>
|
||||
public event Action<SelectionBlueprint> Deselected;
|
||||
public event Action<SelectionBlueprint<T>> Deselected;
|
||||
|
||||
public override bool HandlePositionalInput => ShouldBeAlive;
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
protected SelectionBlueprint(HitObject hitObject)
|
||||
protected SelectionBlueprint(T item)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
Item = item;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
AlwaysPresent = true;
|
||||
@ -87,7 +85,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
protected virtual void OnDeselected()
|
||||
{
|
||||
// selection blueprints are AlwaysPresent while the related DrawableHitObject is visible
|
||||
// selection blueprints are AlwaysPresent while the related item is visible
|
||||
// set the body piece's alpha directly to avoid arbitrarily rendering frame buffers etc. of children.
|
||||
foreach (var d in InternalChildren)
|
||||
d.Hide();
|
||||
@ -129,7 +127,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
|
||||
|
||||
/// <summary>
|
||||
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected.
|
||||
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected via a drag.
|
||||
/// </summary>
|
||||
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
|
||||
|
||||
@ -138,8 +136,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||
|
||||
public virtual Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position;
|
||||
|
||||
/// <summary>
|
||||
/// Handle to perform a partial deletion when the user requests a quick delete (Shift+Right Click).
|
||||
/// </summary>
|
||||
|
16
osu.Game/Rulesets/Objects/Types/IHasColumn.cs
Normal file
16
osu.Game/Rulesets/Objects/Types/IHasColumn.cs
Normal file
@ -0,0 +1,16 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A type of hit object which lies in one of a number of predetermined columns.
|
||||
/// </summary>
|
||||
public interface IHasColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// The column which the hit object lies in.
|
||||
/// </summary>
|
||||
int Column { get; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user