Implement ScoreV1 calculation for OsuRuleset

This commit is contained in:
Dan Balasescu 2023-06-01 13:22:37 +09:00
parent 55a3847036
commit 02111e3854
2 changed files with 162 additions and 5 deletions

View File

@ -11,6 +11,8 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
@ -26,9 +28,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public override int Version => 20220902;
private readonly IWorkingBeatmap workingBeatmap;
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
workingBeatmap = beatmap;
}
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
@ -71,7 +76,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
);
double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
double starRating = basePerformance > 0.00001
? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4)
: 0;
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
double drainRate = beatmap.Difficulty.DrainRate;
@ -90,6 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
StarRating = starRating,
Mods = mods,
TotalScoreV1 = new OsuScoreV1Processor(workingBeatmap.Beatmap, beatmap, mods).TotalScore,
AimDifficulty = aimRating,
SpeedDifficulty = speedRating,
SpeedNoteCount = speedNotes,
@ -142,4 +150,143 @@ namespace osu.Game.Rulesets.Osu.Difficulty
new MultiMod(new OsuModFlashlight(), new OsuModHidden())
};
}
public abstract class ScoreV1Processor
{
protected readonly int DifficultyPeppyStars;
protected readonly double ScoreMultiplier;
protected readonly IBeatmap PlayableBeatmap;
protected ScoreV1Processor(IBeatmap baseBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
{
PlayableBeatmap = playableBeatmap;
int countNormal = 0;
int countSlider = 0;
int countSpinner = 0;
foreach (HitObject obj in baseBeatmap.HitObjects)
{
switch (obj)
{
case IHasPath:
countSlider++;
break;
case IHasDuration:
countSpinner++;
break;
default:
countNormal++;
break;
}
}
int objectCount = countNormal + countSlider + countSpinner;
DifficultyPeppyStars = (int)Math.Round(
(playableBeatmap.Difficulty.DrainRate
+ playableBeatmap.Difficulty.OverallDifficulty
+ playableBeatmap.Difficulty.CircleSize
+ Math.Clamp(objectCount / playableBeatmap.Difficulty.DrainRate * 8, 0, 16)) / 38 * 5);
ScoreMultiplier = 1 * DifficultyPeppyStars * mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
}
}
public class OsuScoreV1Processor : ScoreV1Processor
{
public int TotalScore { get; private set; }
private int combo;
public OsuScoreV1Processor(IBeatmap baseBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
: base(baseBeatmap, playableBeatmap, mods)
{
foreach (var obj in playableBeatmap.HitObjects)
increaseScore(obj);
}
private void increaseScore(HitObject hitObject)
{
bool increaseCombo = true;
bool addScoreComboMultiplier = false;
int scoreIncrease = 0;
switch (hitObject)
{
case SliderHeadCircle:
case SliderTailCircle:
case SliderRepeat:
scoreIncrease = 30;
break;
case SliderTick:
scoreIncrease = 10;
break;
case SpinnerBonusTick:
scoreIncrease = 1100;
increaseCombo = false;
break;
case SpinnerTick:
scoreIncrease = 100;
increaseCombo = false;
break;
case HitCircle:
scoreIncrease = 300;
addScoreComboMultiplier = true;
break;
case Slider:
foreach (var nested in hitObject.NestedHitObjects)
increaseScore(nested);
scoreIncrease = 300;
increaseCombo = false;
addScoreComboMultiplier = true;
break;
case Spinner spinner:
// The spinner object applies a lenience because gameplay mechanics differ from osu-stable.
// We'll redo the calculations to match osu-stable here...
const double maximum_rotations_per_second = 477.0 / 60;
double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(PlayableBeatmap.Difficulty.OverallDifficulty, 3, 5, 7.5);
double secondsDuration = spinner.Duration / 1000;
// The total amount of half spins possible for the entire spinner.
int totalHalfSpinsPossible = (int)(secondsDuration * maximum_rotations_per_second * 2);
// The amount of half spins that are required to successfully complete the spinner (i.e. get a 300).
int halfSpinsRequiredForCompletion = (int)(secondsDuration * minimumRotationsPerSecond);
// To be able to receive bonus points, the spinner must be rotated another 1.5 times.
int halfSpinsRequiredBeforeBonus = halfSpinsRequiredForCompletion + 3;
for (int i = 0; i <= totalHalfSpinsPossible; i++)
{
if (i > halfSpinsRequiredBeforeBonus && (i - halfSpinsRequiredBeforeBonus) % 2 == 0)
increaseScore(new SpinnerBonusTick());
else if (i > 1 && i % 2 == 0)
increaseScore(new SpinnerTick());
}
scoreIncrease = 300;
addScoreComboMultiplier = true;
break;
}
if (addScoreComboMultiplier)
{
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
scoreIncrease += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * ScoreMultiplier));
}
if (increaseCombo)
combo++;
TotalScore += scoreIncrease;
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
@ -27,6 +26,7 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_FLASHLIGHT = 17;
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21;
protected const int ATTRIB_ID_TOTAL_SCORE_V1 = 23;
/// <summary>
/// The mods which were applied to the beatmap.
@ -36,15 +36,21 @@ namespace osu.Game.Rulesets.Difficulty
/// <summary>
/// The combined star rating of all skills.
/// </summary>
[JsonProperty("star_rating", Order = -3)]
[JsonProperty("star_rating", Order = -4)]
public double StarRating { get; set; }
/// <summary>
/// The maximum achievable combo.
/// </summary>
[JsonProperty("max_combo", Order = -2)]
[JsonProperty("max_combo", Order = -3)]
public int MaxCombo { get; set; }
/// <summary>
/// The total score achievable in ScoreV1.
/// </summary>
[JsonProperty("total_score_v1", Order = -2)]
public int TotalScoreV1 { get; set; }
/// <summary>
/// Creates new <see cref="DifficultyAttributes"/>.
/// </summary>
@ -69,7 +75,10 @@ namespace osu.Game.Rulesets.Difficulty
/// <remarks>
/// See: osu_difficulty_attribs table.
/// </remarks>
public virtual IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() => Enumerable.Empty<(int, object)>();
public virtual IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
yield return (ATTRIB_ID_TOTAL_SCORE_V1, TotalScoreV1);
}
/// <summary>
/// Reads osu-web database attribute mappings into this <see cref="DifficultyAttributes"/> object.
@ -78,6 +87,7 @@ namespace osu.Game.Rulesets.Difficulty
/// <param name="onlineInfo">The <see cref="IBeatmapOnlineInfo"/> where more information about the beatmap may be extracted from (such as AR/CS/OD/etc).</param>
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
TotalScoreV1 = (int)values[ATTRIB_ID_TOTAL_SCORE_V1];
}
}
}