diff --git a/osu.Android.props b/osu.Android.props index 1532d4ce23..ca4d88a8a7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index dbfd170ea1..4acaf61cea 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -14,6 +14,7 @@ namespace osu.Desktop.Windows { private Bindable disableWinKey; private IBindable localUserPlaying; + private IBindable isActive; [Resolved] private GameHost host { get; set; } @@ -24,13 +25,16 @@ namespace osu.Desktop.Windows localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); localUserPlaying.BindValueChanged(_ => updateBlocking()); + isActive = host.IsActive.GetBoundCopy(); + isActive.BindValueChanged(_ => updateBlocking()); + disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); disableWinKey.BindValueChanged(_ => updateBlocking(), true); } private void updateBlocking() { - bool shouldDisable = disableWinKey.Value && localUserPlaying.Value; + bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs new file mode 100644 index 0000000000..1335fc2d23 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchPerformanceAttributes : PerformanceAttributes + { + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 439890dac2..8cdbe500f0 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty { } - public override double Calculate(Dictionary categoryDifficulty = null) + public override PerformanceAttributes Calculate() { mods = Score.Mods; @@ -44,15 +44,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty // 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 double lengthBonus = 0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) + (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 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.97, misses); // Combo scaling @@ -80,17 +76,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty } 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.35 * lengthBonus; - // Scale the aim value with accuracy _slightly_ value *= Math.Pow(accuracy(), 5.5); - // Custom multipliers for NoFail. SpunOut is not applicable. if (mods.Any(m => m is ModNoFail)) value *= 0.90; - return value; + return new CatchPerformanceAttributes + { + Total = value + }; } private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1); diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index bfdef893e9..979a04ddf8 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return v; // Todo: osu!mania doesn't output MaxCombo attribute for some reason. - yield return (ATTRIB_ID_STRAIN, StarRating); + yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); } @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { base.FromDatabaseAttributes(values); - StarRating = values[ATTRIB_ID_STRAIN]; + StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER]; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs new file mode 100644 index 0000000000..da9634ba47 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Rulesets.Difficulty; + +namespace osu.Game.Rulesets.Mania.Difficulty +{ + public class ManiaPerformanceAttributes : PerformanceAttributes + { + [JsonProperty("difficulty")] + public double Difficulty { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty("scaled_score")] + public double ScaledScore { get; set; } + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index b04ff3548f..8a8c41bb8a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { } - public override double Calculate(Dictionary categoryDifficulty = null) + public override PerformanceAttributes Calculate() { mods = Score.Mods; scaledScore = Score.TotalScore; @@ -61,48 +61,46 @@ namespace osu.Game.Rulesets.Mania.Difficulty if (mods.Any(m => m is ModEasy)) multiplier *= 0.5; - double strainValue = computeStrainValue(); - double accValue = computeAccuracyValue(strainValue); + double difficultyValue = computeDifficultyValue(); + double accValue = computeAccuracyValue(difficultyValue); double totalValue = Math.Pow( - Math.Pow(strainValue, 1.1) + + Math.Pow(difficultyValue, 1.1) + Math.Pow(accValue, 1.1), 1.0 / 1.1 ) * multiplier; - if (categoryDifficulty != null) + return new ManiaPerformanceAttributes { - categoryDifficulty["Strain"] = strainValue; - categoryDifficulty["Accuracy"] = accValue; - } - - return totalValue; + Difficulty = difficultyValue, + Accuracy = accValue, + ScaledScore = scaledScore, + Total = totalValue + }; } - private double computeStrainValue() + private double computeDifficultyValue() { - // Obtain strain difficulty - double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; + double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; - // Longer maps are worth more - strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); + difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); if (scaledScore <= 500000) - strainValue = 0; + difficultyValue = 0; else if (scaledScore <= 600000) - strainValue *= (scaledScore - 500000) / 100000 * 0.3; + difficultyValue *= (scaledScore - 500000) / 100000 * 0.3; else if (scaledScore <= 700000) - strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25; + difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25; else if (scaledScore <= 800000) - strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20; + difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20; else if (scaledScore <= 900000) - strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15; + difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15; else - strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1; + difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1; - return strainValue; + return difficultyValue; } - private double computeAccuracyValue(double strainValue) + private double computeAccuracyValue(double difficultyValue) { if (Attributes.GreatHitWindow <= 0) return 0; @@ -110,12 +108,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667) - * strainValue + * difficultyValue * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); - // Bonus for many hitcircles - it's harder to keep good accuracy up for longer - // accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); - return accuracyValue; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 4b2e54da17..128ff772fd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyAttributes : DifficultyAttributes { - [JsonProperty("aim_strain")] - public double AimStrain { get; set; } + [JsonProperty("aim_difficulty")] + public double AimDifficulty { get; set; } - [JsonProperty("speed_strain")] - public double SpeedStrain { get; set; } + [JsonProperty("speed_difficulty")] + public double SpeedDifficulty { get; set; } - [JsonProperty("flashlight_rating")] - public double FlashlightRating { get; set; } + [JsonProperty("flashlight_difficulty")] + public double FlashlightDifficulty { get; set; } [JsonProperty("slider_factor")] public double SliderFactor { get; set; } @@ -43,15 +43,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty foreach (var v in base.ToDatabaseAttributes()) yield return v; - yield return (ATTRIB_ID_AIM, AimStrain); - yield return (ATTRIB_ID_SPEED, SpeedStrain); + yield return (ATTRIB_ID_AIM, AimDifficulty); + yield return (ATTRIB_ID_SPEED, SpeedDifficulty); yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty); yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); - yield return (ATTRIB_ID_STRAIN, StarRating); + yield return (ATTRIB_ID_DIFFICULTY, StarRating); if (ShouldSerializeFlashlightRating()) - yield return (ATTRIB_ID_FLASHLIGHT, FlashlightRating); + yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); } @@ -60,13 +60,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty { base.FromDatabaseAttributes(values); - AimStrain = values[ATTRIB_ID_AIM]; - SpeedStrain = values[ATTRIB_ID_SPEED]; + AimDifficulty = values[ATTRIB_ID_AIM]; + SpeedDifficulty = values[ATTRIB_ID_SPEED]; OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; - StarRating = values[ATTRIB_ID_STRAIN]; - FlashlightRating = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); + StarRating = values[ATTRIB_ID_DIFFICULTY]; + FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ed42f333c0..c5b1baaad1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty { StarRating = starRating, Mods = mods, - AimStrain = aimRating, - SpeedStrain = speedRating, - FlashlightRating = flashlightRating, + AimDifficulty = aimRating, + SpeedDifficulty = speedRating, + FlashlightDifficulty = flashlightRating, SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs new file mode 100644 index 0000000000..6c7760d144 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Rulesets.Difficulty; + +namespace osu.Game.Rulesets.Osu.Difficulty +{ + public class OsuPerformanceAttributes : PerformanceAttributes + { + [JsonProperty("aim")] + public double Aim { get; set; } + + [JsonProperty("speed")] + public double Speed { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty("flashlight")] + public double Flashlight { get; set; } + + [JsonProperty("effective_miss_count")] + public double EffectiveMissCount { get; set; } + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8d45c7a8cc..d7d294df47 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { } - public override double Calculate(Dictionary categoryRatings = null) + public override PerformanceAttributes Calculate() { mods = Score.Mods; accuracy = Score.Accuracy; @@ -45,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. - // Custom multipliers for NoFail and SpunOut. if (mods.Any(m => m is OsuModNoFail)) multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); @@ -72,42 +71,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty Math.Pow(flashlightValue, 1.1), 1.0 / 1.1 ) * multiplier; - if (categoryRatings != null) + return new OsuPerformanceAttributes { - categoryRatings.Add("Aim", aimValue); - categoryRatings.Add("Speed", speedValue); - categoryRatings.Add("Accuracy", accuracyValue); - categoryRatings.Add("Flashlight", flashlightValue); - categoryRatings.Add("OD", Attributes.OverallDifficulty); - categoryRatings.Add("AR", Attributes.ApproachRate); - categoryRatings.Add("Max Combo", Attributes.MaxCombo); - } - - return totalValue; + Aim = aimValue, + Speed = speedValue, + Accuracy = accuracyValue, + Flashlight = flashlightValue, + EffectiveMissCount = effectiveMissCount, + Total = totalValue + }; } private double computeAimValue() { - double rawAim = Attributes.AimStrain; + double rawAim = Attributes.AimDifficulty; if (mods.Any(m => m is OsuModTouchDevice)) rawAim = Math.Pow(rawAim, 0.8); 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.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 by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); - // Combo scaling. - if (Attributes.MaxCombo > 0) - aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + aimValue *= getComboScalingFactor(); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -136,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } aimValue *= accuracy; - // It is important to also consider accuracy difficulty when doing that. + // It is important to consider accuracy difficulty when scaling with accuracy. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; return aimValue; @@ -144,9 +136,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeSpeedValue() { - double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; - // Longer maps are worth more. double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; @@ -155,9 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - // Combo scaling. - if (Attributes.MaxCombo > 0) - speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + speedValue *= getComboScalingFactor(); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -227,14 +216,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (!mods.Any(h => h is OsuModFlashlight)) return 0.0; - double rawFlashlight = Attributes.FlashlightRating; + double rawFlashlight = Attributes.FlashlightDifficulty; if (mods.Any(m => m is OsuModTouchDevice)) rawFlashlight = Math.Pow(rawFlashlight, 0.8); double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; - // Add an additional bonus for HDFL. if (mods.Any(h => h is OsuModHidden)) flashlightValue *= 1.3; @@ -242,9 +230,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - // Combo scaling. - if (Attributes.MaxCombo > 0) - flashlightValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + flashlightValue *= getComboScalingFactor(); // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius. flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + @@ -276,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); } + private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 44ba0e2057..03abba29ce 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { } - private double skillMultiplier => 0.15; + private double skillMultiplier => 0.07; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -40,26 +40,31 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double result = 0.0; + OsuDifficultyHitObject lastObj = osuCurrent; + + // This is iterating backwards in time from the current object. for (int i = 0; i < Previous.Count; i++) { - var osuPrevious = (OsuDifficultyHitObject)Previous[i]; - var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); + var currentObj = (OsuDifficultyHitObject)Previous[i]; + var currentHitObject = (OsuHitObject)(currentObj.BaseObject); - if (!(osuPrevious.BaseObject is Spinner)) + if (!(currentObj.BaseObject is Spinner)) { - double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; + double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; - cumulativeStrainTime += osuPrevious.StrainTime; + cumulativeStrainTime += lastObj.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuPrevious.LazyJumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); - result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } + + lastObj = currentObj; } return Math.Pow(smallDistNerf * result, 2.0); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index b2b5d056c3..31f5a6f570 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -9,14 +9,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyAttributes : DifficultyAttributes { - [JsonProperty("stamina_strain")] - public double StaminaStrain { get; set; } + [JsonProperty("stamina_difficulty")] + public double StaminaDifficulty { get; set; } - [JsonProperty("rhythm_strain")] - public double RhythmStrain { get; set; } + [JsonProperty("rhythm_difficulty")] + public double RhythmDifficulty { get; set; } - [JsonProperty("colour_strain")] - public double ColourStrain { get; set; } + [JsonProperty("colour_difficulty")] + public double ColourDifficulty { get; set; } [JsonProperty("approach_rate")] public double ApproachRate { get; set; } @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return v; yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); - yield return (ATTRIB_ID_STRAIN, StarRating); + yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); } @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty base.FromDatabaseAttributes(values); MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; - StarRating = values[ATTRIB_ID_STRAIN]; + StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index e84bee3d28..6afdef3f3c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -91,9 +91,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { StarRating = starRating, Mods = mods, - StaminaStrain = staminaRating, - RhythmStrain = rhythmRating, - ColourStrain = colourRating, + StaminaDifficulty = staminaRating, + RhythmDifficulty = rhythmRating, + ColourDifficulty = colourRating, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), }; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs new file mode 100644 index 0000000000..80552880ea --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Rulesets.Difficulty; + +namespace osu.Game.Rulesets.Taiko.Difficulty +{ + public class TaikoPerformanceAttributes : PerformanceAttributes + { + [JsonProperty("difficulty")] + public double Difficulty { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 90dd733dfd..bcd55f8fae 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - public override double Calculate(Dictionary categoryDifficulty = null) + public override PerformanceAttributes Calculate() { mods = Score.Mods; countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - // Custom multipliers for NoFail and SpunOut. double multiplier = 1.1; // 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 ModNoFail)) @@ -44,43 +43,38 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (mods.Any(m => m is ModHidden)) multiplier *= 1.10; - double strainValue = computeStrainValue(); + double difficultyValue = computeDifficultyValue(); double accuracyValue = computeAccuracyValue(); double totalValue = Math.Pow( - Math.Pow(strainValue, 1.1) + + Math.Pow(difficultyValue, 1.1) + Math.Pow(accuracyValue, 1.1), 1.0 / 1.1 ) * multiplier; - if (categoryDifficulty != null) + return new TaikoPerformanceAttributes { - categoryDifficulty["Strain"] = strainValue; - categoryDifficulty["Accuracy"] = accuracyValue; - } - - return totalValue; + Difficulty = difficultyValue, + Accuracy = accuracyValue, + Total = totalValue + }; } - private double computeStrainValue() + private double computeDifficultyValue() { - double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; + double difficultyValue = 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.1 * Math.Min(1.0, totalHits / 1500.0); - strainValue *= lengthBonus; + difficultyValue *= lengthBonus; - // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - strainValue *= Math.Pow(0.985, countMiss); + difficultyValue *= Math.Pow(0.985, countMiss); if (mods.Any(m => m is ModHidden)) - strainValue *= 1.025; + difficultyValue *= 1.025; 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. - strainValue *= 1.05 * lengthBonus; + difficultyValue *= 1.05 * lengthBonus; - // Scale the speed value with accuracy _slightly_ - return strainValue * Score.Accuracy; + return difficultyValue * Score.Accuracy; } private double computeAccuracyValue() @@ -88,11 +82,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (Attributes.GreatHitWindow <= 0) return 0; - // Lots of arbitrary values from testing. - // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0; - // Bonus for many hitcircles - it's harder to keep good accuracy up for longer + // Bonus for many objects - it's harder to keep good accuracy up for longer return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); } diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index d87ac29d75..686e053246 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestSaveAndReload() { - using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload", bypassCleanup: true)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(bypassCleanup: true)) { try { @@ -149,7 +149,8 @@ namespace osu.Game.Tests.Collections.IO } } - using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload")) + // Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestSaveAndReload))) { try { diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 8a063b3c6e..8d15be44fa 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -61,6 +61,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -94,6 +95,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -160,6 +162,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -168,7 +171,7 @@ namespace osu.Game.Tests.NonVisual public void TestMigrationBetweenTwoTargets() { string customPath = prepareCustomPath(); - string customPath2 = prepareCustomPath("-2"); + string customPath2 = prepareCustomPath(); using (var host = new CustomTestHeadlessGameHost()) { @@ -185,7 +188,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(File.Exists(Path.Combine(customPath2, database_filename))); // some files may have been left behind for whatever reason, but that's not what we're testing here. - customPath = prepareCustomPath(); + cleanupPath(customPath); Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.That(File.Exists(Path.Combine(customPath, database_filename))); @@ -193,6 +196,8 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); + cleanupPath(customPath2); } } } @@ -214,6 +219,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -243,6 +249,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -272,6 +279,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -286,14 +294,18 @@ namespace osu.Game.Tests.NonVisual return path; } - private string prepareCustomPath(string suffix = "") + private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}"); + + private static void cleanupPath(string path) { - string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path{suffix}"); - - if (Directory.Exists(path)) - Directory.Delete(path, true); - - return path; + try + { + if (Directory.Exists(path)) + Directory.Delete(path, true); + } + catch + { + } } public class CustomTestHeadlessGameHost : CleanRunHeadlessGameHost diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index bc0041e2c2..0c49a18c8f 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer public void TestPlayingUsersUpdatedOnJoin() { AddStep("leave room", () => Client.LeaveRoom()); - AddUntilStep("wait for room part", () => Client.Room == null); + AddUntilStep("wait for room part", () => !RoomJoined); AddStep("create room initially in gameplay", () => { diff --git a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs index d5fd803986..cd02f15adf 100644 --- a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs +++ b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs @@ -1,9 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Game.Configuration; -using osu.Game.Input; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Tests.NonVisual { @@ -11,37 +12,32 @@ namespace osu.Game.Tests.NonVisual public class SessionStaticsTest { private SessionStatics sessionStatics; - private IdleTracker sessionIdleTracker; - [SetUp] - public void SetUp() + [Test] + public void TestSessionStaticsReset() { sessionStatics = new SessionStatics(); - sessionIdleTracker = new GameIdleTracker(1000); sessionStatics.SetValue(Static.LoginOverlayDisplayed, true); sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true); sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true); sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d); + sessionStatics.SetValue(Static.SeasonalBackgrounds, new APISeasonalBackgrounds { EndDate = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) }); - sessionIdleTracker.IsIdle.BindValueChanged(e => - { - if (e.NewValue) - sessionStatics.ResetValues(); - }); - } + Assert.IsFalse(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.SeasonalBackgrounds).IsDefault); - [Test] - [Timeout(2000)] - public void TestSessionStaticsReset() - { - sessionIdleTracker.IsIdle.BindValueChanged(e => - { - Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); - Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); - Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); - Assert.IsTrue(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); - }); + sessionStatics.ResetAfterInactivity(); + + Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); + // some statics should not reset despite inactivity. + Assert.IsFalse(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.SeasonalBackgrounds).IsDefault); } } } diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs new file mode 100644 index 0000000000..d33081662d --- /dev/null +++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Online.Rooms; + +namespace osu.Game.Tests.OnlinePlay +{ + [TestFixture] + public class PlaylistExtensionsTest + { + [Test] + public void TestEmpty() + { + // mostly an extreme edge case, i.e. during room creation. + var items = Array.Empty(); + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.Empty); + Assert.That(items.GetCurrentItem(), Is.Null); + Assert.That(items.GetUpcomingItems(), Is.Empty); + }); + } + + [Test] + public void TestPlaylistItemsInOrder() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.Empty); + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[0])); + Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(items)); + }); + } + + [Test] + public void TestPlaylistItemsOutOfOrder() + { + var items = new[] + { + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.Empty); + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[1])); + Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] })); + }); + } + + [Test] + public void TestExpiredPlaylistItemsSkipped() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0] })); + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2])); + Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[2] })); + }); + } + + [Test] + public void TestAllItemsExpired() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] })); + // if all items are expired, the last-played item is expected to be returned. + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2])); + Assert.That(items.GetUpcomingItems(), Is.Empty); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 0b9857486a..7b5e1f4ec7 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var longName = CreateAPIBeatmapSet(Ruleset.Value); longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title"; longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name."; + longName.Source = "wow. even the source field has an impossibly long string in it. this really takes the cake, doesn't it?"; longName.HasExplicitContent = true; longName.TrackId = 444; @@ -251,13 +252,19 @@ namespace osu.Game.Tests.Visual.Beatmaps [Test] public void TestNormal() { - createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + createTestCase(beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo)); + } + + [Test] + public void TestExtra() + { + createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo)); } [Test] public void TestHoverState() { - AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCard(s))); + AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCardNormal(s))); AddStep("Hover card", () => InputManager.MoveMouseTo(firstCard())); AddWaitStep("wait for potential state change", 5); @@ -274,10 +281,10 @@ namespace osu.Game.Tests.Visual.Beatmaps AddWaitStep("wait for potential state change", 5); AddAssert("card is still expanded", () => firstCard().Expanded.Value); - AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last())); + AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last())); AddUntilStep("card is not expanded", () => !firstCard().Expanded.Value); - BeatmapCard firstCard() => this.ChildrenOfType().First(); + BeatmapCardNormal firstCard() => this.ChildrenOfType().First(); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index a718a98aa6..95603b5c04 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void checkForFirstSamplePlayback() { - AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); + AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 5acb44ac45..88c54eb2bb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -20,7 +19,6 @@ using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; using osu.Game.Tests.Resources; -using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -86,11 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddWaitStep("wait for transition", 2); - AddStep("create room", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + ClickButtonWhenEnabled(); AddUntilStep("wait for join", () => Client.RoomJoined); } @@ -104,24 +98,13 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); - clickReadyButton(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); - clickReadyButton(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); } - - private void clickReadyButton() - { - AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType