diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 808faa511b..5b34e46247 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; @@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Catch.Tests { - internal class CatchBeatmapConversionTest : BeatmapConversionTest + internal class CatchBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; @@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests } } - protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); + protected override Ruleset CreateRuleset() => new CatchRuleset(); } internal struct ConvertValue : IEquatable @@ -64,8 +62,4 @@ namespace osu.Game.Rulesets.Catch.Tests => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) && Precision.AlmostEquals(Position, other.Position, conversion_lenience); } - - internal class TestCatchRuleset : CatchRuleset - { - } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index bd67a7d96a..5ae899f6d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests { - internal class ManiaBeatmapConversionTest : BeatmapConversionTest + internal class ManiaBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; @@ -35,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests }; } - protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); + protected override Ruleset CreateRuleset() => new ManiaRuleset(); } internal struct ConvertValue : IEquatable @@ -54,8 +52,4 @@ namespace osu.Game.Rulesets.Mania.Tests && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && Column == other.Column; } - - internal class TestManiaRuleset : ManiaRuleset - { - } } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 3d54043027..386ae5eb05 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -5,17 +5,15 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; using OpenTK; namespace osu.Game.Rulesets.Osu.Tests { - internal class OsuBeatmapConversionTest : BeatmapConversionTest + internal class OsuBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; @@ -42,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests }; } - protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); + protected override Ruleset CreateRuleset() => new OsuRuleset(); } internal struct ConvertValue : IEquatable @@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Osu.Tests && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience) && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience); } - - internal class TestOsuRuleset : OsuRuleset - { - } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index bd8d18af41..3ed072a275 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -36,18 +36,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty new Speed() }; - double sectionEnd = section_length / TimeRate; + double sectionLength = section_length * TimeRate; + + // The first object doesn't generate a strain, so we begin with an incremented section end + double currentSectionEnd = 2 * sectionLength; + foreach (OsuDifficultyHitObject h in beatmap) { - while (h.BaseObject.StartTime > sectionEnd) + while (h.BaseObject.StartTime > currentSectionEnd) { foreach (Skill s in skills) { s.SaveCurrentPeak(); - s.StartNewSectionFrom(sectionEnd); + s.StartNewSectionFrom(currentSectionEnd); } - sectionEnd += section_length; + currentSectionEnd += sectionLength; } foreach (Skill s in skills) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs index e97042a9aa..4443a0e66b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public class OsuDifficultyBeatmap : IEnumerable { private readonly IEnumerator difficultyObjects; - private readonly Queue onScreen = new Queue(); /// /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as @@ -30,65 +29,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// /// Returns an enumerator that enumerates all s in the . - /// The inner loop adds objects that appear on screen into a queue until we need to hit the next object. - /// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen. - /// This means that we can loop through every object that is on screen at the time when a new one appears, - /// allowing us to determine a reading strain for the object that just appeared. /// - public IEnumerator GetEnumerator() - { - while (true) - { - // Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued. - // This means there is always at least one object in the queue unless we reached the end of the map. - do - { - if (!difficultyObjects.MoveNext()) - break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen. - - OsuDifficultyHitObject latest = difficultyObjects.Current; - // Calculate flow values here - - foreach (OsuDifficultyHitObject h in onScreen) - { - // ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext()) - h.TimeUntilHit -= latest.DeltaTime; - // Calculate reading strain here - } - - onScreen.Enqueue(latest); - } - while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one. - - if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects. - yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared. - } - } - + public IEnumerator GetEnumerator() => difficultyObjects; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate) { - // We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object. - OsuHitObject[] triangle = new OsuHitObject[3]; - - // OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning. - if (objects.Count > 1) - { - triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle. - triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject. - } - - // The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump. + // The first jump is formed by the first two hitobjects of the map. // If the map has less than two OsuHitObjects, the enumerator will not return anything. - for (int i = 1; i < objects.Count; ++i) - { - triangle[2] = triangle[1]; - triangle[1] = triangle[0]; - triangle[0] = objects[i]; - - yield return new OsuDifficultyHitObject(triangle, timeRate); - } + for (int i = 1; i < objects.Count; i++) + yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index f95426466b..29de23406b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public class OsuDifficultyHitObject { + private const int normalized_radius = 52; + /// /// The this refers to. /// @@ -28,26 +30,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double DeltaTime { get; private set; } - /// - /// Number of milliseconds until the has to be hit. - /// - public double TimeUntilHit { get; set; } - - private const int normalized_radius = 52; - + private readonly OsuHitObject lastObject; private readonly double timeRate; - private readonly OsuHitObject[] t; - /// /// Initializes the object calculating extra data required for difficulty calculation. /// - public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate) + public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate) { + this.lastObject = lastObject; this.timeRate = timeRate; - t = triangle; - BaseObject = t[0]; + BaseObject = currentObject; + setDistances(); setTimingValues(); // Calculate angle here @@ -63,10 +58,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } - Vector2 lastCursorPosition = t[1].StackedPosition; + Vector2 lastCursorPosition = lastObject.StackedPosition; float lastTravelDistance = 0; - var lastSlider = t[1] as Slider; + var lastSlider = lastObject as Slider; if (lastSlider != null) { computeSliderCursorPosition(lastSlider); @@ -80,8 +75,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private void setTimingValues() { // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure. - DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate); - TimeUntilHit = 450; // BaseObject.PreEmpt; + DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate); } private void computeSliderCursorPosition(Slider slider) @@ -107,7 +101,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing } }); - var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime); + // Skip the head circle + var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime); foreach (var time in scoringTimes) computeVertex(time); computeVertex(slider.EndTime); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs index 938060a664..84a83d331b 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Scoring private double accuracy; private int scoreMaxCombo; - private int count300; - private int count100; - private int count50; + private int countGreat; + private int countGood; + private int countMeh; private int countMiss; public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) @@ -52,9 +52,9 @@ namespace osu.Game.Rulesets.Osu.Scoring mods = Score.Mods; accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; - count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]); - count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]); - count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]); + countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); + countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); + countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); // Don't count scores made with supposedly unranked mods @@ -71,10 +71,10 @@ namespace osu.Game.Rulesets.Osu.Scoring ar = Math.Max(0, ar / 2); double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate; - double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5; - realOverallDifficulty = (80 - 0.5 - hitWindow300) / 6; + realOverallDifficulty = (80 - 0.5 - hitWindowGreat) / 6; // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Scoring int amountHitObjectsWithAccuracy = countHitCircles; if (amountHitObjectsWithAccuracy > 0) - betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6); + betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6); else betterAccuracyPercentage = 0; @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Scoring return accuracyValue; } - private double totalHits => count300 + count100 + count50 + countMiss; - private double totalSuccessfulHits => count300 + count100 + count50; + private double totalHits => countGreat + countGood + countMeh + countMiss; + private double totalSuccessfulHits => countGreat + countGood + countMeh; } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index ca4fc3ec57..93f1b7faf7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -5,16 +5,14 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests { - internal class TaikoBeatmapConversionTest : BeatmapConversionTest + internal class TaikoBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; @@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Tests }; } - protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); + protected override Ruleset CreateRuleset() => new TaikoRuleset(); } internal struct ConvertValue : IEquatable @@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Taiko.Tests && IsSwell == other.IsSwell && IsStrong == other.IsStrong; } - - internal class TestTaikoRuleset : TaikoRuleset - { - } } diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 090bc737dd..6379be9bfc 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Mods public void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 1.4f; - difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio. + difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio. difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ratio, 10.0f); - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; + difficulty.DrainRate = Math.Min(difficulty.DrainRate * ratio, 10.0f); + difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ratio, 10.0f); } } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 2de38d49a9..7470f6ebed 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -16,8 +16,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Tests.Beatmaps { [TestFixture] - public abstract class BeatmapConversionTest - where TRuleset : Ruleset, new() + public abstract class BeatmapConversionTest where TConvertValue : IEquatable { private const string resource_namespace = "Testing.Beatmaps"; @@ -81,12 +80,12 @@ namespace osu.Game.Tests.Beatmaps { var beatmap = getBeatmap(name); - var rulesetInstance = new TRuleset(); + var rulesetInstance = CreateRuleset(); beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); var result = new ConvertResult(); - var converter = CreateConverter(beatmap); + var converter = rulesetInstance.CreateBeatmapConverter(beatmap); converter.ObjectConverted += (orig, converted) => { converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty)); @@ -130,7 +129,7 @@ namespace osu.Game.Tests.Beatmaps } protected abstract IEnumerable CreateConvertValue(HitObject hitObject); - protected abstract IBeatmapConverter CreateConverter(IBeatmap beatmap); + protected abstract Ruleset CreateRuleset(); private class ConvertMapping {