1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:43:05 +08:00

Add initial changes

This commit is contained in:
smoogipoo 2020-05-11 14:50:02 +09:00
parent adf9a90fc4
commit d613888803
9 changed files with 726 additions and 107 deletions

View File

@ -0,0 +1,95 @@
// 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;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
{
public class StaminaCheeseDetector
{
private const int roll_min_repetitions = 12;
private const int tl_min_repetitions = 16;
private List<TaikoDifficultyHitObject> hitObjects;
public void FindCheese(List<TaikoDifficultyHitObject> difficultyHitObjects)
{
this.hitObjects = difficultyHitObjects;
findRolls(3);
findRolls(4);
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;
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;
for (int j = 0; j < patternLength; j++)
{
if (history[j].IsKat != history[j + patternLength].IsKat)
{
isRepeat = false;
}
}
if (!isRepeat)
{
repititionStart = i - 2 * patternLength;
}
int repeatedLength = i - repititionStart;
if (repeatedLength >= roll_min_repetitions)
{
// Console.WriteLine("Found Roll Cheese.\tStart: " + repititionStart + "\tEnd: " + i);
for (int j = repititionStart; j < i; j++)
{
(hitObjects[i]).StaminaCheese = true;
}
}
}
}
private void findTLTap(int parity, bool kat)
{
int tl_length = -2;
for (int i = parity; i < hitObjects.Count; i += 2)
{
if (kat == hitObjects[i].IsKat)
{
tl_length += 2;
}
else
{
tl_length = -2;
}
if (tl_length >= tl_min_repetitions)
{
// Console.WriteLine("Found TL Cheese.\tStart: " + (i - tl_length) + "\tEnd: " + i);
for (int j = i - tl_length; j < i; j++)
{
(hitObjects[i]).StaminaCheese = true;
}
}
}
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Objects;
using osu.Game.Rulesets.Taiko.Objects;
@ -10,11 +11,36 @@ 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 TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
public bool StaminaCheese = false;
public readonly int RhythmID;
public readonly double NoteLength;
public readonly int n;
private int counter = 0;
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate)
: base(hitObject, lastObject, clockRate)
{
HasTypeChange = (lastObject as Hit)?.Type != (hitObject as Hit)?.Type;
NoteLength = DeltaTime;
double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate;
Rhythm = TaikoDifficultyHitObjectRhythm.GetClosest(NoteLength / prevLength);
RhythmID = Rhythm.ID;
HasTypeChange = lastObject is RimHit != hitObject is RimHit;
IsKat = lastObject is RimHit;
HasTimingChange = !TaikoDifficultyHitObjectRhythm.IsRepeat(RhythmID);
n = counter;
counter++;
}
public const int CONST_RHYTHM_ID = 0;
}
}

View File

@ -0,0 +1,124 @@
// 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 static TaikoDifficultyHitObjectRhythm[] commonRhythms;
private static TaikoDifficultyHitObjectRhythm constRhythm;
private static int constRhythmID;
public int ID = 0;
public readonly double Difficulty;
private readonly double ratio;
private static void initialiseCommonRhythms()
{
/*
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 TaikoDifficultyHitObjectRhythm[]
{
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];
}
public bool IsRepeat()
{
return ID == constRhythmID;
}
public static bool IsRepeat(int id)
{
return id == constRhythmID;
}
public bool IsSpeedup()
{
return ratio < 1.0;
}
public bool IsLargeSpeedup()
{
return ratio < 0.49;
}
private TaikoDifficultyHitObjectRhythm(double ratio, double difficulty)
{
this.ratio = ratio;
this.Difficulty = difficulty;
}
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 static TaikoDifficultyHitObjectRhythm GetClosest(double ratio)
{
if (commonRhythms == null)
{
initialiseCommonRhythms();
}
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;
}
}
}

View File

@ -0,0 +1,144 @@
// 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.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
{
public class Colour : Skill
{
protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.3;
private ColourSwitch lastColourSwitch = ColourSwitch.None;
private int sameColourCount = 1;
private int[] previousDonLengths = {0, 0}, previousKatLengths = {0, 0};
private int sameTypeCount = 1;
// TODO: make this smarter (dont initialise with "Don")
private bool previousIsKat = false;
protected override double StrainValueOf(DifficultyHitObject current)
{
return StrainValueOfNew(current);
}
protected double StrainValueOfNew(DifficultyHitObject current)
{
double returnVal = 0.0;
double returnMultiplier = 1.0;
if (previousIsKat != ((TaikoDifficultyHitObject) current).IsKat)
{
returnVal = 1.5 - (1.75 / (sameTypeCount + 0.65));
if (previousIsKat)
{
if (sameTypeCount % 2 == previousDonLengths[0] % 2)
{
returnMultiplier *= 0.8;
}
if (previousKatLengths[0] == sameTypeCount)
{
returnMultiplier *= 0.525;
}
if (previousKatLengths[1] == sameTypeCount)
{
returnMultiplier *= 0.75;
}
previousKatLengths[1] = previousKatLengths[0];
previousKatLengths[0] = sameTypeCount;
}
else
{
if (sameTypeCount % 2 == previousKatLengths[0] % 2)
{
returnMultiplier *= 0.8;
}
if (previousDonLengths[0] == sameTypeCount)
{
returnMultiplier *= 0.525;
}
if (previousDonLengths[1] == sameTypeCount)
{
returnMultiplier *= 0.75;
}
previousDonLengths[1] = previousDonLengths[0];
previousDonLengths[0] = sameTypeCount;
}
sameTypeCount = 1;
previousIsKat = ((TaikoDifficultyHitObject) current).IsKat;
}
else
{
sameTypeCount += 1;
}
return Math.Min(1.25, returnVal) * returnMultiplier;
}
protected double StrainValueOfOld(DifficultyHitObject current)
{
double addition = 0;
// We get an extra addition if we are not a slider or spinner
if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)
{
if (hasColourChange(current))
addition = 0.75;
}
else
{
lastColourSwitch = ColourSwitch.None;
sameColourCount = 1;
}
return addition;
}
private bool hasColourChange(DifficultyHitObject current)
{
var taikoCurrent = (TaikoDifficultyHitObject) current;
if (!taikoCurrent.HasTypeChange)
{
sameColourCount++;
return false;
}
var oldColourSwitch = lastColourSwitch;
var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd;
lastColourSwitch = newColourSwitch;
sameColourCount = 1;
// We only want a bonus if the parity of the color switch changes
return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch;
}
private enum ColourSwitch
{
None,
Even,
Odd
}
}
}

View File

@ -0,0 +1,133 @@
// 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 = 0.0;
private readonly List<TaikoDifficultyHitObject> ratioObjectHistory = new List<TaikoDifficultyHitObject>();
private int ratioHistoryLength = 0;
private const int ratio_history_max_length = 8;
private int rhythmLength = 0;
// 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;
if (noteLengthMS < 160) return Math.Max(0, 1.4 - 0.005 * noteLengthMS);
if (noteLengthMS < 300) return 0.6;
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

@ -0,0 +1,103 @@
// 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.Linq;
using System.Collections.Generic;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Objects;
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;
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 double offhandObjectDuration = double.MaxValue;
// Penalty for tl tap or roll
private double cheesePenalty(double last2NoteDuration)
{
if (last2NoteDuration > 125) return 1;
if (last2NoteDuration < 100) return 0.6;
return 0.6 + (last2NoteDuration - 100) * 0.016;
}
private double speedBonus(double last2NoteDuration)
{
// 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;
bonus *= bonus;
return bonus / 100000;
}
protected override double StrainValueOf(DifficultyHitObject current)
{
noteNumber += 1;
TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject) current;
if (noteNumber % 2 == hand)
{
lastHitObjects.Add(currentHO);
noteDurationHistory.Add(currentHO.NoteLength + offhandObjectDuration);
if (noteNumber == 1)
return 1;
if (noteDurationHistory.Count > maxHistoryLength)
noteDurationHistory.RemoveAt(0);
double shortestRecentNote = min(noteDurationHistory);
double bonus = 0;
bonus += speedBonus(shortestRecentNote);
double objectStaminaStrain = 1 + bonus;
if (currentHO.StaminaCheese) objectStaminaStrain *= cheesePenalty(currentHO.NoteLength + offhandObjectDuration);
return objectStaminaStrain;
}
offhandObjectDuration = currentHO.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;
if (rightHand)
{
hand = 1;
}
}
}
}

View File

@ -1,95 +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 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 Strain : Skill
{
private const double rhythm_change_base_threshold = 0.2;
private const double rhythm_change_base = 2.0;
protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.3;
private ColourSwitch lastColourSwitch = ColourSwitch.None;
private int sameColourCount = 1;
protected override double StrainValueOf(DifficultyHitObject current)
{
double addition = 1;
// We get an extra addition if we are not a slider or spinner
if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)
{
if (hasColourChange(current))
addition += 0.75;
if (hasRhythmChange(current))
addition += 1;
}
else
{
lastColourSwitch = ColourSwitch.None;
sameColourCount = 1;
}
double additionFactor = 1;
// Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50
if (current.DeltaTime < 50)
additionFactor = 0.4 + 0.6 * current.DeltaTime / 50;
return additionFactor * addition;
}
private bool hasRhythmChange(DifficultyHitObject current)
{
// We don't want a division by zero if some random mapper decides to put two HitObjects at the same time.
if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0)
return false;
double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime);
if (timeElapsedRatio >= 8)
return false;
double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold;
}
private bool hasColourChange(DifficultyHitObject current)
{
var taikoCurrent = (TaikoDifficultyHitObject)current;
if (!taikoCurrent.HasTypeChange)
{
sameColourCount++;
return false;
}
var oldColourSwitch = lastColourSwitch;
var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd;
lastColourSwitch = newColourSwitch;
sameColourCount = 1;
// We only want a bonus if the parity of the color switch changes
return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch;
}
private enum ColourSwitch
{
None,
Even,
Odd
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Beatmaps;
@ -19,39 +20,121 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyCalculator : DifficultyCalculator
{
private const double star_scaling_factor = 0.04125;
private const double rhythmSkillMultiplier = 0.15;
private const double colourSkillMultiplier = 0.01;
private const double staminaSkillMultiplier = 0.02;
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
private double readingPenalty(double staminaDifficulty)
{
return Math.Max(0, 1 - staminaDifficulty / 14);
// return 1;
}
private double norm(double p, double v1, double v2, double v3)
{
return Math.Pow(
Math.Pow(v1, p) +
Math.Pow(v2, p) +
Math.Pow(v3, p)
, 1 / p);
}
private double rescale(double sr)
{
if (sr <= 1) return sr;
sr -= 1;
sr = 1.5 * Math.Pow(sr, 0.76);
sr += 1;
return sr;
}
private double combinedDifficulty(Skill colour, Skill rhythm, Skill stamina1, Skill stamina2)
{
double staminaRating = (stamina1.DifficultyValue() + stamina2.DifficultyValue()) * staminaSkillMultiplier;
double readingPenalty = this.readingPenalty(staminaRating);
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 * readingPenalty;
double rhythmPeak = rhythm.StrainPeaks[i] * rhythmSkillMultiplier;
double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier;
peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak));
}
foreach (double strain in peaks.OrderByDescending(d => d))
{
difficulty += strain * weight;
weight *= 0.9;
}
return difficulty;
}
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * staminaSkillMultiplier;
double readingPenalty = this.readingPenalty(staminaRating);
double colourRating = skills[0].DifficultyValue() * colourSkillMultiplier * readingPenalty;
double rhythmRating = skills[1].DifficultyValue() * rhythmSkillMultiplier;
double combinedRating = combinedDifficulty(skills[0], skills[1], skills[2], skills[3]);
// Console.WriteLine("colour\t" + colourRating);
// Console.WriteLine("rhythm\t" + rhythmRating);
// Console.WriteLine("stamina\t" + staminaRating);
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);
HitWindows hitWindows = new TaikoHitWindows();
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
return new TaikoDifficultyAttributes
{
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
StarRating = starRating,
Mods = mods,
// 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,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
Skills = skills
};
}
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
for (int i = 1; i < beatmap.HitObjects.Count; i++)
yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
List<TaikoDifficultyHitObject> taikoDifficultyHitObjects = new List<TaikoDifficultyHitObject>();
for (int i = 2; i < beatmap.HitObjects.Count; i++)
{
taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate));
}
new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects);
for (int i = 0; i < taikoDifficultyHitObjects.Count; i++)
yield return taikoDifficultyHitObjects[i];
}
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() };
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
{
new Colour(),
new Rhythm(),
new Stamina(true),
new Stamina(false),
};
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
@ -60,5 +143,11 @@ 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

@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
strainValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available