mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +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>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
@ -45,7 +49,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
HitType = currentHit?.Type;
|
||||
|
||||
// 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>
|
||||
|
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static TaikoDifficultyHitObjectColour GetInstanceFor(
|
||||
TaikoDifficultyHitObject hitObject, TaikoDifficultyHitObject lastObject)
|
||||
public static TaikoDifficultyHitObjectColour GetInstanceFor(TaikoDifficultyHitObject hitObject)
|
||||
{
|
||||
TaikoDifficultyHitObject lastObject = (TaikoDifficultyHitObject) hitObject.Previous(0);
|
||||
TaikoDifficultyHitObjectColour previous = lastObject?.Colour;
|
||||
bool delta = lastObject == null || hitObject.HitType != lastObject.HitType;
|
||||
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.Utils;
|
||||
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;
|
||||
|
||||
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 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)
|
||||
: base(mods)
|
||||
{
|
||||
@ -47,95 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
// changing from/to a drum roll or a swell does not constitute a colour change.
|
||||
// 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);
|
||||
return ColourEvaluator.EvaluateDifficultyOf(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
// 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 osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
@ -11,6 +13,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class SingleKeyStamina
|
||||
{
|
||||
private const double StrainDecayBase = 0.4;
|
||||
|
||||
private double CurrentStrain = 0;
|
||||
|
||||
private double? previousHitTime;
|
||||
|
||||
/// <summary>
|
||||
@ -24,19 +30,23 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
return 0;
|
||||
}
|
||||
|
||||
double objectStrain = 0.5;
|
||||
objectStrain += speedBonus(current.StartTime - previousHitTime.Value);
|
||||
// CurrentStrain += strainDecay(current.StartTime - current.Previous(0).StartTime);
|
||||
// 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;
|
||||
return objectStrain;
|
||||
return CurrentStrain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a speed bonus dependent on the time since the last hit performed using this key.
|
||||
/// </summary>
|
||||
/// <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.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
@ -17,8 +18,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
/// </remarks>
|
||||
public class Stamina : StrainDecaySkill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
protected override double SkillMultiplier => 3.6;
|
||||
protected override double StrainDecayBase => 0;
|
||||
|
||||
private readonly SingleKeyStamina[] centreKeyStamina =
|
||||
{
|
||||
@ -76,7 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
private const double rhythm_skill_multiplier = 0.014;
|
||||
private const double colour_skill_multiplier = 0.01;
|
||||
private const double rhythm_skill_multiplier = 0.017;
|
||||
private const double colour_skill_multiplier = 0.028;
|
||||
private const double stamina_skill_multiplier = 0.021;
|
||||
|
||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
// Find repetition interval for the final TaikoDifficultyHitObjectColour
|
||||
// TODO: Might be a good idea to refactor this
|
||||
((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour.FindRepetitionInterval();
|
||||
((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour?.FindRepetitionInterval();
|
||||
return difficultyHitObject;
|
||||
}
|
||||
|
||||
@ -76,18 +76,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
|
||||
|
||||
double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
|
||||
staminaRating *= staminaPenalty;
|
||||
// double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
|
||||
// staminaRating *= staminaPenalty;
|
||||
|
||||
//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)
|
||||
{
|
||||
staminaPenalty *= 0.25;
|
||||
}
|
||||
// if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05)
|
||||
// {
|
||||
// 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 starRating = 1.4 * separatedRating + 0.5 * combinedRating;
|
||||
double starRating = 1.9 * combinedRating;
|
||||
starRating = rescale(starRating);
|
||||
|
||||
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.
|
||||
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
||||
/// </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>();
|
||||
|
||||
@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
double colourPeak = colourPeaks[i] * colour_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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user