mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 20:22:55 +08:00
[WIP] Colour rework
This commit is contained in:
parent
56a4034c22
commit
3dd0c4aec8
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||||
|
{
|
||||||
|
public class ColourEvaluator
|
||||||
|
{
|
||||||
|
private static double sigmoid(double val, double center, double width)
|
||||||
|
{
|
||||||
|
return Math.Tanh(Math.E * -(val - center) / width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
TaikoDifficultyHitObject taikoCurrent = (TaikoDifficultyHitObject)current;
|
||||||
|
TaikoDifficultyHitObjectColour colour = taikoCurrent.Colour;
|
||||||
|
if (colour == null) return 0;
|
||||||
|
double objectStrain = 1;
|
||||||
|
if (colour.Delta)
|
||||||
|
{
|
||||||
|
objectStrain /= Math.Pow(colour.DeltaRunLength, 0.25);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
objectStrain *= sigmoid(colour.DeltaRunLength, 3, 3) * 0.3 + 0.3;
|
||||||
|
}
|
||||||
|
objectStrain *= -sigmoid(colour.RepetitionInterval, 8, 8);
|
||||||
|
// Console.WriteLine($"{current.StartTime},{colour.GetHashCode()},{colour.Delta},{colour.DeltaRunLength},{colour.RepetitionInterval},{objectStrain}");
|
||||||
|
return objectStrain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
|
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Colour data for this hit object. This is used by colour evaluator to calculate colour, but can be used
|
||||||
|
/// differently by other skills in the future.
|
||||||
|
/// </summary>
|
||||||
public readonly TaikoDifficultyHitObjectColour Colour;
|
public readonly TaikoDifficultyHitObjectColour Colour;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -45,7 +49,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
HitType = currentHit?.Type;
|
HitType = currentHit?.Type;
|
||||||
|
|
||||||
// Need to be done after HitType is set.
|
// Need to be done after HitType is set.
|
||||||
Colour = TaikoDifficultyHitObjectColour.GetInstanceFor(this, (TaikoDifficultyHitObject) objects.LastOrDefault());
|
if (HitType != null)
|
||||||
|
{
|
||||||
|
// Get previous hit object, while skipping one that does not have defined colour (sliders and spinners).
|
||||||
|
// Without skipping through these, sliders and spinners would have contributed to a colour change for the next note.
|
||||||
|
TaikoDifficultyHitObject previousHitObject = (TaikoDifficultyHitObject)objects.LastOrDefault();
|
||||||
|
while (previousHitObject != null && previousHitObject.Colour == null)
|
||||||
|
{
|
||||||
|
previousHitObject = (TaikoDifficultyHitObject)previousHitObject.Previous(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Colour = TaikoDifficultyHitObjectColour.GetInstanceFor(this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// as a static function instead of constructor to allow for reusing existing instances.
|
/// as a static function instead of constructor to allow for reusing existing instances.
|
||||||
/// TODO: findRepetitionInterval needs to be called a final time after all hitObjects have been processed.
|
/// TODO: findRepetitionInterval needs to be called a final time after all hitObjects have been processed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static TaikoDifficultyHitObjectColour GetInstanceFor(
|
public static TaikoDifficultyHitObjectColour GetInstanceFor(TaikoDifficultyHitObject hitObject)
|
||||||
TaikoDifficultyHitObject hitObject, TaikoDifficultyHitObject lastObject)
|
|
||||||
{
|
{
|
||||||
|
TaikoDifficultyHitObject lastObject = (TaikoDifficultyHitObject) hitObject.Previous(0);
|
||||||
TaikoDifficultyHitObjectColour previous = lastObject?.Colour;
|
TaikoDifficultyHitObjectColour previous = lastObject?.Colour;
|
||||||
bool delta = lastObject == null || hitObject.HitType != lastObject.HitType;
|
bool delta = lastObject == null || hitObject.HitType != lastObject.HitType;
|
||||||
if (previous != null && delta == previous.Delta)
|
if (previous != null && delta == previous.Delta)
|
||||||
|
@ -6,7 +6,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Difficulty.Utils;
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||||
@ -19,27 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
protected override double StrainDecayBase => 0.4;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum number of entries to keep in <see cref="monoHistory"/>.
|
|
||||||
/// </summary>
|
|
||||||
private const int mono_history_max_length = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue with the lengths of the last <see cref="mono_history_max_length"/> most recent mono (single-colour) patterns,
|
|
||||||
/// with the most recent value at the end of the queue.
|
|
||||||
/// </summary>
|
|
||||||
private readonly LimitedCapacityQueue<int> monoHistory = new LimitedCapacityQueue<int>(mono_history_max_length);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="HitType"/> of the last object hit before the one being considered.
|
|
||||||
/// </summary>
|
|
||||||
private HitType? previousHitType;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Length of the current mono pattern.
|
|
||||||
/// </summary>
|
|
||||||
private int currentMonoLength;
|
|
||||||
|
|
||||||
public Colour(Mod[] mods)
|
public Colour(Mod[] mods)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
@ -47,95 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
// changing from/to a drum roll or a swell does not constitute a colour change.
|
return ColourEvaluator.EvaluateDifficultyOf(current);
|
||||||
// hits spaced more than a second apart are also exempt from colour strain.
|
}
|
||||||
if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000))
|
|
||||||
{
|
|
||||||
monoHistory.Clear();
|
|
||||||
|
|
||||||
var currentHit = current.BaseObject as Hit;
|
|
||||||
currentMonoLength = currentHit != null ? 1 : 0;
|
|
||||||
previousHitType = currentHit?.Type;
|
|
||||||
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var taikoCurrent = (TaikoDifficultyHitObject)current;
|
|
||||||
|
|
||||||
double objectStrain = 0.0;
|
|
||||||
|
|
||||||
if (previousHitType != null && taikoCurrent.HitType != previousHitType)
|
|
||||||
{
|
|
||||||
// The colour has changed.
|
|
||||||
objectStrain = 1.0;
|
|
||||||
|
|
||||||
if (monoHistory.Count < 2)
|
|
||||||
{
|
|
||||||
// There needs to be at least two streaks to determine a strain.
|
|
||||||
objectStrain = 0.0;
|
|
||||||
}
|
|
||||||
else if ((monoHistory[^1] + currentMonoLength) % 2 == 0)
|
|
||||||
{
|
|
||||||
// The last streak in the history is guaranteed to be a different type to the current streak.
|
|
||||||
// If the total number of notes in the two streaks is even, nullify this object's strain.
|
|
||||||
objectStrain = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
objectStrain *= repetitionPenalties();
|
|
||||||
currentMonoLength = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentMonoLength += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
previousHitType = taikoCurrent.HitType;
|
|
||||||
return objectStrain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The penalty to apply due to the length of repetition in colour streaks.
|
|
||||||
/// </summary>
|
|
||||||
private double repetitionPenalties()
|
|
||||||
{
|
|
||||||
const int most_recent_patterns_to_compare = 2;
|
|
||||||
double penalty = 1.0;
|
|
||||||
|
|
||||||
monoHistory.Enqueue(currentMonoLength);
|
|
||||||
|
|
||||||
for (int start = monoHistory.Count - most_recent_patterns_to_compare - 1; start >= 0; start--)
|
|
||||||
{
|
|
||||||
if (!isSamePattern(start, most_recent_patterns_to_compare))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int notesSince = 0;
|
|
||||||
for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i];
|
|
||||||
penalty *= repetitionPenalty(notesSince);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return penalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the last <paramref name="mostRecentPatternsToCompare"/> patterns have repeated in the history
|
|
||||||
/// of single-colour note sequences, starting from <paramref name="start"/>.
|
|
||||||
/// </summary>
|
|
||||||
private bool isSamePattern(int start, int mostRecentPatternsToCompare)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < mostRecentPatternsToCompare; i++)
|
|
||||||
{
|
|
||||||
if (monoHistory[start + i] != monoHistory[monoHistory.Count - mostRecentPatternsToCompare + i])
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the strain penalty for a colour pattern repetition.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="notesSince">The number of notes since the last repetition of the pattern.</param>
|
|
||||||
private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||||
{
|
{
|
||||||
@ -11,6 +13,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SingleKeyStamina
|
public class SingleKeyStamina
|
||||||
{
|
{
|
||||||
|
private const double StrainDecayBase = 0.4;
|
||||||
|
|
||||||
|
private double CurrentStrain = 0;
|
||||||
|
|
||||||
private double? previousHitTime;
|
private double? previousHitTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -24,19 +30,23 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double objectStrain = 0.5;
|
// CurrentStrain += strainDecay(current.StartTime - current.Previous(0).StartTime);
|
||||||
objectStrain += speedBonus(current.StartTime - previousHitTime.Value);
|
// CurrentStrain += 0.5 + 0.5 * strainDecay(current.StartTime - current.Previous(0).StartTime);
|
||||||
|
CurrentStrain += 1;
|
||||||
|
CurrentStrain *= ColourEvaluator.EvaluateDifficultyOf(current) * 0.1 + 0.9;
|
||||||
|
CurrentStrain *= strainDecay(current.StartTime - previousHitTime.Value);
|
||||||
previousHitTime = current.StartTime;
|
previousHitTime = current.StartTime;
|
||||||
return objectStrain;
|
return CurrentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies a speed bonus dependent on the time since the last hit performed using this key.
|
/// Applies a speed bonus dependent on the time since the last hit performed using this key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="notePairDuration">The duration between the current and previous note hit using the same key.</param>
|
/// <param name="notePairDuration">The duration between the current and previous note hit using the same key.</param>
|
||||||
private double speedBonus(double notePairDuration)
|
private double strainDecay(double notePairDuration)
|
||||||
{
|
{
|
||||||
return 175 / (notePairDuration + 100);
|
return Math.Pow(StrainDecayBase, notePairDuration / 1000);
|
||||||
|
// return 175 / (notePairDuration + 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||||
@ -17,8 +18,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class Stamina : StrainDecaySkill
|
public class Stamina : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 3.6;
|
||||||
protected override double StrainDecayBase => 0.4;
|
protected override double StrainDecayBase => 0;
|
||||||
|
|
||||||
private readonly SingleKeyStamina[] centreKeyStamina =
|
private readonly SingleKeyStamina[] centreKeyStamina =
|
||||||
{
|
{
|
||||||
@ -76,7 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
}
|
}
|
||||||
|
|
||||||
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
||||||
return getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject);
|
double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject);
|
||||||
|
// objectStrain *= ColourEvaluator.EvaluateDifficultyOf(current) * 0.3 + 0.7;
|
||||||
|
return objectStrain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
public class TaikoDifficultyCalculator : DifficultyCalculator
|
public class TaikoDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const double rhythm_skill_multiplier = 0.014;
|
private const double rhythm_skill_multiplier = 0.017;
|
||||||
private const double colour_skill_multiplier = 0.01;
|
private const double colour_skill_multiplier = 0.028;
|
||||||
private const double stamina_skill_multiplier = 0.021;
|
private const double stamina_skill_multiplier = 0.021;
|
||||||
|
|
||||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
// Find repetition interval for the final TaikoDifficultyHitObjectColour
|
// Find repetition interval for the final TaikoDifficultyHitObjectColour
|
||||||
// TODO: Might be a good idea to refactor this
|
// TODO: Might be a good idea to refactor this
|
||||||
((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour.FindRepetitionInterval();
|
((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour?.FindRepetitionInterval();
|
||||||
return difficultyHitObject;
|
return difficultyHitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,18 +76,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||||
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
|
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
|
||||||
|
|
||||||
double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
|
// double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
|
||||||
staminaRating *= staminaPenalty;
|
// staminaRating *= staminaPenalty;
|
||||||
|
|
||||||
//TODO : This is a temporary fix for the stamina rating of converts, due to their low colour variance.
|
//TODO : This is a temporary fix for the stamina rating of converts, due to their low colour variance.
|
||||||
if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05)
|
// if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05)
|
||||||
{
|
// {
|
||||||
staminaPenalty *= 0.25;
|
// staminaPenalty *= 0.25;
|
||||||
}
|
// }
|
||||||
|
|
||||||
double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty);
|
double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina);
|
||||||
double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating);
|
double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating);
|
||||||
double starRating = 1.4 * separatedRating + 0.5 * combinedRating;
|
double starRating = 1.9 * combinedRating;
|
||||||
starRating = rescale(starRating);
|
starRating = rescale(starRating);
|
||||||
|
|
||||||
HitWindows hitWindows = new TaikoHitWindows();
|
HitWindows hitWindows = new TaikoHitWindows();
|
||||||
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
||||||
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina, double staminaPenalty)
|
private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina)
|
||||||
{
|
{
|
||||||
List<double> peaks = new List<double>();
|
List<double> peaks = new List<double>();
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
||||||
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
||||||
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * staminaPenalty;
|
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;
|
||||||
|
|
||||||
double peak = norm(2, colourPeak, rhythmPeak, staminaPeak);
|
double peak = norm(2, colourPeak, rhythmPeak, staminaPeak);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user