2019-02-12 15:01:25 +08: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.Collections.Generic;
|
2019-05-29 17:25:25 +08:00
|
|
|
|
using System.Linq;
|
2019-02-12 15:01:25 +08:00
|
|
|
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|
|
|
|
using osu.Game.Rulesets.Difficulty.Utils;
|
2021-02-06 12:06:16 +08:00
|
|
|
|
using osu.Game.Rulesets.Mods;
|
2019-02-12 15:01:25 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Difficulty.Skills
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Used to processes strain values of <see cref="DifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
|
|
|
|
|
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public abstract class Skill
|
|
|
|
|
{
|
2019-02-18 14:00:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The peak strain for each <see cref="DifficultyCalculator.SectionLength"/> section of the beatmap.
|
|
|
|
|
/// </summary>
|
2019-05-29 17:25:25 +08:00
|
|
|
|
public IReadOnlyList<double> StrainPeaks => strainPeaks;
|
2019-02-18 14:00:32 +08:00
|
|
|
|
|
2019-02-12 15:01:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract double SkillMultiplier { get; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines how quickly strain decays for the given skill.
|
|
|
|
|
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract double StrainDecayBase { get; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The weight by which each strain value decays.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual double DecayWeight => 0.9;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DifficultyHitObject"/>s that were processed previously. They can affect the strain values of the following objects.
|
|
|
|
|
/// </summary>
|
2019-02-19 12:51:19 +08:00
|
|
|
|
protected readonly LimitedCapacityStack<DifficultyHitObject> Previous = new LimitedCapacityStack<DifficultyHitObject>(2); // Contained objects not used yet
|
2019-02-12 15:01:25 +08:00
|
|
|
|
|
2020-10-09 20:42:43 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The current strain level.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected double CurrentStrain { get; private set; } = 1;
|
|
|
|
|
|
2021-02-06 12:06:16 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Mods for use in skill calculations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected IReadOnlyList<Mod> Mods => mods;
|
|
|
|
|
|
2019-02-12 15:01:25 +08:00
|
|
|
|
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
|
2019-02-18 14:00:32 +08:00
|
|
|
|
|
2019-02-12 15:01:25 +08:00
|
|
|
|
private readonly List<double> strainPeaks = new List<double>();
|
|
|
|
|
|
2021-02-06 12:06:16 +08:00
|
|
|
|
private readonly Mod[] mods;
|
|
|
|
|
|
|
|
|
|
protected Skill(Mod[] mods)
|
|
|
|
|
{
|
|
|
|
|
this.mods = mods;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-12 15:01:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Process(DifficultyHitObject current)
|
|
|
|
|
{
|
2020-10-09 20:42:43 +08:00
|
|
|
|
CurrentStrain *= strainDecay(current.DeltaTime);
|
|
|
|
|
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
|
2019-02-12 15:01:25 +08:00
|
|
|
|
|
2020-10-09 20:42:43 +08:00
|
|
|
|
currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak);
|
2019-02-12 15:01:25 +08:00
|
|
|
|
|
|
|
|
|
Previous.Push(current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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()
|
|
|
|
|
{
|
|
|
|
|
if (Previous.Count > 0)
|
|
|
|
|
strainPeaks.Add(currentSectionPeak);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the initial strain level for a new section.
|
|
|
|
|
/// </summary>
|
2021-02-19 15:04:25 +08:00
|
|
|
|
/// <param name="time">The beginning of the new section in milliseconds, adjusted by clockrate.</param>
|
2020-10-09 20:42:43 +08:00
|
|
|
|
public void StartNewSectionFrom(double time)
|
2019-02-12 15:01:25 +08:00
|
|
|
|
{
|
|
|
|
|
// 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)
|
2020-10-09 20:42:43 +08:00
|
|
|
|
currentSectionPeak = GetPeakStrain(time);
|
2019-02-12 15:01:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 20:42:43 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the peak strain at a point in time.
|
|
|
|
|
/// </summary>
|
2021-02-19 15:04:25 +08:00
|
|
|
|
/// <param name="time">The time to retrieve the peak strain at, adjusted by clockrate.</param>
|
2020-10-09 20:42:43 +08:00
|
|
|
|
/// <returns>The peak strain.</returns>
|
2021-02-19 15:04:25 +08:00
|
|
|
|
protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
|
2020-10-09 20:42:43 +08:00
|
|
|
|
|
2019-02-12 15:01:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the calculated difficulty value representing all processed <see cref="DifficultyHitObject"/>s.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public double DifficultyValue()
|
|
|
|
|
{
|
|
|
|
|
double difficulty = 0;
|
|
|
|
|
double weight = 1;
|
|
|
|
|
|
|
|
|
|
// Difficulty is the weighted sum of the highest strains from every section.
|
2019-05-29 17:25:25 +08:00
|
|
|
|
// We're sorting from highest to lowest strain.
|
|
|
|
|
foreach (double strain in strainPeaks.OrderByDescending(d => d))
|
2019-02-12 15:01:25 +08:00
|
|
|
|
{
|
|
|
|
|
difficulty += strain * weight;
|
|
|
|
|
weight *= DecayWeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return difficulty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Calculates the strain value of a <see cref="DifficultyHitObject"/>. This value is affected by previously processed objects.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract double StrainValueOf(DifficultyHitObject current);
|
|
|
|
|
|
|
|
|
|
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
|
|
|
|
}
|
|
|
|
|
}
|