From 6b8983b489a095c6aa43d7346d9b9d47e1af826e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 18:48:58 +0900 Subject: [PATCH 1/5] Consider intro and outro time as "break" time --- osu.Game/Screens/Play/BreakOverlay.cs | 12 +++++++++--- osu.Game/Screens/Play/Player.cs | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 6fdee85f45..28691c0d09 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; @@ -16,6 +16,8 @@ namespace osu.Game.Screens.Play { public class BreakOverlay : Container { + private readonly ScoreProcessor scoreProcessor; + /// /// The duration of the break overlay fading. /// @@ -60,9 +62,12 @@ namespace osu.Game.Screens.Play private readonly RemainingTimeCounter remainingTimeCounter; private readonly BreakInfo info; private readonly BreakArrows breakArrows; + private readonly double gameplayStartTime; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) + public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) { + this.gameplayStartTime = gameplayStartTime; + this.scoreProcessor = scoreProcessor; RelativeSizeAxes = Axes.Both; Child = fadeContainer = new Container { @@ -154,7 +159,8 @@ namespace osu.Game.Screens.Play } var currentBreak = breaks[CurrentBreakIndex]; - isBreakTime.Value = currentBreak.HasEffect && currentBreak.Contains(time); + + isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor.HasCompleted); } private void initializeBreaks() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6488dc209..7f32db2ebf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { - breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -468,7 +468,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (breakOverlay.IsBreakTime.Value || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + if (breakOverlay.IsBreakTime.Value) completeResume(); else DrawableRuleset.RequestResume(completeResume); From a453129d448ae718973248cd3eed11727425f7b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2019 15:13:50 +0900 Subject: [PATCH 2/5] Fix nullref in tests --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 28691c0d09..9ba18224ce 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Play var currentBreak = breaks[CurrentBreakIndex]; - isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor.HasCompleted); + isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor?.HasCompleted == true); } private void initializeBreaks() From 77ce9642ba616cb668ca8d9259c3b5a4a4792bfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2019 15:21:43 +0900 Subject: [PATCH 3/5] Add test coverage and simplify break time updating --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 13 ++++++ osu.Game/Screens/Play/BreakOverlay.cs | 41 +++++++++++-------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index 879e15c548..19dce303ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -95,6 +95,19 @@ namespace osu.Game.Tests.Visual.Gameplay seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false); } + [TestCase(true)] + [TestCase(false)] + public void TestBeforeGameplayStart(bool withBreaks) + { + setClock(true); + + if (withBreaks) + loadBreaksStep("multiple breaks", testBreaks); + + seekAndAssertBreak("seek to break intro time", -100, true); + seekAndAssertBreak("seek to break intro time", 0, false); + } + private void addShowBreakStep(double seconds) { AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 9ba18224ce..ee8be87352 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -140,27 +140,34 @@ namespace osu.Game.Screens.Play updateBreakTimeBindable(); } - private void updateBreakTimeBindable() + private void updateBreakTimeBindable() => + isBreakTime.Value = getCurrentBreak()?.HasEffect == true + || Clock.CurrentTime < gameplayStartTime + || scoreProcessor?.HasCompleted == true; + + private BreakPeriod getCurrentBreak() { - if (breaks == null || breaks.Count == 0) - return; - - var time = Clock.CurrentTime; - - if (time > breaks[CurrentBreakIndex].EndTime) + if (breaks?.Count > 0) { - while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) - CurrentBreakIndex++; - } - else - { - while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) - CurrentBreakIndex--; + var time = Clock.CurrentTime; + + if (time > breaks[CurrentBreakIndex].EndTime) + { + while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) + CurrentBreakIndex++; + } + else + { + while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) + CurrentBreakIndex--; + } + + var closest = breaks[CurrentBreakIndex]; + + return closest.Contains(time) ? closest : null; } - var currentBreak = breaks[CurrentBreakIndex]; - - isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor?.HasCompleted == true); + return null; } private void initializeBreaks() From ab017ee648df15b51fe635e6c5cec70d96f1635e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2019 15:37:13 +0900 Subject: [PATCH 4/5] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f2d2af5678..b80881e2f6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,6 +53,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 449b4dc4e3..2f633bdbc1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ff6839f5ca..38134074f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - + @@ -81,7 +81,7 @@ - + From 9425e80a5d23f29c45b91a6ef9e009cf61a17faf Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 26 Nov 2019 18:34:23 +0800 Subject: [PATCH 5/5] Unify to use double in performance calculators. --- .../Difficulty/CatchPerformanceCalculator.cs | 36 +++++---- .../Difficulty/OsuPerformanceCalculator.cs | 76 +++++++++---------- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 3 files changed, 56 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index b876c774b2..3c237c86be 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -47,55 +47,53 @@ namespace osu.Game.Rulesets.Catch.Difficulty return 0; // We are heavily relying on aim in catch the beat - double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f; + double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo int numTotalHits = totalComboHits(); // Longer maps are worth more - float lengthBonus = - 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + - (numTotalHits > 3000 ? MathF.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + double lengthBonus = + 0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) + + (numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0); // Longer maps are worth more value *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - value *= Math.Pow(0.97f, misses); + value *= Math.Pow(0.97, misses); // Combo scaling - float beatmapMaxCombo = Attributes.MaxCombo; - if (beatmapMaxCombo > 0) - value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + if (Attributes.MaxCombo > 0) + value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); - float approachRate = (float)Attributes.ApproachRate; - float approachRateFactor = 1.0f; - if (approachRate > 9.0f) - approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 - else if (approachRate < 8.0f) - approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 + double approachRateFactor = 1.0; + if (Attributes.ApproachRate > 9.0) + approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9 + else if (Attributes.ApproachRate < 8.0) + approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8 value *= approachRateFactor; if (mods.Any(m => m is ModHidden)) // Hiddens gives nothing on max approach rate, and more the lower it is - value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10 + value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10 if (mods.Any(m => m is ModFlashlight)) // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. - value *= 1.35f * lengthBonus; + value *= 1.35 * lengthBonus; // Scale the aim value with accuracy _slightly_ - value *= Math.Pow(accuracy(), 5.5f); + value *= Math.Pow(accuracy(), 5.5); // Custom multipliers for NoFail. SpunOut is not applicable. if (mods.Any(m => m is ModNoFail)) - value *= 0.90f; + value *= 0.90; return value; } - private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1); private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 093081b6a1..05c78cbc95 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -55,22 +55,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty return 0; // 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 + double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things if (mods.Any(m => m is OsuModNoFail)) - multiplier *= 0.90f; + multiplier *= 0.90; if (mods.Any(m => m is OsuModSpunOut)) - multiplier *= 0.95f; + multiplier *= 0.95; double aimValue = computeAimValue(); double speedValue = computeSpeedValue(); double accuracyValue = computeAccuracyValue(); double totalValue = Math.Pow( - Math.Pow(aimValue, 1.1f) + - Math.Pow(speedValue, 1.1f) + - Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f + Math.Pow(aimValue, 1.1) + + Math.Pow(speedValue, 1.1) + + Math.Pow(accuracyValue, 1.1), 1.0 / 1.1 ) * multiplier; if (categoryRatings != null) @@ -93,82 +93,82 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => m is OsuModTouchDevice)) rawAim = Math.Pow(rawAim, 0.8); - double aimValue = Math.Pow(5.0f * Math.Max(1.0f, rawAim / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more - double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - aimValue *= Math.Pow(0.97f, countMiss); + aimValue *= Math.Pow(0.97, countMiss); // Combo scaling if (beatmapMaxCombo > 0) - aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); - double approachRateFactor = 1.0f; + double approachRateFactor = 1.0; - if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); - else if (Attributes.ApproachRate < 8.0f) + if (Attributes.ApproachRate > 10.33) + approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33); + else if (Attributes.ApproachRate < 8.0) { - approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate); + approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate); } aimValue *= approachRateFactor; // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); + aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); if (mods.Any(h => h is OsuModFlashlight)) { // Apply object-based bonus for flashlight. - aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) + + aimValue *= 1.0 + 0.35 * Math.Min(1.0, totalHits / 200.0) + (totalHits > 200 - ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) + - (totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) - : 0.0f); + ? 0.3 * Math.Min(1.0, (totalHits - 200) / 300.0) + + (totalHits > 500 ? (totalHits - 500) / 1200.0 : 0.0) + : 0.0); } // Scale the aim value with accuracy _slightly_ - aimValue *= 0.5f + accuracy / 2.0f; + aimValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that - aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; return aimValue; } private double computeSpeedValue() { - double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more - speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + speedValue *= 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - speedValue *= Math.Pow(0.97f, countMiss); + speedValue *= Math.Pow(0.97, countMiss); // Combo scaling if (beatmapMaxCombo > 0) - speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); - double approachRateFactor = 1.0f; - if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); + double approachRateFactor = 1.0; + if (Attributes.ApproachRate > 10.33) + approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33); speedValue *= approachRateFactor; if (mods.Any(m => m is OsuModHidden)) - speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); + speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); // Scale the speed value with accuracy _slightly_ - speedValue *= 0.02f + accuracy; + speedValue *= 0.02 + accuracy; // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; + speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; return speedValue; } @@ -190,15 +190,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; + double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; // Bonus for many hitcircles - it's harder to keep good accuracy up for longer - accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); + accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); if (mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.08f; + accuracyValue *= 1.08; if (mods.Any(m => m is OsuModFlashlight)) - accuracyValue *= 1.02f; + accuracyValue *= 1.02; return accuracyValue; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 70249db0f6..c3638253e4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; // Longer maps are worth more - double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0); + double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); strainValue *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available