public IEnumerable <Issue> Run(BeatmapVerifierContext context) { double od = context.Beatmap.Difficulty.OverallDifficulty; // These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner. // It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners. double warningThreshold = 500 + (od < 5 ? (5 - od) * -21.8 : (od - 5) * 20); // Anything above this is always ok. double problemThreshold = 450 + (od < 5 ? (5 - od) * -17 : (od - 5) * 17); // Anything below this is never ok. foreach (var hitObject in context.Beatmap.HitObjects) { if (!(hitObject is Spinner spinner)) { continue; } if (spinner.Duration < problemThreshold) { yield return(new IssueTemplateTooShort(this).Create(spinner)); } else if (spinner.Duration < warningThreshold) { yield return(new IssueTemplateVeryShort(this).Create(spinner)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { foreach (var hitobject in context.Beatmap.HitObjects) { switch (hitobject) { case Slider slider: { foreach (var issue in sliderIssues(slider)) { yield return(issue); } break; } case HitCircle circle: { if (isOffscreen(circle.StackedPosition, circle.Radius)) { yield return(new IssueTemplateOffscreenCircle(this).Create(circle)); } break; } } } }
public void TestCirclesQuicklyChanging() { const float multiplier = 1.6f; var beatmap = new Beatmap <HitObject> { HitObjects = new List <HitObject> { new HitCircle { StartTime = 0, Position = new Vector2(0) }, new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, new HitCircle { StartTime = 1000, Position = new Vector2(50 + 50 * multiplier, 0) }, // Warning new HitCircle { StartTime = 1500, Position = new Vector2(50 + 50 * multiplier + 50 * multiplier * multiplier, 0) } // Problem } }; var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); Assert.That(issues.First().Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingWarning); Assert.That(issues.Last().Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingProblem); }
private void assertProblem(IBeatmap beatmap, int count = 1) { var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingProblem)); }
private void assertShouldNotOverlap(IBeatmap beatmap, int count = 1) { var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldNotOverlap)); }
private void assertTooShortSpinnerEnd(IBeatmap beatmap, DifficultyRating difficultyRating) { var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerEndGap)); }
private void assertOffscreenSlider(IBeatmap beatmap) { var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); }
public void TestBackgroundSetAndNotInFiles() { beatmap.BeatmapInfo.BeatmapSet?.Files.Clear(); var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); }
public void TestBackgroundNotSet() { beatmap.Metadata.BackgroundFile = string.Empty; var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet); }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { // TODO: This should also apply to *lowest difficulty* Normals - they are skipped for now. if (context.InterpretedDifficulty > DifficultyRating.Easy) { yield break; } var hitObjects = context.Beatmap.HitObjects; for (int i = 0; i < hitObjects.Count - 1; ++i) { if (!(hitObjects[i] is OsuHitObject hitObject) || hitObject is Spinner) { continue; } if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner) { continue; } double deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); if (deltaTime >= hitObject.TimeFadeIn + hitObject.TimePreempt) { // The objects are not visible at the same time (without mods), hence skipping. continue; } float distanceSq = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthSquared; double diameter = (hitObject.Radius - overlap_leniency) * 2; double diameterSq = diameter * diameter; bool areOverlapping = distanceSq < diameterSq; // Slider ends do not need to be overlapped because of slider leniency. if (!areOverlapping && !(hitObject is Slider)) { if (deltaTime < should_overlap_threshold) { yield return(new IssueTemplateShouldOverlap(this).Create(deltaTime, hitObject, nextHitObject)); } else if (deltaTime < should_probably_overlap_threshold) { yield return(new IssueTemplateShouldProbablyOverlap(this).Create(deltaTime, hitObject, nextHitObject)); } } if (areOverlapping && deltaTime > should_not_overlap_threshold) { yield return(new IssueTemplateShouldNotOverlap(this).Create(deltaTime, hitObject, nextHitObject)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var lyrics = context.Beatmap.HitObjects.OfType <Lyric>(); foreach (var note in context.Beatmap.HitObjects.OfType <Note>()) { if (note.ParentLyric == null || !lyrics.Contains(note.ParentLyric)) { yield return(new IssueTemplateInvalidParentLyric(this).Create(note)); } } }
private IEnumerable <Issue> run(HitObject lyric) { var beatmap = new Beatmap { HitObjects = new List <HitObject> { lyric } }; var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); return(check.Run(context)); }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { if (context.InterpretedDifficulty > DifficultyRating.Easy) { yield break; } foreach (var hitObject in context.Beatmap.HitObjects) { if (hitObject is Slider slider && slider.SpanDuration < span_duration_threshold) { yield return(new IssueTemplateTooShort(this).Create(slider)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { foreach (var hitObject in context.Beatmap.HitObjects) { if (!(hitObject is IHasDuration hasDuration)) { continue; } if (hasDuration.Duration < leniency) { yield return(new IssueTemplateZeroLength(this).Create(hitObject, hasDuration.Duration)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; foreach (var file in beatmapSet.Files) { using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.GetStoragePath())) { if (data?.Length == 0) { yield return(new IssueTemplateZeroBytes(this).Create(file.Filename)); } } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { foreach (var lyric in context.Beatmap.HitObjects.OfType <Lyric>()) { var invalidRubyTags = checkInvalidRubyTags(lyric); if (invalidRubyTags.Any()) { yield return(new IssueTemplateInvalidRuby(this).Create(lyric, invalidRubyTags)); } var invalidRomajiTags = checkInvalidRomajiTags(lyric); if (invalidRomajiTags.Any()) { yield return(new IssueTemplateInvalidRomaji(this).Create(lyric, invalidRomajiTags)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { if (!context.Beatmap.HitObjects.Any()) { yield break; } mapHasHitsounds = false; objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; var hitObjectsIncludingNested = new List <HitObject>(); foreach (var hitObject in context.Beatmap.HitObjects) { // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). foreach (var nestedHitObject in hitObject.NestedHitObjects) { hitObjectsIncludingNested.Add(nestedHitObject); } hitObjectsIncludingNested.Add(hitObject); } var hitObjectsByEndTime = hitObjectsIncludingNested.OrderBy(o => o.GetEndTime()).ToList(); int hitObjectCount = hitObjectsByEndTime.Count; for (int i = 0; i < hitObjectCount; ++i) { var hitObject = hitObjectsByEndTime[i]; // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. bool isLastObject = i == hitObjectCount - 1; foreach (var issue in applyHitsoundUpdate(hitObject, isLastObject)) { yield return(issue); } } if (!mapHasHitsounds) { yield return(new IssueTemplateNoHitsounds(this).Create()); } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var languages = availableTranslateInBeatmap(context.Beatmap); if (languages == null || languages.Length == 0) { yield break; } var lyric = context.Beatmap.HitObjects.OfType <Lyric>().ToList(); if (lyric.Count == 0) { yield break; } foreach (var language in languages) { var notTranslateLyrics = lyric.Where(x => { if (!x.Translates.ContainsKey(language)) { return(true); } if (string.IsNullOrWhiteSpace(x.Translates[language])) { return(true); } return(false); }); if (notTranslateLyrics.Count() == lyric.Count) { yield return(new IssueTemplateMissingTranslate(this).Create(language)); } else if (notTranslateLyrics.Any()) { yield return(new IssueTemplateMissingPartialTranslate(this).Create(language)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { foreach (var lyric in context.Beatmap.HitObjects.OfType <Lyric>()) { var invalidLyricTime = checkInvalidLyricTime(lyric); if (invalidLyricTime.Any()) { yield return(new IssueTemplateInvalidLyricTime(this).Create(lyric, invalidLyricTime)); } var invalidTimeTags = checkInvalidTimeTags(lyric); var missingStartTimeTag = checkMissingStartTimeTag(lyric); var missingEndTimeTag = checkMissingEndTimeTag(lyric); if (invalidTimeTags.Any() || missingStartTimeTag || missingEndTimeTag) { yield return(new IssueTemplateInvalidTimeTag(this).Create(lyric, invalidTimeTags, missingStartTimeTag, missingEndTimeTag)); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { string backgroundFile = context.Beatmap.Metadata?.BackgroundFile; if (backgroundFile == null) { yield break; } var texture = context.WorkingBeatmap.Background; if (texture == null) { yield break; } if (texture.Width > max_width || texture.Height > max_height) { yield return(new IssueTemplateTooHighResolution(this).Create(texture.Width, texture.Height)); } if (texture.Width < min_width || texture.Height < min_height) { yield return(new IssueTemplateTooLowResolution(this).Create(texture.Width, texture.Height)); } else if (texture.Width < low_width || texture.Height < low_height) { yield return(new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height)); } string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet?.GetPathForFile(backgroundFile); using (Stream stream = context.WorkingBeatmap.GetStream(storagePath)) { double filesizeMb = stream.Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) { yield return(new IssueTemplateTooUncompressed(this).Create(filesizeMb)); } } }
private void load(OverlayColourProvider colours) { generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapVerifier(); context = new BeatmapVerifierContext(beatmap, workingBeatmap.Value, verify.InterpretedDifficulty.Value); verify.InterpretedDifficulty.BindValueChanged(difficulty => context.InterpretedDifficulty = difficulty.NewValue); RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { new Box { Colour = colours.Background3, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Child = table = new IssueTable(), }, new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(20), Children = new Drawable[] { new RoundedButton { Text = "Refresh", Action = refresh, Size = new Vector2(120, 40), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, } }, }; }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; if (beatmapSet != null) { foreach (var file in beatmapSet.Files) { using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) { if (data == null) { continue; } var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); if (decodeStream == 0) { // If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it. // Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check. if (hasAudioExtension(file.Filename) && probablyHasAudioData(data)) { yield return(new IssueTemplateBadFormat(this).Create(file.Filename)); } continue; } long length = Bass.ChannelGetLength(decodeStream); double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000; // Extremely short audio files do not play on some soundcards, resulting in nothing being heard in-game for some users. if (ms > 0 && ms < ms_threshold) { yield return(new IssueTemplateTooShort(this).Create(file.Filename, ms)); } } } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { foreach (var hitObject in context.Beatmap.HitObjects) { // Worth keeping in mind: The samples of an object always play at its end time. // Objects like spinners have no sound at its start because of this, while hold notes have nested objects to accomplish this. foreach (var nestedHitObject in hitObject.NestedHitObjects) { foreach (var issue in getVolumeIssues(hitObject, nestedHitObject)) { yield return(issue); } } foreach (var issue in getVolumeIssues(hitObject)) { yield return(issue); } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { string filename = GetFilename(context.Beatmap); if (string.IsNullOrEmpty(filename)) { yield return(new IssueTemplateNoneSet(this).Create(TypeOfFile)); yield break; } // If the file is set, also make sure it still exists. string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet?.GetPathForFile(filename); if (storagePath != null) { yield break; } yield return(new IssueTemplateDoesNotExist(this).Create(TypeOfFile, filename)); }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var hitObjects = context.Beatmap.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]; // 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)); } } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var controlPointInfo = context.Beatmap.ControlPointInfo; foreach (var hitobject in context.Beatmap.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); } } } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var audioFile = context.Beatmap.Metadata?.AudioFile; if (audioFile == null) { yield break; } var track = context.WorkingBeatmap.Track; if (track?.Bitrate == null || track.Bitrate.Value == 0) { yield return(new IssueTemplateNoBitrate(this).Create()); } else if (track.Bitrate.Value > max_bitrate) { yield return(new IssueTemplateTooHighBitrate(this).Create(track.Bitrate.Value)); } else if (track.Bitrate.Value < min_bitrate) { yield return(new IssueTemplateTooLowBitrate(this).Create(track.Bitrate.Value)); } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { foreach (var lyric in context.Beatmap.HitObjects.OfType <Lyric>()) { if (lyric.Language == null) { yield return(new IssueTemplateNotFillLanguage(this).Create(lyric)); } // todo : check lyric layout. if (string.IsNullOrWhiteSpace(lyric.Text)) { yield return(new IssueTemplateNoText(this).Create(lyric)); } if (lyric.Singers == null || lyric.Singers.Length == 0) { yield return(new IssueTemplateNoSinger(this).Create(lyric)); } // todo : check is singer in singer list. } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) { var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; var videoPaths = new List <string>(); foreach (var layer in context.WorkingBeatmap.Storyboard.Layers) { foreach (var element in layer.Elements) { if (!(element is StoryboardVideo video)) { continue; } // Ensures we don't check the same video file multiple times in case of multiple elements using it. if (!videoPaths.Contains(video.Path)) { videoPaths.Add(video.Path); } } } foreach (var filename in videoPaths) { string storagePath = beatmapSet.GetPathForFile(filename); if (storagePath == null) { // There's an element in the storyboard that requires this resource, so it being missing is worth warning about. yield return(new IssueTemplateMissingFile(this).Create(filename)); continue; } Issue issue; try { // We use TagLib here for platform invariance; BASS cannot detect audio presence on Linux. using (Stream data = context.WorkingBeatmap.GetStream(storagePath)) using (File tagFile = File.Create(new StreamFileAbstraction(filename, data))) { if (tagFile.Properties.AudioChannels == 0) { continue; } } issue = new IssueTemplateHasAudioTrack(this).Create(filename); } catch (CorruptFileException) { issue = new IssueTemplateFileError(this).Create(filename, "Corrupt file"); } catch (UnsupportedFormatException) { issue = new IssueTemplateFileError(this).Create(filename, "Unsupported format"); } yield return(issue); } }
public IEnumerable <Issue> Run(BeatmapVerifierContext context) => checks.SelectMany(check => check.Run(context));