UNPKG

osu-standard-stable

Version:

osu!stable version of osu!standard ruleset based on osu!lazer source code.

1,217 lines (1,174 loc) 75.4 kB
'use strict'; var osuClasses = require('osu-classes'); class StandardNoMod extends osuClasses.NoMod { } class StandardNoFail extends osuClasses.NoFail { } class StandardEasy extends osuClasses.Easy { } class StandardTouchDevice { constructor() { this.name = 'Touch Device'; this.acronym = 'TD'; this.bitwise = osuClasses.ModBitwise.TouchDevice; this.type = osuClasses.ModType.System; this.multiplier = 1; this.isRanked = true; this.incompatibles = osuClasses.ModBitwise.None; } } class StandardHidden extends osuClasses.Hidden { applyToBeatmap(beatmap) { for (const hitObject of beatmap.hitObjects) { this._applyFadeAdjustments(hitObject); } } _applyFadeAdjustments(hitObject) { hitObject.timeFadeIn = hitObject.timePreempt * StandardHidden.FADE_IN_DURATION_MULTIPLIER; for (const nestedObject of hitObject.nestedHitObjects) { this._applyFadeAdjustments(nestedObject); } } } StandardHidden.FADE_IN_DURATION_MULTIPLIER = 0.4; StandardHidden.FADE_OUT_DURATION_MULTIPLIER = 0.3; class StandardHitWindows extends osuClasses.HitWindows { isHitResultAllowed(result) { switch (result) { case osuClasses.HitResult.Great: case osuClasses.HitResult.Ok: case osuClasses.HitResult.Meh: case osuClasses.HitResult.Miss: return true; } return false; } _getRanges() { return StandardHitWindows.OSU_RANGES; } } StandardHitWindows.OSU_RANGES = [ new osuClasses.DifficultyRange(osuClasses.HitResult.Great, 80, 50, 20), new osuClasses.DifficultyRange(osuClasses.HitResult.Ok, 140, 100, 60), new osuClasses.DifficultyRange(osuClasses.HitResult.Meh, 200, 150, 100), new osuClasses.DifficultyRange(osuClasses.HitResult.Miss, 400, 400, 400), ]; class StandardHitObject extends osuClasses.HitObject { constructor() { super(...arguments); this.timePreempt = 600; this.timeFadeIn = 400; this._scale = 0.5; this._stackHeight = 0; this._stackOffset = new osuClasses.Vector2(0, 0); this.currentComboIndex = 0; this.comboIndex = 0; this.comboIndexWithOffsets = 0; this.comboOffset = 0; this.lastInCombo = false; this.isNewCombo = false; this.hitWindows = new StandardHitWindows(); } get endPosition() { return this.startPosition; } get endX() { return this.endPosition.x; } set endX(value) { this.endPosition.x = value; } get endY() { return this.endPosition.y; } set endY(value) { this.endPosition.y = value; } get scale() { return this._scale; } set scale(value) { this._scale = value; const stackOffset = Math.fround(Math.fround(this._stackHeight * this._scale) * Math.fround(-6.4)); this._stackOffset.x = this._stackOffset.y = stackOffset; } get radius() { return StandardHitObject.OBJECT_RADIUS * this._scale; } get stackHeight() { return this._stackHeight; } set stackHeight(value) { this._stackHeight = value; const stackOffset = Math.fround(Math.fround(this._stackHeight * this._scale) * Math.fround(-6.4)); this._stackOffset.x = this._stackOffset.y = stackOffset; this.nestedHitObjects.forEach((n) => { n.stackHeight = this._stackHeight; }); } get stackedOffset() { return this._stackOffset; } set stackedOffset(value) { this._stackOffset = value; } get stackedStartPosition() { return this.startPosition.fadd(this.stackedOffset); } get stackedEndPosition() { return this.endPosition.fadd(this.stackedOffset); } applyDefaultsToSelf(controlPoints, difficulty) { super.applyDefaultsToSelf(controlPoints, difficulty); this.timePreempt = Math.fround(osuClasses.BeatmapDifficultySection.range(difficulty.approachRate, 1800, 1200, 450)); this.timeFadeIn = 400 * Math.min(1, this.timePreempt / StandardHitObject.PREEMPT_MIN); const scale = Math.fround(Math.fround(0.7) * Math.fround(difficulty.circleSize - 5)); this.scale = Math.fround(Math.fround(1 - Math.fround(scale / 5)) / 2); } clone() { const cloned = super.clone(); cloned.stackHeight = this.stackHeight; cloned.scale = this.scale; cloned.currentComboIndex = this.currentComboIndex; cloned.comboIndex = this.comboIndex; cloned.comboIndexWithOffsets = this.comboIndexWithOffsets; cloned.comboOffset = this.comboOffset; cloned.lastInCombo = this.lastInCombo; return cloned; } } StandardHitObject.OBJECT_RADIUS = 64; StandardHitObject.BASE_SCORING_DISTANCE = 100; StandardHitObject.PREEMPT_MIN = 450; class Circle extends StandardHitObject { } class SliderHead extends Circle { } class SliderTick extends StandardHitObject { constructor() { super(...arguments); this.spanIndex = 0; this.spanStartTime = 0; this.hitWindows = StandardHitWindows.empty; } applyDefaultsToSelf(controlPoints, difficulty) { super.applyDefaultsToSelf(controlPoints, difficulty); const offset = this.spanIndex > 0 ? 200 : this.timePreempt * Math.fround(0.66); this.timePreempt = (this.startTime - this.spanStartTime) / 2 + offset; } clone() { const cloned = super.clone(); cloned.spanIndex = this.spanIndex; cloned.spanStartTime = this.spanStartTime; return cloned; } } class SliderEnd extends Circle { constructor(slider) { super(); this.repeatIndex = 0; this.hitWindows = StandardHitWindows.empty; this._slider = slider; } get spanDuration() { return this._slider.spanDuration; } applyDefaultsToSelf(controlPoints, difficulty) { super.applyDefaultsToSelf(controlPoints, difficulty); if (this.repeatIndex > 0) { this.timeFadeIn = 0; this.timePreempt = this.spanDuration * 2; return; } const FIRST_END_CIRCLE_PREEMPT_ADJUST = Math.fround(2 / 3); this.timePreempt = this.startTime - this._slider.startTime + this._slider.timePreempt * FIRST_END_CIRCLE_PREEMPT_ADJUST; } } class SliderRepeat extends SliderEnd { } class SliderTail extends SliderEnd { } class SpinnerTick extends StandardHitObject { constructor() { super(...arguments); this.hitWindows = StandardHitWindows.empty; } } class SpinnerBonusTick extends SpinnerTick { } class StandardEventGenerator extends osuClasses.EventGenerator { static *generateSliderTicks(slider) { for (const event of osuClasses.EventGenerator.generate(slider)) { const offset = slider.path.positionAt(event.progress); switch (event.eventType) { case osuClasses.SliderEventType.Head: { const head = new SliderHead(); head.startPosition = slider.startPosition; head.startTime = event.startTime; yield head; break; } case osuClasses.SliderEventType.Repeat: { const repeat = new SliderRepeat(slider); repeat.repeatIndex = event.spanIndex; repeat.startTime = event.startTime; repeat.startPosition = slider.startPosition.fadd(offset); repeat.scale = slider.scale; yield repeat; break; } case osuClasses.SliderEventType.LegacyLastTick: { const tail = new SliderTail(slider); tail.repeatIndex = event.spanIndex; tail.startTime = event.startTime; tail.startPosition = slider.endPosition; yield tail; break; } case osuClasses.SliderEventType.Tick: { const tick = new SliderTick(); tick.spanIndex = event.spanIndex; tick.spanStartTime = event.spanStartTime; tick.startTime = event.startTime; tick.startPosition = slider.startPosition.fadd(offset); tick.scale = slider.scale; yield tick; } } } } static *generateSpinnerTicks(spinner) { const totalSpins = spinner.maximumBonusSpins + spinner.spinsRequired; for (let i = 0; i < totalSpins; ++i) { const tick = i < spinner.spinsRequired ? new SpinnerTick() : new SpinnerBonusTick(); tick.startTime = spinner.startTime + (i + 1 / totalSpins) * spinner.duration; yield tick; } } } class Slider extends StandardHitObject { constructor() { super(...arguments); this.tickDistance = 0; this.tickRate = 1; this.velocity = 1; this.lazyTravelDistance = 0; this.lazyTravelTime = 0; this.hitWindows = StandardHitWindows.empty; this.path = new osuClasses.SliderPath(); this.nodeSamples = []; this.repeats = 0; this.startPosition = new osuClasses.Vector2(0, 0); } get startX() { this._updateHeadPosition(); return this.startPosition.floatX; } set startX(value) { this.startPosition.x = value; this._updateHeadPosition(); } get startY() { this._updateHeadPosition(); return this.startPosition.floatY; } set startY(value) { this.startPosition.y = value; this._updateHeadPosition(); } get endX() { this._updateTailPosition(); return this.endPosition.floatX; } set endX(value) { this.endPosition.x = value; this._updateTailPosition(); } get endY() { this._updateTailPosition(); return this.endPosition.floatY; } set endY(value) { this.endPosition.y = value; this._updateTailPosition(); } get head() { const obj = this.nestedHitObjects.find((n) => n instanceof SliderHead); return obj || null; } get tail() { const obj = this.nestedHitObjects.find((n) => n instanceof SliderTail); return obj || null; } get distance() { return this.path.distance; } set distance(value) { this.path.distance = value; } get spans() { return this.repeats + 1; } get spanDuration() { return this.duration / this.spans; } get duration() { return this.endTime - this.startTime; } get endPosition() { const endPoint = this.path.curvePositionAt(1, this.spans); if (isFinite(endPoint.x) && isFinite(endPoint.y)) { return this.startPosition.fadd(endPoint); } const controlPoints = this.path.controlPoints; if (controlPoints.length) { return controlPoints[controlPoints.length - 1].position; } return this.startPosition; } get endTime() { return this.startTime + this.spans * this.distance / this.velocity; } applyDefaultsToSelf(controlPoints, difficulty) { super.applyDefaultsToSelf(controlPoints, difficulty); const timingPoint = controlPoints.timingPointAt(this.startTime); const difficultyPoint = controlPoints.difficultyPointAt(this.startTime); const scoringDistance = StandardHitObject.BASE_SCORING_DISTANCE * difficulty.sliderMultiplier * difficultyPoint.sliderVelocity; const generateTicks = difficultyPoint.generateTicks; this.velocity = scoringDistance / timingPoint.beatLength; this.tickDistance = generateTicks ? (scoringDistance / difficulty.sliderTickRate) * this.tickRate : Infinity; } createNestedHitObjects() { this.nestedHitObjects = []; for (const nested of StandardEventGenerator.generateSliderTicks(this)) { this.nestedHitObjects.push(nested); } } _updateHeadPosition() { if (this.head !== null) { this.head.startPosition = this.startPosition; } } _updateTailPosition() { if (this.tail !== null) { this.tail.startPosition = this.endPosition; } } clone() { const cloned = super.clone(); cloned.nodeSamples = this.nodeSamples.map((n) => n.map((s) => s.clone())); cloned.velocity = this.velocity; cloned.repeats = this.repeats; cloned.path = this.path.clone(); cloned.tickDistance = this.tickDistance; cloned.tickRate = this.tickRate; cloned.legacyLastTickOffset = this.legacyLastTickOffset; return cloned; } } class Spinner extends StandardHitObject { constructor() { super(...arguments); this.spinsRequired = 1; this.maximumBonusSpins = 1; this.endTime = 0; this.hitWindows = StandardHitWindows.empty; } get duration() { return this.endTime - this.startTime; } applyDefaultsToSelf(controlPoints, difficulty) { super.applyDefaultsToSelf(controlPoints, difficulty); const secondsDuration = this.duration / 1000; const minimumRotations = Spinner.STABLE_MATCHING_FUDGE * osuClasses.BeatmapDifficultySection.range(difficulty.overallDifficulty, 3, 5, 7.5); const rotationDifference = Spinner.MAXIMUM_ROTATIONS - minimumRotations; this.spinsRequired = Math.trunc(secondsDuration * minimumRotations); this.maximumBonusSpins = Math.trunc(secondsDuration * rotationDifference); } createNestedHitObjects() { this.nestedHitObjects = []; for (const nested of StandardEventGenerator.generateSpinnerTicks(this)) { this.nestedHitObjects.push(nested); } } clone() { const cloned = super.clone(); cloned.endTime = this.endTime; cloned.spinsRequired = this.spinsRequired; cloned.maximumBonusSpins = this.maximumBonusSpins; return cloned; } } Spinner.STABLE_MATCHING_FUDGE = 0.6; Spinner.MAXIMUM_ROTATIONS = 8; class StandardHardRock extends osuClasses.HardRock { applyToHitObjects(hitObjects) { hitObjects.forEach((hitObject) => { StandardHardRock._reflectVertically(hitObject); }); } static _reflectVertically(hitObject) { hitObject.startY = StandardHardRock.BASE_SIZE.y - hitObject.startY; if (!(hitObject instanceof Slider)) return; const slider = hitObject; const nestedHitObjects = slider.nestedHitObjects; nestedHitObjects.forEach((nested) => { if (nested instanceof SliderTick || nested instanceof SliderRepeat) { nested.startY = StandardHardRock.BASE_SIZE.y - nested.startY; } }); slider.path.controlPoints.forEach((point) => { point.position.y *= -1; }); slider.path.invalidate(); slider.endY = slider.endPosition.y; } } StandardHardRock.BASE_SIZE = new osuClasses.Vector2(512, 384); class StandardSuddenDeath extends osuClasses.SuddenDeath { } class StandardDoubleTime extends osuClasses.DoubleTime { } class StandardRelax extends osuClasses.Relax { } class StandardHalfTime extends osuClasses.HalfTime { } class StandardNightcore extends osuClasses.Nightcore { } class StandardFlashlight extends osuClasses.Flashlight { } class StandardAutoplay extends osuClasses.Autoplay { } class StandardSpunOut { constructor() { this.name = 'Spun Out'; this.acronym = 'SO'; this.bitwise = osuClasses.ModBitwise.SpunOut; this.type = osuClasses.ModType.Automation; this.multiplier = 0.9; this.isRanked = true; this.incompatibles = osuClasses.ModBitwise.Autoplay | osuClasses.ModBitwise.Cinema | osuClasses.ModBitwise.Relax2; } } class StandardAutopilot { constructor() { this.name = 'Autopilot'; this.acronym = 'AP'; this.bitwise = osuClasses.ModBitwise.Relax2; this.type = osuClasses.ModType.Automation; this.multiplier = 1; this.isRanked = false; this.incompatibles = osuClasses.ModBitwise.NoFail | osuClasses.ModBitwise.SuddenDeath | osuClasses.ModBitwise.Perfect | osuClasses.ModBitwise.Autoplay | osuClasses.ModBitwise.Cinema | osuClasses.ModBitwise.Relax | osuClasses.ModBitwise.SpunOut; } } class StandardPerfect extends osuClasses.Perfect { } class StandardCinema extends osuClasses.Cinema { } class StandardModCombination extends osuClasses.ModCombination { get mode() { return 0; } get _availableMods() { return [ new StandardNoMod(), new StandardNoFail(), new StandardEasy(), new StandardTouchDevice(), new StandardHidden(), new StandardHardRock(), new StandardSuddenDeath(), new StandardDoubleTime(), new StandardRelax(), new StandardHalfTime(), new StandardNightcore(), new StandardFlashlight(), new StandardAutoplay(), new StandardSpunOut(), new StandardAutopilot(), new StandardPerfect(), new StandardCinema(), ]; } } class StandardBeatmap extends osuClasses.RulesetBeatmap { constructor() { super(...arguments); this.mods = new StandardModCombination(); this.hitObjects = []; } get mode() { return 0; } get maxCombo() { return this.hitObjects.reduce((combo, obj) => { if (obj instanceof Circle) { return combo + 1; } if (obj instanceof Slider) { return combo + obj.nestedHitObjects.length; } if (obj instanceof Spinner) { return combo + 1; } return combo; }, 0); } get circles() { return this.hitObjects.filter((h) => h instanceof Circle); } get sliders() { return this.hitObjects.filter((h) => h instanceof Slider); } get spinners() { return this.hitObjects.filter((h) => h instanceof Spinner); } } class StandardBeatmapConverter extends osuClasses.BeatmapConverter { canConvert(beatmap) { return beatmap.hitObjects.every((h) => { return h.startPosition; }); } *convertHitObjects(beatmap) { const hitObjects = beatmap.hitObjects; for (const hitObject of hitObjects) { if (hitObject instanceof StandardHitObject) { yield hitObject.clone(); continue; } yield this._convertHitObject(hitObject, beatmap); } } _convertHitObject(hitObject, beatmap) { const slidable = hitObject; const spinnable = hitObject; if (slidable.path) { return this._convertSlider(slidable, beatmap); } if (typeof spinnable.endTime === 'number') { return this._convertSpinner(spinnable); } return this._convertCircle(hitObject); } _convertCircle(obj) { const converted = new Circle(); this._copyProperties(converted, obj); return converted; } _convertSlider(obj, beatmap) { var _a; const converted = new Slider(); this._copyProperties(converted, obj); converted.repeats = obj.repeats; converted.nodeSamples = obj.nodeSamples; converted.path = obj.path; converted.legacyLastTickOffset = (_a = obj === null || obj === void 0 ? void 0 : obj.legacyLastTickOffset) !== null && _a !== void 0 ? _a : 0; converted.tickRate = 1; if (beatmap.fileFormat < 8) { const diffPoint = beatmap.controlPoints.difficultyPointAt(obj.startTime); converted.tickRate = Math.fround(1 / diffPoint.sliderVelocity); } return converted; } _convertSpinner(obj) { const converted = new Spinner(); this._copyProperties(converted, obj); converted.endTime = obj.endTime; return converted; } _copyProperties(converted, obj) { var _a, _b, _c; const posObj = obj; const comboObj = obj; converted.startPosition = (_a = posObj === null || posObj === void 0 ? void 0 : posObj.startPosition) !== null && _a !== void 0 ? _a : new osuClasses.Vector2(0, 0); converted.startTime = obj.startTime; converted.hitType = obj.hitType; converted.hitSound = obj.hitSound; converted.samples = obj.samples; converted.comboOffset = (_b = comboObj === null || comboObj === void 0 ? void 0 : comboObj.comboOffset) !== null && _b !== void 0 ? _b : 0; converted.isNewCombo = (_c = comboObj === null || comboObj === void 0 ? void 0 : comboObj.isNewCombo) !== null && _c !== void 0 ? _c : false; } createBeatmap() { return new StandardBeatmap(); } } class StandardBeatmapProcessor extends osuClasses.BeatmapProcessor { postProcess(beatmap) { super.postProcess(beatmap); beatmap.fileFormat >= 6 ? this._applyStackingNew(beatmap) : this._applyStackingOld(beatmap); return beatmap; } _applyStackingNew(beatmap) { const hitObjects = beatmap.hitObjects; const stackLeniency = beatmap.general.stackLeniency; const stackDistance = StandardBeatmapProcessor.STACK_DISTANCE; const startIndex = 0; const endIndex = hitObjects.length - 1; let extendedEndIndex = endIndex; if (endIndex < hitObjects.length - 1) { for (let i = endIndex; i >= startIndex; --i) { let stackBaseIndex = i; for (let n = stackBaseIndex + 1; n < hitObjects.length; ++n) { const stackBaseObject = hitObjects[stackBaseIndex]; if (stackBaseObject instanceof Spinner) break; const objectN = hitObjects[n]; if (objectN instanceof Spinner) continue; const endTime = stackBaseObject.endTime || stackBaseObject.startTime; const stackThreshold = objectN.timePreempt * stackLeniency; if (objectN.startTime - endTime > stackThreshold) { break; } const distance1 = stackBaseObject.startPosition.fdistance(objectN.startPosition) < stackDistance; const distance2 = (stackBaseObject instanceof Slider) && stackBaseObject.endPosition.fdistance(objectN.startPosition) < stackDistance; if (distance1 || distance2) { stackBaseIndex = n; objectN.stackHeight = 0; } } if (stackBaseIndex > extendedEndIndex) { extendedEndIndex = stackBaseIndex; if (extendedEndIndex === hitObjects.length - 1) { break; } } } } let extendedStartIndex = startIndex; for (let i = extendedEndIndex; i > startIndex; --i) { let n = i; let objectI = hitObjects[i]; if (objectI.stackHeight !== 0 || (objectI instanceof Spinner)) { continue; } const stackThreshold = objectI.timePreempt * stackLeniency; if (objectI instanceof Circle) { while (--n >= 0) { const objectN = hitObjects[n]; if (objectN instanceof Spinner) continue; const endTime = objectN.endTime || objectN.startTime; if (objectI.startTime - endTime > stackThreshold) { break; } if (n < extendedStartIndex) { objectN.stackHeight = 0; extendedStartIndex = n; } const distanceNI = objectN.endPosition.fdistance(objectI.startPosition); if ((objectN instanceof Slider) && distanceNI < stackDistance) { const offset = objectI.stackHeight - objectN.stackHeight + 1; for (let j = n + 1; j <= i; ++j) { const objectJ = hitObjects[j]; const distanceNJ = objectN.endPosition.fdistance(objectJ.startPosition); if (distanceNJ < stackDistance) { objectJ.stackHeight -= offset; } } break; } if (objectN.startPosition.fdistance(objectI.startPosition) < stackDistance) { objectN.stackHeight = objectI.stackHeight + 1; objectI = objectN; } } } else if (objectI instanceof Slider) { while (--n >= startIndex) { const objectN = hitObjects[n]; if (objectN instanceof Spinner) continue; if (objectI.startTime - objectN.startTime > stackThreshold) { break; } const distance = objectN.endPosition.fdistance(objectI.startPosition); if (distance < stackDistance) { objectN.stackHeight = objectI.stackHeight + 1; objectI = objectN; } } } } } _applyStackingOld(beatmap) { const hitObjects = beatmap.hitObjects; const stackLeniency = beatmap.general.stackLeniency; const stackDistance = StandardBeatmapProcessor.STACK_DISTANCE; for (let i = 0, len = hitObjects.length; i < len; ++i) { const currHitObject = hitObjects[i]; if (currHitObject.stackHeight !== 0 && !(currHitObject instanceof Slider)) { continue; } let startTime = currHitObject.endTime || currHitObject.startTime; let sliderStack = 0; for (let j = i + 1; j < len; ++j) { const stackThreshold = hitObjects[i].timePreempt * stackLeniency; if (hitObjects[j].startTime - stackThreshold > startTime) { break; } const pos2 = currHitObject.endPosition; if (hitObjects[j].startPosition.fdistance(currHitObject.startPosition) < stackDistance) { ++currHitObject.stackHeight; startTime = hitObjects[j].endTime || hitObjects[j].startTime; } else if (hitObjects[j].startPosition.fdistance(pos2) < stackDistance) { hitObjects[j].stackHeight -= ++sliderStack; startTime = hitObjects[j].endTime || hitObjects[j].startTime; } } } } } StandardBeatmapProcessor.STACK_DISTANCE = 3; class StandardDifficultyAttributes extends osuClasses.DifficultyAttributes { constructor() { super(...arguments); this.aimDifficulty = 0; this.speedDifficulty = 0; this.speedNoteCount = 0; this.flashlightDifficulty = 0; this.sliderFactor = 0; this.approachRate = 0; this.overallDifficulty = 0; this.drainRate = 0; this.hitCircleCount = 0; this.sliderCount = 0; this.spinnerCount = 0; } } class StandardPerformanceAttributes extends osuClasses.PerformanceAttributes { constructor(mods, totalPerformance) { super(mods, totalPerformance); this.aimPerformance = 0; this.speedPerformance = 0; this.accuracyPerformance = 0; this.flashlightPerformance = 0; this.effectiveMissCount = 0; this.mods = mods; } } var _a; class StandardDifficultyHitObject extends osuClasses.DifficultyHitObject { constructor(hitObject, lastObject, lastLastObject, clockRate, objects, index) { super(hitObject, lastObject, clockRate, objects, index); this.lazyJumpDistance = 0; this.minimumJumpDistance = 0; this.minimumJumpTime = 0; this.travelDistance = 0; this.travelTime = 0; this.angle = null; this.hitWindowGreat = 0; this.baseObject = hitObject; this.lastObject = lastObject; this._lastLastObject = lastLastObject; this.strainTime = Math.max(this.deltaTime, _a.MIN_DELTA_TIME); this.hitWindowGreat = this.baseObject instanceof Slider && this.baseObject.head ? 2 * this.baseObject.head.hitWindows.windowFor(osuClasses.HitResult.Great) / clockRate : 2 * this.baseObject.hitWindows.windowFor(osuClasses.HitResult.Great) / clockRate; this._setDistances(clockRate); } opacityAt(time, hidden) { if (time > this.baseObject.startTime) return 0; const fadeInStartTime = this.baseObject.startTime - this.baseObject.timePreempt; const fadeInDuration = this.baseObject.timeFadeIn; if (hidden) { const fadeOutStartTime = this.baseObject.startTime - this.baseObject.timePreempt + this.baseObject.timeFadeIn; const fadeOutDuration = this.baseObject.timePreempt * StandardHidden.FADE_OUT_DURATION_MULTIPLIER; return Math.min(osuClasses.MathUtils.clamp((time - fadeInStartTime) / fadeInDuration, 0, 1), 1.0 - osuClasses.MathUtils.clamp((time - fadeOutStartTime) / fadeOutDuration, 0, 1)); } return osuClasses.MathUtils.clamp((time - fadeInStartTime) / fadeInDuration, 0, 1); } _setDistances(clockRate) { var _b, _c; const baseObj = this.baseObject; const lastObj = this.lastObject; if (baseObj instanceof Slider) { this._computeSliderCursorPosition(baseObj); this.travelDistance = Math.fround(Math.fround(baseObj.lazyTravelDistance) * Math.fround(Math.pow(1 + baseObj.repeats / 2.5, 1 / 2.5))); this.travelTime = Math.max(baseObj.lazyTravelTime / clockRate, _a.MIN_DELTA_TIME); } if (baseObj instanceof Spinner || lastObj instanceof Spinner) { return; } let scalingFactor = Math.fround(_a.NORMALIZED_RADIUS / Math.fround(baseObj.radius)); if (baseObj.radius < 30) { const smallCircleBonus = Math.fround(Math.min(Math.fround(30 - Math.fround(baseObj.radius)), 5) / 50); scalingFactor = Math.fround(scalingFactor * Math.fround(1 + smallCircleBonus)); } const lastCursorPosition = this._getEndCursorPosition(lastObj); const scaledStackPos = baseObj.stackedStartPosition.fscale(scalingFactor); const scaledCursorPos = lastCursorPosition.fscale(scalingFactor); this.lazyJumpDistance = scaledStackPos.fsubtract(scaledCursorPos).flength(); this.minimumJumpTime = this.strainTime; this.minimumJumpDistance = this.lazyJumpDistance; if (lastObj instanceof Slider) { const lastTraveTime = Math.max(lastObj.lazyTravelTime / clockRate, _a.MIN_DELTA_TIME); this.minimumJumpTime = Math.max(this.strainTime - lastTraveTime, _a.MIN_DELTA_TIME); const tailStackPos = (_c = (_b = lastObj.tail) === null || _b === void 0 ? void 0 : _b.stackedStartPosition) !== null && _c !== void 0 ? _c : lastObj.stackedStartPosition; const baseStackPos = baseObj.stackedStartPosition; const tailJumpDistance = Math.fround(tailStackPos.fsubtract(baseStackPos).flength() * scalingFactor); const maxSliderRadius = _a.MAXIMUM_SLIDER_RADIUS; const assumedSliderRadius = _a.ASSUMED_SLIDER_RADIUS; this.minimumJumpDistance = osuClasses.MathUtils.clamp(this.lazyJumpDistance - (maxSliderRadius - assumedSliderRadius), 0, Math.fround(tailJumpDistance - maxSliderRadius)); } if (this._lastLastObject !== null && !(this._lastLastObject instanceof Spinner)) { const lastLastCursorPosition = this._getEndCursorPosition(this._lastLastObject); const v1 = lastLastCursorPosition.fsubtract(lastObj.stackedStartPosition); const v2 = baseObj.stackedStartPosition.fsubtract(lastCursorPosition); const dot = v1.fdot(v2); const det = Math.fround(Math.fround(v1.floatX * v2.floatY) - Math.fround(v1.floatY * v2.floatX)); this.angle = Math.abs(Math.atan2(det, dot)); } } _computeSliderCursorPosition(slider) { if (slider.lazyEndPosition) return; const lastNested = slider.nestedHitObjects[slider.nestedHitObjects.length - 1]; slider.lazyTravelTime = lastNested.startTime - slider.startTime; let endTimeMin = slider.lazyTravelTime / slider.spanDuration; endTimeMin = endTimeMin % 2 >= 1 ? 1 - endTimeMin % 1 : endTimeMin % 1; const endPosition = slider.path.positionAt(endTimeMin); slider.lazyEndPosition = slider.stackedStartPosition.fadd(endPosition); let currCursorPosition = slider.stackedStartPosition; const scalingFactor = _a.NORMALIZED_RADIUS / slider.radius; for (let i = 1; i < slider.nestedHitObjects.length; ++i) { const currMovementObj = slider.nestedHitObjects[i]; let currMovement = currMovementObj.stackedStartPosition.fsubtract(currCursorPosition); let currMovementLength = scalingFactor * currMovement.flength(); let requiredMovement = _a.ASSUMED_SLIDER_RADIUS; if (i === slider.nestedHitObjects.length - 1) { const lazyMovement = slider.lazyEndPosition.fsubtract(currCursorPosition); if (lazyMovement.flength() < currMovement.flength()) { currMovement = lazyMovement; } currMovementLength = scalingFactor * currMovement.flength(); } else if (currMovementObj instanceof SliderRepeat) { requiredMovement = _a.NORMALIZED_RADIUS; } if (currMovementLength > requiredMovement) { const movementScale = Math.fround((currMovementLength - requiredMovement) / currMovementLength); currCursorPosition = currCursorPosition.fadd(currMovement.fscale(movementScale)); currMovementLength *= (currMovementLength - requiredMovement) / currMovementLength; slider.lazyTravelDistance = Math.fround(slider.lazyTravelDistance + Math.fround(currMovementLength)); } if (i === slider.nestedHitObjects.length - 1) { slider.lazyEndPosition = currCursorPosition; } } } _getEndCursorPosition(hitObject) { var _b; if (hitObject instanceof Slider) { this._computeSliderCursorPosition(hitObject); return (_b = hitObject.lazyEndPosition) !== null && _b !== void 0 ? _b : hitObject.stackedStartPosition; } return hitObject.stackedStartPosition; } } _a = StandardDifficultyHitObject; StandardDifficultyHitObject.NORMALIZED_RADIUS = 50; StandardDifficultyHitObject.MIN_DELTA_TIME = 25; StandardDifficultyHitObject.MAXIMUM_SLIDER_RADIUS = Math.fround(_a.NORMALIZED_RADIUS * Math.fround(2.4)); StandardDifficultyHitObject.ASSUMED_SLIDER_RADIUS = Math.fround(_a.NORMALIZED_RADIUS * Math.fround(1.8)); class StandardStrainSkill extends osuClasses.StrainSkill { constructor() { super(...arguments); this._reducedSectionCount = 10; this._reducedStrainBaseline = 0.75; this._difficultyMultiplier = StandardStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; } get difficultyValue() { let difficulty = 0; let weight = 1; const strains = [...this.getCurrentStrainPeaks()] .filter((p) => p > 0) .sort((a, b) => b - a); const lerp = (start, final, amount) => { return start + (final - start) * amount; }; const length = Math.min(strains.length, this._reducedSectionCount); for (let i = 0; i < length; ++i) { const value = Math.fround(i / this._reducedSectionCount); const clamp = osuClasses.MathUtils.clamp01(value); const scale = Math.log10(lerp(1, 10, clamp)); strains[i] *= lerp(this._reducedStrainBaseline, 1, scale); } strains.sort((a, b) => b - a); strains.forEach((strain) => { difficulty += strain * weight; weight *= this._decayWeight; }); return difficulty * this._difficultyMultiplier; } } StandardStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER = 1.06; class AimEvaluator { static evaluateDifficultyOf(current, withSliders) { var _a; if (current.baseObject instanceof Spinner) return 0; if (current.index <= 1) return 0; if (((_a = current.previous(0)) === null || _a === void 0 ? void 0 : _a.baseObject) instanceof Spinner) return 0; const osuCurrObj = current; const osuLastObj = current.previous(0); const osuLastLastObj = current.previous(1); let currVelocity = osuCurrObj.lazyJumpDistance / osuCurrObj.strainTime; if ((osuLastObj.baseObject instanceof Slider) && withSliders) { const travelVelocity = osuLastObj.travelDistance / osuLastObj.travelTime; const movementVelocity = osuCurrObj.minimumJumpDistance / osuCurrObj.minimumJumpTime; currVelocity = Math.max(currVelocity, movementVelocity + travelVelocity); } let prevVelocity = osuLastObj.lazyJumpDistance / osuLastObj.strainTime; if ((osuLastLastObj.baseObject instanceof Slider) && withSliders) { const travelVelocity = osuLastLastObj.travelDistance / osuLastLastObj.travelTime; const movementVelocity = osuLastObj.minimumJumpDistance / osuLastObj.minimumJumpTime; prevVelocity = Math.max(prevVelocity, movementVelocity + travelVelocity); } let wideAngleBonus = 0; let acuteAngleBonus = 0; let sliderBonus = 0; let velocityChangeBonus = 0; let aimStrain = currVelocity; const strainTime1 = Math.max(osuCurrObj.strainTime, osuLastObj.strainTime); const strainTime2 = Math.min(osuCurrObj.strainTime, osuLastObj.strainTime); if (strainTime1 < 1.25 * strainTime2) { if (osuCurrObj.angle !== null && osuLastObj.angle !== null && osuLastLastObj.angle !== null) { const currAngle = osuCurrObj.angle; const lastAngle = osuLastObj.angle; const lastLastAngle = osuLastLastObj.angle; const angleBonus = Math.min(currVelocity, prevVelocity); wideAngleBonus = this._calcWideAngleBonus(currAngle); acuteAngleBonus = this._calcAcuteAngleBonus(currAngle); if (osuCurrObj.strainTime > 100) { acuteAngleBonus = 0; } else { acuteAngleBonus *= this._calcAcuteAngleBonus(lastAngle); acuteAngleBonus *= Math.min(angleBonus, 125 / osuCurrObj.strainTime); const x1 = Math.PI / 2 * Math.min(1, (100 - osuCurrObj.strainTime) / 25); acuteAngleBonus *= Math.pow(Math.sin(x1), 2); const clamp = Math.min(Math.max(osuCurrObj.lazyJumpDistance, 50), 100); const x2 = Math.PI / 2 * (clamp - 50) / 50; acuteAngleBonus *= Math.pow(Math.sin(x2), 2); } const pow1 = Math.pow(this._calcWideAngleBonus(lastAngle), 3); wideAngleBonus *= angleBonus * (1 - Math.min(wideAngleBonus, pow1)); const pow2 = Math.pow(this._calcAcuteAngleBonus(lastLastAngle), 3); acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.min(acuteAngleBonus, pow2)); } } if (Math.max(prevVelocity, currVelocity) !== 0) { prevVelocity = (osuLastObj.lazyJumpDistance + osuLastLastObj.travelDistance) / osuLastObj.strainTime; currVelocity = (osuCurrObj.lazyJumpDistance + osuLastObj.travelDistance) / osuCurrObj.strainTime; const abs1 = Math.abs(prevVelocity - currVelocity); const max1 = Math.max(prevVelocity, currVelocity); const distRatio = Math.pow(Math.sin(Math.PI / 2 * abs1 / max1), 2); const min2 = Math.min(osuCurrObj.strainTime, osuLastObj.strainTime); const abs2 = Math.abs(prevVelocity - currVelocity); const overlapVelocityBuff = Math.min(125 / min2, abs2); velocityChangeBonus = overlapVelocityBuff * distRatio; const min4 = Math.min(osuCurrObj.strainTime, osuLastObj.strainTime); const max4 = Math.max(osuCurrObj.strainTime, osuLastObj.strainTime); velocityChangeBonus *= Math.pow(min4 / max4, 2); } if (osuLastObj.baseObject instanceof Slider) { sliderBonus = osuLastObj.travelDistance / osuLastObj.travelTime; } const acuteBonus = acuteAngleBonus * this.ACUTE_ANGLE_MULTIPLIER; const wideBonus = wideAngleBonus * this.WIDE_ANGLE_MULTIPLIER; const velocityBonus = velocityChangeBonus * this.VELOCITY_CHANGE_MULTIPLIER; aimStrain += Math.max(acuteBonus, wideBonus + velocityBonus); if (withSliders) { aimStrain += sliderBonus * this.SLIDER_MULTIPLIER; } return aimStrain; } static _calcWideAngleBonus(angle) { const clamp = Math.min(5 / 6 * Math.PI, Math.max(Math.PI / 6, angle)); const x = 3 / 4 * (clamp - Math.PI / 6); return Math.pow(Math.sin(x), 2); } static _calcAcuteAngleBonus(angle) { return 1 - this._calcWideAngleBonus(angle); } } AimEvaluator.WIDE_ANGLE_MULTIPLIER = 1.5; AimEvaluator.ACUTE_ANGLE_MULTIPLIER = 1.95; AimEvaluator.SLIDER_MULTIPLIER = 1.35; AimEvaluator.VELOCITY_CHANGE_MULTIPLIER = 0.75; class FlashlightEvaluator { static evaluateDifficultyOf(current, hidden) { if (current.baseObject instanceof Spinner) return 0; const osuCurrent = current; const osuHitObject = osuCurrent.baseObject; const scalingFactor = 52 / osuHitObject.radius; let smallDistNerf = 1; let cumulativeStrainTime = 0; let result = 0; let angleRepeatCount = 0; let lastObj = osuCurrent; for (let i = 0; i < Math.min(current.index, 10); i++) { const currentObj = current.previous(i); const currentHitObject = currentObj.baseObject; if (!(currentObj.baseObject instanceof Spinner)) { const jumpDistance = osuHitObject.stackedStartPosition .fsubtract(currentHitObject.stackedEndPosition) .flength(); cumulativeStrainTime += lastObj.strainTime; if (i === 0) { smallDistNerf = Math.min(1, jumpDistance / 75); } const stackNerf = Math.min(1, (currentObj.lazyJumpDistance / scalingFactor) / 25); const opacity = osuCurrent.opacityAt(currentHitObject.startTime, hidden); const opacityBonus = 1 + this.MAX_OPACITY_BONUS * (1 - opacity); result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; if (currentObj.angle !== null && osuCurrent.angle !== null) { if (Math.abs(currentObj.angle - osuCurrent.angle) < 0.02) { angleRepeatCount += Math.max(1 - 0.1 * i, 0); } } } lastObj = currentObj; } result = Math.pow(smallDistNerf * result, 2.0); if (hidden) result *= 1 + this.HIDDEN_BONUS; result *= this.MIN_ANGLE_MULTIPLIER + (1 - this.MIN_ANGLE_MULTIPLIER) / (angleRepeatCount + 1); let sliderBonus = 0; if (osuCurrent.baseObject instanceof Slider) { const slider = osuCurrent.baseObject; const pixelTravelDistance = slider.lazyTravelDistance / scalingFactor; const max1 = Math.max(0, pixelTravelDistance / osuCurrent.travelTime - this.MIN_VELOCITY); sliderBonus = Math.pow(max1, 0.5); sliderBonus *= pixelTravelDistance; if (slider.repeats > 0) { sliderBonus /= (slider.repeats + 1); } } result += sliderBonus * this.SLIDER_MULTIPLIER; return result; } } FlashlightEvaluator.MAX_OPACITY_BONUS = 0.4; FlashlightEvaluator.HIDDEN_BONUS = 0.2; FlashlightEvaluator.MIN_VELOCITY = 0.5; FlashlightEvaluator.SLIDER_MULTIPLIER = 1.3; FlashlightEvaluator.MIN_ANGLE_MULTIPLIER = 0.2; class RhythmEvaluator { static evaluateDifficultyOf(current) { var _a, _b, _c, _d; if (current.baseObject instanceof Spinner) return 0; let startRatio = 0; let firstDeltaSwitch = false; let rhythmStart = 0; let previousIslandSize = 0; let rhythmComplexitySum = 0; let islandSize = 1; const historicalNoteCount = Math.min(current.index, 32); while (rhythmStart < historicalNoteCount - 2 && current.startTime - ((_b = (_a = current.previous(rhythmStart)) === null || _a === void 0 ? void 0 : _a.startTime) !== null && _b !== void 0 ? _b : 0) < this.HISTORY_TIME_MAX) { rhythmStart++; } for (let i = rhythmStart; i > 0; --i) { const currObj = current.previous(i - 1); const prevObj = current.previous(i); const lastObj = current.previous(i + 1); let currHistoricalDecay = (this.HISTORY_TIME_MAX - (current.startTime - currObj.startTime)) / this.HISTORY_TIME_MAX; currHistoricalDecay = Math.min((historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); const currDelta = currObj.strainTime; const prevDelta = prevObj.strainTime; const lastDelta = lastObj.strainTime; const x1 = Math.min(prevDelta, currDelta) / Math.max(prevDelta, currDelta); const currRatio = 1 + 6 * Math.min(0.5, Math.pow(Math.sin(Math.PI / x1), 2)); const x2 = Math.max(0, Math.abs(prevDelta - currDelta) - currObj.hitWindowGreat * 0.3); let windowPenalty = Math.min(1, x2 / (currObj.hitWindowGreat * 0.3)); windowPenalty = Math.min(1, windowPenalty); let effectiveRatio = windowPenalty * currRatio; if (firstDeltaSwitch) { if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) { if (islandSize < 7) islandSize++; } else { if (((_c = current.previous(i - 1)) === null || _c === void 0 ? void 0 : _c.baseObject) instanceof Slider) { effectiveRatio *= 0.125; } if (((_d = current.previous(i)) === null || _d === void 0 ? void 0 : _d.baseObject) instanceof Slider) { effectiveRatio *= 0.25; } if (previousIslandSize === islandSize) { effectiveRatio *= 0.25; } if (previousIslandSize % 2 === islandSize % 2) { effectiveRatio *= 0.50; } if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) { effectiveRatio *= 0.125; } const sqrt1 = Math.sqrt(effectiveRatio * startRatio); const sqrt2 = Math.sqrt(4 + islandSize); const sqrt3 = Math.sqrt(4 + previousIslandSize); rhythmComplexitySum += sqrt1 * currHistoricalDecay * sqrt2 / 2 * sqrt3 / 2; startRatio = effectiveRatio; previousIslandSize = islandSize; if (prevDelta * 1.25 < currDelta) { firstDeltaSwitch = false; } islandSize = 1; } } else if (prevDelta > 1.25 * currDelta) { firstDeltaSwitch = true; startRatio = effectiveRatio; islandSize = 1; } } return Math.sqrt(4 + rhythmComplexitySum * this.RHYTHM_MULTIPLIER) / 2; } } RhythmEvaluator.HISTORY_TIME_MAX = 5000; RhythmEvaluator.RHYTHM_MULTIPLIER = 0.75; class SpeedEvaluator { static evaluateDifficultyOf(current) { var _a; if (current.baseObject instanceof Spinner) return 0; const osuCurrObj = current; const osuPrevObj = current.index > 0 ? current.previous(0) : null; const osuNextObj = current.next(0); let strainTime = osuCurrObj.strainTime; let doubletapness = 1; if (osuNextObj !== null) { const currDeltaTime = Math.max(1, osuCurrObj.deltaTime); const nextD