mirror of
https://github.com/ppy/osu.git
synced 2026-05-15 21:03:13 +08:00
102 lines
4.3 KiB
C#
102 lines
4.3 KiB
C#
// 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.Rulesets.Edit.Checks.Components;
|
|
using osu.Game.Rulesets.Objects;
|
|
using System;
|
|
|
|
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;
|
|
private const double almost_concurrent_threshold = 10.0;
|
|
|
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects");
|
|
|
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
|
{
|
|
new IssueTemplateConcurrent(this),
|
|
new IssueTemplateAlmostConcurrent(this)
|
|
};
|
|
|
|
public virtual IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
|
{
|
|
var hitObjects = context.CurrentDifficulty.Playable.HitObjects;
|
|
|
|
for (int i = 0; i < hitObjects.Count - 1; ++i)
|
|
{
|
|
var hitobject = hitObjects[i];
|
|
|
|
for (int j = i + 1; j < hitObjects.Count; ++j)
|
|
{
|
|
var nextHitobject = hitObjects[j];
|
|
|
|
// Two hitobjects cannot be concurrent without also being concurrent with all objects in between.
|
|
// So if the next object is not concurrent or almost concurrent, then we know no future objects will be either.
|
|
if (!AreConcurrent(hitobject, nextHitobject) && !AreAlmostConcurrent(hitobject, nextHitobject))
|
|
break;
|
|
|
|
if (AreConcurrent(hitobject, nextHitobject))
|
|
{
|
|
yield return new IssueTemplateConcurrent(this).Create(hitobject, nextHitobject);
|
|
}
|
|
else if (AreAlmostConcurrent(hitobject, nextHitobject))
|
|
{
|
|
yield return new IssueTemplateAlmostConcurrent(this).Create(hitobject, nextHitobject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected bool AreConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency;
|
|
|
|
protected bool AreAlmostConcurrent(HitObject hitobject, HitObject nextHitobject) =>
|
|
Math.Abs(nextHitobject.StartTime - hitobject.GetEndTime()) < almost_concurrent_threshold;
|
|
|
|
public class IssueTemplateConcurrent : IssueTemplate
|
|
{
|
|
public IssueTemplateConcurrent(ICheck check)
|
|
: base(check, IssueType.Problem, "{0}")
|
|
{
|
|
}
|
|
|
|
public Issue Create(HitObject hitobject, HitObject nextHitobject)
|
|
{
|
|
var hitobjects = new List<HitObject> { hitobject, nextHitobject };
|
|
string message = hitobject.GetType() == nextHitobject.GetType()
|
|
? $"{hitobject.GetType().Name}s are concurrent here."
|
|
: $"{hitobject.GetType().Name} and {nextHitobject.GetType().Name} are concurrent here.";
|
|
|
|
return new Issue(hitobjects, this, message)
|
|
{
|
|
Time = nextHitobject.StartTime
|
|
};
|
|
}
|
|
}
|
|
|
|
public class IssueTemplateAlmostConcurrent : IssueTemplate
|
|
{
|
|
public IssueTemplateAlmostConcurrent(ICheck check)
|
|
: base(check, IssueType.Problem, "{0}")
|
|
{
|
|
}
|
|
|
|
public Issue Create(HitObject hitobject, HitObject nextHitobject)
|
|
{
|
|
var hitobjects = new List<HitObject> { hitobject, nextHitobject };
|
|
string message = hitobject.GetType() == nextHitobject.GetType()
|
|
? $"{hitobject.GetType().Name}s are less than {almost_concurrent_threshold}ms apart."
|
|
: $"{hitobject.GetType().Name} and {nextHitobject.GetType().Name} are less than {almost_concurrent_threshold}ms apart.";
|
|
|
|
return new Issue(hitobjects, this, message)
|
|
{
|
|
Time = nextHitobject.StartTime
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|