mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 11:12:59 +08:00
OsuDifficultyBeatmap enumeration logic made clearer, more documentation added.
This commit is contained in:
parent
afb4443763
commit
01585027b1
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
|
|||||||
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)
|
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)
|
||||||
{
|
{
|
||||||
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Objects);
|
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Objects);
|
||||||
Skill[] skills = new Skill[]
|
Skill[] skills =
|
||||||
{
|
{
|
||||||
new Aim(),
|
new Aim(),
|
||||||
new Speed()
|
new Speed()
|
||||||
|
@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
|
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
|
||||||
private readonly Queue<OsuDifficultyHitObject> onScreen = new Queue<OsuDifficultyHitObject>();
|
private readonly Queue<OsuDifficultyHitObject> onScreen = new Queue<OsuDifficultyHitObject>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an enumerable, which handles the preprocessing of HitObjects, getting them ready to be used in difficulty calculation.
|
||||||
|
/// </summary>
|
||||||
public OsuDifficultyBeatmap(List<OsuHitObject> objects)
|
public OsuDifficultyBeatmap(List<OsuHitObject> objects)
|
||||||
{
|
{
|
||||||
// Sort HitObjects by StartTime - they are not correctly ordered in some cases.
|
// Sort HitObjects by StartTime - they are not correctly ordered in some cases.
|
||||||
@ -20,38 +23,40 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
difficultyObjects = createDifficultyObjectEnumerator(objects);
|
difficultyObjects = createDifficultyObjectEnumerator(objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that enumerates all difficulty objects in the beatmap.
|
||||||
|
/// The inner loop adds notes that appear on screen into a queue until we need to hit the next note,
|
||||||
|
/// the outer loop returns notes from this queue one at a time, only after they had to be hit, and should no longer be on screen.
|
||||||
|
/// This means that we can loop through every object that is on screen at the time when a new one appears,
|
||||||
|
/// allowing us to determine a reading strain for the note that just appeared.
|
||||||
|
/// </summary>
|
||||||
public IEnumerator<OsuDifficultyHitObject> GetEnumerator()
|
public IEnumerator<OsuDifficultyHitObject> GetEnumerator()
|
||||||
{
|
{
|
||||||
do
|
while (true)
|
||||||
{
|
{
|
||||||
// Add upcoming notes to the queue until we have at least one note that had been hit and can be dequeued.
|
// Add upcoming notes to the queue until we have at least one note that had been hit and can be dequeued.
|
||||||
// This means there is always at least one note in the queue unless we reached the end of the map.
|
// This means there is always at least one note in the queue unless we reached the end of the map.
|
||||||
bool hasNext;
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
hasNext = difficultyObjects.MoveNext();
|
if (!difficultyObjects.MoveNext())
|
||||||
if (onScreen.Count == 0 && !hasNext)
|
break; // New notes can't be added anymore, but we still need to dequeue and return the ones already on screen.
|
||||||
yield break; // Stop if we have an empty enumerator.
|
|
||||||
|
|
||||||
if (hasNext)
|
OsuDifficultyHitObject latest = difficultyObjects.Current;
|
||||||
|
// Calculate flow values here
|
||||||
|
|
||||||
|
foreach (OsuDifficultyHitObject h in onScreen)
|
||||||
{
|
{
|
||||||
OsuDifficultyHitObject latest = difficultyObjects.Current;
|
h.TimeUntilHit -= latest.DeltaTime;
|
||||||
// Calculate flow values here
|
// Calculate reading strain here
|
||||||
|
|
||||||
foreach (OsuDifficultyHitObject h in onScreen)
|
|
||||||
{
|
|
||||||
h.MsUntilHit -= latest.Ms;
|
|
||||||
// Calculate reading strain here
|
|
||||||
}
|
|
||||||
|
|
||||||
onScreen.Enqueue(latest);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
while (onScreen.Peek().MsUntilHit > 0 && hasNext); // Keep adding new notes on screen while there is still time before we have to hit the next one.
|
|
||||||
|
|
||||||
|
onScreen.Enqueue(latest);
|
||||||
|
}
|
||||||
|
while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new notes on screen while there is still time before we have to hit the next one.
|
||||||
|
|
||||||
|
if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the notes.
|
||||||
yield return onScreen.Dequeue(); // Remove and return notes one by one that had to be hit before the latest note appeared.
|
yield return onScreen.Dequeue(); // Remove and return notes one by one that had to be hit before the latest note appeared.
|
||||||
}
|
}
|
||||||
while (onScreen.Count > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
@ -8,6 +8,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyHitObject
|
public class OsuDifficultyHitObject
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current note. Attributes are calculated relative to previous ones.
|
||||||
|
/// </summary>
|
||||||
public OsuHitObject BaseObject { get; }
|
public OsuHitObject BaseObject { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -18,14 +21,20 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Milliseconds elapsed since the StartTime of the previous note.
|
/// Milliseconds elapsed since the StartTime of the previous note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Ms { get; private set; }
|
public double DeltaTime { get; private set; }
|
||||||
|
|
||||||
public double MsUntilHit { get; set; }
|
/// <summary>
|
||||||
|
/// Number of milliseconds until the note has to be hit.
|
||||||
|
/// </summary>
|
||||||
|
public double TimeUntilHit { get; set; }
|
||||||
|
|
||||||
private const int normalized_radius = 52;
|
private const int normalized_radius = 52;
|
||||||
|
|
||||||
private readonly OsuHitObject[] t;
|
private readonly OsuHitObject[] t;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a wrapper around a HitObject calculating additional data required for difficulty calculation.
|
||||||
|
/// </summary>
|
||||||
public OsuDifficultyHitObject(OsuHitObject[] triangle)
|
public OsuDifficultyHitObject(OsuHitObject[] triangle)
|
||||||
{
|
{
|
||||||
t = triangle;
|
t = triangle;
|
||||||
@ -51,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
private void setTimingValues()
|
private void setTimingValues()
|
||||||
{
|
{
|
||||||
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
|
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
|
||||||
Ms = Math.Max(40, t[0].StartTime - t[1].StartTime);
|
DeltaTime = Math.Max(40, t[0].StartTime - t[1].StartTime);
|
||||||
MsUntilHit = 450; // BaseObject.PreEmpt;
|
TimeUntilHit = 450; // BaseObject.PreEmpt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
|
|||||||
protected override double SkillMultiplier => 26.25;
|
protected override double SkillMultiplier => 26.25;
|
||||||
protected override double StrainDecayBase => 0.15;
|
protected override double StrainDecayBase => 0.15;
|
||||||
|
|
||||||
protected override double StrainValue() => Math.Pow(Current.Distance, 0.99) / Current.Ms;
|
protected override double StrainValue() => Math.Pow(Current.Distance, 0.99) / Current.DeltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,25 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
|
|||||||
{
|
{
|
||||||
public abstract class Skill
|
public abstract class Skill
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
|
||||||
|
/// </summary>
|
||||||
protected abstract double SkillMultiplier { get; }
|
protected abstract double SkillMultiplier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines how quickly strain decays for the given skill.
|
||||||
|
/// For example a value of 0.15 indicates that strain decays to 15% of it's original value in one second.
|
||||||
|
/// </summary>
|
||||||
protected abstract double StrainDecayBase { get; }
|
protected abstract double StrainDecayBase { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The note that will be processed.
|
||||||
|
/// </summary>
|
||||||
protected OsuDifficultyHitObject Current;
|
protected OsuDifficultyHitObject Current;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notes that were processed previously. They can affect the strain value of the current note.
|
||||||
|
/// </summary>
|
||||||
protected History<OsuDifficultyHitObject> Previous = new History<OsuDifficultyHitObject>(2); // Contained objects not used yet
|
protected History<OsuDifficultyHitObject> Previous = new History<OsuDifficultyHitObject>(2); // Contained objects not used yet
|
||||||
|
|
||||||
private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap.
|
private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap.
|
||||||
@ -28,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
|
|||||||
{
|
{
|
||||||
Current = h;
|
Current = h;
|
||||||
|
|
||||||
currentStrain *= strainDecay(Current.Ms);
|
currentStrain *= strainDecay(Current.DeltaTime);
|
||||||
if (!(Current.BaseObject is Spinner))
|
if (!(Current.BaseObject is Spinner))
|
||||||
currentStrain += StrainValue() * SkillMultiplier;
|
currentStrain += StrainValue() * SkillMultiplier;
|
||||||
|
|
||||||
@ -78,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
|
|||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the strain value of the current note. This value is affected by previous notes.
|
||||||
|
/// </summary>
|
||||||
protected abstract double StrainValue();
|
protected abstract double StrainValue();
|
||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
|
|||||||
else
|
else
|
||||||
speedValue = 0.95;
|
speedValue = 0.95;
|
||||||
|
|
||||||
return speedValue / Current.Ms;
|
return speedValue / Current.DeltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ using System.Collections.Generic;
|
|||||||
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
|
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An indexed stack with Push() only, which disposes items at the bottom once the size limit has been reached.
|
/// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full.
|
||||||
/// Indexing starts at the top of the stack.
|
/// Indexing starts at the top of the stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class History<T> : IEnumerable<T>
|
public class History<T> : IEnumerable<T>
|
||||||
@ -16,17 +16,28 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
|
|||||||
public int Count { get; private set; }
|
public int Count { get; private set; }
|
||||||
|
|
||||||
private readonly T[] array;
|
private readonly T[] array;
|
||||||
private readonly int size;
|
private readonly int capacity;
|
||||||
private int marker; // Marks the position of the most recently added item.
|
private int marker; // Marks the position of the most recently added item.
|
||||||
|
|
||||||
public History(int size)
|
//
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the History class that is empty and has the specified capacity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">The number of items the History can hold.</param>
|
||||||
|
public History(int capacity)
|
||||||
{
|
{
|
||||||
this.size = size;
|
if (capacity < 0)
|
||||||
array = new T[size];
|
throw new ArgumentOutOfRangeException();
|
||||||
marker = size; // Set marker to the end of the array, outside of the indexed range by one.
|
|
||||||
|
this.capacity = capacity;
|
||||||
|
array = new T[capacity];
|
||||||
|
marker = capacity; // Set marker to the end of the array, outside of the indexed range by one.
|
||||||
}
|
}
|
||||||
|
|
||||||
public T this[int i] // Index 0 returns the most recently added item.
|
/// <summary>
|
||||||
|
/// The most recently added item is returned at index 0.
|
||||||
|
/// </summary>
|
||||||
|
public T this[int i]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@ -34,8 +45,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
|
|||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
i += marker;
|
i += marker;
|
||||||
if (i > size - 1)
|
if (i > capacity - 1)
|
||||||
i -= size;
|
i -= capacity;
|
||||||
|
|
||||||
return array[i];
|
return array[i];
|
||||||
}
|
}
|
||||||
@ -48,22 +59,25 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
|
|||||||
public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition.
|
public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition.
|
||||||
{
|
{
|
||||||
if (marker == 0)
|
if (marker == 0)
|
||||||
marker = size - 1;
|
marker = capacity - 1;
|
||||||
else
|
else
|
||||||
--marker;
|
--marker;
|
||||||
|
|
||||||
array[marker] = item;
|
array[marker] = item;
|
||||||
|
|
||||||
if (Count < size)
|
if (Count < capacity)
|
||||||
++Count;
|
++Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator which enumerates items in the history starting from the most recently added item.
|
||||||
|
/// </summary>
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (int i = marker; i < size; ++i)
|
for (int i = marker; i < capacity; ++i)
|
||||||
yield return array[i];
|
yield return array[i];
|
||||||
|
|
||||||
if (Count == size)
|
if (Count == capacity)
|
||||||
for (int i = 0; i < marker; ++i)
|
for (int i = 0; i < marker; ++i)
|
||||||
yield return array[i];
|
yield return array[i];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user