From 29fe0b471cf508d77ae4e4752046be84862a2694 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Fri, 21 Apr 2017 18:52:08 +0900 Subject: [PATCH 1/8] Fix drum rolls not taking into account nodal samples when converting from drum rolls to hit circles. --- .../Beatmaps/TaikoBeatmapConverter.cs | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index cf7b9ce710..4806f2568b 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -66,6 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps var distanceData = obj as IHasDistance; var repeatsData = obj as IHasRepeats; var endTimeData = obj as IHasEndTime; + var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information List samples = obj.Samples; @@ -102,16 +103,43 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { + var allSamples = new List>(); + + if (curveData != null) + { + allSamples.Add(curveData.HeadSamples); + allSamples.AddRange(curveData.RepeatSamples); + allSamples.Add(curveData.TailSamples); + } + else + allSamples.Add(samples); + + int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { - // Todo: This should generate different type of hits (including strongs) - // depending on hitobject sound additions (not implemented fully yet) - yield return new CentreHit + List currentSamples = allSamples[i]; + bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + + if (isRim) { - StartTime = j, - Samples = obj.Samples, - IsStrong = strong, - }; + yield return new RimHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong + }; + } + else + { + yield return new CentreHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong, + }; + } + + i = (i + 1) % allSamples.Count; } } else From f750325aa13eb1ad40f519344c681b3783bbf05a Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Fri, 21 Apr 2017 20:10:14 +0900 Subject: [PATCH 2/8] Bring up to date. --- .../Beatmaps/TaikoBeatmapConverter.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 4806f2568b..1278549f1a 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -103,16 +103,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - var allSamples = new List>(); - + List> allSamples; if (curveData != null) - { - allSamples.Add(curveData.HeadSamples); - allSamples.AddRange(curveData.RepeatSamples); - allSamples.Add(curveData.TailSamples); - } + allSamples = curveData.RepeatSamples; else - allSamples.Add(samples); + allSamples = new List> { samples }; int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) From ac5f70b7658e26a36b58dca036d6cad1779f4d95 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Wed, 26 Apr 2017 13:48:18 +0900 Subject: [PATCH 3/8] Fix drum roll conversion not generating strong hits. --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 1278549f1a..3bbe349c77 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { List currentSamples = allSamples[i]; bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); if (isRim) { From 28f7e0cdba97e80c882e4d7f11921ef0835117a6 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Wed, 26 Apr 2017 14:12:21 +0900 Subject: [PATCH 4/8] Introduce SampleInfoList as List to reduce generic nesting. Fix CI warnings. --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 +++--- .../Beatmaps/TaikoBeatmapConverter.cs | 10 +++------- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 4 ++-- osu.Game/Audio/SampleInfoList.cs | 19 +++++++++++++++++++ osu.Game/Rulesets/Objects/HitObject.cs | 2 +- .../Legacy/Catch/ConvertHitObjectParser.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 8 ++++---- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- .../Legacy/Mania/ConvertHitObjectParser.cs | 2 +- .../Legacy/Osu/ConvertHitObjectParser.cs | 2 +- .../Legacy/Taiko/ConvertHitObjectParser.cs | 2 +- .../Rulesets/Objects/Types/IHasRepeats.cs | 2 +- osu.Game/osu.Game.csproj | 1 + 13 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Audio/SampleInfoList.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4d9d3f97ed..6c0147a3de 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects set { Curve.Distance = value; } } - public List> RepeatSamples { get; set; } = new List>(); + public List RepeatSamples { get; set; } = new List(); public int RepeatCount { get; set; } = 1; private int stackHeight; @@ -117,12 +117,12 @@ namespace osu.Game.Rulesets.Osu.Objects StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, - Samples = Samples.Select(s => new SampleInfo + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", Volume = s.Volume - }).ToList() + })) }; } } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 3bbe349c77..0784c94059 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information - List samples = obj.Samples; + SampleInfoList samples = obj.Samples; bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); @@ -103,16 +103,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples; - if (curveData != null) - allSamples = curveData.RepeatSamples; - else - allSamples = new List> { samples }; + List allSamples = curveData != null ? curveData.RepeatSamples : new List(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { - List currentSamples = allSamples[i]; + SampleInfoList currentSamples = allSamples[i]; bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 4f89fb8248..f79c01b643 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Taiko.Objects TickSpacing = tickSpacing, StartTime = t, IsStrong = IsStrong, - Samples = Samples.Select(s => new SampleInfo + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", Volume = s.Volume - }).ToList() + })) }); first = false; diff --git a/osu.Game/Audio/SampleInfoList.cs b/osu.Game/Audio/SampleInfoList.cs new file mode 100644 index 0000000000..75d2d1d23f --- /dev/null +++ b/osu.Game/Audio/SampleInfoList.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Audio +{ + public class SampleInfoList : List + { + public SampleInfoList() + { + } + + public SampleInfoList(IEnumerable elements) + { + AddRange(elements); + } + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 12e4ca6df1..edbdc27365 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Objects /// and can be treated as the default samples for the hit object. /// /// - public List Samples = new List(); + public SampleInfoList Samples = new SampleInfoList(); /// /// Applies default values to this HitObject. diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 1550772f8e..5c534456ef 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index ad028e0cee..c5551082ec 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } // Generate the final per-node samples - var nodeSamples = new List>(nodes); + var nodeSamples = new List(nodes); for (int i = 0; i <= repeatCount; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples); /// /// Creates a legacy Spinner-type hit object. @@ -214,9 +214,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The hit object. protected abstract HitObject CreateSpinner(Vector2 position, double endTime); - private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) + private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) { - var soundTypes = new List + var soundTypes = new SampleInfoList { new SampleInfo { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 06391a9906..7580404e81 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public CurveType CurveType { get; set; } public double Distance { get; set; } - public List> RepeatSamples { get; set; } + public List RepeatSamples { get; set; } public int RepeatCount { get; set; } = 1; public double EndTime { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index b21857797f..224f068323 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index e45a161f52..41bf142831 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 2de2f217d1..0d755d7527 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 2fe2424d49..5abad2d661 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). /// - List> RepeatSamples { get; } + List RepeatSamples { get; } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cc9fe00cdc..cb491055c4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -71,6 +71,7 @@ + From 4c2985b6d129c74dcdb4b19f17dad449ddf09c9f Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Wed, 26 Apr 2017 14:40:40 +0900 Subject: [PATCH 5/8] Use CRLF instead of LF. --- osu.Game/Audio/SampleInfoList.cs | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Audio/SampleInfoList.cs b/osu.Game/Audio/SampleInfoList.cs index 75d2d1d23f..594341bbb1 100644 --- a/osu.Game/Audio/SampleInfoList.cs +++ b/osu.Game/Audio/SampleInfoList.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; - -namespace osu.Game.Audio -{ - public class SampleInfoList : List - { - public SampleInfoList() - { - } - - public SampleInfoList(IEnumerable elements) - { - AddRange(elements); - } - } +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Audio +{ + public class SampleInfoList : List + { + public SampleInfoList() + { + } + + public SampleInfoList(IEnumerable elements) + { + AddRange(elements); + } + } } \ No newline at end of file From 22be765323edf11307b0734724e540409f82aa32 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Apr 2017 14:45:30 +0900 Subject: [PATCH 6/8] Update HitObject.cs --- osu.Game/Rulesets/Objects/HitObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index edbdc27365..46fb5fcf70 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -5,7 +5,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; namespace osu.Game.Rulesets.Objects { From d9dec9d444dd666e1a618bc31a2ec2db899948c6 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Wed, 26 Apr 2017 15:50:08 +0900 Subject: [PATCH 7/8] Implement Taiko difficulty calculation. --- .../Objects/TaikoHitObjectDifficulty.cs | 127 ++++++++++++++++++ .../TaikoDifficultyCalculator.cs | 121 ++++++++++++++++- .../osu.Game.Rulesets.Taiko.csproj | 1 + osu.Game/Beatmaps/DifficultyCalculator.cs | 4 + 4 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs new file mode 100644 index 0000000000..5056a52346 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs @@ -0,0 +1,127 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + internal class TaikoHitObjectDifficulty + { + /// + /// Factor by how much individual / overall strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// + internal const double DECAY_BASE = 0.30; + + private const double type_change_bonus = 0.75; + private const double rhythm_change_bonus = 1.0; + private const double rhythm_change_base_threshold = 0.2; + private const double rhythm_change_base = 2.0; + + internal TaikoHitObject BaseHitObject; + + /// + /// Measures note density in a way + /// + internal double Strain = 1; + + private double timeElapsed = 0; + private int sameTypeSince = 1; + + private bool isRim => BaseHitObject is RimHit; + + public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) + { + this.BaseHitObject = baseHitObject; + } + + internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) + { + // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. + // See Taiko feedback thread. + timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); + + double addition = 1; + + // Only if we are no slider or spinner we get an extra addition + if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit + && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past + { + addition += typeChangeAddition(previousHitObject); + addition += rhythmChangeAddition(previousHitObject); + } + + double additionFactor = 1.0; + // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50 + if (timeElapsed < 50.0) + additionFactor = 0.4 + 0.6 * timeElapsed / 50.0; + + Strain = previousHitObject.Strain * decay + addition * additionFactor; + } + + private TypeSwitch lastTypeSwitchEven = TypeSwitch.None; + private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject) + { + // If we don't have the same hit type, trigger a type change! + if (previousHitObject.isRim != isRim) + { + lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd; + + // We only want a bonus if the parity of the type switch changes! + switch (previousHitObject.lastTypeSwitchEven) + { + case TypeSwitch.Even: + if (lastTypeSwitchEven == TypeSwitch.Odd) + return type_change_bonus; + break; + case TypeSwitch.Odd: + if (lastTypeSwitchEven == TypeSwitch.Even) + return type_change_bonus; + break; + } + } + // No type change? Increment counter and keep track of last type switch + else + { + lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven; + sameTypeSince = previousHitObject.sameTypeSince + 1; + } + + return 0; + } + + private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject) + { + // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time. + if (timeElapsed == 0 || previousHitObject.timeElapsed == 0) + return 0; + + double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed); + + if (timeElapsedRatio >= 8) + return 0; + + double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; + + if (isWithinChangeThreshold(difference)) + return rhythm_change_bonus; + + return 0; + } + + private bool isWithinChangeThreshold(double value) + { + return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold; + } + + private enum TypeSwitch + { + None, + Even, + Odd + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs index cd61709db8..f80c777f05 100644 --- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs @@ -6,18 +6,133 @@ using osu.Game.Rulesets.Beatmaps; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; using System.Collections.Generic; +using System.Globalization; +using System; namespace osu.Game.Rulesets.Taiko { - public class TaikoDifficultyCalculator : DifficultyCalculator + internal class TaikoDifficultyCalculator : DifficultyCalculator { - public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap) + private const double star_scaling_factor = 0.04125; + + /// + /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. + /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. + /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. + /// + private const double strain_step = 400; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + private const double decay_weight = 0.9; + + /// + /// HitObjects are stored as a member variable. + /// + private List difficultyHitObjects = new List(); + + public TaikoDifficultyCalculator(Beatmap beatmap) + : base(beatmap) { } protected override double CalculateInternal(Dictionary categoryDifficulty) { - return 0; + // Fill our custom DifficultyHitObject class, that carries additional information + difficultyHitObjects.Clear(); + + foreach (var hitObject in Objects) + difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject)); + + // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. + difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); + + if (!calculateStrainValues()) return 0; + + double starRating = calculateDifficulty() * star_scaling_factor; + + if (categoryDifficulty != null) + { + categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture)); + categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture)); + } + + return starRating; + } + + private bool calculateStrainValues() + { + // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. + List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator(); + + if (!hitObjectsEnumerator.MoveNext()) return false; + + TaikoHitObjectDifficulty currentHitObject = hitObjectsEnumerator.Current; + TaikoHitObjectDifficulty nextHitObject; + + // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. + while (hitObjectsEnumerator.MoveNext()) + { + nextHitObject = hitObjectsEnumerator.Current; + nextHitObject.CalculateStrains(currentHitObject, TimeRate); + currentHitObject = nextHitObject; + } + + return true; + } + + private double calculateDifficulty() + { + double actualStrainStep = strain_step * TimeRate; + + // Find the highest strain value within each strain step + List highestStrains = new List(); + double intervalEndTime = actualStrainStep; + double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval + + TaikoHitObjectDifficulty previousHitObject = null; + foreach (var hitObject in difficultyHitObjects) + { + // While we are beyond the current interval push the currently available maximum to our strain list + while (hitObject.BaseHitObject.StartTime > intervalEndTime) + { + highestStrains.Add(maximumStrain); + + // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay + // until the beginning of the next interval. + if (previousHitObject == null) + { + maximumStrain = 0; + } + else + { + double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + maximumStrain = previousHitObject.Strain * decay; + } + + // Go to the next time interval + intervalEndTime += actualStrainStep; + } + + // Obtain maximum strain + maximumStrain = Math.Max(hitObject.Strain, maximumStrain); + + previousHitObject = hitObject; + } + + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } + + return difficulty; } protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index c668b90ec4..f890e32f90 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -81,6 +81,7 @@ + diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 727c89049f..8e9266b644 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -35,6 +35,10 @@ namespace osu.Game.Beatmaps protected DifficultyCalculator(Beatmap beatmap) { Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects; + + foreach (var h in Objects) + h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty); + PreprocessHitObjects(); } From 47cd91fa633ffdb468218fa5a2b7339e8cad2f4a Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Wed, 26 Apr 2017 17:04:57 +0900 Subject: [PATCH 8/8] CI fixes. --- .../Objects/TaikoHitObjectDifficulty.cs | 4 +-- .../TaikoDifficultyCalculator.cs | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs index 5056a52346..c8bb73abbb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs @@ -27,14 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Objects /// internal double Strain = 1; - private double timeElapsed = 0; + private double timeElapsed; private int sameTypeSince = 1; private bool isRim => BaseHitObject is RimHit; public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) { - this.BaseHitObject = baseHitObject; + BaseHitObject = baseHitObject; } internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs index f80c777f05..33e9510f1c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko /// /// HitObjects are stored as a member variable. /// - private List difficultyHitObjects = new List(); + private readonly List difficultyHitObjects = new List(); public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap) @@ -64,22 +64,22 @@ namespace osu.Game.Rulesets.Taiko private bool calculateStrainValues() { // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator(); - - if (!hitObjectsEnumerator.MoveNext()) return false; - - TaikoHitObjectDifficulty currentHitObject = hitObjectsEnumerator.Current; - TaikoHitObjectDifficulty nextHitObject; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) + using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) { - nextHitObject = hitObjectsEnumerator.Current; - nextHitObject.CalculateStrains(currentHitObject, TimeRate); - currentHitObject = nextHitObject; - } + if (!hitObjectsEnumerator.MoveNext()) return false; - return true; + TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current; + + // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. + while (hitObjectsEnumerator.MoveNext()) + { + var next = hitObjectsEnumerator.Current; + next?.CalculateStrains(current, TimeRate); + current = next; + } + + return true; + } } private double calculateDifficulty()