mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 10:02:59 +08:00
Store hitobject history in the hitobject
This commit is contained in:
parent
d84119ac06
commit
26985ca8af
@ -49,6 +49,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
CatchHitObject lastObject = null;
|
CatchHitObject lastObject = null;
|
||||||
|
|
||||||
|
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
||||||
foreach (var hitObject in beatmap.HitObjects
|
foreach (var hitObject in beatmap.HitObjects
|
||||||
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj })
|
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj })
|
||||||
@ -60,10 +63,15 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (lastObject != null)
|
if (lastObject != null)
|
||||||
yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatcherWidth);
|
{
|
||||||
|
objects.Add(new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatcherWidth, objects, index));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
lastObject = hitObject;
|
lastObject = hitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -24,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly double StrainTime;
|
public readonly double StrainTime;
|
||||||
|
|
||||||
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth, List<DifficultyHitObject> objects, int position)
|
||||||
: base(hitObject, lastObject, clockRate)
|
: base(hitObject, lastObject, clockRate, objects, position)
|
||||||
{
|
{
|
||||||
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
float scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
float scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
||||||
|
@ -62,8 +62,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
|
|
||||||
LegacySortHelper<HitObject>.Sort(sortedObjects, Comparer<HitObject>.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime)));
|
LegacySortHelper<HitObject>.Sort(sortedObjects, Comparer<HitObject>.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime)));
|
||||||
|
|
||||||
|
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||||
|
|
||||||
for (int i = 1; i < sortedObjects.Length; i++)
|
for (int i = 1; i < sortedObjects.Length; i++)
|
||||||
yield return new ManiaDifficultyHitObject(sortedObjects[i], sortedObjects[i - 1], clockRate);
|
objects.Add(new ManiaDifficultyHitObject(sortedObjects[i], sortedObjects[i - 1], clockRate, objects, i - 1));
|
||||||
|
|
||||||
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
|
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -11,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject;
|
public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject;
|
||||||
|
|
||||||
public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
|
public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, List<DifficultyHitObject> objects, int position)
|
||||||
: base(hitObject, lastObject, clockRate)
|
: base(hitObject, lastObject, clockRate, objects, position)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,9 +84,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
return individualStrain + overallStrain - CurrentStrain;
|
return individualStrain + overallStrain - CurrentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double CalculateInitialStrain(double offset)
|
protected override double CalculateInitialStrain(double offset, DifficultyHitObject current)
|
||||||
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
|
=> applyDecay(individualStrain, offset - current.Previous(0).StartTime, individual_decay_base)
|
||||||
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
|
+ applyDecay(overallStrain, offset - current.Previous(0).StartTime, overall_decay_base);
|
||||||
|
|
||||||
private double applyDecay(double value, double deltaTime, double decayBase)
|
private double applyDecay(double value, double deltaTime, double decayBase)
|
||||||
=> value * Math.Pow(decayBase, deltaTime / 1000);
|
=> value * Math.Pow(decayBase, deltaTime / 1000);
|
||||||
|
@ -87,16 +87,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
{
|
{
|
||||||
|
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||||
|
|
||||||
// The first jump is formed by the first two hitobjects of the map.
|
// The first jump is formed by the first two hitobjects of the map.
|
||||||
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
||||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||||
{
|
objects.Add(new OsuDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate, objects, i - 1));
|
||||||
var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null;
|
|
||||||
var last = beatmap.HitObjects[i - 1];
|
|
||||||
var current = beatmap.HitObjects[i];
|
|
||||||
|
|
||||||
yield return new OsuDifficultyHitObject(current, lastLast, last, clockRate);
|
return objects;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||||
{
|
{
|
||||||
private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
private const int normalised_radius = 50;
|
||||||
private const int min_delta_time = 25;
|
private const int min_delta_time = 25;
|
||||||
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
||||||
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
||||||
@ -74,10 +75,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
private readonly OsuHitObject lastLastObject;
|
private readonly OsuHitObject lastLastObject;
|
||||||
private readonly OsuHitObject lastObject;
|
private readonly OsuHitObject lastObject;
|
||||||
|
|
||||||
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate)
|
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, List<DifficultyHitObject> objects, int position)
|
||||||
: base(hitObject, lastObject, clockRate)
|
: base(hitObject, lastObject, clockRate, objects, position)
|
||||||
{
|
{
|
||||||
this.lastLastObject = (OsuHitObject)lastLastObject;
|
lastLastObject = position > 1 ? (OsuHitObject)Previous(1).BaseObject : null;
|
||||||
this.lastObject = (OsuHitObject)lastObject;
|
this.lastObject = (OsuHitObject)lastObject;
|
||||||
|
|
||||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||||
|
@ -22,8 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private readonly bool withSliders;
|
private readonly bool withSliders;
|
||||||
|
|
||||||
protected override int HistoryLength => 2;
|
|
||||||
|
|
||||||
private const double wide_angle_multiplier = 1.5;
|
private const double wide_angle_multiplier = 1.5;
|
||||||
private const double acute_angle_multiplier = 2.0;
|
private const double acute_angle_multiplier = 2.0;
|
||||||
private const double slider_multiplier = 1.5;
|
private const double slider_multiplier = 1.5;
|
||||||
@ -36,12 +34,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double strainValueOf(DifficultyHitObject current)
|
private double strainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner)
|
if (current.BaseObject is Spinner || current.Position <= 1 || current.Previous(0).BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||||
var osuLastObj = (OsuDifficultyHitObject)Previous[0];
|
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||||
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
|
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
||||||
|
|
||||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
||||||
@ -152,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
|
||||||
protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);
|
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||||
|
|
||||||
protected override double StrainValueAt(DifficultyHitObject current)
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
private double skillMultiplier => 0.05;
|
private double skillMultiplier => 0.05;
|
||||||
private double strainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
protected override double DecayWeight => 1.0;
|
protected override double DecayWeight => 1.0;
|
||||||
protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
|
|
||||||
|
|
||||||
private readonly bool hidden;
|
private readonly bool hidden;
|
||||||
|
|
||||||
@ -51,9 +50,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
OsuDifficultyHitObject lastObj = osuCurrent;
|
OsuDifficultyHitObject lastObj = osuCurrent;
|
||||||
|
|
||||||
// This is iterating backwards in time from the current object.
|
// This is iterating backwards in time from the current object.
|
||||||
for (int i = 0; i < Previous.Count; i++)
|
for (int i = 0; i < Math.Min(current.Position, 10); i++)
|
||||||
{
|
{
|
||||||
var currentObj = (OsuDifficultyHitObject)Previous[i];
|
var currentObj = (OsuDifficultyHitObject)current.Previous(i);
|
||||||
var currentHitObject = (OsuHitObject)(currentObj.BaseObject);
|
var currentHitObject = (OsuHitObject)(currentObj.BaseObject);
|
||||||
|
|
||||||
if (!(currentObj.BaseObject is Spinner))
|
if (!(currentObj.BaseObject is Spinner))
|
||||||
@ -89,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
|
||||||
protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);
|
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||||
|
|
||||||
protected override double StrainValueAt(DifficultyHitObject current)
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
|
@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
protected override int ReducedSectionCount => 5;
|
protected override int ReducedSectionCount => 5;
|
||||||
protected override double DifficultyMultiplier => 1.04;
|
protected override double DifficultyMultiplier => 1.04;
|
||||||
protected override int HistoryLength => 32;
|
|
||||||
|
|
||||||
private readonly double greatWindow;
|
private readonly double greatWindow;
|
||||||
|
|
||||||
public Speed(Mod[] mods, double hitWindowGreat)
|
public Speed(Mod[] mods, double hitWindowGreat)
|
||||||
@ -55,20 +53,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
bool firstDeltaSwitch = false;
|
bool firstDeltaSwitch = false;
|
||||||
|
|
||||||
|
int historicalNoteCount = Math.Min(current.Position, 32);
|
||||||
|
|
||||||
int rhythmStart = 0;
|
int rhythmStart = 0;
|
||||||
|
|
||||||
while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max)
|
while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max)
|
||||||
rhythmStart++;
|
rhythmStart++;
|
||||||
|
|
||||||
for (int i = rhythmStart; i > 0; i--)
|
for (int i = rhythmStart; i > 0; i--)
|
||||||
{
|
{
|
||||||
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1];
|
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
|
||||||
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i];
|
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(i);
|
||||||
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1];
|
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(i + 1);
|
||||||
|
|
||||||
double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
|
double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
|
||||||
|
|
||||||
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
|
currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count.
|
||||||
|
|
||||||
double currDelta = currObj.StrainTime;
|
double currDelta = currObj.StrainTime;
|
||||||
double prevDelta = prevObj.StrainTime;
|
double prevDelta = prevObj.StrainTime;
|
||||||
@ -90,10 +90,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
if (current.Previous(i - 1).BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
||||||
effectiveRatio *= 0.125;
|
effectiveRatio *= 0.125;
|
||||||
|
|
||||||
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
if (current.Previous(i).BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
||||||
effectiveRatio *= 0.25;
|
effectiveRatio *= 0.25;
|
||||||
|
|
||||||
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
||||||
@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
// derive strainTime for calculation
|
// derive strainTime for calculation
|
||||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||||
var osuPrevObj = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null;
|
var osuPrevObj = current.Position > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
||||||
|
|
||||||
double strainTime = osuCurrObj.StrainTime;
|
double strainTime = osuCurrObj.StrainTime;
|
||||||
double greatWindowFull = greatWindow * 2;
|
double greatWindowFull = greatWindow * 2;
|
||||||
@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
|
||||||
protected override double CalculateInitialStrain(double time) => (currentStrain * currentRhythm) * strainDecay(time - Previous[0].StartTime);
|
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime);
|
||||||
|
|
||||||
protected override double StrainValueAt(DifficultyHitObject current)
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Utils;
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
@ -34,9 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of all <see cref="TaikoDifficultyHitObject"/>s in the map.
|
/// The list of all <see cref="TaikoDifficultyHitObject"/>s in the map.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<TaikoDifficultyHitObject> hitObjects;
|
private readonly List<DifficultyHitObject> hitObjects;
|
||||||
|
|
||||||
public StaminaCheeseDetector(List<TaikoDifficultyHitObject> hitObjects)
|
public StaminaCheeseDetector(List<DifficultyHitObject> hitObjects)
|
||||||
{
|
{
|
||||||
this.hitObjects = hitObjects;
|
this.hitObjects = hitObjects;
|
||||||
}
|
}
|
||||||
@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// <param name="patternLength">The length of a single repeating pattern to consider (triplets/quadruplets).</param>
|
/// <param name="patternLength">The length of a single repeating pattern to consider (triplets/quadruplets).</param>
|
||||||
private void findRolls(int patternLength)
|
private void findRolls(int patternLength)
|
||||||
{
|
{
|
||||||
var history = new LimitedCapacityQueue<TaikoDifficultyHitObject>(2 * patternLength);
|
var history = new LimitedCapacityQueue<DifficultyHitObject>(2 * patternLength);
|
||||||
|
|
||||||
// for convenience, we're tracking the index of the item *before* our suspected repeat's start,
|
// for convenience, we're tracking the index of the item *before* our suspected repeat's start,
|
||||||
// as that index can be simply subtracted from the current index to get the number of elements in between
|
// as that index can be simply subtracted from the current index to get the number of elements in between
|
||||||
@ -97,11 +98,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the objects stored in <paramref name="history"/> contain a repetition of a pattern of length <paramref name="patternLength"/>.
|
/// Determines whether the objects stored in <paramref name="history"/> contain a repetition of a pattern of length <paramref name="patternLength"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static bool containsPatternRepeat(LimitedCapacityQueue<TaikoDifficultyHitObject> history, int patternLength)
|
private static bool containsPatternRepeat(LimitedCapacityQueue<DifficultyHitObject> history, int patternLength)
|
||||||
{
|
{
|
||||||
for (int j = 0; j < patternLength; j++)
|
for (int j = 0; j < patternLength; j++)
|
||||||
{
|
{
|
||||||
if (history[j].HitType != history[j + patternLength].HitType)
|
if (((TaikoDifficultyHitObject)history[j]).HitType != ((TaikoDifficultyHitObject)history[j + patternLength]).HitType)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
|
|
||||||
for (int i = parity; i < hitObjects.Count; i += 2)
|
for (int i = parity; i < hitObjects.Count; i += 2)
|
||||||
{
|
{
|
||||||
if (hitObjects[i].HitType == type)
|
if (((TaikoDifficultyHitObject)hitObjects[i]).HitType == type)
|
||||||
tlLength += 2;
|
tlLength += 2;
|
||||||
else
|
else
|
||||||
tlLength = -2;
|
tlLength = -2;
|
||||||
@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
private void markObjectsAsCheese(int start, int end)
|
private void markObjectsAsCheese(int start, int end)
|
||||||
{
|
{
|
||||||
for (int i = start; i <= end; i++)
|
for (int i = start; i <= end; i++)
|
||||||
hitObjects[i].StaminaCheese = true;
|
((TaikoDifficultyHitObject)hitObjects[i]).StaminaCheese = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -24,11 +25,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly HitType? HitType;
|
public readonly HitType? HitType;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The index of the object in the beatmap.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int ObjectIndex;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the object should carry a penalty due to being hittable using special techniques
|
/// Whether the object should carry a penalty due to being hittable using special techniques
|
||||||
/// making it easier to do so.
|
/// making it easier to do so.
|
||||||
@ -42,16 +38,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="hitObject"/>.</param>
|
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="hitObject"/>.</param>
|
||||||
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
|
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
|
||||||
/// <param name="clockRate">The rate of the gameplay clock. Modified by speed-changing mods.</param>
|
/// <param name="clockRate">The rate of the gameplay clock. Modified by speed-changing mods.</param>
|
||||||
/// <param name="objectIndex">The index of the object in the beatmap.</param>
|
/// <param name="objects">The list of <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
||||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex)
|
/// <param name="position">The index of the object in the beatmap.</param>
|
||||||
: base(hitObject, lastObject, clockRate)
|
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, List<DifficultyHitObject> objects, int position)
|
||||||
|
: base(hitObject, lastObject, clockRate, objects, position)
|
||||||
{
|
{
|
||||||
var currentHit = hitObject as Hit;
|
var currentHit = hitObject as Hit;
|
||||||
|
|
||||||
Rhythm = getClosestRhythm(lastObject, lastLastObject, clockRate);
|
Rhythm = getClosestRhythm(lastObject, lastLastObject, clockRate);
|
||||||
HitType = currentHit?.Type;
|
HitType = currentHit?.Type;
|
||||||
|
|
||||||
ObjectIndex = objectIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
if (!samePattern(start, mostRecentPatternsToCompare))
|
if (!samePattern(start, mostRecentPatternsToCompare))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int notesSince = hitObject.ObjectIndex - rhythmHistory[start].ObjectIndex;
|
int notesSince = hitObject.Position - rhythmHistory[start].Position;
|
||||||
penalty *= repetitionPenalty(notesSince);
|
penalty *= repetitionPenalty(notesSince);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
|
|
||||||
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
||||||
|
|
||||||
if (hitObject.ObjectIndex % 2 == hand)
|
if (hitObject.Position % 2 == hand)
|
||||||
{
|
{
|
||||||
double objectStrain = 1;
|
double objectStrain = 1;
|
||||||
|
|
||||||
if (hitObject.ObjectIndex == 1)
|
if (hitObject.Position == 1)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration);
|
notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration);
|
||||||
|
@ -47,13 +47,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
{
|
{
|
||||||
List<TaikoDifficultyHitObject> taikoDifficultyHitObjects = new List<TaikoDifficultyHitObject>();
|
List<DifficultyHitObject> taikoDifficultyHitObjects = new List<DifficultyHitObject>();
|
||||||
|
|
||||||
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
taikoDifficultyHitObjects.Add(
|
taikoDifficultyHitObjects.Add(
|
||||||
new TaikoDifficultyHitObject(
|
new TaikoDifficultyHitObject(
|
||||||
beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i
|
beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, taikoDifficultyHitObjects, i - 2
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
||||||
@ -10,6 +11,13 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DifficultyHitObject
|
public class DifficultyHitObject
|
||||||
{
|
{
|
||||||
|
private readonly List<DifficultyHitObject> difficultyHitObjects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The position of this <see cref="DifficultyHitObject"/> in the <see cref="difficultyHitObjects"/> list.
|
||||||
|
/// </summary>
|
||||||
|
public int Position;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="HitObject"/> this <see cref="DifficultyHitObject"/> wraps.
|
/// The <see cref="HitObject"/> this <see cref="DifficultyHitObject"/> wraps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -41,13 +49,21 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
|||||||
/// <param name="hitObject">The <see cref="HitObject"/> which this <see cref="DifficultyHitObject"/> wraps.</param>
|
/// <param name="hitObject">The <see cref="HitObject"/> which this <see cref="DifficultyHitObject"/> wraps.</param>
|
||||||
/// <param name="lastObject">The last <see cref="HitObject"/> which occurs before <paramref name="hitObject"/> in the beatmap.</param>
|
/// <param name="lastObject">The last <see cref="HitObject"/> which occurs before <paramref name="hitObject"/> in the beatmap.</param>
|
||||||
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
||||||
public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
|
/// <param name="objects">The list of <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
||||||
|
/// <param name="position">The position of this <see cref="DifficultyHitObject"/> in the <see cref="difficultyHitObjects"/> list.</param>
|
||||||
|
public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, List<DifficultyHitObject> objects, int position)
|
||||||
{
|
{
|
||||||
|
difficultyHitObjects = objects;
|
||||||
|
Position = position;
|
||||||
BaseObject = hitObject;
|
BaseObject = hitObject;
|
||||||
LastObject = lastObject;
|
LastObject = lastObject;
|
||||||
DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate;
|
DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate;
|
||||||
StartTime = hitObject.StartTime / clockRate;
|
StartTime = hitObject.StartTime / clockRate;
|
||||||
EndTime = hitObject.GetEndTime() / clockRate;
|
EndTime = hitObject.GetEndTime() / clockRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DifficultyHitObject Previous(int backwardsIndex) => difficultyHitObjects[Position - (backwardsIndex + 1)];
|
||||||
|
|
||||||
|
public DifficultyHitObject Next(int forwardsIndex) => difficultyHitObjects[Position + (forwardsIndex + 1)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Utils;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Difficulty.Skills
|
namespace osu.Game.Rulesets.Difficulty.Skills
|
||||||
@ -12,21 +11,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
|||||||
/// A bare minimal abstract skill for fully custom skill implementations.
|
/// A bare minimal abstract skill for fully custom skill implementations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This class should be considered a "processing" class and not persisted, as it keeps references to
|
/// This class should be considered a "processing" class and not persisted.
|
||||||
/// gameplay objects after processing is run (see <see cref="Previous"/>).
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public abstract class Skill
|
public abstract class Skill
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// <see cref="DifficultyHitObject"/>s that were processed previously. They can affect the strain values of the following objects.
|
|
||||||
/// </summary>
|
|
||||||
protected readonly ReverseQueue<DifficultyHitObject> Previous;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Number of previous <see cref="DifficultyHitObject"/>s to keep inside the <see cref="Previous"/> queue.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual int HistoryLength => 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mods for use in skill calculations.
|
/// Mods for use in skill calculations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -37,17 +25,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
|||||||
protected Skill(Mod[] mods)
|
protected Skill(Mod[] mods)
|
||||||
{
|
{
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
Previous = new ReverseQueue<DifficultyHitObject>(HistoryLength + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ProcessInternal(DifficultyHitObject current)
|
internal void ProcessInternal(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
while (Previous.Count > HistoryLength)
|
|
||||||
Previous.Dequeue();
|
|
||||||
|
|
||||||
Process(current);
|
Process(current);
|
||||||
|
|
||||||
Previous.Enqueue(current);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double CalculateInitialStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
|
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => CurrentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||||
|
|
||||||
protected override double StrainValueAt(DifficultyHitObject current)
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
|
@ -47,13 +47,13 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
|||||||
protected sealed override void Process(DifficultyHitObject current)
|
protected sealed override void Process(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||||
if (Previous.Count == 0)
|
if (current.Position == 0)
|
||||||
currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||||
|
|
||||||
while (current.StartTime > currentSectionEnd)
|
while (current.StartTime > currentSectionEnd)
|
||||||
{
|
{
|
||||||
saveCurrentPeak();
|
saveCurrentPeak();
|
||||||
startNewSectionFrom(currentSectionEnd);
|
startNewSectionFrom(currentSectionEnd, current);
|
||||||
currentSectionEnd += SectionLength;
|
currentSectionEnd += SectionLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,19 +72,21 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
|||||||
/// Sets the initial strain level for a new section.
|
/// Sets the initial strain level for a new section.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The beginning of the new section in milliseconds.</param>
|
/// <param name="time">The beginning of the new section in milliseconds.</param>
|
||||||
private void startNewSectionFrom(double time)
|
/// <param name="current">The current hit object.</param>
|
||||||
|
private void startNewSectionFrom(double time, DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
// The maximum strain of the new section is not zero by default
|
// The maximum strain of the new section is not zero by default
|
||||||
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
||||||
currentSectionPeak = CalculateInitialStrain(time);
|
currentSectionPeak = CalculateInitialStrain(time, current);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the peak strain at a point in time.
|
/// Retrieves the peak strain at a point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve the peak strain at.</param>
|
/// <param name="time">The time to retrieve the peak strain at.</param>
|
||||||
|
/// <param name="current">The current hit object.</param>
|
||||||
/// <returns>The peak strain.</returns>
|
/// <returns>The peak strain.</returns>
|
||||||
protected abstract double CalculateInitialStrain(double time);
|
protected abstract double CalculateInitialStrain(double time, DifficultyHitObject current);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap,
|
/// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap,
|
||||||
|
Loading…
Reference in New Issue
Block a user