1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 15:07:52 +08:00

Add ScoreV1 calculation for TaikoRuleset

This commit is contained in:
Dan Balasescu 2023-06-13 02:33:22 +09:00
parent b9f485b551
commit aa644832dc
5 changed files with 213 additions and 2 deletions

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addFlyingHit(HitType hitType) private void addFlyingHit(HitType hitType)
{ {
var tick = new DrumRollTick { HitWindows = HitWindows.Empty, StartTime = DrawableRuleset.Playfield.Time.Current }; var tick = new DrumRollTick(null) { HitWindows = HitWindows.Empty, StartTime = DrawableRuleset.Playfield.Time.Current };
DrawableDrumRollTick h; DrawableDrumRollTick h;
DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType });

View File

@ -27,9 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override int Version => 20220902; public override int Version => 20220902;
private readonly IWorkingBeatmap workingBeatmap;
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
workingBeatmap = beatmap;
} }
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
@ -86,6 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
HitWindows hitWindows = new TaikoHitWindows(); HitWindows hitWindows = new TaikoHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
TaikoScoreV1Processor sv1Processor = new TaikoScoreV1Processor(workingBeatmap.Beatmap, beatmap, mods);
return new TaikoDifficultyAttributes return new TaikoDifficultyAttributes
{ {
StarRating = starRating, StarRating = starRating,
@ -96,6 +101,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
PeakDifficulty = combinedRating, PeakDifficulty = combinedRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit), MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
LegacyTotalScore = sv1Processor.TotalScore,
LegacyComboScore = sv1Processor.ComboScore,
LegacyBonusScore = sv1Processor.BonusScore
}; };
} }

View File

@ -0,0 +1,196 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
internal class TaikoScoreV1Processor
{
public int TotalScore => BaseScore + ComboScore + BonusScore;
/// <summary>
/// Amount of score that is combo-and-difficulty-multiplied, excluding mod multipliers.
/// </summary>
public int ComboScore { get; private set; }
/// <summary>
/// Amount of score that is NOT combo-and-difficulty-multiplied.
/// </summary>
public int BaseScore { get; private set; }
/// <summary>
/// Amount of score whose judgements would be treated as "bonus" in ScoreV2.
/// </summary>
public int BonusScore { get; private set; }
private int combo;
private readonly double modMultiplier;
private readonly int difficultyPeppyStars;
private readonly IBeatmap playableBeatmap;
private readonly IReadOnlyList<Mod> mods;
public TaikoScoreV1Processor(IBeatmap baseBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
{
this.playableBeatmap = playableBeatmap;
this.mods = mods;
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(
(baseBeatmap.Difficulty.DrainRate
+ baseBeatmap.Difficulty.OverallDifficulty
+ baseBeatmap.Difficulty.CircleSize
+ Math.Clamp(objectCount / baseBeatmap.Difficulty.DrainRate * 8, 0, 16)) / 38 * 5);
modMultiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
foreach (var obj in playableBeatmap.HitObjects)
simulateHit(obj);
}
private void simulateHit(HitObject hitObject)
{
bool increaseCombo = true;
bool addScoreComboMultiplier = false;
bool isBonus = false;
int scoreIncrease = 0;
switch (hitObject)
{
case SwellTick:
scoreIncrease = 300;
increaseCombo = false;
break;
case DrumRollTick:
scoreIncrease = 300;
increaseCombo = false;
isBonus = true;
break;
case Swell swell:
// The taiko swell generally does not match the osu-stable implementation in any way.
// We'll redo the calculations to match osu-stable here...
double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(playableBeatmap.Difficulty.OverallDifficulty, 3, 5, 7.5);
double secondsDuration = swell.Duration / 1000;
// The amount of half spins that are required to successfully complete the spinner (i.e. get a 300).
int halfSpinsRequiredForCompletion = (int)(secondsDuration * minimumRotationsPerSecond);
halfSpinsRequiredForCompletion = (int)Math.Max(1, halfSpinsRequiredForCompletion * 1.65f);
if (mods.Any(m => m is ModDoubleTime))
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 0.75f));
if (mods.Any(m => m is ModHalfTime))
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 1.5f));
for (int i = 0; i <= halfSpinsRequiredForCompletion; i++)
simulateHit(new SwellTick());
scoreIncrease = 300;
addScoreComboMultiplier = true;
increaseCombo = false;
isBonus = true;
break;
case Hit:
scoreIncrease = 300;
addScoreComboMultiplier = true;
break;
case DrumRoll:
foreach (var nested in hitObject.NestedHitObjects)
simulateHit(nested);
return;
}
if (hitObject is DrumRollTick tick)
{
if (playableBeatmap.ControlPointInfo.EffectPointAt(tick.Parent.StartTime).KiaiMode)
scoreIncrease = (int)(scoreIncrease * 1.2f);
if (tick.IsStrong)
scoreIncrease += scoreIncrease / 5;
}
// The score increase directly contributed to by the combo-multiplied portion.
int comboScoreIncrease = 0;
if (addScoreComboMultiplier)
{
int oldScoreIncrease = scoreIncrease;
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
scoreIncrease += (int)(scoreIncrease / 35 * 2 * (difficultyPeppyStars + 1) * modMultiplier) * (Math.Min(100, combo) / 10);
if (hitObject is Swell)
{
if (playableBeatmap.ControlPointInfo.EffectPointAt(hitObject.GetEndTime()).KiaiMode)
scoreIncrease = (int)(scoreIncrease * 1.2f);
}
else
{
if (playableBeatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
scoreIncrease = (int)(scoreIncrease * 1.2f);
}
comboScoreIncrease = scoreIncrease - oldScoreIncrease;
}
if (hitObject is Swell || (hitObject is TaikoStrongableHitObject strongable && strongable.IsStrong))
{
scoreIncrease *= 2;
comboScoreIncrease *= 2;
}
scoreIncrease -= comboScoreIncrease;
if (addScoreComboMultiplier)
ComboScore += comboScoreIncrease;
if (isBonus)
BonusScore += scoreIncrease;
else
BaseScore += scoreIncrease;
if (increaseCombo)
combo++;
if (hitObject is Swell)
{
}
}
}
}

View File

@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
AddNested(new DrumRollTick AddNested(new DrumRollTick(this)
{ {
FirstTick = first, FirstTick = first,
TickSpacing = tickSpacing, TickSpacing = tickSpacing,

View File

@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
public class DrumRollTick : TaikoStrongableHitObject public class DrumRollTick : TaikoStrongableHitObject
{ {
public readonly DrumRoll Parent;
/// <summary> /// <summary>
/// Whether this is the first (initial) tick of the slider. /// Whether this is the first (initial) tick of the slider.
/// </summary> /// </summary>
@ -27,6 +29,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary> /// </summary>
public double HitWindow => TickSpacing / 2; public double HitWindow => TickSpacing / 2;
public DrumRollTick(DrumRoll parent)
{
Parent = parent;
}
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;