mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Update with latest changes
This commit is contained in:
parent
5852a37eb7
commit
68027fcc2c
@ -14,25 +14,26 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
|
||||
public void FindCheese(List<TaikoDifficultyHitObject> difficultyHitObjects)
|
||||
{
|
||||
this.hitObjects = difficultyHitObjects;
|
||||
hitObjects = difficultyHitObjects;
|
||||
findRolls(3);
|
||||
findRolls(4);
|
||||
findTLTap(0, true);
|
||||
findTLTap(1, true);
|
||||
findTLTap(0, false);
|
||||
findTLTap(1, false);
|
||||
findTlTap(0, true);
|
||||
findTlTap(1, true);
|
||||
findTlTap(0, false);
|
||||
findTlTap(1, false);
|
||||
}
|
||||
|
||||
private void findRolls(int patternLength)
|
||||
{
|
||||
List<TaikoDifficultyHitObject> history = new List<TaikoDifficultyHitObject>();
|
||||
|
||||
int repititionStart = 0;
|
||||
int repetitionStart = 0;
|
||||
|
||||
for (int i = 0; i < hitObjects.Count; i++)
|
||||
{
|
||||
history.Add(hitObjects[i]);
|
||||
if (history.Count < 2 * patternLength) continue;
|
||||
|
||||
if (history.Count > 2 * patternLength) history.RemoveAt(0);
|
||||
|
||||
bool isRepeat = true;
|
||||
@ -47,43 +48,41 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
|
||||
if (!isRepeat)
|
||||
{
|
||||
repititionStart = i - 2 * patternLength;
|
||||
repetitionStart = i - 2 * patternLength;
|
||||
}
|
||||
|
||||
int repeatedLength = i - repititionStart;
|
||||
int repeatedLength = i - repetitionStart;
|
||||
|
||||
if (repeatedLength >= roll_min_repetitions)
|
||||
{
|
||||
// Console.WriteLine("Found Roll Cheese.\tStart: " + repititionStart + "\tEnd: " + i);
|
||||
for (int j = repititionStart; j < i; j++)
|
||||
for (int j = repetitionStart; j < i; j++)
|
||||
{
|
||||
(hitObjects[i]).StaminaCheese = true;
|
||||
hitObjects[i].StaminaCheese = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findTLTap(int parity, bool kat)
|
||||
private void findTlTap(int parity, bool kat)
|
||||
{
|
||||
int tl_length = -2;
|
||||
int tlLength = -2;
|
||||
|
||||
for (int i = parity; i < hitObjects.Count; i += 2)
|
||||
{
|
||||
if (kat == hitObjects[i].IsKat)
|
||||
{
|
||||
tl_length += 2;
|
||||
tlLength += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
tl_length = -2;
|
||||
tlLength = -2;
|
||||
}
|
||||
|
||||
if (tl_length >= tl_min_repetitions)
|
||||
if (tlLength >= tl_min_repetitions)
|
||||
{
|
||||
// Console.WriteLine("Found TL Cheese.\tStart: " + (i - tl_length) + "\tEnd: " + i);
|
||||
for (int j = i - tl_length; j < i; j++)
|
||||
for (int j = i - tlLength; j < i; j++)
|
||||
{
|
||||
(hitObjects[i]).StaminaCheese = true;
|
||||
hitObjects[i].StaminaCheese = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@ -9,38 +12,31 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
public class TaikoDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
public readonly bool HasTypeChange;
|
||||
public readonly bool HasTimingChange;
|
||||
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
|
||||
public readonly bool IsKat;
|
||||
|
||||
public bool StaminaCheese = false;
|
||||
|
||||
public readonly int RhythmID;
|
||||
|
||||
public readonly double NoteLength;
|
||||
|
||||
public readonly int n;
|
||||
private int counter = 0;
|
||||
public readonly int N;
|
||||
|
||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, TaikoDifficultyHitObjectRhythm rhythm)
|
||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int n, IEnumerable<TaikoDifficultyHitObjectRhythm> commonRhythms)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
var lastHit = lastObject as Hit;
|
||||
var currentHit = hitObject as Hit;
|
||||
|
||||
NoteLength = DeltaTime;
|
||||
double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate;
|
||||
Rhythm = rhythm.GetClosest(NoteLength / prevLength);
|
||||
RhythmID = Rhythm.ID;
|
||||
HasTypeChange = lastHit?.Type != currentHit?.Type;
|
||||
IsKat = lastHit?.Type == HitType.Rim;
|
||||
HasTimingChange = !rhythm.IsRepeat(RhythmID);
|
||||
Rhythm = getClosestRhythm(NoteLength / prevLength, commonRhythms);
|
||||
IsKat = currentHit?.Type == HitType.Rim;
|
||||
|
||||
n = counter;
|
||||
counter++;
|
||||
N = n;
|
||||
}
|
||||
|
||||
public const int CONST_RHYTHM_ID = 0;
|
||||
private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio, IEnumerable<TaikoDifficultyHitObjectRhythm> commonRhythms)
|
||||
{
|
||||
return commonRhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,107 +1,19 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
public class TaikoDifficultyHitObjectRhythm
|
||||
{
|
||||
private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms;
|
||||
private readonly TaikoDifficultyHitObjectRhythm constRhythm;
|
||||
private int constRhythmID;
|
||||
|
||||
public int ID = 0;
|
||||
public readonly double Difficulty;
|
||||
private readonly double ratio;
|
||||
public readonly double Ratio;
|
||||
public readonly bool IsRepeat;
|
||||
|
||||
public bool IsRepeat()
|
||||
public TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty, bool isRepeat)
|
||||
{
|
||||
return ID == constRhythmID;
|
||||
}
|
||||
|
||||
public bool IsRepeat(int id)
|
||||
{
|
||||
return id == constRhythmID;
|
||||
}
|
||||
|
||||
public bool IsSpeedup()
|
||||
{
|
||||
return ratio < 1.0;
|
||||
}
|
||||
|
||||
public bool IsLargeSpeedup()
|
||||
{
|
||||
return ratio < 0.49;
|
||||
}
|
||||
|
||||
public TaikoDifficultyHitObjectRhythm()
|
||||
{
|
||||
/*
|
||||
|
||||
ALCHYRS CODE
|
||||
|
||||
If (change < 0.48) Then 'sometimes gaps are slightly different due to position rounding
|
||||
Return 0.65 'This number increases value of anything that more than doubles speed. Affects doubles.
|
||||
ElseIf (change < 0.52) Then
|
||||
Return 0.5 'speed doubling - this one affects pretty much every map other than stream maps
|
||||
ElseIf change <= 0.9 Then
|
||||
Return 1.0 'This number increases value of 1/4 -> 1/6 and other weird rhythms.
|
||||
ElseIf change < 0.95 Then
|
||||
Return 0.25 '.9
|
||||
ElseIf change > 1.95 Then
|
||||
Return 0.3 'half speed or more - this affects pretty much every map
|
||||
ElseIf change > 1.15 Then
|
||||
Return 0.425 'in between - this affects (mostly) 1/6 -> 1/4
|
||||
ElseIf change > 1.05 Then
|
||||
Return 0.15 '.9, small speed changes
|
||||
|
||||
*/
|
||||
|
||||
commonRhythms = new[]
|
||||
{
|
||||
new TaikoDifficultyHitObjectRhythm(1, 1, 0.1),
|
||||
new TaikoDifficultyHitObjectRhythm(2, 1, 0.3),
|
||||
new TaikoDifficultyHitObjectRhythm(1, 2, 0.5),
|
||||
new TaikoDifficultyHitObjectRhythm(3, 1, 0.3),
|
||||
new TaikoDifficultyHitObjectRhythm(1, 3, 0.35),
|
||||
new TaikoDifficultyHitObjectRhythm(3, 2, 0.6),
|
||||
new TaikoDifficultyHitObjectRhythm(2, 3, 0.4),
|
||||
new TaikoDifficultyHitObjectRhythm(5, 4, 0.5),
|
||||
new TaikoDifficultyHitObjectRhythm(4, 5, 0.7)
|
||||
};
|
||||
|
||||
for (int i = 0; i < commonRhythms.Length; i++)
|
||||
{
|
||||
commonRhythms[i].ID = i;
|
||||
}
|
||||
|
||||
constRhythmID = 0;
|
||||
constRhythm = commonRhythms[constRhythmID];
|
||||
}
|
||||
|
||||
private TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty)
|
||||
{
|
||||
this.ratio = ((double)numerator) / ((double)denominator);
|
||||
this.Difficulty = difficulty;
|
||||
}
|
||||
|
||||
// Code is inefficient - we are searching exhaustively through the sorted list commonRhythms
|
||||
public TaikoDifficultyHitObjectRhythm GetClosest(double ratio)
|
||||
{
|
||||
TaikoDifficultyHitObjectRhythm closestRhythm = commonRhythms[0];
|
||||
double closestDistance = Double.MaxValue;
|
||||
|
||||
foreach (TaikoDifficultyHitObjectRhythm r in commonRhythms)
|
||||
{
|
||||
if (Math.Abs(r.ratio - ratio) < closestDistance)
|
||||
{
|
||||
closestRhythm = r;
|
||||
closestDistance = Math.Abs(r.ratio - ratio);
|
||||
}
|
||||
}
|
||||
|
||||
return closestRhythm;
|
||||
Ratio = numerator / (double)denominator;
|
||||
Difficulty = difficulty;
|
||||
IsRepeat = isRepeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
@ -16,106 +15,100 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
|
||||
private bool prevIsKat = false;
|
||||
private NoteColour prevNoteColour = NoteColour.None;
|
||||
|
||||
private int currentMonoLength = 1;
|
||||
private List<int> monoHistory = new List<int>();
|
||||
private readonly int mono_history_max_length = 5;
|
||||
private int monoHistoryLength = 0;
|
||||
private readonly List<int> monoHistory = new List<int>();
|
||||
private const int mono_history_max_length = 5;
|
||||
|
||||
private double sameParityPenalty()
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private double repititionPenalty(int notesSince)
|
||||
private double repetitionPenalty(int notesSince)
|
||||
{
|
||||
double d = notesSince;
|
||||
return Math.Atan(d / 30) / (Math.PI / 2);
|
||||
double n = notesSince;
|
||||
return Math.Min(1.0, 0.032 * n);
|
||||
}
|
||||
|
||||
private double patternLengthPenalty(int patternLength)
|
||||
private double repetitionPenalties()
|
||||
{
|
||||
double shortPatternPenalty = Math.Min(0.25 * patternLength, 1.0);
|
||||
double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0);
|
||||
return Math.Min(shortPatternPenalty, longPatternPenalty);
|
||||
double penalty = 1.0;
|
||||
|
||||
monoHistory.Add(currentMonoLength);
|
||||
|
||||
if (monoHistory.Count > mono_history_max_length)
|
||||
monoHistory.RemoveAt(0);
|
||||
|
||||
for (int l = 2; l <= mono_history_max_length / 2; l++)
|
||||
{
|
||||
for (int start = monoHistory.Count - l - 1; start >= 0; start--)
|
||||
{
|
||||
bool samePattern = true;
|
||||
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i])
|
||||
{
|
||||
samePattern = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (samePattern) // Repetition found!
|
||||
{
|
||||
int notesSince = 0;
|
||||
for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i];
|
||||
penalty *= repetitionPenalty(notesSince);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
double objectDifficulty = 0.0;
|
||||
|
||||
if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)
|
||||
if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000))
|
||||
{
|
||||
|
||||
TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)current;
|
||||
|
||||
if (currentHO.IsKat == prevIsKat)
|
||||
{
|
||||
currentMonoLength += 1;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
|
||||
objectDifficulty = 1.0;
|
||||
|
||||
if (monoHistoryLength > 0 && (monoHistory[monoHistoryLength - 1] + currentMonoLength) % 2 == 0)
|
||||
{
|
||||
objectDifficulty *= sameParityPenalty();
|
||||
}
|
||||
|
||||
monoHistory.Add(currentMonoLength);
|
||||
monoHistoryLength += 1;
|
||||
|
||||
if (monoHistoryLength > mono_history_max_length)
|
||||
{
|
||||
monoHistory.RemoveAt(0);
|
||||
monoHistoryLength -= 1;
|
||||
}
|
||||
|
||||
for (int l = 2; l <= mono_history_max_length / 2; l++)
|
||||
{
|
||||
for (int start = monoHistoryLength - l - 1; start >= 0; start--)
|
||||
{
|
||||
bool samePattern = true;
|
||||
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
if (monoHistory[start + i] != monoHistory[monoHistoryLength - l + i])
|
||||
{
|
||||
samePattern = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (samePattern) // Repitition found!
|
||||
{
|
||||
int notesSince = 0;
|
||||
for (int i = start; i < monoHistoryLength; i++) notesSince += monoHistory[i];
|
||||
objectDifficulty *= repititionPenalty(notesSince);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentMonoLength = 1;
|
||||
prevIsKat = currentHO.IsKat;
|
||||
|
||||
}
|
||||
|
||||
prevNoteColour = NoteColour.None;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/*
|
||||
string path = @"out.txt";
|
||||
using (StreamWriter sw = File.AppendText(path))
|
||||
{
|
||||
if (((TaikoDifficultyHitObject)current).IsKat) sw.WriteLine("k " + Math.Min(1.25, returnVal) * returnMultiplier);
|
||||
else sw.WriteLine("d " + Math.Min(1.25, returnVal) * returnMultiplier);
|
||||
}
|
||||
*/
|
||||
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
||||
|
||||
return objectDifficulty;
|
||||
double objectStrain = 0.0;
|
||||
|
||||
NoteColour noteColour = hitObject.IsKat ? NoteColour.Ka : NoteColour.Don;
|
||||
|
||||
if (noteColour == NoteColour.Don && prevNoteColour == NoteColour.Ka ||
|
||||
noteColour == NoteColour.Ka && prevNoteColour == NoteColour.Don)
|
||||
{
|
||||
objectStrain = 1.0;
|
||||
|
||||
if (monoHistory.Count < 2)
|
||||
objectStrain = 0.0;
|
||||
else if ((monoHistory[^1] + currentMonoLength) % 2 == 0)
|
||||
objectStrain *= sameParityPenalty();
|
||||
|
||||
objectStrain *= repetitionPenalties();
|
||||
currentMonoLength = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMonoLength += 1;
|
||||
}
|
||||
|
||||
prevNoteColour = noteColour;
|
||||
return objectStrain;
|
||||
}
|
||||
|
||||
private enum NoteColour
|
||||
{
|
||||
Don,
|
||||
Ka,
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
115
osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
Normal file
115
osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
Normal file
@ -0,0 +1,115 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
public class Rhythm : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 10;
|
||||
protected override double StrainDecayBase => 0;
|
||||
private const double strain_decay = 0.96;
|
||||
private double currentStrain;
|
||||
|
||||
private readonly List<TaikoDifficultyHitObject> rhythmHistory = new List<TaikoDifficultyHitObject>();
|
||||
private const int rhythm_history_max_length = 8;
|
||||
|
||||
private int notesSinceRhythmChange;
|
||||
|
||||
private double repetitionPenalty(int notesSince)
|
||||
{
|
||||
return Math.Min(1.0, 0.032 * notesSince);
|
||||
}
|
||||
|
||||
// Finds repetitions and applies penalties
|
||||
private double repetitionPenalties(TaikoDifficultyHitObject hitobject)
|
||||
{
|
||||
double penalty = 1;
|
||||
|
||||
rhythmHistory.Add(hitobject);
|
||||
|
||||
if (rhythmHistory.Count > rhythm_history_max_length)
|
||||
rhythmHistory.RemoveAt(0);
|
||||
|
||||
for (int l = 2; l <= rhythm_history_max_length / 2; l++)
|
||||
{
|
||||
for (int start = rhythmHistory.Count - l - 1; start >= 0; start--)
|
||||
{
|
||||
bool samePattern = true;
|
||||
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - l + i].Rhythm)
|
||||
{
|
||||
samePattern = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (samePattern) // Repetition found!
|
||||
{
|
||||
int notesSince = hitobject.N - rhythmHistory[start].N;
|
||||
penalty *= repetitionPenalty(notesSince);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
private double patternLengthPenalty(int patternLength)
|
||||
{
|
||||
double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0);
|
||||
double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0);
|
||||
return Math.Min(shortPatternPenalty, longPatternPenalty);
|
||||
}
|
||||
|
||||
// Penalty for notes so slow that alternating is not necessary.
|
||||
private double speedPenalty(double noteLengthMs)
|
||||
{
|
||||
if (noteLengthMs < 80) return 1;
|
||||
if (noteLengthMs < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMs);
|
||||
|
||||
currentStrain = 0.0;
|
||||
notesSinceRhythmChange = 0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
if (!(current.BaseObject is Hit))
|
||||
{
|
||||
currentStrain = 0.0;
|
||||
notesSinceRhythmChange = 0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
currentStrain *= strain_decay;
|
||||
|
||||
TaikoDifficultyHitObject hitobject = (TaikoDifficultyHitObject)current;
|
||||
notesSinceRhythmChange += 1;
|
||||
|
||||
if (hitobject.Rhythm.IsRepeat)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double objectStrain = hitobject.Rhythm.Difficulty;
|
||||
|
||||
objectStrain *= repetitionPenalties(hitobject);
|
||||
objectStrain *= patternLengthPenalty(notesSinceRhythmChange);
|
||||
objectStrain *= speedPenalty(hitobject.NoteLength);
|
||||
|
||||
notesSinceRhythmChange = 0;
|
||||
|
||||
currentStrain += objectStrain;
|
||||
return currentStrain;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
public class Rhythm : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0;
|
||||
private const double strain_decay = 0.96;
|
||||
private double currentStrain;
|
||||
|
||||
private readonly List<TaikoDifficultyHitObject> ratioObjectHistory = new List<TaikoDifficultyHitObject>();
|
||||
private int ratioHistoryLength;
|
||||
private const int ratio_history_max_length = 8;
|
||||
|
||||
private int rhythmLength;
|
||||
|
||||
// Penalty for repeated sequences of rhythm changes
|
||||
private double repititionPenalty(double timeSinceRepititionMS)
|
||||
{
|
||||
double t = Math.Atan(timeSinceRepititionMS / 3000) / (Math.PI / 2);
|
||||
return t;
|
||||
}
|
||||
|
||||
private double repititionPenalty(int notesSince)
|
||||
{
|
||||
double t = notesSince * 150;
|
||||
t = Math.Atan(t / 3000) / (Math.PI / 2);
|
||||
return t;
|
||||
}
|
||||
|
||||
// Penalty for short patterns
|
||||
// Must be low to buff maps like wizodmiot
|
||||
// Must not be too low for maps like inverse world
|
||||
private double patternLengthPenalty(int patternLength)
|
||||
{
|
||||
double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0);
|
||||
double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0);
|
||||
return Math.Min(shortPatternPenalty, longPatternPenalty);
|
||||
}
|
||||
|
||||
// Penalty for notes so slow that alting is not necessary.
|
||||
private double speedPenalty(double noteLengthMS)
|
||||
{
|
||||
if (noteLengthMS < 80) return 1;
|
||||
// return Math.Max(0, 1.4 - 0.005 * noteLengthMS);
|
||||
if (noteLengthMS < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMS);
|
||||
if (noteLengthMS < 210) return 0.6;
|
||||
|
||||
currentStrain = 0.0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Penalty for the first rhythm change in a pattern
|
||||
private const double first_burst_penalty = 0.1;
|
||||
private bool prevIsSpeedup = true;
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject dho)
|
||||
{
|
||||
currentStrain *= strain_decay;
|
||||
|
||||
TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)dho;
|
||||
rhythmLength += 1;
|
||||
|
||||
if (!currentHO.HasTimingChange)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double objectDifficulty = currentHO.Rhythm.Difficulty;
|
||||
|
||||
// find repeated ratios
|
||||
|
||||
ratioObjectHistory.Add(currentHO);
|
||||
ratioHistoryLength += 1;
|
||||
|
||||
if (ratioHistoryLength > ratio_history_max_length)
|
||||
{
|
||||
ratioObjectHistory.RemoveAt(0);
|
||||
ratioHistoryLength -= 1;
|
||||
}
|
||||
|
||||
for (int l = 2; l <= ratio_history_max_length / 2; l++)
|
||||
{
|
||||
for (int start = ratioHistoryLength - l - 1; start >= 0; start--)
|
||||
{
|
||||
bool samePattern = true;
|
||||
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
if (ratioObjectHistory[start + i].RhythmID != ratioObjectHistory[ratioHistoryLength - l + i].RhythmID)
|
||||
{
|
||||
samePattern = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (samePattern) // Repitition found!
|
||||
{
|
||||
int notesSince = currentHO.n - ratioObjectHistory[start].n;
|
||||
objectDifficulty *= repititionPenalty(notesSince);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentHO.Rhythm.IsSpeedup())
|
||||
{
|
||||
objectDifficulty *= 1;
|
||||
if (currentHO.Rhythm.IsLargeSpeedup()) objectDifficulty *= 1;
|
||||
if (prevIsSpeedup) objectDifficulty *= 1;
|
||||
|
||||
prevIsSpeedup = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevIsSpeedup = false;
|
||||
}
|
||||
|
||||
objectDifficulty *= patternLengthPenalty(rhythmLength);
|
||||
objectDifficulty *= speedPenalty(currentHO.NoteLength);
|
||||
|
||||
rhythmLength = 0;
|
||||
|
||||
currentStrain += objectDifficulty;
|
||||
return currentStrain;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,91 +2,78 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
public class Stamina : Skill
|
||||
{
|
||||
private int hand;
|
||||
private int noteNumber = 0;
|
||||
private readonly int hand;
|
||||
|
||||
protected override double SkillMultiplier => 1;
|
||||
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
// i only add strain every second note so its kind of like using 0.16
|
||||
|
||||
private readonly int maxHistoryLength = 2;
|
||||
private List<double> noteDurationHistory = new List<double>();
|
||||
|
||||
private List<TaikoDifficultyHitObject> lastHitObjects = new List<TaikoDifficultyHitObject>();
|
||||
private const int max_history_length = 2;
|
||||
private readonly List<double> notePairDurationHistory = new List<double>();
|
||||
|
||||
private double offhandObjectDuration = double.MaxValue;
|
||||
|
||||
// Penalty for tl tap or roll
|
||||
private double cheesePenalty(double last2NoteDuration)
|
||||
private double cheesePenalty(double notePairDuration)
|
||||
{
|
||||
if (last2NoteDuration > 125) return 1;
|
||||
if (last2NoteDuration < 100) return 0.6;
|
||||
if (notePairDuration > 125) return 1;
|
||||
if (notePairDuration < 100) return 0.6;
|
||||
|
||||
return 0.6 + (last2NoteDuration - 100) * 0.016;
|
||||
return 0.6 + (notePairDuration - 100) * 0.016;
|
||||
}
|
||||
|
||||
private double speedBonus(double last2NoteDuration)
|
||||
private double speedBonus(double notePairDuration)
|
||||
{
|
||||
// note that we are only looking at every 2nd note, so a 300bpm stream has a note duration of 100ms.
|
||||
if (last2NoteDuration >= 200) return 0;
|
||||
double bonus = 200 - last2NoteDuration;
|
||||
if (notePairDuration >= 200) return 0;
|
||||
|
||||
double bonus = 200 - notePairDuration;
|
||||
bonus *= bonus;
|
||||
return bonus / 100000;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
noteNumber += 1;
|
||||
|
||||
TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)current;
|
||||
|
||||
if (noteNumber % 2 == hand)
|
||||
if (!(current.BaseObject is Hit))
|
||||
{
|
||||
lastHitObjects.Add(currentHO);
|
||||
noteDurationHistory.Add(currentHO.NoteLength + offhandObjectDuration);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (noteNumber == 1)
|
||||
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
||||
|
||||
if (hitObject.N % 2 == hand)
|
||||
{
|
||||
double objectStrain = 1;
|
||||
|
||||
if (hitObject.N == 1)
|
||||
return 1;
|
||||
|
||||
if (noteDurationHistory.Count > maxHistoryLength)
|
||||
noteDurationHistory.RemoveAt(0);
|
||||
notePairDurationHistory.Add(hitObject.NoteLength + offhandObjectDuration);
|
||||
|
||||
double shortestRecentNote = min(noteDurationHistory);
|
||||
double bonus = 0;
|
||||
bonus += speedBonus(shortestRecentNote);
|
||||
if (notePairDurationHistory.Count > max_history_length)
|
||||
notePairDurationHistory.RemoveAt(0);
|
||||
|
||||
double objectStaminaStrain = 1 + bonus;
|
||||
if (currentHO.StaminaCheese) objectStaminaStrain *= cheesePenalty(currentHO.NoteLength + offhandObjectDuration);
|
||||
double shortestRecentNote = notePairDurationHistory.Min();
|
||||
objectStrain += speedBonus(shortestRecentNote);
|
||||
|
||||
return objectStaminaStrain;
|
||||
if (hitObject.StaminaCheese)
|
||||
objectStrain *= cheesePenalty(hitObject.NoteLength + offhandObjectDuration);
|
||||
|
||||
return objectStrain;
|
||||
}
|
||||
|
||||
offhandObjectDuration = currentHO.NoteLength;
|
||||
offhandObjectDuration = hitObject.NoteLength;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static double min(List<double> l)
|
||||
{
|
||||
double minimum = double.MaxValue;
|
||||
|
||||
foreach (double d in l)
|
||||
{
|
||||
if (d < minimum)
|
||||
minimum = d;
|
||||
}
|
||||
|
||||
return minimum;
|
||||
}
|
||||
|
||||
public Stamina(bool rightHand)
|
||||
{
|
||||
hand = 0;
|
||||
|
@ -20,9 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double rhythmSkillMultiplier = 0.15;
|
||||
private const double colourSkillMultiplier = 0.01;
|
||||
private const double staminaSkillMultiplier = 0.02;
|
||||
private const double rhythm_skill_multiplier = 0.014;
|
||||
private const double colour_skill_multiplier = 0.01;
|
||||
private const double stamina_skill_multiplier = 0.02;
|
||||
|
||||
private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms =
|
||||
{
|
||||
new TaikoDifficultyHitObjectRhythm(1, 1, 0.0, true),
|
||||
new TaikoDifficultyHitObjectRhythm(2, 1, 0.3, false),
|
||||
new TaikoDifficultyHitObjectRhythm(1, 2, 0.5, false),
|
||||
new TaikoDifficultyHitObjectRhythm(3, 1, 0.3, false),
|
||||
new TaikoDifficultyHitObjectRhythm(1, 3, 0.35, false),
|
||||
new TaikoDifficultyHitObjectRhythm(3, 2, 0.6, false),
|
||||
new TaikoDifficultyHitObjectRhythm(2, 3, 0.4, false),
|
||||
new TaikoDifficultyHitObjectRhythm(5, 4, 0.5, false),
|
||||
new TaikoDifficultyHitObjectRhythm(4, 5, 0.7, false)
|
||||
};
|
||||
|
||||
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
@ -32,6 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty)
|
||||
{
|
||||
if (colorDifficulty <= 0) return 0.79 - 0.25;
|
||||
|
||||
return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2;
|
||||
}
|
||||
|
||||
@ -46,25 +60,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
private double rescale(double sr)
|
||||
{
|
||||
if (sr <= 1) return sr;
|
||||
sr -= 1;
|
||||
sr = 1.6 * Math.Pow(sr, 0.7);
|
||||
sr += 1;
|
||||
return sr;
|
||||
if (sr < 0) return sr;
|
||||
|
||||
return 10.43 * Math.Log(sr / 8 + 1);
|
||||
}
|
||||
|
||||
private double combinedDifficulty(double staminaPenalty, Skill colour, Skill rhythm, Skill stamina1, Skill stamina2)
|
||||
private double locallyCombinedDifficulty(double staminaPenalty, Skill colour, Skill rhythm, Skill stamina1, Skill stamina2)
|
||||
{
|
||||
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
List<double> peaks = new List<double>();
|
||||
|
||||
for (int i = 0; i < colour.StrainPeaks.Count; i++)
|
||||
{
|
||||
double colourPeak = colour.StrainPeaks[i] * colourSkillMultiplier;
|
||||
double rhythmPeak = rhythm.StrainPeaks[i] * rhythmSkillMultiplier;
|
||||
double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier * staminaPenalty;
|
||||
double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier;
|
||||
double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier;
|
||||
double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty;
|
||||
peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak));
|
||||
}
|
||||
|
||||
@ -82,21 +93,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * staminaSkillMultiplier;
|
||||
double colourRating = skills[0].DifficultyValue() * colourSkillMultiplier;
|
||||
double rhythmRating = skills[1].DifficultyValue() * rhythmSkillMultiplier;
|
||||
double colourRating = skills[0].DifficultyValue() * colour_skill_multiplier;
|
||||
double rhythmRating = skills[1].DifficultyValue() * rhythm_skill_multiplier;
|
||||
double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * stamina_skill_multiplier;
|
||||
|
||||
double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
|
||||
staminaRating *= staminaPenalty;
|
||||
|
||||
double combinedRating = combinedDifficulty(staminaPenalty, skills[0], skills[1], skills[2], skills[3]);
|
||||
|
||||
// Console.WriteLine("colour\t" + colourRating);
|
||||
// Console.WriteLine("rhythm\t" + rhythmRating);
|
||||
// Console.WriteLine("stamina\t" + staminaRating);
|
||||
double combinedRating = locallyCombinedDifficulty(staminaPenalty, skills[0], skills[1], skills[2], skills[3]);
|
||||
double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating);
|
||||
// Console.WriteLine("combinedRating\t" + combinedRating);
|
||||
// Console.WriteLine("separatedRating\t" + separatedRating);
|
||||
double starRating = 1.4 * separatedRating + 0.5 * combinedRating;
|
||||
starRating = rescale(starRating);
|
||||
|
||||
@ -111,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
RhythmStrain = rhythmRating,
|
||||
ColourStrain = colourRating,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
|
||||
GreatHitWindow = (int)hitWindows.WindowFor(HitResult.Great) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||
Skills = skills
|
||||
};
|
||||
@ -120,18 +125,23 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
List<TaikoDifficultyHitObject> taikoDifficultyHitObjects = new List<TaikoDifficultyHitObject>();
|
||||
var rhythm = new TaikoDifficultyHitObjectRhythm();
|
||||
|
||||
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
// Check for negative durations
|
||||
if (beatmap.HitObjects[i].StartTime > beatmap.HitObjects[i - 1].StartTime && beatmap.HitObjects[i - 1].StartTime > beatmap.HitObjects[i - 2].StartTime)
|
||||
taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, rhythm));
|
||||
{
|
||||
taikoDifficultyHitObjects.Add(
|
||||
new TaikoDifficultyHitObject(
|
||||
beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i, commonRhythms
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects);
|
||||
for (int i = 0; i < taikoDifficultyHitObjects.Count; i++)
|
||||
yield return taikoDifficultyHitObjects[i];
|
||||
foreach (var hitobject in taikoDifficultyHitObjects)
|
||||
yield return hitobject;
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||
@ -149,10 +159,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
new TaikoModEasy(),
|
||||
new TaikoModHardRock(),
|
||||
};
|
||||
|
||||
/*
|
||||
protected override DifficultyAttributes VirtualCalculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
=> taikoCalculate(beatmap, mods, clockRate);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
strainValue *= Math.Pow(0.985, countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (Attributes.MaxCombo > 0)
|
||||
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0);
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
strainValue *= 1.025;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user