protected override double StrainValueOf(DifficultyHitObject current) { if (!(current.BaseObject is Hit)) { return(0.0); } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; if (hitObject.ObjectIndex % 2 == hand) { double objectStrain = 1; if (hitObject.ObjectIndex == 1) { return(1); } notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration); double shortestRecentNote = notePairDurationHistory.Min(); objectStrain += speedBonus(shortestRecentNote); if (hitObject.StaminaCheese) { objectStrain *= cheesePenalty(hitObject.DeltaTime + offhandObjectDuration); } return(objectStrain); } offhandObjectDuration = hitObject.DeltaTime; return(0); }
protected override double StrainValueOf(DifficultyHitObject current) { // drum rolls and swells are exempt. if (!(current.BaseObject is Hit)) { resetRhythmAndStrain(); return(0.0); } currentStrain *= strain_decay; TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; notesSinceRhythmChange += 1; // rhythm difficulty zero (due to rhythm not changing) => no rhythm strain. if (hitObject.Rhythm.Difficulty == 0.0) { return(0.0); } double objectStrain = hitObject.Rhythm.Difficulty; objectStrain *= repetitionPenalties(hitObject); objectStrain *= patternLengthPenalty(notesSinceRhythmChange); objectStrain *= speedPenalty(hitObject.DeltaTime); // careful - needs to be done here since calls above read this value notesSinceRhythmChange = 0; currentStrain += objectStrain; return(currentStrain); }
protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); currentStrain += strainValueOf(current) * skillMultiplier; return(currentStrain); }
protected override double StrainValueOf(DifficultyHitObject current) { double addition = 1; // We get an extra addition if we are not a slider or spinner if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) { if (hasColourChange(current)) { addition += 0.75; } if (hasRhythmChange(current)) { addition += 1; } } else { lastColourSwitch = ColourSwitch.None; sameColourCount = 1; } double additionFactor = 1; // Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50 if (current.DeltaTime < 50) { additionFactor = 0.4 + 0.6 * current.DeltaTime / 50; } return(additionFactor * addition); }
protected override double StrainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) { return(0); } var osuCurrent = (OsuDifficultyHitObject)current; double result = 0; if (Previous.Count > 0) { 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(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(osuCurrent.JumpDistance); double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); return(Math.Max( result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime )); }
protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; var endTime = maniaCurrent.BaseObject.GetEndTime(); 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); }
protected override double StrainValueOf(DifficultyHitObject current) { // changing from/to a drum roll or a swell does not constitute a colour change. // hits spaced more than a second apart are also exempt from colour strain. if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) { monoHistory.Clear(); var currentHit = current.BaseObject as Hit; currentMonoLength = currentHit != null ? 1 : 0; previousHitType = currentHit?.Type; return(0.0); } var taikoCurrent = (TaikoDifficultyHitObject)current; double objectStrain = 0.0; if (previousHitType != null && taikoCurrent.HitType != previousHitType) { // The colour has changed. objectStrain = 1.0; if (monoHistory.Count < 2) { // There needs to be at least two streaks to determine a strain. objectStrain = 0.0; } else if ((monoHistory[^ 1] + currentMonoLength) % 2 == 0)
protected override double StrainValueOf(DifficultyHitObject current) { HitokoriDifficultyHitObject hitokoriCurrent = (HitokoriDifficultyHitObject)current; double bpm = Math.Min(hitokoriCurrent.BPM, MAX_BPM); return(Math.Pow(bpm / BASE_BPM, 0.6)); }
protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); currentStrain += FlashlightEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skillMultiplier; return(currentStrain); }
private double strainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) { return(0); } var osuCurrent = (OsuDifficultyHitObject)current; var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); double scalingFactor = 52.0 / osuHitObject.Radius; double smallDistNerf = 1.0; double cumulativeStrainTime = 0.0; double result = 0.0; OsuDifficultyHitObject lastObj = osuCurrent; // This is iterating backwards in time from the current object. for (int i = 0; i < Previous.Count; i++) { var currentObj = (OsuDifficultyHitObject)Previous[i]; var currentHitObject = (OsuHitObject)(currentObj.BaseObject); if (!(currentObj.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; cumulativeStrainTime += lastObj.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) { smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); } // We also want to nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden)); result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } lastObj = currentObj; } result = Math.Pow(smallDistNerf * result, 2.0); // Additional bonus for Hidden due to there being no approach circles. if (hidden) { result *= 1.0 + hidden_bonus; } return(result); }
protected (double snapAim, double flowAim) CalculateAimValues(DifficultyHitObject current) { var osuCurrent = (OsuDifficultyHitObject)current; double result = 0; double controlBonus = 0; double flowAngleBonus = 1.0; if (Previous.Count > 0) { 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(osuPrevious.JumpDistance - scale, 0) * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) * Math.Max(osuCurrent.JumpDistance - scale, 0)); result = 1.475 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); double flowAngleDistanceScaling = Math.Min(Math.Max(0, osuCurrent.JumpDistance - 90) / 14.0, 1.0) * Math.Min(Math.Max(0, osuPrevious.JumpDistance - 90) / 14.0, 1.0); flowAngleBonus = Math.Sin(1.5 * (flow_angle_begin - Math.Max(Math.PI / 2, osuCurrent.Angle.Value))); flowAngleBonus = 1.0 + Math.Max(0, flowAngleBonus) * flowAngleDistanceScaling * flow_angle_factor; } controlBonus = calculateControlBonus(osuCurrent, osuPrevious); } double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance); double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); if (osuCurrent.OverlapScaling != null) { jumpDistanceExp *= osuCurrent.OverlapScaling.Value; travelDistanceExp *= osuCurrent.OverlapScaling.Value; } double repeatJumpPenalty = calculateRepeatJumpPenalty(osuCurrent); double aimValue = Math.Max( result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime ) + controlBonus; double distanceOffset = Math.Pow(jumpDistanceExp + travelDistanceExp, 1.7) / 400; double flowAmount = 1.0 / (1.0 + Math.Pow(Math.E, osuCurrent.StrainTime - 126.0 + distanceOffset)); double flowBonus = flowAmount * flow_factor * flowAngleBonus; aimValue *= (1.0 - repeatJumpPenalty); aimValue *= (1.0 + flowBonus); return(aimValue * (1.0 - flowAmount), aimValue *flowAmount); }
protected override double OldStrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; oldLastPlayerPosition ??= catchCurrent.LastNormalizedPosition; float playerPosition = Math.Clamp( oldLastPlayerPosition.Value, catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) ); float distanceMoved = playerPosition - oldLastPlayerPosition.Value; double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate); double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); double sqrtStrain = Math.Sqrt(weightedStrainTime); double edgeDashBonus = 0; // Direction change bonus. if (Math.Abs(distanceMoved) > 0.1) { if (Math.Abs(oldLastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(oldLastDistanceMoved)) { double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50; double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(oldLastDistanceMoved)) / 70, 0.38); distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0); } // Base bonus for every movement, giving some weight to streams. distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } // Bonus for edge dashes. if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f) { if (!catchCurrent.LastObject.HyperDash) { edgeDashBonus += 5.7; } else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values } oldLastPlayerPosition = playerPosition; oldLastDistanceMoved = distanceMoved; lastStrainTime = catchCurrent.StrainTime; return(distanceAddition / weightedStrainTime); }
protected override double StrainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) { return(0); } var osuCurrent = (OsuDifficultyHitObject)current; var osuPrevious = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null; double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); double strainTime = osuCurrent.StrainTime; double greatWindowFull = greatWindow * 2; double speedWindowRatio = strainTime / greatWindowFull; // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) if (osuPrevious != null && strainTime < greatWindowFull && osuPrevious.StrainTime > strainTime) { strainTime = Interpolation.Lerp(osuPrevious.StrainTime, strainTime, speedWindowRatio); } // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1); double speedBonus = 1.0; if (strainTime < min_speed_bonus) { speedBonus = 1 + Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); } double angleBonus = 1.0; if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) { 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 && 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 - osuCurrent.Angle.Value) / pi_over_4); } } } return((1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime); }
protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier; currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow); return(currentStrain * currentRhythm); }
/// <summary> /// Processes the specified hit object for current strain factor. /// </summary> public void Process(DifficultyHitObject obj) { currentStrain *= GetStrainDecay(obj.DeltaTime); currentStrain += CalculateStrain(obj); currentStrainPeak = Math.Max(currentStrain, currentStrainPeak); previousObjects.Push(obj); }
/// <summary> /// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly. /// </summary> public void Process(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); currentStrain += StrainValueOf(current) * SkillMultiplier; currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); Previous.Push(current); }
protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); currentStrain += strainValueOf(current) * skillMultiplier; currentRhythm = calculateRhythmBonus(current); return(currentStrain * currentRhythm); }
protected override double StrainValueOf(DifficultyHitObject current) { // No need to aim HardBeat if (current.BaseObject is HardBeat) { return(0); } var tauCurrent = (TauDifficultyHitObject)current; var note = (TauHitObject)current.BaseObject; var notePrev = (TauHitObject)current.LastObject; var noteDif = (TauDifficultyHitObject)current; var paddle_size = noteDif.beatmap.BeatmapInfo.BaseDifficulty.CircleSize; var jumpAngle = Math.Abs(note.Angle - notePrev.Angle) * 0.5; var paddle_size_bonus = 0.01f * Math.Pow(paddle_size - 4, 3) + 1; double result = 0; double angleBonus; double speedmult = 1; if (Previous.Count > 0) { var tauPrevious = (TauDifficultyHitObject)Previous[0]; var x = tauPrevious.StrainTime; var y = -Math.Log10(tauPrevious.StrainTime); speedmult = Math.Max(y + 3.2, 0); if (tauCurrent.Angle != null && tauCurrent.Angle.Value > angle_bonus_begin) { const double min_jump = 5; angleBonus = Math.Sqrt( Math.Max(tauPrevious.JumpDistance - min_jump, 0) * Math.Pow(Math.Sin(tauCurrent.Angle.Value - angle_bonus_begin), 2) * Math.Max(tauCurrent.JumpDistance - min_jump, 0)); result = 30 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, tauPrevious.StrainTime); } } double jumpDistanceExp = applyDiminishingExp(tauCurrent.JumpDistance); double travelDistanceExp = applyDiminishingExp(tauCurrent.TravelDistance); double angle_strain = (result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(tauCurrent.StrainTime, timing_threshold)); double flat_strain = (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / tauCurrent.StrainTime; // strainPeaks.Add(Math.Max(option1,option2)); return(Math.Max( angle_strain * paddle_size_bonus, flat_strain * paddle_size_bonus ) * speedmult); }
internal void ProcessInternal(DifficultyHitObject current) { while (Previous.Count > HistoryLength) { Previous.Dequeue(); } Process(current); Previous.Enqueue(current); }
protected override double StrainValueOf(DifficultyHitObject current) { if (!(current.BaseObject is Hit)) { return(0.0); } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; return(getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject)); }
protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; double endTime = maniaCurrent.EndTime; int column = maniaCurrent.BaseObject.Column; double closestEndTime = Math.Abs(endTime - maniaCurrent.LastObject.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 bool isOverlapping = false; // Fill up the holdEndTimes array for (int i = 0; i < holdEndTimes.Length; ++i) { // The current note is overlapped if a previous note or end is overlapping the current note body isOverlapping |= Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1)) { holdFactor = 1.25; } closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - holdEndTimes[i])); // Decay individual strains individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base); } holdEndTimes[column] = endTime; // 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 = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime))); } // Increase individual strain in own column individualStrains[column] += 2.0 * holdFactor; individualStrain = individualStrains[column]; overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor; return(individualStrain + overallStrain - CurrentStrain); }
protected override double StrainValueOf(DifficultyHitObject current) { var tauCurrent = (TauDifficultyHitObject)current; double distance = Math.Min(single_spacing_threshold, tauCurrent.TravelDistance + tauCurrent.JumpDistance); double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime); double speedBonus = 1.0; if (deltaTime < min_speed_bonus) { speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); } double NoteMultiplier = 1; double angleBonus = 1.0; if (current.BaseObject is HardBeat) { NoteMultiplier = 1.5; // Increase Multiplier for alternating from beats to hardbeats and back if (tauCurrent.lastObject is Beat) { NoteMultiplier *= 1.25; } if (tauCurrent.lastLastObject is HardBeat && tauCurrent.lastObject is Beat) { NoteMultiplier *= 1.1; } } else if (tauCurrent.Angle != null && tauCurrent.Angle.Value < angle_bonus_begin) { angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - tauCurrent.Angle.Value)), 2) / 3.57; if (tauCurrent.Angle.Value < pi_over_2) { angleBonus = 1.28; if (distance < 90 && tauCurrent.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 - tauCurrent.Angle.Value) / pi_over_4); } } } speedBonus *= NoteMultiplier; return((1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / tauCurrent.StrainTime); }
public double StrainValueOf(DifficultyHitObject current) { if (previousHitTime == null) { previousHitTime = current.StartTime; return(0); } double objectStrain = 0.5; objectStrain += speedBonus(current.StartTime - previousHitTime.Value); previousHitTime = current.StartTime; return(objectStrain); }
protected override double StrainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) { return(0); } (double snapAim, double flowAim) = CalculateAimValues(current); double strainValue = snapAim + flowAim; AddTotalStrain(strainValue); return(strainValue); }
private double strainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) { return(0); } var osuCurrent = (OsuDifficultyHitObject)current; var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); double scalingFactor = 52.0 / osuHitObject.Radius; double smallDistNerf = 1.0; double cumulativeStrainTime = 0.0; double result = 0.0; OsuDifficultyHitObject lastObj = osuCurrent; // This is iterating backwards in time from the current object. for (int i = 0; i < Previous.Count; i++) { var currentObj = (OsuDifficultyHitObject)Previous[i]; var currentHitObject = (OsuHitObject)(currentObj.BaseObject); if (!(currentObj.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; cumulativeStrainTime += lastObj.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) { smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); } // We also want to nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } lastObj = currentObj; } return(Math.Pow(smallDistNerf * result, 2.0)); }
/// <summary> /// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly. /// </summary> protected sealed override void Process(DifficultyHitObject current) { // The first object doesn't generate a strain, so we begin with an incremented section end if (Previous.Count == 0) { currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; } while (current.StartTime > currentSectionEnd) { saveCurrentPeak(); startNewSectionFrom(currentSectionEnd); currentSectionEnd += SectionLength; } currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); }
protected override double StrainValueOf(DifficultyHitObject current) { HitokoriDifficultyHitObject hitokoriCurrent = (HitokoriDifficultyHitObject)current; double strain = CalculateAngleStrain(hitokoriCurrent.HitAngle); if (hitokoriCurrent.ChangedDirection) { strain *= DIRECTION_CHANGE_BONUS; } if (hitokoriCurrent.HoldAngle != null) { strain += CalculateAngleStrain(hitokoriCurrent.HoldAngle.Value); } return(1 + strain); }
public static double EvaluateDifficultyOf(DifficultyHitObject current, double greatWindow) { if (current.BaseObject is Spinner) { return(0); } // derive strainTime for calculation var osuCurrObj = (OsuDifficultyHitObject)current; var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null; var osuNextObj = (OsuDifficultyHitObject)current.Next(0); double strainTime = osuCurrObj.StrainTime; double greatWindowFull = greatWindow * 2; double doubletapness = 1; // Nerf doubletappable doubles. if (osuNextObj != null) { double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime); double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime); double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference); double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2); doubletapness = Math.Pow(speedRatio, 1 - windowRatio); } // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1); // derive speedBonus for calculation double speedBonus = 1.0; if (strainTime < min_speed_bonus) { speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); } double travelDistance = osuPrevObj?.TravelDistance ?? 0; double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance); return((speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) * doubletapness / strainTime); }
protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; var endTime = maniaCurrent.BaseObject.GetEndTime(); var column = maniaCurrent.BaseObject.Column; 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 holdEndTimes array for (int i = 0; i < holdEndTimes.Length; ++i) { // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1)) { 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 (Precision.AlmostEquals(endTime, holdEndTimes[i], 1)) { holdAddition = 0; } // We give a slight bonus to everything if something is held meanwhile if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1)) { holdFactor = 1.25; } // Decay individual strains individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base); } holdEndTimes[column] = endTime; // Increase individual strain in own column individualStrains[column] += 2.0 * holdFactor; individualStrain = individualStrains[column]; overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor; return(individualStrain + overallStrain - CurrentStrain); }
private bool hasColourChange(DifficultyHitObject current) { var taikoCurrent = (TaikoDifficultyHitObject)current; if (!taikoCurrent.HasTypeChange) { sameColourCount++; return(false); } var oldColourSwitch = lastColourSwitch; var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; lastColourSwitch = newColourSwitch; sameColourCount = 1; // We only want a bonus if the parity of the color switch changes return(oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch); }