Created
January 26, 2018 03:05
-
-
Save smoogipoo/eca412396557825e5656360df6a813bd to your computer and use it in GitHub Desktop.
Mania DiffCalc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Text; | |
using osu.GameModes.Edit.AiMod; | |
using osu.GameModes.Play; | |
using osu.GameModes.Play.Rulesets.Mania; | |
using osu.GameplayElements.HitObjects; | |
using osu.GameplayElements.HitObjects.Mania; | |
using osu.GameplayElements.Scoring; | |
using osu_common; | |
namespace osu.GameplayElements.Beatmaps | |
{ | |
public class BeatmapDifficultyCalculatorMania : BeatmapDifficultyCalculator | |
{ | |
// 0.01 is good for stars | |
private const double STAR_SCALING_FACTOR = 0.018; | |
protected override PlayModes PlayMode => PlayModes.OsuMania; | |
public static Mods RelevantMods => Mods.DoubleTime | Mods.HalfTime | Mods.HardRock | Mods.Easy | Mods.KeyMod; | |
/// <summary> | |
/// HitObjects are stored as a member variable. | |
/// </summary> | |
internal List<DifficultyHitObjectMania> DifficultyHitObjects; | |
public override Mods[] ModCombinations | |
{ | |
get | |
{ | |
Mods[] mods = new Mods[BASE_MOD_COMBINATIONS.Length]; | |
for (int i = 0; i < BASE_MOD_COMBINATIONS.Length; ++i) | |
mods[i] = keyMod | BASE_MOD_COMBINATIONS[i]; | |
return mods; | |
} | |
} | |
private Mods keyMod; | |
public BeatmapDifficultyCalculatorMania(Beatmap beatmap, Mods keyMod) | |
: base(beatmap) | |
{ | |
// Let the mania calculator only calculate difficulties for one specific key-mod at a time. | |
Debug.Assert((keyMod & ~Mods.KeyMod) == Mods.None); | |
// No key-mods for mania-specific maps. Conversions don't exist. | |
if (beatmap.PlayMode == PlayModes.OsuMania) | |
keyMod = Mods.None; | |
this.keyMod = keyMod; | |
} | |
internal override HitObjectManager NewHitObjectManager() | |
{ | |
return new HitObjectManagerMania(false); | |
} | |
protected override bool ModsRequireReload(Mods mods) | |
{ | |
// If we switch keymods we need to reload | |
return (HitObjectManager.ActiveMods & Mods.KeyMod) != (mods & Mods.KeyMod); | |
} | |
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty) | |
{ | |
// Fill our custom DifficultyHitObject class, that carries additional information | |
DifficultyHitObjects = new List<DifficultyHitObjectMania>(HitObjects.Count); | |
foreach (HitObject hitObject in HitObjects) | |
{ | |
//DifficultyHitObjects.Add(new DifficultyHitObjectMania(hitObject)); | |
HitCircleManiaRow maniaRow = hitObject as HitCircleManiaRow; | |
if (maniaRow != null) | |
{ | |
maniaRow.HitObjects.ForEach(n => | |
{ | |
DifficultyHitObjects.Add(new DifficultyHitObjectMania(n)); // This hitcircle should ALWAYS be of type HitCircleMania | |
}); | |
} | |
else if (hitObject is HitCircleMania) | |
{ | |
DifficultyHitObjects.Add(new DifficultyHitObjectMania((HitCircleMania)hitObject)); | |
} | |
else | |
Debug.Print("Unknown mania circle type at: " + hitObject.StartTime); | |
} | |
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. Not using CompareTo, since it results in a crash (HitObjectBase inherits MarshalByRefObject) | |
DifficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime - b.BaseHitObject.StartTime); | |
if (!CalculateStrainValues()) return 0; | |
double starRating = CalculateDifficulty() * STAR_SCALING_FACTOR; | |
if (categoryDifficulty != null) | |
{ | |
categoryDifficulty.Add("Strain", starRating.ToString("0.00", GameBase.nfi)); | |
// Mania already multiplied by the timerate, so essentially the hitwindow stays the same across doubletime and halftime. | |
// Ceiling is required to lessen the rounding error | |
categoryDifficulty.Add("Hit window 300", Math.Ceiling(HitObjectManager.HitWindow300 / TimeRate).ToString("0", GameBase.nfi)); | |
categoryDifficulty.Add("Score multiplier", ModManager.ScoreMultiplier(HitObjectManager.ActiveMods & ~(Mods.ScoreIncreaseMods), PlayModes.OsuMania, Beatmap).ToString("0.00", GameBase.nfi)); | |
} | |
return starRating; | |
} | |
protected override bool CalculateStrainValues() | |
{ | |
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. | |
List<DifficultyHitObjectMania>.Enumerator hitObjectsEnumerator = DifficultyHitObjects.GetEnumerator(); | |
if (!hitObjectsEnumerator.MoveNext()) return false; | |
DifficultyHitObjectMania currentHitObject = hitObjectsEnumerator.Current; | |
DifficultyHitObjectMania nextHitObject; | |
// 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()) | |
{ | |
nextHitObject = hitObjectsEnumerator.Current; | |
nextHitObject.CalculateStrains(currentHitObject, TimeRate); | |
currentHitObject = nextHitObject; | |
} | |
return true; | |
} | |
/// <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> | |
protected const double STRAIN_STEP = 400; | |
/// <summary> | |
/// The weighting of each strain value decays to this number * it's previous value | |
/// </summary> | |
protected const double DECAY_WEIGHT = 0.9; | |
protected override double CalculateDifficulty() | |
{ | |
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 | |
DifficultyHitObjectMania previousHitObject = null; | |
foreach (DifficultyHitObjectMania hitObject in DifficultyHitObjects) | |
{ | |
// 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(DifficultyHitObjectMania.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); | |
double overallDecay = Math.Pow(DifficultyHitObjectMania.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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using osu.GameModes.Edit.AiMod; | |
using osu.GameModes.Play.Rulesets.Mania; | |
using Microsoft.Xna.Framework; | |
using osu.GameplayElements.Beatmaps; | |
namespace osu.GameplayElements.HitObjects.Mania | |
{ | |
internal class DifficultyHitObjectMania | |
{ | |
// Factor by how much individual / overall strain decays per second. Those values are results of tweaking a lot and taking into account general feedback. | |
internal static readonly double INDIVIDUAL_DECAY_BASE = 0.125; | |
internal static readonly double OVERALL_DECAY_BASE = 0.30; | |
internal HitCircleMania BaseHitObject; | |
private double[] heldUntil; | |
/// <summary> | |
/// Measures jacks or more generally: repeated presses of the same button | |
/// </summary> | |
private 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; | |
internal DifficultyHitObjectMania(HitCircleMania baseHitObject) | |
{ | |
BaseHitObject = baseHitObject; | |
int columnCount = BaseHitObject.hitObjectManager.ManiaStage.Columns.Count; | |
individualStrains = new double[columnCount]; | |
heldUntil = new double[columnCount]; | |
for (int i = 0; i < columnCount; ++i) | |
{ | |
individualStrains[i] = 0; | |
heldUntil[i] = 0; | |
} | |
} | |
internal void CalculateStrains(DifficultyHitObjectMania previousHitObject, double timeRate) | |
{ | |
// TODO: Factor in holds | |
double addition = 1.0; | |
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 < BaseHitObject.hitObjectManager.ManiaStage.Columns.Count; ++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] && BaseHitObject.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 (BaseHitObject.EndTime == heldUntil[i]) | |
{ | |
holdAddition = 0; | |
} | |
// We give a slight bonus to everything if something is held meanwhile | |
if (heldUntil[i] > BaseHitObject.EndTime) | |
{ | |
holdFactor = 1.25; | |
} | |
// Decay individual strains | |
individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay; | |
} | |
heldUntil[BaseHitObject.Column] = BaseHitObject.EndTime; | |
// Increase individual strain in own column | |
IndividualStrain += (2.0/* + (double)SpeedMania.Column / 8.0*/) * holdFactor; | |
OverallStrain = previousHitObject.OverallStrain * overallDecay + (addition + holdAddition) * holdFactor; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment