1
0
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:
smoogipoo 2020-06-08 16:30:26 +09:00
parent 5852a37eb7
commit 68027fcc2c
9 changed files with 295 additions and 427 deletions

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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
}
}
}

View 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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
*/
}
}

View File

@ -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;