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

Merge remote-tracking branch 'apollo/dho' into colour-rework

This commit is contained in:
vun 2022-06-02 18:34:46 +08:00
commit c9877084c4
18 changed files with 88 additions and 97 deletions

View File

@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{ {
CatchHitObject lastObject = null; CatchHitObject lastObject = null;
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
// 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 +62,12 @@ 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, objects.Count));
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)

View File

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

View File

@ -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, objects.Count));
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.

View File

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

View File

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

View File

@ -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, objects.Count));
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)

View File

@ -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;
@ -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 = (OsuHitObject)Previous(1)?.BaseObject;
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.

View File

@ -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)
{ {

View File

@ -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)
{ {

View File

@ -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)
{ {

View File

@ -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;
@ -26,31 +27,25 @@ 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>
/// Creates a new difficulty hit object. /// Creates a new difficulty hit object.
/// </summary> /// </summary>
/// <param name="hitObject">The gameplay <see cref="HitObject"/> associated with this difficulty object.</param> /// <param name="hitObject">The gameplay <see cref="HitObject"/> associated with this difficulty object.</param>
/// <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="lastDifficulty">The <see cref="TaikoDifficultyHitObject"/> for <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, TaikoDifficultyHitObject lastDifficulty, double clockRate, int objectIndex) /// /// <param name="position">The position of this <see cref="DifficultyHitObject"/> in the <paramref name="objects"/> list.</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;
// Need to be done after HitType is set. // Need to be done after HitType is set.
Colour = TaikoDifficultyHitObjectColour.GetInstanceFor(this, lastDifficulty); Colour = TaikoDifficultyHitObjectColour.GetInstanceFor(this, (TaikoDifficultyHitObject) objects.LastOrDefault());
} }
/// <summary> /// <summary>

View File

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

View File

@ -46,27 +46,21 @@ 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> difficultyHitObject = new List<DifficultyHitObject>();
for (int i = 2; i < beatmap.HitObjects.Count; i++) for (int i = 2; i < beatmap.HitObjects.Count; i++)
{ {
taikoDifficultyHitObjects.Add( difficultyHitObject.Add(
new TaikoDifficultyHitObject( new TaikoDifficultyHitObject(
beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], taikoDifficultyHitObjects.DefaultIfEmpty(null).LastOrDefault(), clockRate, i beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, difficultyHitObject, difficultyHitObject.Count
) )
); );
} }
// Find repetition interval for the final TaikoDifficultyHitObjectColour // Find repetition interval for the final TaikoDifficultyHitObjectColour
// TODO: Might be a good idea to refactor this // TODO: Might be a good idea to refactor this
taikoDifficultyHitObjects.Last().Colour.FindRepetitionInterval(); ((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour.FindRepetitionInterval();
return difficultyHitObject;
taikoDifficultyHitObjects.ForEach((item) =>
{
Console.WriteLine($"{item.StartTime}, {item.Colour.GetHashCode()}, {item.Colour.Delta}, {item.Colour.DeltaRunLength}, {item.Colour.RepetitionInterval}");
});
return taikoDifficultyHitObjects;
} }
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)

View File

@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Difficulty
foreach (var skill in skills) foreach (var skill in skills)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
skill.ProcessInternal(hitObject); skill.Process(hitObject);
} }
} }
@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Difficulty
foreach (var skill in skills) foreach (var skill in skills)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
skill.ProcessInternal(hitObject); skill.Process(hitObject);
} }
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));

View File

@ -1,6 +1,8 @@
// 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 System.Linq;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Difficulty.Preprocessing namespace osu.Game.Rulesets.Difficulty.Preprocessing
@ -10,6 +12,13 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
/// </summary> /// </summary>
public class DifficultyHitObject public class DifficultyHitObject
{ {
private readonly IReadOnlyList<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 +50,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.ElementAtOrDefault(Position - (backwardsIndex + 1));
public DifficultyHitObject Next(int forwardsIndex) => difficultyHitObjects.ElementAtOrDefault(Position + (forwardsIndex + 1));
} }
} }

View File

@ -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,24 +25,13 @@ 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)
{
while (Previous.Count > HistoryLength)
Previous.Dequeue();
Process(current);
Previous.Enqueue(current);
} }
/// <summary> /// <summary>
/// Process a <see cref="DifficultyHitObject"/>. /// Process a <see cref="DifficultyHitObject"/>.
/// </summary> /// </summary>
/// <param name="current">The <see cref="DifficultyHitObject"/> to process.</param> /// <param name="current">The <see cref="DifficultyHitObject"/> to process.</param>
protected abstract void Process(DifficultyHitObject current); public abstract void Process(DifficultyHitObject current);
/// <summary> /// <summary>
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point. /// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.

View File

@ -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)
{ {

View File

@ -44,16 +44,16 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// <summary> /// <summary>
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly. /// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
/// </summary> /// </summary>
protected sealed override void Process(DifficultyHitObject current) public 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,