/// <summary> Adds any point in time where no object in the other beatmap is within 3 ms, but
        /// is within the consistency range, depending on which divisor this point in time is in.<para/>
        /// This usually means the mapper has interpreted the same sound(s) differently from the other beatmap,
        /// so we add it as a potential inconsistency.</summary>
        private void TryAddInconsistentPlace(List <double> differenceTimes, Beatmap otherBeatmap, double otherTime)
        {
            List <double> inconsistencies = differenceTimes.Where(time =>
            {
                if (Math.Abs(time - otherTime) < 3)
                {
                    return(false);
                }

                UninheritedLine line = otherBeatmap.GetTimingLine <UninheritedLine>(time);
                double msPerBeat     = line.msPerBeat;

                if (Math.Abs(time - otherTime) >= msPerBeat)
                {
                    return(false);
                }

                double consistencyRange = GetConsistencyRange(otherBeatmap, time, msPerBeat, otherTime);
                return
                (time + consistencyRange > otherTime &&
                 time - consistencyRange < otherTime);
            }).ToList();

            foreach (double inconsistency in inconsistencies)
            {
                if (!inconsistentPlaces.Any(place => place.Item1 == inconsistency))
                {
                    inconsistentPlaces.Add(new Tuple <double, double, Beatmap>(inconsistency, otherTime, otherBeatmap));
                }
            }
        }
示例#2
0
        /// <summary> Adds any point in time where no object in the other beatmap is within 3 ms, but
        /// is within the consistency range, depending on which divisor this point in time is in.<para/>
        /// This usually means the mapper has interpreted the same sound(s) differently from the other beatmap,
        /// so we add it as a potential inconsistency.</summary>
        private void TryAddInconsistentPlace(List <double> aTimeDifferences, Beatmap anOtherBeatmap, double anOtherTime)
        {
            List <double> inconsistencies = aTimeDifferences.Where(aTime =>
            {
                if (Math.Abs(aTime - anOtherTime) >= 3)
                {
                    UninheritedLine line = anOtherBeatmap.GetTimingLine <UninheritedLine>(aTime);
                    double msPerBeat     = line.msPerBeat;

                    if (Math.Abs(aTime - anOtherTime) < msPerBeat)
                    {
                        double consistencyRange = GetConsistencyRange(anOtherBeatmap, aTime, msPerBeat, anOtherTime);
                        return
                        (aTime + consistencyRange > anOtherTime &&
                         aTime - consistencyRange < anOtherTime);
                    }
                }

                return(false);
            }).ToList();

            foreach (double inconsistency in inconsistencies)
            {
                if (!inconsistentPlaces.Any(aPlace => aPlace.Item1 == inconsistency))
                {
                    inconsistentPlaces.Add(new Tuple <double, double, Beatmap>(inconsistency, anOtherTime, anOtherBeatmap));
                }
            }
        }
示例#3
0
        /// <summary> Returns the beat number from offset 0 at which the countdown would start, accounting for
        /// countdown offset and speed. No countdown if less than 0. </summary>
        public double GetCountdownStartBeat()
        {
            // If there are no objects, this does not apply.
            if (GetHitObject(0) == null)
            {
                return(0);
            }

            // always 6 beats before the first, but the first beat can be cut by having the first beat 5 ms after 0.
            UninheritedLine line = GetTimingLine <UninheritedLine>(0);

            double firstBeatTime = line.offset;

            while (firstBeatTime - line.msPerBeat > 0)
            {
                firstBeatTime -= line.msPerBeat;
            }

            double firstObjectTime = GetHitObject(0).time;
            int    firstObjectBeat = Timestamp.Round((firstObjectTime - firstBeatTime) / line.msPerBeat);

            // Apparently double does not result in the countdown needing half as much time, but rather closer to 0.45 times as much.
            double countdownMultiplier =
                generalSettings.countdown == GeneralSettings.Countdown.None ? 1 :
                generalSettings.countdown == GeneralSettings.Countdown.Half ? 2 :
                0.45;

            return(firstObjectBeat -
                   ((firstBeatTime > 5 ? 5 : 6) + generalSettings.countdownBeatOffset) * countdownMultiplier);
        }
        public override IEnumerable <Issue> GetIssues(BeatmapSet aBeatmapSet)
        {
            Beatmap refBeatmap = aBeatmapSet.beatmaps[0];

            foreach (Beatmap beatmap in aBeatmapSet.beatmaps)
            {
                foreach (TimingLine line in refBeatmap.timingLines)
                {
                    if (line is UninheritedLine uninheritLine)
                    {
                        UninheritedLine otherUninheritLine =
                            beatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                                aLine => aLine.offset == uninheritLine.offset);

                        double offset = Timestamp.Round(uninheritLine.offset);

                        if (otherUninheritLine == null)
                        {
                            yield return(new Issue(GetTemplate("Missing"), beatmap,
                                                   Timestamp.Get(offset), refBeatmap));
                        }
                        else
                        {
                            if (uninheritLine.meter != otherUninheritLine.meter)
                            {
                                yield return(new Issue(GetTemplate("Inconsistent Meter"), beatmap,
                                                       Timestamp.Get(offset), refBeatmap));
                            }

                            if (uninheritLine.msPerBeat != otherUninheritLine.msPerBeat)
                            {
                                yield return(new Issue(GetTemplate("Inconsistent BPM"), beatmap,
                                                       Timestamp.Get(offset), refBeatmap));
                            }
                        }
                    }
                }

                // Check the other way around as well, to make sure the reference map has all uninherited lines this map has.
                foreach (TimingLine line in beatmap.timingLines)
                {
                    if (line is UninheritedLine)
                    {
                        UninheritedLine otherLine =
                            refBeatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                                aLine => aLine.offset == line.offset);

                        double offset = (int)Math.Floor(line.offset);

                        if (otherLine == null)
                        {
                            yield return(new Issue(GetTemplate("Missing"), refBeatmap,
                                                   Timestamp.Get(offset), beatmap));
                        }
                    }
                }
            }
        }
        private static string RenderTicks(Beatmap aBeatmap, double aStartTime, double anEndTime)
        {
            double sampleTime     = aStartTime;
            double prevSampleTime = aStartTime;

            return
                (String.Concat(
                     aBeatmap.timingLines.OfType <UninheritedLine>().Select(aLine =>
            {
                UninheritedLine nextLine = aBeatmap.GetNextTimingLine <UninheritedLine>(aLine.offset);
                double nextSwap = nextLine?.offset ?? anEndTime;

                StringBuilder tickDivs = new StringBuilder();

                // To get precision down to both 1/16th and 1/12th of a beat we need to sample...
                // 16 = 2^4, 12 = 2^2*3, 2^4*3 = 48 times per beat.
                // We're going to intentionally ignore 1/5, 1/7, and 1/9, as we'd be sampling way too much.
                int samplesPerBeat = 48;
                for (int i = 0; i < (nextSwap - aLine.offset) / aLine.msPerBeat * samplesPerBeat; ++i)
                {
                    // Add the practical unsnap to avoid things getting unsnapped the further into the map you go.
                    sampleTime =
                        aLine.offset + i * aLine.msPerBeat / samplesPerBeat +
                        aBeatmap.GetPracticalUnsnap(aLine.offset + i * aLine.msPerBeat / samplesPerBeat);

                    bool hasEdge =
                        aBeatmap.GetHitObject(sampleTime)?.GetEdgeTimes().Any(anEdgeTime =>
                                                                              Math.Abs(anEdgeTime - sampleTime) < 2) ?? false;

                    if (i % (samplesPerBeat / 4) == 0 ||
                        hasEdge && (
                            i % (samplesPerBeat / 12) == 0 ||
                            i % (samplesPerBeat / 16) == 0))
                    {
                        tickDivs.Append(
                            DivAttr("overview-timeline-tick",
                                    " style=\"margin-left:" + ((sampleTime - prevSampleTime) / ZOOM_FACTOR) + "px\"",
                                    Div("overview-timeline-ticks-base " + (hasEdge ? " hasobject " : "") + (
                                            i % (samplesPerBeat * aLine.meter) == 0 ? "overview-timeline-ticks-largewhite" :
                                            i % (samplesPerBeat * 1) == 0 ?           "overview-timeline-ticks-white" :
                                            i % (samplesPerBeat / 2) == 0 ?           "overview-timeline-ticks-red" :
                                            i % (samplesPerBeat / 3) == 0 ?           "overview-timeline-ticks-magenta" :
                                            i % (samplesPerBeat / 4) == 0 ?           "overview-timeline-ticks-blue" :
                                            i % (samplesPerBeat / 6) == 0 ?           "overview-timeline-ticks-purple" :
                                            i % (samplesPerBeat / 8) == 0 ?           "overview-timeline-ticks-yellow" :
                                            i % (samplesPerBeat / 12) == 0 ?          "overview-timeline-ticks-gray" :
                                            i % (samplesPerBeat / 16) == 0 ?          "overview-timeline-ticks-gray" :
                                            "overview-timeline-ticks-unsnapped")
                                        )
                                    ));

                        prevSampleTime = sampleTime;
                    }
                }

                return tickDivs.ToString();
            })));
        }
        /// <summary> Returns whether the offset aligns in such a way that one line is a multiple of 4 beats away
        /// from the other, and the BPM and timing signature (meter) is the same. </summary>
        private bool DownbeatsAlign(Beatmap beatmap, UninheritedLine line, UninheritedLine otherLine)
        {
            bool negligibleDownbeatOffset = GetBeatOffset(otherLine, line, otherLine.meter) <= 1;

            return
                (otherLine.bpm == line.bpm &&
                 otherLine.meter == line.meter &&
                 negligibleDownbeatOffset);
        }
示例#7
0
        /// <summary> Returns whether the offset aligns in such a way that one line is a multiple of 4 beats away
        /// from the other, and the BPM and timing signature (meter) is the same. </summary>
        private static bool DownbeatsAlign(UninheritedLine line, UninheritedLine otherLine)
        {
            bool negligibleDownbeatOffset = GetBeatOffset(otherLine, line, otherLine.meter) <= 1;

            return
                (otherLine.bpm.AlmostEqual(line.bpm) &&
                 otherLine.meter == line.meter &&
                 negligibleDownbeatOffset);
        }
示例#8
0
        /// <summary> Returns the ms difference between two timing lines, where the timing lines reset offset every given number of beats. </summary>
        private double GetBeatOffset(UninheritedLine aLine, UninheritedLine aNextLine, double aBeatOffset)
        {
            double beatsIn = (aNextLine.offset - aLine.offset) / aLine.msPerBeat;
            double offset  = beatsIn % aBeatOffset;

            return
                (Math.Min(
                     Math.Abs(offset),
                     Math.Abs(offset - aBeatOffset)) *
                 aLine.msPerBeat);
        }
        /// <summary> Returns the ms difference between two timing lines, where the timing lines reset offset every
        /// given number of beats. </summary>
        private double GetBeatOffset(UninheritedLine line, UninheritedLine nextLine, double beatModulo)
        {
            double beatsIn = (nextLine.offset - line.offset) / line.msPerBeat;
            double offset  = beatsIn % beatModulo;

            return
                (Math.Min(
                     Math.Abs(offset),
                     Math.Abs(offset - beatModulo)) *
                 line.msPerBeat);
        }
示例#10
0
        /// <summary> Returns how many ms into a beat the given time is. </summary>
        public double GetOffsetIntoBeat(double aTime)
        {
            UninheritedLine line = GetTimingLine <UninheritedLine>(aTime);

            // gets how many miliseconds into a beat we are
            double time       = aTime - line.offset;
            double division   = time / line.msPerBeat;
            double fraction   = division - (float)Math.Floor(division);
            double beatOffset = fraction * line.msPerBeat;

            return(beatOffset);
        }
示例#11
0
        private string GetSnappingGap(Beatmap beatmap, HitObject hitObject)
        {
            HitObject previousObject        = hitObject.PrevOrFirst();
            double    lastObjectTime        = previousObject.GetEdgeTimes().Last();
            double    snappedCurrentObject  = hitObject.time + beatmap.GetPracticalUnsnap(hitObject.time);
            double    snappedPreviousObject = lastObjectTime + beatmap.GetPracticalUnsnap(lastObjectTime);
            double    deltaTime             = snappedCurrentObject - snappedPreviousObject;

            UninheritedLine timingLine = beatmap.GetTimingLine <UninheritedLine>(snappedCurrentObject);

            var snapping    = Math.Round(deltaTime / timingLine.msPerBeat, 2);
            var snappingStr = new Fraction(snapping).ToString();

            return(snappingStr);
        }
示例#12
0
        private IEnumerable <Issue> GetRecoveryIssues(Beatmap aBeatmap, Spinner aSpinner)
        {
            HitObject nextObject = aBeatmap.GetNextHitObject(aSpinner.time);

            // Do not check time between two spinners since all you'd need to do is keep spinning.
            if (nextObject != null && !(nextObject is Spinner))
            {
                double recoveryTime = nextObject.time - aSpinner.endTime;

                UninheritedLine line               = aBeatmap.GetTimingLine <UninheritedLine>(nextObject.time);
                double          bpmScaling         = GetScaledTiming(line.bpm);
                double          recoveryTimeScaled = recoveryTime / bpmScaling;

                double[] recoveryTimeExpected = new double[] { 1000, 500, 250 }; // 4, 2 and 1 beats respectively, 240 bpm

                // Tries both scaled and regular recoveries, and only if both are exceeded does it create an issue.
                for (int diffIndex = 0; diffIndex < recoveryTimeExpected.Length; ++diffIndex)
                {
                    // Picks whichever is greatest of the scaled and regular versions.
                    double expectedScaledMultiplier = (bpmScaling < 1 ? bpmScaling : 1);
                    double expectedRecovery         = Math.Ceiling(recoveryTimeExpected[diffIndex] * expectedScaledMultiplier * expectedMultiplier);

                    double problemThreshold = recoveryTimeExpected[diffIndex];
                    double warningThreshold = recoveryTimeExpected[diffIndex] * 1.2;

                    if (recoveryTimeScaled < problemThreshold && recoveryTime < problemThreshold)
                    {
                        yield return(new Issue(GetTemplate("Problem Recovery"), aBeatmap,
                                               Timestamp.Get(aSpinner, nextObject), recoveryTime, expectedRecovery)
                                     .ForDifficulties((Beatmap.Difficulty)diffIndex));
                    }

                    else if (recoveryTimeScaled < warningThreshold && recoveryTime < warningThreshold)
                    {
                        yield return(new Issue(GetTemplate("Warning Recovery"), aBeatmap,
                                               Timestamp.Get(aSpinner, nextObject), recoveryTime, expectedRecovery)
                                     .ForDifficulties((Beatmap.Difficulty)diffIndex));
                    }
                }
            }
        }
示例#13
0
        /// <summary> Returns the unsnap ignoring all of the game's rounding and other approximations. </summary>
        public double GetTheoreticalUnsnap(double aTime, int aSecondDivisor = 16, int aThirdDivisor = 12)
        {
            UninheritedLine line = GetTimingLine <UninheritedLine>(aTime);

            double beatOffset      = GetOffsetIntoBeat(aTime);
            double currentFraction = beatOffset / line.msPerBeat;

            // 1/16
            double desiredFractionSecond    = (float)Math.Round(currentFraction * aSecondDivisor) / aSecondDivisor;
            double differenceFractionSecond = currentFraction - desiredFractionSecond;
            double theoreticalUnsnapSecond  = differenceFractionSecond * line.msPerBeat;

            // 1/12
            double desiredFractionThird    = (float)Math.Round(currentFraction * aThirdDivisor) / aThirdDivisor;
            double differenceFractionThird = currentFraction - desiredFractionThird;
            double theoreticalUnsnapThird  = differenceFractionThird * line.msPerBeat;

            // picks the smaller of the two as unsnap
            return(Math.Abs(theoreticalUnsnapThird) > Math.Abs(theoreticalUnsnapSecond)
                ? theoreticalUnsnapSecond : theoreticalUnsnapThird);
        }
        public override IEnumerable <Issue> GetIssues(BeatmapSet beatmapSet)
        {
            IEnumerable <Beatmap> taikoBeatmaps = beatmapSet.beatmaps.Where(beatmap => beatmap.generalSettings.mode == Beatmap.Mode.Taiko);
            Beatmap refBeatmap = taikoBeatmaps.First();

            foreach (Beatmap beatmap in taikoBeatmaps)
            {
                foreach (UninheritedLine line in refBeatmap.timingLines.OfType <UninheritedLine>())
                {
                    UninheritedLine respectiveLine =
                        beatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                            otherLine => Timestamp.Round(otherLine.offset) == Timestamp.Round(line.offset));

                    double offset = Timestamp.Round(line.offset);

                    if (line.omitsBarLine != respectiveLine.omitsBarLine)
                    {
                        yield return(new Issue(GetTemplate("Inconsistent"), beatmap,
                                               Timestamp.Get(offset), refBeatmap));
                    }
                }
            }
        }
示例#15
0
        public override IEnumerable <Issue> GetIssues(Beatmap aBeatmap)
        {
            // Since the list of timing lines is sorted by time we can just check the previous line.
            for (int i = 1; i < aBeatmap.timingLines.Count; ++i)
            {
                if (aBeatmap.timingLines[i - 1].offset == aBeatmap.timingLines[i].offset)
                {
                    if (aBeatmap.timingLines[i - 1].uninherited == aBeatmap.timingLines[i].uninherited)
                    {
                        string inheritance =
                            aBeatmap.timingLines[i].uninherited ?
                            "uninherited" : "inherited";

                        yield return(new Issue(GetTemplate("Concurrent"), aBeatmap,
                                               Timestamp.Get(aBeatmap.timingLines[i].offset), inheritance));
                    }
                    else if (
                        aBeatmap.timingLines[i - 1].kiai != aBeatmap.timingLines[i].kiai ||
                        aBeatmap.timingLines[i - 1].volume != aBeatmap.timingLines[i].volume ||
                        aBeatmap.timingLines[i - 1].sampleset != aBeatmap.timingLines[i].sampleset ||
                        aBeatmap.timingLines[i - 1].customIndex != aBeatmap.timingLines[i].customIndex)
                    {
                        string conflictingGreenSettings = "";
                        string conflictingRedSettings   = "";

                        InheritedLine   greenLine = null;
                        UninheritedLine redLine   = null;

                        // We've guaranteed that one line is inherited and the other is
                        // uninherited, so we can figure out both by checking one.
                        string precedence = "";
                        if (aBeatmap.timingLines[i - 1] is InheritedLine)
                        {
                            greenLine  = aBeatmap.timingLines[i - 1] as InheritedLine;
                            redLine    = aBeatmap.timingLines[i] as UninheritedLine;
                            precedence = "Red overrides green";
                        }
                        else
                        {
                            greenLine  = aBeatmap.timingLines[i] as InheritedLine;
                            redLine    = aBeatmap.timingLines[i - 1] as UninheritedLine;
                            precedence = "Green overrides red";
                        }

                        if (greenLine.kiai != redLine.kiai)
                        {
                            conflictingGreenSettings += (conflictingGreenSettings.Length > 0 ? ", " : "") + (greenLine.kiai ? "kiai" : "no kiai");
                            conflictingRedSettings   += (conflictingRedSettings.Length > 0   ? ", " : "") + (redLine.kiai   ? "kiai" : "no kiai");
                        }
                        if (greenLine.volume != redLine.volume)
                        {
                            conflictingGreenSettings += (conflictingGreenSettings.Length > 0 ? ", " : "") + $"{greenLine.volume}% volume";
                            conflictingRedSettings   += (conflictingRedSettings.Length > 0   ? ", " : "") + $"{redLine.volume}% volume";
                        }
                        if (greenLine.sampleset != redLine.sampleset)
                        {
                            conflictingGreenSettings += (conflictingGreenSettings.Length > 0 ? ", " : "") + $"{greenLine.sampleset} sampleset";
                            conflictingRedSettings   += (conflictingRedSettings.Length > 0   ? ", " : "") + $"{redLine.sampleset} sampleset";
                        }
                        if (greenLine.customIndex != redLine.customIndex)
                        {
                            conflictingGreenSettings += (conflictingGreenSettings.Length > 0 ? ", " : "") + $"custom {greenLine.customIndex}";
                            conflictingRedSettings   += (conflictingRedSettings.Length > 0   ? ", " : "") + $"custom {redLine.customIndex}";
                        }

                        yield return(new Issue(GetTemplate("Conflicting"), aBeatmap,
                                               Timestamp.Get(aBeatmap.timingLines[i].offset),
                                               conflictingGreenSettings, conflictingRedSettings,
                                               precedence));
                    }
                }
            }
        }
 /// <summary> Returns whether the offset aligns in such a way that one line is a multiple of 4 measures away
 /// from the other (1 measure = 4 beats in 4/4 meter). This first checks that the downbeat structure is the same.
 /// <br></br><br></br>
 /// In the Nightcore mod, cymbals can be heard every 4 measures. </summary>
 private bool NightcoreCymbalsAlign(Beatmap beatmap, UninheritedLine line, UninheritedLine otherLine) =>
 DownbeatsAlign(beatmap, line, otherLine) && GetBeatOffset(otherLine, line, 4 * otherLine.meter) <= 1;
 /// <summary> Returns whether the bar lines from the first line align perfectly with those of the second.
 /// Assumes the two lines have identical BPM and meter, use <see cref="DownbeatsAlign"/> for that. </summary>
 private bool BarLinesAlign(Beatmap beatmap, UninheritedLine line, UninheritedLine otherLine) =>
 // Even differences in 1 ms would be visible since it'd make 2 barlines next to each other.
 GetBeatOffset(otherLine, line, otherLine.meter) == 0;
示例#18
0
 /// <summary> Returns whether the offset aligns in such a way that one line is a multiple of 4 measures away
 /// from the other (1 measure = 4 beats in 4/4 meter). This first checks that the downbeat structure is the same.
 /// <br></br><br></br>
 /// In the Nightcore mod, cymbals can be heard every 4 measures. </summary>
 private static bool NightcoreCymbalsAlign(UninheritedLine line, UninheritedLine otherLine) =>
 DownbeatsAlign(line, otherLine) && GetBeatOffset(otherLine, line, 4 * otherLine.meter) <= 1;
        private IEnumerable <Issue> GetUninheritedLineIssues(Beatmap beatmap)
        {
            List <TimingLine> lines = beatmap.timingLines.ToList();

            for (int i = 1; i < lines.Count; ++i)
            {
                if (!(lines[i] is UninheritedLine currentLine))
                {
                    continue;
                }

                // Can't do lines[i - 1] since that could give a green line on the same offset, which we don't want.
                TimingLine      previousLine            = beatmap.GetTimingLine(currentLine.offset - 1);
                UninheritedLine previousUninheritedLine = beatmap.GetTimingLine <UninheritedLine>(currentLine.offset - 1);

                if (!DownbeatsAlign(beatmap, currentLine, previousUninheritedLine))
                {
                    continue;
                }

                bool changesNCCymbals = false;
                if (!NightcoreCymbalsAlign(beatmap, currentLine, previousUninheritedLine))
                {
                    changesNCCymbals = true;
                }

                bool omittingBarline   = false;
                bool correctingBarline = false;
                if (CanOmitBarLine(beatmap))
                {
                    // e.g. red line used mid-measure to account for bpm change shouldn't create a barline, so it's omitted, but the
                    // end of the measure won't have a barline unless another red line is placed there to correct it, hence both used.
                    omittingBarline   = currentLine.omitsBarLine;
                    correctingBarline = previousUninheritedLine.omitsBarLine && !BarLinesAlign(beatmap, currentLine, previousUninheritedLine);
                    // Omitting bar lines isn't commonly seen in standard, so it's likely that people will
                    // miss incorrect usages of it, hence warn if it's the only thing keeping it used.
                    if ((omittingBarline || correctingBarline) && beatmap.generalSettings.mode != Beatmap.Mode.Standard)
                    {
                        continue;
                    }
                }

                List <string> notImmediatelyObvious = new List <string>();
                if (omittingBarline)
                {
                    notImmediatelyObvious.Add("omitting first barline");
                }
                if (correctingBarline)
                {
                    notImmediatelyObvious.Add($"correcting the omitted barline at {Timestamp.Get(previousUninheritedLine.offset)}");
                }
                if (changesNCCymbals)
                {
                    notImmediatelyObvious.Add("nightcore mod cymbals");
                }
                string notImmediatelyObviousStr = string.Join(" and ", notImmediatelyObvious);

                if (!IsLineUsed(beatmap, currentLine, previousLine))
                {
                    if (notImmediatelyObvious.Count == 0)
                    {
                        yield return(new Issue(GetTemplate("Problem"),
                                               beatmap, Timestamp.Get(currentLine.offset)));
                    }
                    else
                    {
                        yield return(new Issue(GetTemplate("Warning"),
                                               beatmap, Timestamp.Get(currentLine.offset), notImmediatelyObviousStr));
                    }
                }
                else
                {
                    if (notImmediatelyObvious.Count == 0)
                    {
                        yield return(new Issue(GetTemplate("Problem Inherited"),
                                               beatmap, Timestamp.Get(currentLine.offset)));
                    }
                    else
                    {
                        yield return(new Issue(GetTemplate("Warning Inherited"),
                                               beatmap, Timestamp.Get(currentLine.offset), notImmediatelyObviousStr));
                    }
                }
            }
        }
        public override IEnumerable <Issue> GetIssues(BeatmapSet beatmapSet)
        {
            Beatmap refBeatmap = beatmapSet.beatmaps[0];

            foreach (Beatmap beatmap in beatmapSet.beatmaps)
            {
                foreach (UninheritedLine line in refBeatmap.timingLines.OfType <UninheritedLine>())
                {
                    UninheritedLine respectiveLine =
                        beatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                            otherLine => Timestamp.Round(otherLine.offset) == Timestamp.Round(line.offset));

                    double offset = Timestamp.Round(line.offset);

                    if (respectiveLine == null)
                    {
                        yield return(new Issue(GetTemplate("Missing"), beatmap,
                                               Timestamp.Get(offset), refBeatmap));
                    }
                    else
                    {
                        if (line.meter != respectiveLine.meter)
                        {
                            yield return(new Issue(GetTemplate("Inconsistent Meter"), beatmap,
                                                   Timestamp.Get(offset), refBeatmap));
                        }

                        if (line.msPerBeat != respectiveLine.msPerBeat)
                        {
                            yield return(new Issue(GetTemplate("Inconsistent BPM"), beatmap,
                                                   Timestamp.Get(offset), refBeatmap));
                        }

                        // Including decimal unsnaps
                        UninheritedLine respectiveLineExact =
                            beatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                                otherLine => otherLine.offset == line.offset);

                        if (respectiveLineExact == null)
                        {
                            yield return(new Issue(GetTemplate("Missing Minor"), beatmap,
                                                   Timestamp.Get(offset), refBeatmap));
                        }
                    }
                }

                // Check the other way around as well, to make sure the reference map has all uninherited lines this map has.
                foreach (UninheritedLine line in beatmap.timingLines.OfType <UninheritedLine>())
                {
                    UninheritedLine respectiveLine =
                        refBeatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                            otherLine => Timestamp.Round(otherLine.offset) == Timestamp.Round(line.offset));

                    double offset = Timestamp.Round(line.offset);

                    if (respectiveLine == null)
                    {
                        yield return(new Issue(GetTemplate("Missing"), refBeatmap,
                                               Timestamp.Get(offset), beatmap));
                    }
                    else
                    {
                        // Including decimal unsnaps
                        UninheritedLine respectiveLineExact =
                            refBeatmap.timingLines.OfType <UninheritedLine>().FirstOrDefault(
                                otherLine => otherLine.offset == line.offset);

                        if (respectiveLineExact == null)
                        {
                            yield return(new Issue(GetTemplate("Missing Minor"), refBeatmap,
                                                   Timestamp.Get(offset), beatmap));
                        }
                    }
                }
            }
        }
示例#21
0
        public override IEnumerable <DiffInstance> Translate(IEnumerable <DiffInstance> aDiffs)
        {
            List <Tuple <DiffInstance, TimingLine> > addedTimingLines   = new List <Tuple <DiffInstance, TimingLine> >();
            List <Tuple <DiffInstance, TimingLine> > removedTimingLines = new List <Tuple <DiffInstance, TimingLine> >();

            foreach (DiffInstance diff in aDiffs)
            {
                TimingLine timingLine = null;
                try
                {
                    timingLine = new TimingLine(diff.difference.Split(','), beatmap: null);
                }
                catch
                {
                    // Failing to parse a changed line shouldn't stop it from showing.
                }

                if (timingLine != null)
                {
                    if (diff.diffType == DiffType.Added)
                    {
                        addedTimingLines.Add(new Tuple <DiffInstance, TimingLine>(diff, timingLine));
                    }
                    else
                    {
                        removedTimingLines.Add(new Tuple <DiffInstance, TimingLine>(diff, timingLine));
                    }
                }
                else
                {
                    // Shows the raw .osu line change.
                    yield return(diff);
                }
            }

            foreach (Tuple <DiffInstance, TimingLine> addedTuple in addedTimingLines)
            {
                DiffInstance addedDiff = addedTuple.Item1;
                TimingLine   addedLine = addedTuple.Item2;

                string stamp = Timestamp.Get(addedLine.offset);
                string type  = addedLine.uninherited ? "Uninherited line" : "Inherited line";

                bool found = false;
                foreach (TimingLine removedLine in removedTimingLines.Select(aTuple => aTuple.Item2).ToList())
                {
                    if (!addedLine.offset.AlmostEqual(removedLine.offset))
                    {
                        continue;
                    }

                    string removedType = removedLine.uninherited ? "Uninherited line" : "Inherited line";
                    if (type != removedType)
                    {
                        continue;
                    }

                    List <string> changes = new List <string>();

                    if (addedLine.kiai != removedLine.kiai)
                    {
                        changes.Add("Kiai changed from " + (removedLine.kiai ? "enabled" : "disabled") +
                                    " to " + (addedLine.kiai ? "enabled" : "disabled") + ".");
                    }

                    if (addedLine.meter != removedLine.meter)
                    {
                        changes.Add("Timing signature changed from " + removedLine.meter + "/4" +
                                    " to " + addedLine.meter + "/4.");
                    }

                    if (addedLine.sampleset != removedLine.sampleset)
                    {
                        changes.Add("Sampleset changed from " +
                                    removedLine.sampleset.ToString().ToLower() + " to " +
                                    addedLine.sampleset.ToString().ToLower() + ".");
                    }

                    if (addedLine.customIndex != removedLine.customIndex)
                    {
                        changes.Add("Custom sampleset index changed from " +
                                    removedLine.customIndex.ToString().ToLower() + " to " +
                                    addedLine.customIndex.ToString().ToLower() + ".");
                    }

                    if (!addedLine.volume.AlmostEqual(removedLine.volume))
                    {
                        changes.Add("Volume changed from " + removedLine.volume +
                                    " to " + addedLine.volume + ".");
                    }

                    if (type == "Uninherited line")
                    {
                        UninheritedLine addedUninherited   = new UninheritedLine(addedLine.code.Split(','), beatmap: null);
                        UninheritedLine removedUninherited = new UninheritedLine(removedLine.code.Split(','), beatmap: null);

                        if (!addedUninherited.bpm.AlmostEqual(removedUninherited.bpm))
                        {
                            changes.Add("BPM changed from " + removedUninherited.bpm +
                                        " to " + addedUninherited.bpm + ".");
                        }
                    }
                    else if (!addedLine.svMult.AlmostEqual(removedLine.svMult))
                    {
                        changes.Add("Slider velocity multiplier changed from " + removedLine.svMult +
                                    " to " + addedLine.svMult + ".");
                    }

                    if (changes.Count == 1)
                    {
                        yield return(new DiffInstance(stamp + changes[0],
                                                      Section, DiffType.Changed, new List <string>(), addedDiff.snapshotCreationDate));
                    }
                    else if (changes.Count > 1)
                    {
                        yield return(new DiffInstance(stamp + type + " changed.",
                                                      Section, DiffType.Changed, changes, addedDiff.snapshotCreationDate));
                    }

                    found = true;
                    removedTimingLines.RemoveAll(aTuple => aTuple.Item2.code == removedLine.code);
                }

                if (!found)
                {
                    yield return(new DiffInstance(stamp + type + " added.",
                                                  Section, DiffType.Added, new List <string>(), addedDiff.snapshotCreationDate));
                }
            }

            foreach (Tuple <DiffInstance, TimingLine> removedTuple in removedTimingLines)
            {
                DiffInstance removedDiff = removedTuple.Item1;
                TimingLine   removedLine = removedTuple.Item2;

                string stamp = Timestamp.Get(removedLine.offset);
                string type  = removedLine.uninherited ? "Uninherited line" : "Inherited line";

                yield return(new DiffInstance(stamp + type + " removed.",
                                              Section, DiffType.Removed, new List <string>(), removedDiff.snapshotCreationDate));
            }
        }
示例#22
0
        public override IEnumerable <Issue> GetIssues(Beatmap aBeatmap)
        {
            foreach (HitObject hitObject in aBeatmap.hitObjects)
            {
                if (hitObject is Spinner spinner)
                {
                    HitObject nextObject = aBeatmap.GetNextHitObject(hitObject.time);

                    // Don't check time between two spinners since all you'd need to do is keep spinning.
                    while (nextObject != null && nextObject is Spinner)
                    {
                        nextObject = aBeatmap.GetNextHitObject(nextObject.time);
                    }

                    if (nextObject != null)
                    {
                        double spinnerTime  = spinner.endTime - spinner.time;
                        double recoveryTime = nextObject.time - spinner.endTime;

                        UninheritedLine line               = aBeatmap.GetTimingLine <UninheritedLine>(nextObject.time);
                        double          bpmScaling         = GetScaledTiming(line.bpm);
                        double          recoveryTimeScaled = recoveryTime / bpmScaling;

                        // Equal to the ms length of a beat in 180 BPM divided by the same thing in 240 BPM.
                        // So when multiplied by the expected time, we get the time that the Ranking Criteria wanted, which is based on 180 BPM.
                        double expectedMultiplier = 4 / 3d;

                        double[] spinnerTimeExpected = new double[] { 1000, 500, 250 }; // 4, 2 and 1 beats respectively, 240 bpm

                        for (int diffIndex = 0; diffIndex < spinnerTimeExpected.Length; ++diffIndex)
                        {
                            if (spinnerTime < spinnerTimeExpected[diffIndex])
                            {
                                yield return(new Issue(GetTemplate("Problem Length"), aBeatmap,
                                                       Timestamp.Get(spinner), spinnerTime, Math.Ceiling(spinnerTimeExpected[diffIndex] * expectedMultiplier))
                                             .ForDifficulties((Beatmap.Difficulty)diffIndex));
                            }

                            else if (spinnerTime < spinnerTimeExpected[diffIndex] * 1.2) // same thing but 200 bpm instead
                            {
                                yield return(new Issue(GetTemplate("Warning Length"), aBeatmap,
                                                       Timestamp.Get(spinner), spinnerTime, Math.Ceiling(spinnerTimeExpected[diffIndex] * expectedMultiplier))
                                             .ForDifficulties((Beatmap.Difficulty)diffIndex));
                            }
                        }

                        double[] recoveryTimeExpected = new double[] { 1000, 500, 250 }; // 4, 2 and 1 beats respectively, 240 bpm

                        // Tries both scaled and regular recoveries, and only if both are exceeded does it create an issue.
                        for (int diffIndex = 0; diffIndex < recoveryTimeExpected.Length; ++diffIndex)
                        {
                            // Picks whichever is greatest of the scaled and regular versions.
                            double expectedScaledMultiplier = (bpmScaling < 1 ? bpmScaling : 1);

                            if (recoveryTimeScaled < recoveryTimeExpected[diffIndex] && recoveryTime < recoveryTimeExpected[diffIndex])
                            {
                                yield return(new Issue(GetTemplate("Problem Recovery"), aBeatmap,
                                                       Timestamp.Get(spinner, nextObject), recoveryTime,
                                                       Math.Ceiling(recoveryTimeExpected[diffIndex] * expectedScaledMultiplier * expectedMultiplier))
                                             .ForDifficulties((Beatmap.Difficulty)diffIndex));
                            }

                            else if (recoveryTimeScaled < recoveryTimeExpected[diffIndex] * 1.2 && recoveryTime < recoveryTimeExpected[diffIndex] * 1.2)
                            {
                                yield return(new Issue(GetTemplate("Warning Recovery"), aBeatmap,
                                                       Timestamp.Get(spinner, nextObject), recoveryTime,
                                                       Math.Ceiling(recoveryTimeExpected[diffIndex] * expectedScaledMultiplier * expectedMultiplier))
                                             .ForDifficulties((Beatmap.Difficulty)diffIndex));
                            }
                        }
                    }
                }
            }
        }