1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-20 05:56:42 +08:00

Add check/issue classes

This commit is contained in:
Naxess 2021-04-07 14:35:33 +02:00
parent d58ef5310b
commit b24ce66a0d
7 changed files with 314 additions and 10 deletions

View File

@ -0,0 +1,25 @@
// 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 System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Checks;
using osu.Game.Screens.Edit.Verify.Components;
namespace osu.Game.Rulesets.Edit
{
public abstract class Checker
{
// These are all mode-invariant, hence here instead of in e.g. `OsuChecker`.
private readonly List<BeatmapCheck> beatmapChecks = new List<BeatmapCheck>
{
new CheckMetadataVowels()
};
public virtual IEnumerable<Issue> Run(IBeatmap beatmap)
{
return beatmapChecks.SelectMany(check => check.Run(beatmap));
}
}
}

View File

@ -0,0 +1,19 @@
// 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;
namespace osu.Game.Screens.Edit.Verify.Components
{
public abstract class BeatmapCheck : Check<IBeatmap>
{
/// <summary>
/// Returns zero, one, or several issues detected by this
/// check on the given beatmap.
/// </summary>
/// <param name="beatmap">The beatmap to run the check on.</param>
/// <returns></returns>
public abstract override IEnumerable<Issue> Run(IBeatmap beatmap);
}
}

View File

@ -0,0 +1,41 @@
// 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;
namespace osu.Game.Screens.Edit.Verify.Components
{
public abstract class Check
{
/// <summary>
/// Returns the <see cref="CheckMetadata"/> for this check.
/// Basically, its information.
/// </summary>
/// <returns></returns>
public abstract CheckMetadata Metadata();
/// <summary>
/// The templates for issues that this check may use.
/// Basically, what issues this check can detect.
/// </summary>
/// <returns></returns>
public abstract IEnumerable<IssueTemplate> Templates();
protected Check()
{
foreach (var template in Templates())
template.Origin = this;
}
}
public abstract class Check<T> : Check
{
/// <summary>
/// Returns zero, one, or several issues detected by
/// this check on the given object.
/// </summary>
/// <param name="obj">The object to run the check on.</param>
/// <returns></returns>
public abstract IEnumerable<Issue> Run(T obj);
}
}

View File

@ -0,0 +1,62 @@
// 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.Screens.Edit.Verify
{
public class CheckMetadata
{
/// <summary>
/// The category of an issue.
/// </summary>
public enum CheckCategory
{
/// <summary> Anything to do with control points. </summary>
Timing,
/// <summary> Anything to do with artist, title, creator, etc. </summary>
Metadata,
/// <summary> Anything to do with non-audio files, e.g. background, skin, sprites, and video. </summary>
Resources,
/// <summary> Anything to do with audio files, e.g. song and hitsounds. </summary>
Audio,
/// <summary> Anything to do with files that don't fit into the above, e.g. unused, osu, or osb. </summary>
Files,
/// <summary> Anything to do with hitobjects unrelated to spread. </summary>
Compose,
/// <summary> Anything to do with difficulty levels or their progression. </summary>
Spread,
/// <summary> Anything to do with variables like CS, OD, AR, HP, and global SV. </summary>
Settings,
/// <summary> Anything to do with hitobject feedback. </summary>
Hitsounds,
/// <summary> Anything to do with storyboarding, breaks, video offset, etc. </summary>
Events
}
/// <summary>
/// The category this check belongs to. E.g. <see cref="CheckCategory.Metadata"/>,
/// <see cref="CheckCategory.Timing"/>, or <see cref="CheckCategory.Compose"/>.
/// </summary>
public readonly CheckCategory Category;
/// <summary>
/// Describes the issue(s) that this check looks for. Keep this brief, such that
/// it fits into "No {description}". E.g. "Offscreen objects" / "Too short sliders".
/// </summary>
public readonly string Description;
public CheckMetadata(CheckCategory category, string description)
{
Category = category;
Description = description;
}
}
}

View File

@ -0,0 +1,83 @@
// 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 System.Linq;
using osu.Game.Extensions;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Edit.Verify.Components
{
public class Issue
{
/// <summary>
/// The time which this issue is associated with, if any, otherwise null.
/// </summary>
public double? Time;
/// <summary>
/// The hitobjects which this issue is associated with. Empty by default.
/// </summary>
public IReadOnlyList<HitObject> HitObjects;
/// <summary>
/// The template which this issue is using. This provides properties
/// such as the <see cref="IssueTemplate.IssueType"/>, and the
/// <see cref="IssueTemplate.UnformattedMessage"/>.
/// </summary>
public IssueTemplate Template;
/// <summary>
/// The arguments that give this issue its context, based on the
/// <see cref="IssueTemplate"/>. These are then substituted into the
/// <see cref="IssueTemplate.UnformattedMessage"/>.
/// E.g. timestamps, which diff is being compared to, what some volume is, etc.
/// </summary>
public object[] Arguments;
public Issue(IssueTemplate template, params object[] args)
{
Time = null;
HitObjects = System.Array.Empty<HitObject>();
Template = template;
Arguments = args;
if (template.Origin == null)
{
throw new ArgumentException(
"A template had no origin. Make sure the `Templates()` method contains all templates used."
);
}
}
public Issue(double? time, IssueTemplate template, params object[] args)
: this(template, args)
{
Time = time;
}
public Issue(IEnumerable<HitObject> hitObjects, IssueTemplate template, params object[] args)
: this(template, args)
{
Time = hitObjects.FirstOrDefault()?.StartTime;
HitObjects = hitObjects.ToArray();
}
public override string ToString()
{
return Template.Message(Arguments);
}
public string GetEditorTimestamp()
{
// TODO: Editor timestamp formatting is handled in https://github.com/ppy/osu/pull/12030
// We may be able to use that here too (if we decouple it from the HitObjectComposer class).
if (Time == null)
return string.Empty;
return Time.Value.ToEditorFormattedString();
}
}
}

View File

@ -0,0 +1,84 @@
// 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 Humanizer;
using osu.Framework.Graphics;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Verify.Components
{
public class IssueTemplate
{
/// <summary>
/// The type, or severity, of an issue. This decides its priority.
/// </summary>
public enum IssueType
{
/// <summary> A must-fix in the vast majority of cases. </summary>
Problem = 3,
/// <summary> A possible mistake. Often requires critical thinking. </summary>
Warning = 2,
// TODO: Try/catch all checks run and return error templates if exceptions occur.
/// <summary> An error occurred and a complete check could not be made. </summary>
Error = 1,
// TODO: Negligible issues should be hidden by default.
/// <summary> A possible mistake so minor/unlikely that it can often be safely ignored. </summary>
Negligible = 0,
}
/// <summary>
/// The check that this template originates from.
/// </summary>
public Check Origin;
/// <summary>
/// The type of the issue. E.g. <see cref="IssueType.Problem"/>,
/// <see cref="IssueType.Warning"/>, or <see cref="IssueType.Negligible"/>.
/// </summary>
public readonly IssueType Type;
/// <summary>
/// The unformatted message given when this issue is detected.
/// This gets populated later when an issue is constructed with this template.
/// E.g. "Inconsistent snapping (1/{0}) with [{1}] (1/{2})."
/// </summary>
public readonly string UnformattedMessage;
public IssueTemplate(IssueType type, string unformattedMessage)
{
Type = type;
UnformattedMessage = unformattedMessage;
}
/// <summary>
/// Returns the formatted message given the arguments used to format it.
/// </summary>
/// <param name="args">The arguments used to format the message.</param>
/// <returns></returns>
public string Message(params object[] args) => UnformattedMessage.FormatWith(args);
public static readonly Color4 PROBLEM_RED = new Colour4(1.0f, 0.4f, 0.4f, 1.0f);
public static readonly Color4 WARNING_YELLOW = new Colour4(1.0f, 0.8f, 0.2f, 1.0f);
public static readonly Color4 NEGLIGIBLE_GREEN = new Colour4(0.33f, 0.8f, 0.5f, 1.0f);
public static readonly Color4 ERROR_GRAY = new Colour4(0.5f, 0.5f, 0.5f, 1.0f);
/// <summary>
/// Returns the colour corresponding to the type of this issue.
/// </summary>
/// <returns></returns>
public Colour4 TypeColour()
{
return Type switch
{
IssueType.Problem => PROBLEM_RED,
IssueType.Warning => WARNING_YELLOW,
IssueType.Negligible => NEGLIGIBLE_GREEN,
IssueType.Error => ERROR_GRAY,
_ => Color4.White
};
}
}
}

View File

@ -1,10 +0,0 @@
// 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.Screens.Edit.Verify
{
public class Issue
{
public readonly double Time;
}
}