1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 21:27:24 +08:00

Refactor to encapsulate strain logic into Skill class

As strains are an implementation detail of the current Skill calculations, it makes sense that strain related logic should be encapsulated within the Skill class.
This commit is contained in:
Samuel Cattini-Schultz 2021-04-03 20:47:39 +11:00
parent eb1e850f99
commit 5b2dcea8a8
7 changed files with 49 additions and 51 deletions

View File

@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
private const double star_scaling_factor = 0.153;
protected override int SectionLength => 750;
private float halfCatcherWidth;
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)

View File

@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
protected override double DecayWeight => 0.94;
protected override int SectionLength => 750;
protected readonly float HalfCatcherWidth;
private float? lastPlayerPosition;

View File

@ -7,7 +7,6 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
var endTime = maniaCurrent.BaseObject.GetEndTime();
var endTime = maniaCurrent.EndTime;
var column = maniaCurrent.BaseObject.Column;
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
for (int i = 0; i < holdEndTimes.Length; ++i)
{
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1))
if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1))
holdAddition = 1.0;
// ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1

View File

@ -133,11 +133,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
List<double> peaks = new List<double>();
for (int i = 0; i < colour.StrainPeaks.Count; i++)
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
var staminaRightPeaks = staminaRight.GetCurrentStrainPeaks().ToList();
var staminaLeftPeaks = staminaLeft.GetCurrentStrainPeaks().ToList();
for (int i = 0; i < colourPeaks.Count; i++)
{
double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier;
double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier;
double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty;
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
double staminaPeak = (staminaRightPeaks[i] + staminaLeftPeaks[i]) * stamina_skill_multiplier * staminaPenalty;
peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak));
}

View File

@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Difficulty
{
public abstract class DifficultyCalculator
{
/// <summary>
/// The length of each strain section.
/// </summary>
protected virtual int SectionLength => 400;
private readonly Ruleset ruleset;
private readonly WorkingBeatmap beatmap;
@ -71,32 +66,14 @@ namespace osu.Game.Rulesets.Difficulty
var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList();
double sectionLength = SectionLength * clockRate;
// The first object doesn't generate a strain, so we begin with an incremented section end
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
foreach (DifficultyHitObject h in difficultyHitObjects)
foreach (var hitObject in difficultyHitObjects)
{
while (h.BaseObject.StartTime > currentSectionEnd)
foreach (var skill in skills)
{
foreach (Skill s in skills)
{
s.SaveCurrentPeak();
s.StartNewSectionFrom(currentSectionEnd / clockRate);
}
currentSectionEnd += sectionLength;
skill.Process(hitObject);
}
foreach (Skill s in skills)
s.Process(h);
}
// The peak strain will not be saved for the last section in the above loop
foreach (Skill s in skills)
s.SaveCurrentPeak();
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
public readonly HitObject LastObject;
/// <summary>
/// Amount of time elapsed between <see cref="BaseObject"/> and <see cref="LastObject"/>.
/// Amount of time elapsed between <see cref="BaseObject"/> and <see cref="LastObject"/>, adjusted by clockrate.
/// </summary>
public readonly double DeltaTime;

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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;
@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary>
public abstract class Skill
{
/// <summary>
/// The peak strain for each <see cref="DifficultyCalculator.SectionLength"/> section of the beatmap.
/// </summary>
public IReadOnlyList<double> StrainPeaks => strainPeaks;
/// <summary>
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
/// </summary>
@ -47,6 +42,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary>
protected double CurrentStrain { get; private set; } = 1;
/// <summary>
/// The length of each strain section.
/// </summary>
protected virtual int SectionLength => 400;
/// <summary>
/// Mods for use in skill calculations.
/// </summary>
@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Difficulty.Skills
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
private double currentSectionEnd;
private readonly List<double> strainPeaks = new List<double>();
private readonly Mod[] mods;
@ -68,6 +70,17 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary>
public void Process(DifficultyHitObject current)
{
// The first object doesn't generate a strain, so we begin with an incremented section end
if (Previous.Count == 0)
currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
while (current.StartTime > currentSectionEnd)
{
saveCurrentPeak();
startNewSectionFrom(currentSectionEnd);
currentSectionEnd += SectionLength;
}
CurrentStrain *= strainDecay(current.DeltaTime);
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
@ -79,22 +92,20 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// <summary>
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
/// </summary>
public void SaveCurrentPeak()
private void saveCurrentPeak()
{
if (Previous.Count > 0)
strainPeaks.Add(currentSectionPeak);
strainPeaks.Add(currentSectionPeak);
}
/// <summary>
/// Sets the initial strain level for a new section.
/// </summary>
/// <param name="time">The beginning of the new section in milliseconds, adjusted by clockrate.</param>
public void StartNewSectionFrom(double time)
/// <param name="time">The beginning of the new section in milliseconds.</param>
private void startNewSectionFrom(double time)
{
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
if (Previous.Count > 0)
currentSectionPeak = GetPeakStrain(time);
currentSectionPeak = GetPeakStrain(time);
}
/// <summary>
@ -105,7 +116,13 @@ namespace osu.Game.Rulesets.Difficulty.Skills
protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
/// <summary>
/// Returns the calculated difficulty value representing all processed <see cref="DifficultyHitObject"/>s.
/// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap,
/// including the peak of the current section.
/// </summary>
public IEnumerable<double> GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak);
/// <summary>
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
/// </summary>
public double DifficultyValue()
{
@ -114,7 +131,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
// Difficulty is the weighted sum of the highest strains from every section.
// We're sorting from highest to lowest strain.
foreach (double strain in strainPeaks.OrderByDescending(d => d))
foreach (double strain in GetCurrentStrainPeaks().OrderByDescending(d => d))
{
difficulty += strain * weight;
weight *= DecayWeight;