2023-06-12 17:33:22 +00:00
|
|
|
// 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.Linq;
|
|
|
|
using osu.Game.Beatmaps;
|
2023-06-19 12:38:13 +00:00
|
|
|
using osu.Game.Rulesets.Judgements;
|
2023-06-12 17:33:22 +00:00
|
|
|
using osu.Game.Rulesets.Objects;
|
|
|
|
using osu.Game.Rulesets.Objects.Types;
|
2023-06-19 12:38:13 +00:00
|
|
|
using osu.Game.Rulesets.Scoring;
|
2023-09-04 08:43:23 +00:00
|
|
|
using osu.Game.Rulesets.Scoring.Legacy;
|
2023-06-12 17:33:22 +00:00
|
|
|
using osu.Game.Rulesets.Taiko.Objects;
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Taiko.Difficulty
|
|
|
|
{
|
2023-07-04 08:32:54 +00:00
|
|
|
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
|
2023-06-12 17:33:22 +00:00
|
|
|
{
|
2023-06-19 12:38:13 +00:00
|
|
|
private int legacyBonusScore;
|
2023-09-04 08:43:23 +00:00
|
|
|
private int standardisedBonusScore;
|
2023-06-12 17:33:22 +00:00
|
|
|
private int combo;
|
|
|
|
|
2023-06-26 13:19:01 +00:00
|
|
|
private int difficultyPeppyStars;
|
|
|
|
private IBeatmap playableBeatmap = null!;
|
2023-06-12 17:33:22 +00:00
|
|
|
|
2023-09-04 08:43:23 +00:00
|
|
|
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
2023-06-12 17:33:22 +00:00
|
|
|
{
|
|
|
|
this.playableBeatmap = playableBeatmap;
|
|
|
|
|
2023-06-26 13:19:01 +00:00
|
|
|
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
|
|
|
|
2023-06-12 17:33:22 +00:00
|
|
|
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;
|
|
|
|
|
2023-06-23 15:58:45 +00:00
|
|
|
int drainLength = 0;
|
|
|
|
|
|
|
|
if (baseBeatmap.HitObjects.Count > 0)
|
|
|
|
{
|
|
|
|
int breakLength = baseBeatmap.Breaks.Select(b => (int)Math.Round(b.EndTime) - (int)Math.Round(b.StartTime)).Sum();
|
|
|
|
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
|
|
|
}
|
|
|
|
|
2023-06-12 17:33:22 +00:00
|
|
|
difficultyPeppyStars = (int)Math.Round(
|
|
|
|
(baseBeatmap.Difficulty.DrainRate
|
|
|
|
+ baseBeatmap.Difficulty.OverallDifficulty
|
|
|
|
+ baseBeatmap.Difficulty.CircleSize
|
2023-06-27 07:47:42 +00:00
|
|
|
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
2023-06-12 17:33:22 +00:00
|
|
|
|
2023-09-04 08:43:23 +00:00
|
|
|
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
foreach (var obj in playableBeatmap.HitObjects)
|
2023-09-04 08:43:23 +00:00
|
|
|
simulateHit(obj, ref attributes);
|
|
|
|
|
|
|
|
attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
|
|
|
|
|
|
|
|
return attributes;
|
2023-06-12 17:33:22 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 08:43:23 +00:00
|
|
|
private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attributes)
|
2023-06-12 17:33:22 +00:00
|
|
|
{
|
|
|
|
bool increaseCombo = true;
|
|
|
|
bool addScoreComboMultiplier = false;
|
2023-06-19 12:38:13 +00:00
|
|
|
|
2023-06-12 17:33:22 +00:00
|
|
|
bool isBonus = false;
|
2023-06-19 12:38:13 +00:00
|
|
|
HitResult bonusResult = HitResult.None;
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
int scoreIncrease = 0;
|
|
|
|
|
|
|
|
switch (hitObject)
|
|
|
|
{
|
|
|
|
case SwellTick:
|
|
|
|
scoreIncrease = 300;
|
|
|
|
increaseCombo = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DrumRollTick:
|
|
|
|
scoreIncrease = 300;
|
|
|
|
increaseCombo = false;
|
|
|
|
isBonus = true;
|
2023-06-19 12:38:13 +00:00
|
|
|
bonusResult = HitResult.SmallBonus;
|
2023-06-12 17:33:22 +00:00
|
|
|
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);
|
|
|
|
|
2023-09-04 08:43:23 +00:00
|
|
|
//
|
|
|
|
// Normally, this multiplier depends on the active mods (DT = 0.75, HT = 1.5). For simplicity, however, we'll only consider the worst case.
|
|
|
|
// This way, scores remain beatable at the cost of the conversion being slightly inaccurate.
|
|
|
|
// - A perfect DT/NM score will have less than 1M total score (excluding bonus).
|
|
|
|
// - A perfect HT score will have 1M total score (excluding bonus).
|
|
|
|
//
|
|
|
|
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 1.5f));
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
for (int i = 0; i <= halfSpinsRequiredForCompletion; i++)
|
2023-09-04 08:43:23 +00:00
|
|
|
simulateHit(new SwellTick(), ref attributes);
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
scoreIncrease = 300;
|
|
|
|
addScoreComboMultiplier = true;
|
|
|
|
increaseCombo = false;
|
|
|
|
isBonus = true;
|
2023-06-19 12:38:13 +00:00
|
|
|
bonusResult = HitResult.LargeBonus;
|
2023-06-12 17:33:22 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Hit:
|
|
|
|
scoreIncrease = 300;
|
|
|
|
addScoreComboMultiplier = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DrumRoll:
|
|
|
|
foreach (var nested in hitObject.NestedHitObjects)
|
2023-09-04 08:43:23 +00:00
|
|
|
simulateHit(nested, ref attributes);
|
2023-06-12 17:33:22 +00:00
|
|
|
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;
|
|
|
|
|
2023-09-04 08:43:23 +00:00
|
|
|
scoreIncrease += (int)(scoreIncrease / 35f * 2 * (difficultyPeppyStars + 1)) * (Math.Min(100, combo) / 10);
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
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)
|
2023-09-04 08:43:23 +00:00
|
|
|
attributes.ComboScore += comboScoreIncrease;
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
if (isBonus)
|
2023-06-19 12:38:13 +00:00
|
|
|
{
|
|
|
|
legacyBonusScore += scoreIncrease;
|
2023-09-04 08:43:23 +00:00
|
|
|
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
2023-06-19 12:38:13 +00:00
|
|
|
}
|
2023-06-12 17:33:22 +00:00
|
|
|
else
|
2023-09-04 08:43:23 +00:00
|
|
|
attributes.AccuracyScore += scoreIncrease;
|
2023-06-12 17:33:22 +00:00
|
|
|
|
|
|
|
if (increaseCombo)
|
|
|
|
combo++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|