mirror of
https://github.com/ppy/osu.git
synced 2024-11-12 01:47:24 +08:00
Merge branch 'master' into new-diffcalc-catch
This commit is contained in:
commit
cf3e13c984
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(new OsuRuleset(), beatmap);
|
||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
@ -13,10 +12,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
public double ApproachRate;
|
||||
public double OverallDifficulty;
|
||||
public int MaxCombo;
|
||||
|
||||
public OsuDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
@ -13,53 +16,19 @@ using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const int section_length = 400;
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
|
||||
public OsuLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new OsuDifficultyAttributes(mods, 0);
|
||||
|
||||
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), clockRate);
|
||||
Skill[] skills =
|
||||
{
|
||||
new Aim(),
|
||||
new Speed()
|
||||
};
|
||||
|
||||
double sectionLength = section_length * clockRate;
|
||||
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
||||
|
||||
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
|
||||
{
|
||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||
{
|
||||
foreach (Skill s in skills)
|
||||
{
|
||||
s.SaveCurrentPeak();
|
||||
s.StartNewSectionFrom(currentSectionEnd);
|
||||
}
|
||||
|
||||
currentSectionEnd += sectionLength;
|
||||
}
|
||||
|
||||
foreach (Skill s in skills)
|
||||
s.Process(h);
|
||||
}
|
||||
|
||||
// The peak strain will not be saved for the last section in the above loop
|
||||
foreach (Skill s in skills)
|
||||
s.SaveCurrentPeak();
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new OsuDifficultyAttributes { Mods = mods };
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
@ -73,8 +42,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
|
||||
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||
|
||||
return new OsuDifficultyAttributes(mods, starRating)
|
||||
return new OsuDifficultyAttributes
|
||||
{
|
||||
StarRating = starRating,
|
||||
Mods = mods,
|
||||
AimStrain = aimRating,
|
||||
SpeedStrain = speedRating,
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
@ -83,6 +54,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
};
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
// 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.
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||
{
|
||||
new Aim(),
|
||||
new Speed()
|
||||
};
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
{
|
||||
new OsuModDoubleTime(),
|
@ -1,50 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
|
||||
/// which contains extra data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
|
||||
{
|
||||
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
|
||||
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
|
||||
{
|
||||
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
|
||||
// This should probably happen before the objects reach the difficulty calculator.
|
||||
difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
|
||||
/// </summary>
|
||||
public IEnumerator<OsuDifficultyHitObject> GetEnumerator() => difficultyObjects;
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
|
||||
{
|
||||
// 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.
|
||||
for (int i = 1; i < objects.Count; i++)
|
||||
{
|
||||
var lastLast = i > 1 ? objects[i - 2] : null;
|
||||
var last = objects[i - 1];
|
||||
var current = objects[i];
|
||||
|
||||
yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +1,20 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// A wrapper around <see cref="OsuHitObject"/> extending it with additional data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public class OsuDifficultyHitObject
|
||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const int normalized_radius = 52;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="OsuHitObject"/> this <see cref="OsuDifficultyHitObject"/> refers to.
|
||||
/// </summary>
|
||||
public OsuHitObject BaseObject { get; }
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
@ -30,40 +26,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public double TravelDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the StartTime of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double DeltaTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
|
||||
/// </summary>
|
||||
public double StrainTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Calculated as the angle between the circles (current-2, current-1, current).
|
||||
/// </summary>
|
||||
public double? Angle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
private readonly OsuHitObject lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
private readonly double timeRate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object calculating extra data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public OsuDifficultyHitObject(OsuHitObject lastLastObject, OsuHitObject lastObject, OsuHitObject currentObject, double timeRate)
|
||||
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
this.lastLastObject = lastLastObject;
|
||||
this.lastObject = lastObject;
|
||||
this.timeRate = timeRate;
|
||||
|
||||
BaseObject = currentObject;
|
||||
this.lastLastObject = (OsuHitObject)lastLastObject;
|
||||
this.lastObject = (OsuHitObject)lastObject;
|
||||
|
||||
setDistances();
|
||||
setTimingValues();
|
||||
// Calculate angle here
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(50, DeltaTime);
|
||||
}
|
||||
|
||||
private void setDistances()
|
||||
@ -102,14 +88,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
}
|
||||
}
|
||||
|
||||
private void setTimingValues()
|
||||
{
|
||||
DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate;
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(50, DeltaTime);
|
||||
}
|
||||
|
||||
private void computeSliderCursorPosition(Slider slider)
|
||||
{
|
||||
if (slider.LazyEndPosition != null)
|
||||
|
@ -2,7 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
@ -17,33 +20,40 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
protected override double SkillMultiplier => 26.25;
|
||||
protected override double StrainDecayBase => 0.15;
|
||||
|
||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
|
||||
double result = 0;
|
||||
|
||||
const double scale = 90;
|
||||
|
||||
double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||
|
||||
if (Previous.Count > 0)
|
||||
{
|
||||
if (current.Angle != null && current.Angle.Value > angle_bonus_begin)
|
||||
var osuPrevious = (OsuDifficultyHitObject)Previous[0];
|
||||
|
||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
|
||||
{
|
||||
const double scale = 90;
|
||||
|
||||
var angleBonus = Math.Sqrt(
|
||||
Math.Max(Previous[0].JumpDistance - scale, 0)
|
||||
* Math.Pow(Math.Sin(current.Angle.Value - angle_bonus_begin), 2)
|
||||
* Math.Max(current.JumpDistance - scale, 0));
|
||||
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, Previous[0].StrainTime);
|
||||
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
||||
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
||||
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
||||
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
||||
}
|
||||
}
|
||||
|
||||
double jumpDistanceExp = applyDiminishingExp(current.JumpDistance);
|
||||
double travelDistanceExp = applyDiminishingExp(current.TravelDistance);
|
||||
double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
|
||||
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
||||
|
||||
return Math.Max(
|
||||
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(current.StrainTime, timing_threshold),
|
||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / current.StrainTime
|
||||
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
||||
);
|
||||
}
|
||||
|
||||
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||
}
|
||||
}
|
||||
|
@ -1,103 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to processes strain values of <see cref="OsuDifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
|
||||
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
||||
/// </summary>
|
||||
public abstract class Skill
|
||||
{
|
||||
protected const double SINGLE_SPACING_THRESHOLD = 125;
|
||||
protected const double STREAM_SPACING_THRESHOLD = 110;
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines how quickly strain decays for the given skill.
|
||||
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
|
||||
/// </summary>
|
||||
protected abstract double StrainDecayBase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="OsuDifficultyHitObject"/>s that were processed previously. They can affect the strain values of the following objects.
|
||||
/// </summary>
|
||||
protected readonly 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 currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
|
||||
private readonly List<double> strainPeaks = new List<double>();
|
||||
|
||||
/// <summary>
|
||||
/// Process an <see cref="OsuDifficultyHitObject"/> and update current strain values accordingly.
|
||||
/// </summary>
|
||||
public void Process(OsuDifficultyHitObject current)
|
||||
{
|
||||
currentStrain *= strainDecay(current.DeltaTime);
|
||||
if (!(current.BaseObject is Spinner))
|
||||
currentStrain += StrainValueOf(current) * SkillMultiplier;
|
||||
|
||||
currentSectionPeak = Math.Max(currentStrain, currentSectionPeak);
|
||||
|
||||
Previous.Push(current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
|
||||
/// </summary>
|
||||
public void SaveCurrentPeak()
|
||||
{
|
||||
if (Previous.Count > 0)
|
||||
strainPeaks.Add(currentSectionPeak);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial strain level for a new section.
|
||||
/// </summary>
|
||||
/// <param name="offset">The beginning of the new section in milliseconds</param>
|
||||
public void StartNewSectionFrom(double offset)
|
||||
{
|
||||
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
|
||||
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
||||
if (Previous.Count > 0)
|
||||
currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the calculated difficulty value representing all processed <see cref="OsuDifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
public double DifficultyValue()
|
||||
{
|
||||
strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
foreach (double strain in strainPeaks)
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the strain value of an <see cref="OsuDifficultyHitObject"/>. This value is affected by previously processed objects.
|
||||
/// </summary>
|
||||
protected abstract double StrainValueOf(OsuDifficultyHitObject current);
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
||||
}
|
||||
}
|
@ -2,7 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
@ -11,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Speed : Skill
|
||||
{
|
||||
private const double single_spacing_threshold = 125;
|
||||
|
||||
private const double angle_bonus_begin = 5 * Math.PI / 6;
|
||||
private const double pi_over_4 = Math.PI / 4;
|
||||
private const double pi_over_2 = Math.PI / 2;
|
||||
@ -22,9 +27,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
private const double max_speed_bonus = 45; // ~330BPM
|
||||
private const double speed_balancing_factor = 40;
|
||||
|
||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
double distance = Math.Min(SINGLE_SPACING_THRESHOLD, current.TravelDistance + current.JumpDistance);
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
|
||||
double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
|
||||
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
|
||||
|
||||
double speedBonus = 1.0;
|
||||
@ -32,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
|
||||
|
||||
double angleBonus = 1.0;
|
||||
if (current.Angle != null && current.Angle.Value < angle_bonus_begin)
|
||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin)
|
||||
{
|
||||
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - current.Angle.Value)), 2) / 3.57;
|
||||
if (current.Angle.Value < pi_over_2)
|
||||
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57;
|
||||
if (osuCurrent.Angle.Value < pi_over_2)
|
||||
{
|
||||
angleBonus = 1.28;
|
||||
if (distance < 90 && current.Angle.Value < pi_over_4)
|
||||
if (distance < 90 && osuCurrent.Angle.Value < pi_over_4)
|
||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
|
||||
else if (distance < 90)
|
||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - current.Angle.Value) / pi_over_4);
|
||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4);
|
||||
}
|
||||
}
|
||||
|
||||
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / SINGLE_SPACING_THRESHOLD, 3.5)) / current.StrainTime;
|
||||
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,86 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class History<T> : IEnumerable<T>
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
|
||||
private readonly T[] array;
|
||||
private readonly int capacity;
|
||||
private int marker; // Marks the position of the most recently added item.
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (capacity < 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
this.capacity = capacity;
|
||||
array = new T[capacity];
|
||||
marker = capacity; // Set marker to the end of the array, outside of the indexed range by one.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The most recently added item is returned at index 0.
|
||||
/// </summary>
|
||||
public T this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i < 0 || i > Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
i += marker;
|
||||
if (i > capacity - 1)
|
||||
i -= capacity;
|
||||
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the item as the most recent one in the history.
|
||||
/// The oldest item is disposed if the history is full.
|
||||
/// </summary>
|
||||
public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition.
|
||||
{
|
||||
if (marker == 0)
|
||||
marker = capacity - 1;
|
||||
else
|
||||
--marker;
|
||||
|
||||
array[marker] = item;
|
||||
|
||||
if (Count < capacity)
|
||||
++Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator which enumerates items in the history starting from the most recently added one.
|
||||
/// </summary>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = marker; i < capacity; ++i)
|
||||
yield return array[i];
|
||||
|
||||
if (Count == capacity)
|
||||
for (int i = 0; i < marker; ++i)
|
||||
yield return array[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
|
||||
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(this, beatmap);
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user