The provided code implements the `Strain` skill within the context...

September 1, 2025 at 04:21 PM

using System; using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Difficulty.Skills { public class Strain : StrainDecaySkill { private const double individual_decay_base = 0.125; private const double overall_decay_base = 0.30; private const double release_threshold = 30; protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 1; private readonly double[] startTimes; private readonly double[] endTimes; private readonly double[] individualStrains; private double individualStrain; private double overallStrain; public Strain(Mod[] mods, int totalColumns) : base(mods) { startTimes = new double[totalColumns]; endTimes = new double[totalColumns]; individualStrains = new double[totalColumns]; overallStrain = 1; } protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; double startTime = maniaCurrent.StartTime; double endTime = maniaCurrent.EndTime; int column = maniaCurrent.BaseObject.Column; bool isOverlapping = false; double closestEndTime = Math.Abs(endTime - startTime); // Lowest value we can assume with the current information 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 for (int i = 0; i < endTimes.Length; ++i) { // The current note is overlapped if a previous note or end is overlapping the current note body isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1) && Precision.DefinitelyBigger(startTime, startTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) && Precision.DefinitelyBigger(startTime, startTimes[i], 1)) holdFactor = 1.25; closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i])); } // The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending. // Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away. // holdAddition // ^ // 1.0 + - - - - - -+----------- // | / // 0.5 + - - - - -/ Sigmoid Curve // | /| // 0.0 +--------+-+---------------> Release Difference / ms // release_threshold if (isOverlapping) holdAddition = DifficultyCalculationUtils.Logistic(x: closestEndTime, multiplier: 0.27, midpointOffset: release_threshold); // Decay and increase individualStrains in own column individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); individualStrains[column] += 2.0 * holdFactor; // For notes at the same time (in a chord), the individualStrain should be the hardest individualStrain out of those columns individualStrain = maniaCurrent.DeltaTime <= 1 ? Math.Max(individualStrain, individualStrains[column]) : individualStrains[column]; // Decay and increase overallStrain overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base); overallStrain += (1 + holdAddition) * holdFactor; // Update startTimes and endTimes arrays startTimes[column] = startTime; endTimes[column] = endTime; // By subtracting CurrentStrain, this skill effectively only considers the maximum strain of any one hitobject within each strain section. return individualStrain + overallStrain - CurrentStrain; } protected override double CalculateInitialStrain(double offset, DifficultyHitObject current) => applyDecay(individualStrain, offset - current.Previous(0).StartTime, individual_decay_base) + applyDecay(overallStrain, offset - current.Previous(0).StartTime, overall_decay_base); private double applyDecay(double value, double deltaTime, double decayBase) => value * Math.Pow(decayBase, deltaTime / 1000); } }

The provided code implements the Strain skill within the context of the osu! game framework and its difficulty calculation system. It is part of the Mania ruleset and contributes to the calculation of the strain difficulty of a Mania map. Here's an overview of what the code does:

General Overview:

The Strain class inherits from StrainDecaySkill, a base class designed for skills that are calculated based on decaying strain over time. This applies specifically to rhythm games like osu! where difficulty is measured by how challenging a set of notes (hit objects) are to execute within the game mechanics. In the case of Mania (a "key-based" rhythm game mode), strain is typically used to quantify the challenge associated with concurrently pressing and releasing keys across multiple columns.


Key Components:

  1. Strain Calculation Basics:

    • The class tracks two primary strain values:
      • individualStrain: Tracks how challenging it is to hit notes in a single column.
      • overallStrain: Tracks the overall strain across all columns.
    • These values are decayed over time to simulate the reduced impact of earlier notes on the current section's difficulty.
  2. Decay Mechanism:

    • Strain values decay over time according to a formula, reducing the influence of older notes. The applyDecay function implements this:
      private double applyDecay(double value, double deltaTime, double decayBase)
          => value * Math.Pow(decayBase, deltaTime / 1000);
      
    • The decayBase values for individual columns and the overall strain are set to 0.125 and 0.30, respectively.
  3. Strain Value Calculation (StrainValueOf):

    • For each DifficultyHitObject (a processed note in the map), the StrainValueOf method calculates the strain depending on the note's timing, column, and overlap with other notes.
    • Key factors include:
      • Time intervals (e.g., DeltaTime between consecutive notes).
      • Multi-key (chord) presses.
      • Overlaps between hold notes and other notes.
      • A "hold addition" adjustment for awkward releases or overlapping notes, using a logistic decay curve when the release happens near the release_threshold of 30ms.
  4. Specific Logic:

    • Column-specific strain:
      • The individualStrains array stores the strain for each column. It updates based on the timing and difficulty of the notes in that column.
    • Overall strain:
      • The overallStrain affects the difficulty across all columns rather than a single column.
    • Hold note mechanics:
      • Overlapping hold notes create special adjustments to strain.
  5. Start and End Time Tracking:

    • Arrays startTimes and endTimes store the start and end times of the most recent notes in each column. These are used to determine note overlaps and assist in the overall strain calculation.
  6. Map Initialization:

    • The constructor initializes necessary arrays (e.g., startTimes, endTimes) to track timing information for columns. It also sets the initial strain values.
  7. Final Strain Output:

    • The strain value is calculated by combining the individualStrain and overallStrain. The outcome is adjusted by subtracting CurrentStrain to normalize the strain within each section.

High-Level Purpose:

  • Mania Difficulty Assessment: The Strain skill quantifies how difficult a section of notes is for a player to execute based on overlapping notes, column-specific patterns, and timing between notes. It processes notes column by column and factors in the unique challenges of hold notes, chord streams, and staggered releases.

  • Player Effort Simulation: By simulating key mechanics like decaying strain, overlapping patterns, and timing thresholds, the class models the effort a player must exert to clear a specific section of the map.


This code is designed to be a part of osu!'s Mania difficulty calculation process, helping compute a map difficulty rating that reflects the challenge of executing the map's note patterns. It's used by the game to assign a difficulty star rating to Mania beatmaps.

Generate your own explanations
Download our vscode extension
Read other generated explanations

Built by @thebuilderjr
Sponsored by beam analytics
Read our terms and privacy policy
Forked from openai-quickstart-node