Merge pull request #19940 from smoogipoo/legacy-combo-increase

Add `legacy_combo_increase` hit result type for legacy scores
This commit is contained in:
Salman Ahmed 2022-08-25 16:17:35 +03:00 committed by GitHub
commit 0d6b7fde92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 9 deletions

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -314,15 +315,55 @@ namespace osu.Game.Tests.Rulesets.Scoring
}), Is.EqualTo(expectedScore).Within(0.5d));
}
#pragma warning disable CS0618
[Test]
public void TestLegacyComboIncrease()
{
Assert.That(HitResult.LegacyComboIncrease.IncreasesCombo(), Is.True);
Assert.That(HitResult.LegacyComboIncrease.BreaksCombo(), Is.False);
Assert.That(HitResult.LegacyComboIncrease.AffectsCombo(), Is.True);
Assert.That(HitResult.LegacyComboIncrease.AffectsAccuracy(), Is.False);
Assert.That(HitResult.LegacyComboIncrease.IsBasic(), Is.False);
Assert.That(HitResult.LegacyComboIncrease.IsTick(), Is.False);
Assert.That(HitResult.LegacyComboIncrease.IsBonus(), Is.False);
Assert.That(HitResult.LegacyComboIncrease.IsHit(), Is.True);
Assert.That(HitResult.LegacyComboIncrease.IsScorable(), Is.True);
Assert.That(HitResultExtensions.ALL_TYPES, Does.Not.Contain(HitResult.LegacyComboIncrease));
// Cannot be used to apply results.
Assert.Throws<ArgumentException>(() => scoreProcessor.ApplyBeatmap(new Beatmap
{
HitObjects = { new TestHitObject(HitResult.LegacyComboIncrease) }
}));
ScoreInfo testScore = new ScoreInfo
{
MaxCombo = 1,
Statistics = new Dictionary<HitResult, int>
{
{ HitResult.Great, 1 }
},
MaximumStatistics = new Dictionary<HitResult, int>
{
{ HitResult.Great, 1 },
{ HitResult.LegacyComboIncrease, 1 }
}
};
double totalScore = new TestScoreProcessor().ComputeFinalScore(ScoringMode.Standardised, testScore);
Assert.That(totalScore, Is.EqualTo(750_000)); // 500K from accuracy (100%), and 250K from combo (50%).
}
#pragma warning restore CS0618
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new System.NotImplementedException();
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new System.NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description => string.Empty;
public override string ShortName => string.Empty;
@ -352,5 +393,33 @@ namespace osu.Game.Tests.Rulesets.Scoring
this.maxResult = maxResult;
}
}
private class TestScoreProcessor : ScoreProcessor
{
protected override double DefaultAccuracyPortion => 0.5;
protected override double DefaultComboPortion => 0.5;
public TestScoreProcessor()
: base(new TestRuleset())
{
}
// ReSharper disable once MemberHidesStaticFromOuterClass
private class TestRuleset : Ruleset
{
protected override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great };
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description => string.Empty;
public override string ShortName => string.Empty;
}
}
}
}

View File

@ -119,8 +119,20 @@ namespace osu.Game.Rulesets.Scoring
[EnumMember(Value = "ignore_hit")]
[Order(12)]
IgnoreHit,
/// <summary>
/// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy).
/// </summary>
/// <remarks>
/// DO NOT USE.
/// </remarks>
[EnumMember(Value = "legacy_combo_increase")]
[Order(99)]
[Obsolete("Do not use.")]
LegacyComboIncrease = 99
}
#pragma warning disable CS0618
public static class HitResultExtensions
{
/// <summary>
@ -150,6 +162,7 @@ namespace osu.Game.Rulesets.Scoring
case HitResult.Perfect:
case HitResult.LargeTickHit:
case HitResult.LargeTickMiss:
case HitResult.LegacyComboIncrease:
return true;
default:
@ -161,13 +174,25 @@ namespace osu.Game.Rulesets.Scoring
/// Whether a <see cref="HitResult"/> affects the accuracy portion of the score.
/// </summary>
public static bool AffectsAccuracy(this HitResult result)
=> IsScorable(result) && !IsBonus(result);
{
// LegacyComboIncrease is a special type which is neither a basic, tick, bonus, or accuracy-affecting result.
if (result == HitResult.LegacyComboIncrease)
return false;
return IsScorable(result) && !IsBonus(result);
}
/// <summary>
/// Whether a <see cref="HitResult"/> is a non-tick and non-bonus result.
/// </summary>
public static bool IsBasic(this HitResult result)
=> IsScorable(result) && !IsTick(result) && !IsBonus(result);
{
// LegacyComboIncrease is a special type which is neither a basic, tick, bonus, or accuracy-affecting result.
if (result == HitResult.LegacyComboIncrease)
return false;
return IsScorable(result) && !IsTick(result) && !IsBonus(result);
}
/// <summary>
/// Whether a <see cref="HitResult"/> should be counted as a tick.
@ -225,12 +250,19 @@ namespace osu.Game.Rulesets.Scoring
/// <summary>
/// Whether a <see cref="HitResult"/> is scorable.
/// </summary>
public static bool IsScorable(this HitResult result) => result >= HitResult.Miss && result < HitResult.IgnoreMiss;
public static bool IsScorable(this HitResult result)
{
// LegacyComboIncrease is not actually scorable (in terms of usable by rulesets for that purpose), but needs to be defined as such to be correctly included in statistics output.
if (result == HitResult.LegacyComboIncrease)
return true;
return result >= HitResult.Miss && result < HitResult.IgnoreMiss;
}
/// <summary>
/// An array of all scorable <see cref="HitResult"/>s.
/// </summary>
public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).ToArray();
public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Except(new[] { HitResult.LegacyComboIncrease }).ToArray();
/// <summary>
/// Whether a <see cref="HitResult"/> is valid within a given <see cref="HitResult"/> range.
@ -251,4 +283,5 @@ namespace osu.Game.Rulesets.Scoring
return result > minResult && result < maxResult;
}
}
#pragma warning restore CS0618
}

View File

@ -61,6 +61,11 @@ namespace osu.Game.Rulesets.Scoring
/// <param name="result">The <see cref="JudgementResult"/> to apply.</param>
public void ApplyResult(JudgementResult result)
{
#pragma warning disable CS0618
if (result.Type == HitResult.LegacyComboIncrease)
throw new ArgumentException(@$"A {nameof(HitResult.LegacyComboIncrease)} hit result cannot be applied.");
#pragma warning restore CS0618
JudgedHits++;
lastAppliedResult = result;

View File

@ -536,6 +536,9 @@ namespace osu.Game.Rulesets.Scoring
{
extractScoringValues(scoreInfo.Statistics, out current, out maximum);
current.MaxCombo = scoreInfo.MaxCombo;
if (scoreInfo.MaximumStatistics.Count > 0)
extractScoringValues(scoreInfo.MaximumStatistics, out _, out maximum);
}
/// <summary>
@ -591,7 +594,8 @@ namespace osu.Game.Rulesets.Scoring
if (result.IsBonus())
current.BonusScore += count * Judgement.ToNumericResult(result);
else
if (result.AffectsAccuracy())
{
// The maximum result of this judgement if it wasn't a miss.
// E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT).