diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f0d9ece06f..c6e7988543 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] - [TestCase(ScoringMode.Classic, HitResult.Meh, 41)] - [TestCase(ScoringMode.Classic, HitResult.Ok, 46)] - [TestCase(ScoringMode.Classic, HitResult.Great, 72)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 20)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 23)] + [TestCase(ScoringMode.Classic, HitResult.Great, 36)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { scoreProcessor.Mode.Value = scoringMode; @@ -86,17 +86,17 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 68)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 81)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 109)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 149)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 9)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 15)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 86)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 104)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 140)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 190)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 190)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 18)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 31)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 18)] - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 18)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 12)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 36)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 36)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -128,8 +128,8 @@ namespace osu.Game.Tests.Rulesets.Scoring /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 69)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 60)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 34)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 30)] public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bbe854f2dd..a328f8e8c0 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -168,8 +168,7 @@ namespace osu.Game.Online.Rooms RoomID.Value = other.RoomID.Value; Name.Value = other.Name.Value; - if (other.Category.Value != RoomCategory.Spotlight) - Category.Value = other.Category.Value; + Category.Value = other.Category.Value; if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) Host.Value = other.Host.Value; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 79861c0ecc..d5a5aa4592 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxAchievableCombo, + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); @@ -222,12 +222,11 @@ namespace osu.Game.Rulesets.Scoring /// Computes the total score. /// /// The to compute the total score in. - /// The maximum combo achievable in the beatmap. /// The accuracy percentage achieved by the player. - /// The proportion of achieved by the player. + /// The proportion of the max combo achieved by the player. /// Any statistics to be factored in. /// The total score. - public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, Dictionary statistics) + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { switch (mode) { @@ -238,10 +237,16 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: + int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + + // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. + if (totalHitObjects == 0) + totalHitObjects = 1; + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - double scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * (maxCombo + 1), 2) * 18; + double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; + return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; } } @@ -265,7 +270,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 532c6b42a3..963c4a77ca 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -184,7 +184,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } /// diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 98c4b15f7f..afe75c5ef7 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -19,6 +19,7 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -71,6 +72,9 @@ namespace osu.Game.Screens.Menu [CanBeNull] private readonly Func createNextScreen; + [Resolved] + private RulesetStore rulesets { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . @@ -117,7 +121,11 @@ namespace osu.Game.Screens.Menu // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. if (initialBeatmap == null) { - if (!loadThemedIntro()) + // Intro beatmaps are generally made using the osu! ruleset. + // It might not be present in test projects for other rulesets. + bool osuRulesetPresent = rulesets.GetRuleset(0) != null; + + if (!loadThemedIntro() && osuRulesetPresent) { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this.