2018-04-13 17:19:50 +08:00
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2018-05-15 16:40:19 +08:00
using System ;
using System.Collections.Generic ;
2018-05-16 13:47:28 +08:00
using System.Linq ;
2018-04-13 17:19:50 +08:00
using osu.Game.Beatmaps ;
2018-05-15 16:40:19 +08:00
using osu.Game.Rulesets.Difficulty ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Mania.Beatmaps ;
2018-06-06 15:20:17 +08:00
using osu.Game.Rulesets.Mania.Mods ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Mania.Objects ;
using osu.Game.Rulesets.Mods ;
2018-05-15 16:40:19 +08:00
namespace osu.Game.Rulesets.Mania.Difficulty
2018-04-13 17:19:50 +08:00
{
2018-04-19 21:04:12 +08:00
internal class ManiaDifficultyCalculator : DifficultyCalculator
2018-04-13 17:19:50 +08:00
{
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 ;
2018-06-14 16:04:31 +08:00
private readonly bool isForCurrentRuleset ;
2018-06-14 14:54:05 +08:00
public ManiaDifficultyCalculator ( Ruleset ruleset , WorkingBeatmap beatmap )
: base ( ruleset , beatmap )
2018-04-13 17:19:50 +08:00
{
2018-06-17 17:01:28 +08:00
isForCurrentRuleset = beatmap . BeatmapInfo . Ruleset . Equals ( ruleset . RulesetInfo ) ;
2018-04-13 17:19:50 +08:00
}
2018-06-14 14:54:05 +08:00
protected override DifficultyAttributes Calculate ( IBeatmap beatmap , Mod [ ] mods , double timeRate )
2018-04-13 17:19:50 +08:00
{
2018-06-21 11:04:14 +08:00
if ( ! beatmap . HitObjects . Any ( ) )
return new ManiaDifficultyAttributes ( mods , 0 ) ;
2018-06-14 14:54:05 +08:00
var difficultyHitObjects = new List < ManiaHitObjectDifficulty > ( ) ;
2018-04-13 17:19:50 +08:00
2018-06-14 14:54:05 +08:00
int columnCount = ( ( ManiaBeatmap ) beatmap ) . TotalColumns ;
2018-04-13 17:19:50 +08:00
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
2018-05-16 13:47:28 +08:00
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
2018-06-14 14:54:05 +08:00
difficultyHitObjects . AddRange ( beatmap . HitObjects . Select ( h = > new ManiaHitObjectDifficulty ( ( ManiaHitObject ) h , columnCount ) ) . OrderBy ( h = > h . BaseHitObject . StartTime ) ) ;
2018-04-13 17:19:50 +08:00
2018-06-14 14:54:05 +08:00
if ( ! calculateStrainValues ( difficultyHitObjects , timeRate ) )
return new DifficultyAttributes ( mods , 0 ) ;
2018-04-13 17:19:50 +08:00
2018-06-14 15:25:44 +08:00
2018-06-14 14:54:05 +08:00
double starRating = calculateDifficulty ( difficultyHitObjects , timeRate ) * star_scaling_factor ;
2018-04-13 17:19:50 +08:00
2018-06-14 15:25:44 +08:00
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
} ;
2018-04-13 17:19:50 +08:00
}
2018-06-14 14:54:05 +08:00
private bool calculateStrainValues ( List < ManiaHitObjectDifficulty > objects , double timeRate )
2018-04-13 17:19:50 +08:00
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
2018-06-14 14:54:05 +08:00
using ( var hitObjectsEnumerator = objects . GetEnumerator ( ) )
2018-04-13 17:19:50 +08:00
{
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 ;
2018-06-14 14:54:05 +08:00
next ? . CalculateStrains ( current , timeRate ) ;
2018-04-13 17:19:50 +08:00
current = next ;
}
return true ;
}
}
2018-06-14 14:54:05 +08:00
private double calculateDifficulty ( List < ManiaHitObjectDifficulty > objects , double timeRate )
2018-04-13 17:19:50 +08:00
{
2018-06-14 14:54:05 +08:00
double actualStrainStep = strain_step * timeRate ;
2018-04-13 17:19:50 +08:00
// 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 ;
2018-06-14 14:54:05 +08:00
foreach ( var hitObject in objects )
2018-04-13 17:19:50 +08:00
{
// 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 ;
}
2018-06-06 15:20:17 +08:00
2018-06-14 16:04:31 +08:00
protected override Mod [ ] DifficultyAdjustmentMods
2018-06-06 15:20:17 +08:00
{
2018-06-14 16:04:31 +08:00
get
{
2018-06-17 23:48:35 +08:00
var mods = new Mod [ ]
2018-06-14 16:04:31 +08:00
{
new ManiaModDoubleTime ( ) ,
new ManiaModHalfTime ( ) ,
new ManiaModEasy ( ) ,
new ManiaModHardRock ( ) ,
2018-06-17 23:48:35 +08:00
} ;
if ( isForCurrentRuleset )
return mods ;
2018-06-14 14:54:05 +08:00
2018-06-17 23:48:35 +08:00
// if we are a convert, we can be played in any key mod.
return mods . Concat ( new Mod [ ]
{
2018-06-14 16:04:31 +08:00
new ManiaModKey1 ( ) ,
new ManiaModKey2 ( ) ,
new ManiaModKey3 ( ) ,
new ManiaModKey4 ( ) ,
new ManiaModKey5 ( ) ,
new ManiaModKey6 ( ) ,
new ManiaModKey7 ( ) ,
new ManiaModKey8 ( ) ,
new ManiaModKey9 ( ) ,
2018-06-17 23:48:35 +08:00
} ) . ToArray ( ) ;
2018-06-14 16:04:31 +08:00
}
}
2018-04-13 17:19:50 +08:00
}
}