mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 02:32:55 +08:00
Implement new difficulty calculator for Rulesets.Mania
This commit is contained in:
parent
a8faa942a6
commit
68725dc005
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(new ManiaRuleset(), beatmap);
|
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
|
||||||
|
|
||||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
public double GreatHitWindow;
|
public double GreatHitWindow;
|
||||||
|
|
||||||
public ManiaDifficultyAttributes(Mod[] mods, double starRating)
|
public ManiaDifficultyAttributes(Mod[] mods)
|
||||||
: base(mods, starRating)
|
: base(mods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
// 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.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.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||||
|
{
|
||||||
|
public class ManiaDifficultyCalculator : DifficultyCalculator
|
||||||
|
{
|
||||||
|
private const double star_scaling_factor = 0.018;
|
||||||
|
|
||||||
|
private int columnCount;
|
||||||
|
|
||||||
|
private readonly bool isForCurrentRuleset;
|
||||||
|
|
||||||
|
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
|
: base(ruleset, beatmap)
|
||||||
|
{
|
||||||
|
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate)
|
||||||
|
{
|
||||||
|
var maniaAttributes = (ManiaDifficultyAttributes)attributes;
|
||||||
|
|
||||||
|
var overallStrain = skills.OfType<Overall>().Single().DifficultyValue();
|
||||||
|
var highestIndividual = skills.OfType<Individual>().Max(s => s.DifficultyValue());
|
||||||
|
|
||||||
|
maniaAttributes.StarRating = (overallStrain + highestIndividual) * star_scaling_factor;
|
||||||
|
|
||||||
|
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||||
|
maniaAttributes.GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate)
|
||||||
|
{
|
||||||
|
columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||||
|
|
||||||
|
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||||
|
yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], timeRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Skill[] CreateSkills()
|
||||||
|
{
|
||||||
|
var skills = new List<Skill> { new Overall(columnCount) };
|
||||||
|
|
||||||
|
for (int i = 0; i < columnCount; i++)
|
||||||
|
skills.Add(new Individual(i, columnCount));
|
||||||
|
|
||||||
|
return skills.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new ManiaDifficultyAttributes(mods);
|
||||||
|
|
||||||
|
protected override Mod[] DifficultyAdjustmentMods
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var mods = new Mod[]
|
||||||
|
{
|
||||||
|
new ManiaModDoubleTime(),
|
||||||
|
new ManiaModHalfTime(),
|
||||||
|
new ManiaModEasy(),
|
||||||
|
new ManiaModHardRock(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isForCurrentRuleset)
|
||||||
|
return mods;
|
||||||
|
|
||||||
|
// if we are a convert, we can be played in any key mod.
|
||||||
|
return mods.Concat(new Mod[]
|
||||||
|
{
|
||||||
|
new ManiaModKey1(),
|
||||||
|
new ManiaModKey2(),
|
||||||
|
new ManiaModKey3(),
|
||||||
|
new ManiaModKey4(),
|
||||||
|
new ManiaModKey5(),
|
||||||
|
new ManiaModKey6(),
|
||||||
|
new ManiaModKey7(),
|
||||||
|
new ManiaModKey8(),
|
||||||
|
new ManiaModKey9(),
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,173 +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 System.Linq;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
|
||||||
{
|
|
||||||
internal class ManiaLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
|
||||||
{
|
|
||||||
private const double star_scaling_factor = 0.018;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step.
|
|
||||||
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
|
|
||||||
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
|
|
||||||
/// </summary>
|
|
||||||
private const double strain_step = 400;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The weighting of each strain value decays to this number * it's previous value
|
|
||||||
/// </summary>
|
|
||||||
private const double decay_weight = 0.9;
|
|
||||||
|
|
||||||
private readonly bool isForCurrentRuleset;
|
|
||||||
|
|
||||||
public ManiaLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
|
||||||
: base(ruleset, beatmap)
|
|
||||||
{
|
|
||||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
|
||||||
{
|
|
||||||
if (!beatmap.HitObjects.Any())
|
|
||||||
return new ManiaDifficultyAttributes(mods, 0);
|
|
||||||
|
|
||||||
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
|
||||||
|
|
||||||
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
|
||||||
|
|
||||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
|
||||||
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
|
|
||||||
difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
|
|
||||||
|
|
||||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
|
||||||
return new ManiaDifficultyAttributes(mods, 0);
|
|
||||||
|
|
||||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
|
||||||
|
|
||||||
return new ManiaDifficultyAttributes(mods, starRating)
|
|
||||||
{
|
|
||||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
|
||||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
|
|
||||||
{
|
|
||||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
|
||||||
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
|
||||||
{
|
|
||||||
if (!hitObjectsEnumerator.MoveNext())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current;
|
|
||||||
|
|
||||||
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
|
|
||||||
while (hitObjectsEnumerator.MoveNext())
|
|
||||||
{
|
|
||||||
var next = hitObjectsEnumerator.Current;
|
|
||||||
next?.CalculateStrains(current, timeRate);
|
|
||||||
current = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private double calculateDifficulty(List<ManiaHitObjectDifficulty> objects, double timeRate)
|
|
||||||
{
|
|
||||||
double actualStrainStep = strain_step * timeRate;
|
|
||||||
|
|
||||||
// Find the highest strain value within each strain step
|
|
||||||
List<double> highestStrains = new List<double>();
|
|
||||||
double intervalEndTime = actualStrainStep;
|
|
||||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
|
||||||
|
|
||||||
ManiaHitObjectDifficulty previousHitObject = null;
|
|
||||||
foreach (var hitObject in objects)
|
|
||||||
{
|
|
||||||
// While we are beyond the current interval push the currently available maximum to our strain list
|
|
||||||
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
|
||||||
{
|
|
||||||
highestStrains.Add(maximumStrain);
|
|
||||||
|
|
||||||
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
|
|
||||||
// until the beginning of the next interval.
|
|
||||||
if (previousHitObject == null)
|
|
||||||
{
|
|
||||||
maximumStrain = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
|
||||||
double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
|
||||||
maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go to the next time interval
|
|
||||||
intervalEndTime += actualStrainStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain maximum strain
|
|
||||||
double strain = hitObject.IndividualStrain + hitObject.OverallStrain;
|
|
||||||
maximumStrain = Math.Max(strain, maximumStrain);
|
|
||||||
|
|
||||||
previousHitObject = hitObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the weighted sum over the highest strains for each interval
|
|
||||||
double difficulty = 0;
|
|
||||||
double weight = 1;
|
|
||||||
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
|
||||||
|
|
||||||
foreach (double strain in highestStrains)
|
|
||||||
{
|
|
||||||
difficulty += weight * strain;
|
|
||||||
weight *= decay_weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return difficulty;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var mods = new Mod[]
|
|
||||||
{
|
|
||||||
new ManiaModDoubleTime(),
|
|
||||||
new ManiaModHalfTime(),
|
|
||||||
new ManiaModEasy(),
|
|
||||||
new ManiaModHardRock(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isForCurrentRuleset)
|
|
||||||
return mods;
|
|
||||||
|
|
||||||
// if we are a convert, we can be played in any key mod.
|
|
||||||
return mods.Concat(new Mod[]
|
|
||||||
{
|
|
||||||
new ManiaModKey1(),
|
|
||||||
new ManiaModKey2(),
|
|
||||||
new ManiaModKey3(),
|
|
||||||
new ManiaModKey4(),
|
|
||||||
new ManiaModKey5(),
|
|
||||||
new ManiaModKey6(),
|
|
||||||
new ManiaModKey7(),
|
|
||||||
new ManiaModKey8(),
|
|
||||||
new ManiaModKey9(),
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing
|
||||||
|
{
|
||||||
|
public class ManiaDifficultyHitObject : DifficultyHitObject
|
||||||
|
{
|
||||||
|
public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject;
|
||||||
|
|
||||||
|
public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate)
|
||||||
|
: base(hitObject, lastObject, timeRate)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
Normal file
47
osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||||
|
{
|
||||||
|
public class Individual : Skill
|
||||||
|
{
|
||||||
|
protected override double SkillMultiplier => 1;
|
||||||
|
protected override double StrainDecayBase => 0.125;
|
||||||
|
|
||||||
|
private readonly double[] holdEndTimes;
|
||||||
|
|
||||||
|
private readonly int column;
|
||||||
|
|
||||||
|
public Individual(int column, int columnCount)
|
||||||
|
{
|
||||||
|
this.column = column;
|
||||||
|
|
||||||
|
holdEndTimes = new double[columnCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||||
|
var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (maniaCurrent.BaseObject.Column != column)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// We give a slight bonus if something is held meanwhile
|
||||||
|
return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
Normal file
56
osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||||
|
{
|
||||||
|
public class Overall : Skill
|
||||||
|
{
|
||||||
|
protected override double SkillMultiplier => 1;
|
||||||
|
protected override double StrainDecayBase => 0.3;
|
||||||
|
|
||||||
|
private readonly double[] holdEndTimes;
|
||||||
|
|
||||||
|
private readonly int columnCount;
|
||||||
|
|
||||||
|
public Overall(int columnCount)
|
||||||
|
{
|
||||||
|
this.columnCount = columnCount;
|
||||||
|
|
||||||
|
holdEndTimes = new double[columnCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||||
|
var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
|
||||||
|
|
||||||
|
double holdFactor = 1.0; // Factor in case something else is held
|
||||||
|
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
||||||
|
|
||||||
|
for (int i = 0; i < columnCount; i++)
|
||||||
|
{
|
||||||
|
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
||||||
|
if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i])
|
||||||
|
holdAddition = 1.0;
|
||||||
|
|
||||||
|
// ... this addition only is valid if there is _no_ other note with the same ending.
|
||||||
|
// Releasing multiple notes at the same time is just as easy as releasing one
|
||||||
|
if (endTime == holdEndTimes[i])
|
||||||
|
holdAddition = 0;
|
||||||
|
|
||||||
|
// We give a slight bonus if something is held meanwhile
|
||||||
|
if (holdEndTimes[i] > endTime)
|
||||||
|
holdFactor = 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
|
||||||
|
|
||||||
|
return (1 + holdAddition) * holdFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
||||||
|
|
||||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(this, beatmap);
|
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override int? LegacyID => 3;
|
public override int? LegacyID => 3;
|
||||||
|
|
||||||
|
@ -1,112 +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 osu.Game.Rulesets.Objects.Types;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects
|
|
||||||
{
|
|
||||||
internal class ManiaHitObjectDifficulty
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Factor by how much individual / overall strain decays per second.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These values are results of tweaking a lot and taking into account general feedback.
|
|
||||||
/// </remarks>
|
|
||||||
internal const double INDIVIDUAL_DECAY_BASE = 0.125;
|
|
||||||
internal const double OVERALL_DECAY_BASE = 0.30;
|
|
||||||
|
|
||||||
internal ManiaHitObject BaseHitObject;
|
|
||||||
|
|
||||||
private readonly int beatmapColumnCount;
|
|
||||||
|
|
||||||
private readonly double endTime;
|
|
||||||
private readonly double[] heldUntil;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Measures jacks or more generally: repeated presses of the same button
|
|
||||||
/// </summary>
|
|
||||||
private readonly double[] individualStrains;
|
|
||||||
|
|
||||||
internal double IndividualStrain
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return individualStrains[BaseHitObject.Column];
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
individualStrains[BaseHitObject.Column] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Measures note density in a way
|
|
||||||
/// </summary>
|
|
||||||
internal double OverallStrain = 1;
|
|
||||||
|
|
||||||
public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount)
|
|
||||||
{
|
|
||||||
BaseHitObject = baseHitObject;
|
|
||||||
|
|
||||||
endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime;
|
|
||||||
|
|
||||||
beatmapColumnCount = columnCount;
|
|
||||||
heldUntil = new double[beatmapColumnCount];
|
|
||||||
individualStrains = new double[beatmapColumnCount];
|
|
||||||
|
|
||||||
for (int i = 0; i < beatmapColumnCount; ++i)
|
|
||||||
{
|
|
||||||
individualStrains[i] = 0;
|
|
||||||
heldUntil[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate)
|
|
||||||
{
|
|
||||||
// TODO: Factor in holds
|
|
||||||
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
|
|
||||||
double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000);
|
|
||||||
double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000);
|
|
||||||
|
|
||||||
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
|
|
||||||
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
|
||||||
|
|
||||||
// Fill up the heldUntil array
|
|
||||||
for (int i = 0; i < beatmapColumnCount; ++i)
|
|
||||||
{
|
|
||||||
heldUntil[i] = previousHitObject.heldUntil[i];
|
|
||||||
|
|
||||||
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
|
||||||
if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i])
|
|
||||||
{
|
|
||||||
holdAddition = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1
|
|
||||||
if (endTime == heldUntil[i])
|
|
||||||
{
|
|
||||||
holdAddition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We give a slight bonus to everything if something is held meanwhile
|
|
||||||
if (heldUntil[i] > endTime)
|
|
||||||
{
|
|
||||||
holdFactor = 1.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decay individual strains
|
|
||||||
individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay;
|
|
||||||
}
|
|
||||||
|
|
||||||
heldUntil[BaseHitObject.Column] = endTime;
|
|
||||||
|
|
||||||
// Increase individual strain in own column
|
|
||||||
IndividualStrain += 2.0 * holdFactor;
|
|
||||||
|
|
||||||
OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user