osu-mania-stable
Version:
osu!stable version of osu!mania ruleset based on osu!lazer source code.
1,325 lines (1,282 loc) • 79.9 kB
JavaScript
'use strict';
var osuClasses = require('osu-classes');
class ManiaHitObject extends osuClasses.HitObject {
constructor() {
super(...arguments);
this._originalColumn = 0;
this._column = 0;
}
get originalColumn() {
return this._originalColumn;
}
set originalColumn(value) {
this._originalColumn = value;
this._column = value;
}
get column() {
return this._column;
}
set column(value) {
this._column = value;
}
get startX() {
return this._column;
}
get endX() {
return this.startX;
}
clone() {
const cloned = super.clone();
cloned.originalColumn = this.originalColumn;
cloned.column = this.column;
return cloned;
}
}
class Note extends ManiaHitObject {
}
class HoldHead extends Note {
}
class HoldTail extends Note {
}
class HoldTick extends ManiaHitObject {
}
class ManiaEventGenerator extends osuClasses.EventGenerator {
static *generateHoldTicks(hold) {
if (hold.tickInterval === 0) {
return;
}
const head = new HoldHead();
head.startTime = hold.startTime;
head.column = hold.column;
yield head;
const tickInterval = hold.tickInterval;
let time = hold.startTime + tickInterval;
const endTime = hold.endTime - tickInterval;
while (time <= endTime) {
const tick = new HoldTick();
tick.startTime = time;
yield tick;
time += tickInterval;
}
const tail = new HoldTail();
tail.startTime = hold.endTime;
tail.column = hold.column;
yield tail;
}
}
class Hold extends ManiaHitObject {
constructor() {
super(...arguments);
this.tickInterval = 50;
this.nodeSamples = [];
this.endTime = 0;
this.hitType = osuClasses.HitType.Hold;
}
get duration() {
return this.endTime - this.startTime;
}
get head() {
const head = this.nestedHitObjects.find((n) => n instanceof HoldHead);
if (head) {
head.startTime = this.startTime;
}
return head || null;
}
get tail() {
const tail = this.nestedHitObjects.find((n) => n instanceof HoldTail);
if (tail) {
tail.startTime = this.endTime;
}
return tail || null;
}
get column() {
return this._column;
}
set column(value) {
this._column = value;
if (this.head) {
this.head.column = value;
}
if (this.tail) {
this.tail.column = value;
}
}
applyDefaultsToSelf(controlPoints, difficulty) {
super.applyDefaultsToSelf(controlPoints, difficulty);
const timingPoint = controlPoints.timingPointAt(this.startTime);
this.tickInterval = timingPoint.beatLength / difficulty.sliderTickRate;
}
createNestedHitObjects() {
this.nestedHitObjects = [];
for (const nested of ManiaEventGenerator.generateHoldTicks(this)) {
this.nestedHitObjects.push(nested);
}
}
clone() {
const cloned = super.clone();
cloned.endTime = this.endTime;
cloned.nodeSamples = this.nodeSamples.map((n) => n.map((s) => s.clone()));
cloned.tickInterval = this.tickInterval;
return cloned;
}
}
class ManiaAutoplay extends osuClasses.Autoplay {
}
class ManiaCinema extends osuClasses.Cinema {
}
class ManiaDoubleTime extends osuClasses.DoubleTime {
constructor() {
super(...arguments);
this.multiplier = 1;
}
}
class ManiaDualStages {
constructor() {
this.name = 'Dual Stages';
this.acronym = 'DS';
this.bitwise = osuClasses.ModBitwise.KeyCoop;
this.type = osuClasses.ModType.Conversion;
this.multiplier = 1;
this.isRanked = false;
this.incompatibles = osuClasses.ModBitwise.None;
}
applyToConverter(converter) {
converter.isDual = true;
}
}
class ManiaEasy extends osuClasses.Easy {
}
class ManiaFadeIn {
constructor() {
this.name = 'Fade In';
this.acronym = 'FI';
this.bitwise = osuClasses.ModBitwise.FadeIn;
this.type = osuClasses.ModType.DifficultyIncrease;
this.multiplier = 1;
this.isRanked = true;
this.incompatibles = osuClasses.ModBitwise.Hidden | osuClasses.ModBitwise.Flashlight;
}
}
class ManiaFlashlight extends osuClasses.Flashlight {
constructor() {
super(...arguments);
this.multiplier = 1;
this.incompatibles = osuClasses.ModBitwise.Hidden | osuClasses.ModBitwise.FadeIn;
}
}
class ManiaHalfTime extends osuClasses.HalfTime {
constructor() {
super(...arguments);
this.multiplier = 0.5;
}
}
class ManiaHardRock extends osuClasses.HardRock {
constructor() {
super(...arguments);
this.multiplier = 1;
}
}
class ManiaHidden extends osuClasses.Hidden {
constructor() {
super(...arguments);
this.multiplier = 1;
this.incompatibles = osuClasses.ModBitwise.FadeIn | osuClasses.ModBitwise.Flashlight;
}
}
class ManiaKeyMod {
constructor() {
this.type = osuClasses.ModType.Conversion;
this.multiplier = 1;
this.isRanked = true;
this.incompatibles = osuClasses.ModBitwise.KeyMod;
}
applyToConverter(converter) {
converter.targetColumns = this.keyCount;
}
}
class ManiaKey1 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'One Key';
this.acronym = '1K';
this.bitwise = osuClasses.ModBitwise.Key1;
this.keyCount = 1;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key1;
}
}
class ManiaKey2 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Two Keys';
this.acronym = '2K';
this.bitwise = osuClasses.ModBitwise.Key2;
this.keyCount = 2;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key2;
}
}
class ManiaKey3 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Three Keys';
this.acronym = '3K';
this.bitwise = osuClasses.ModBitwise.Key3;
this.keyCount = 3;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key3;
}
}
class ManiaKey4 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Four Keys';
this.acronym = '4K';
this.bitwise = osuClasses.ModBitwise.Key4;
this.keyCount = 4;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key4;
}
}
class ManiaKey5 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Five Keys';
this.acronym = '5K';
this.bitwise = osuClasses.ModBitwise.Key5;
this.keyCount = 5;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key5;
}
}
class ManiaKey6 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Six Keys';
this.acronym = '6K';
this.bitwise = osuClasses.ModBitwise.Key6;
this.keyCount = 6;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key6;
}
}
class ManiaKey7 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Seven Keys';
this.acronym = '7K';
this.bitwise = osuClasses.ModBitwise.Key7;
this.keyCount = 7;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key7;
}
}
class ManiaKey8 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Eight Keys';
this.acronym = '8K';
this.bitwise = osuClasses.ModBitwise.Key8;
this.keyCount = 8;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key8;
}
}
class ManiaKey9 extends ManiaKeyMod {
constructor() {
super(...arguments);
this.name = 'Nine Keys';
this.acronym = '9K';
this.bitwise = osuClasses.ModBitwise.Key9;
this.keyCount = 9;
this.incompatibles = osuClasses.ModBitwise.KeyMod ^ osuClasses.ModBitwise.Key9;
}
}
class ManiaMirror {
constructor() {
this.name = 'Mirror';
this.acronym = 'MR';
this.bitwise = osuClasses.ModBitwise.Mirror;
this.type = osuClasses.ModType.Conversion;
this.multiplier = 1;
this.isRanked = true;
this.incompatibles = osuClasses.ModBitwise.None;
}
applyToBeatmap(beatmap) {
beatmap.hitObjects.forEach((h) => {
h.column = beatmap.totalColumns - 1 - h.column;
});
}
}
class ManiaNoMod extends osuClasses.NoMod {
}
class ManiaNoFail extends osuClasses.NoFail {
}
class ManiaSuddenDeath extends osuClasses.SuddenDeath {
}
class ManiaNightcore extends osuClasses.Nightcore {
}
class ManiaPerfect extends osuClasses.Perfect {
}
class ManiaRandom {
constructor() {
this.name = 'Random';
this.acronym = 'RD';
this.bitwise = osuClasses.ModBitwise.Random;
this.type = osuClasses.ModType.Conversion;
this.multiplier = 1;
this.isRanked = false;
this.incompatibles = osuClasses.ModBitwise.None;
}
applyToBeatmap(beatmap) {
const random = [];
const shuffled = [];
for (let i = 0; i < beatmap.totalColumns; ++i) {
random.push(Math.random());
shuffled.push(i);
}
shuffled.sort((a, b) => random[shuffled.indexOf(a)] - random[shuffled.indexOf(b)]);
beatmap.hitObjects.forEach((h) => (h.column = shuffled[h.column]));
}
}
class ManiaModCombination extends osuClasses.ModCombination {
get mode() {
return 3;
}
get _availableMods() {
return [
new ManiaNoMod(),
new ManiaNoFail(),
new ManiaEasy(),
new ManiaHidden(),
new ManiaFadeIn(),
new ManiaHardRock(),
new ManiaSuddenDeath(),
new ManiaDoubleTime(),
new ManiaHalfTime(),
new ManiaNightcore(),
new ManiaFlashlight(),
new ManiaAutoplay(),
new ManiaPerfect(),
new ManiaCinema(),
new ManiaKey1(),
new ManiaKey2(),
new ManiaKey3(),
new ManiaKey4(),
new ManiaKey5(),
new ManiaKey6(),
new ManiaKey7(),
new ManiaKey8(),
new ManiaKey9(),
new ManiaRandom(),
new ManiaDualStages(),
new ManiaMirror(),
];
}
}
class ManiaBeatmap extends osuClasses.RulesetBeatmap {
constructor(stage, columns) {
super();
this.mods = new ManiaModCombination();
this.stages = [];
this.originalTotalColumns = 0;
this.hitObjects = [];
this.stages.push(stage);
this.originalTotalColumns = columns || stage.columns;
}
get mode() {
return 3;
}
get totalColumns() {
return this.stages.reduce((c, s) => c + s.columns, 0);
}
get maxCombo() {
return this.hitObjects.reduce((combo, obj) => {
if (obj instanceof Note) {
return combo + 1;
}
if (obj instanceof Hold) {
return combo + 1 + Math.trunc((obj.endTime - obj.startTime) / 100);
}
return combo;
}, 0);
}
get notes() {
return this.hitObjects.filter((h) => h instanceof Note);
}
get holds() {
return this.hitObjects.filter((h) => h instanceof Hold);
}
}
class PatternGenerator {
constructor(hitObject, beatmap, originalBeatmap, previousPattern, rng) {
this._conversionDiff = null;
this.hitObject = hitObject;
this.beatmap = beatmap;
this.originalBeatmap = originalBeatmap;
this.previousPattern = previousPattern;
this.totalColumns = beatmap.totalColumns;
this.randomStart = beatmap.totalColumns === 8 ? 1 : 0;
this.rng = rng;
}
getColumn(position, allowSpecial = false) {
if (allowSpecial && this.totalColumns === 8) {
const divisor = Math.round(512 / 7 * 100000) / 100000;
const x = Math.floor(position / divisor);
return Math.max(0, Math.min(x, 6)) + 1;
}
const divisor = Math.round(512 / this.totalColumns * 100000) / 100000;
const x = Math.floor(position / divisor);
return Math.max(0, Math.min(x, this.totalColumns - 1));
}
getRandomNoteCount(p2, p3, p4 = 0, p5 = 0, p6 = 0) {
if (p2 < 0 || p2 > 1) {
throw new Error('p2 is not in range 0-1');
}
if (p3 < 0 || p3 > 1) {
throw new Error('p3 is not in range 0-1');
}
if (p4 < 0 || p4 > 1) {
throw new Error('p4 is not in range 0-1');
}
if (p5 < 0 || p5 > 1) {
throw new Error('p5 is not in range 0-1');
}
if (p6 < 0 || p6 > 1) {
throw new Error('p6 is not in range 0-1');
}
const value = this.rng.nextDouble();
if (value >= 1 - p6)
return 6;
if (value >= 1 - p5)
return 5;
if (value >= 1 - p4)
return 4;
if (value >= 1 - p3)
return 3;
return value >= 1 - p2 ? 2 : 1;
}
get conversionDifficulty() {
if (this._conversionDiff !== null) {
return this._conversionDiff;
}
const hitObjects = this.originalBeatmap.hitObjects;
const firstObject = hitObjects[0];
const lastObject = hitObjects[hitObjects.length - 1];
const firstStartTime = firstObject.startTime || 0;
const lastStartTime = lastObject.startTime || 0;
const drain = lastStartTime - firstStartTime - this.originalBeatmap.totalBreakTime;
let drainTime = Math.trunc(drain / 1000);
if (drainTime === 0)
drainTime = 10000;
const difficulty = this.originalBeatmap.difficulty;
this._conversionDiff = Math.max(4, Math.min(difficulty.approachRate, 7));
this._conversionDiff += difficulty.drainRate;
this._conversionDiff /= 1.5;
this._conversionDiff += (hitObjects.length / drainTime) * Math.fround(9);
this._conversionDiff = Math.fround((this._conversionDiff / 38) * 5) / 1.15;
this._conversionDiff = Math.min(this._conversionDiff, 12);
return this._conversionDiff;
}
findAvailableColumn(column, options) {
const patterns = options.patterns || [];
const lowerBound = options.lowerBound || this.randomStart;
const upperBound = options.upperBound || this.totalColumns;
const nextColumn = options.nextColumn || (() => {
return this.getRandomColumn(lowerBound, upperBound);
});
const validate = options.validate || (() => true);
const isValid = (c) => {
return validate(c) !== false
&& !patterns.find((p) => p.columnHasObject(c));
};
if (isValid(column))
return column;
let hasValidColumns = false;
for (let i = lowerBound; i < upperBound; ++i) {
hasValidColumns = isValid(i);
if (hasValidColumns)
break;
}
if (!hasValidColumns) {
throw new Error('There were not enough columns to complete conversion.');
}
do {
column = nextColumn(column);
} while (!isValid(column));
return column;
}
getRandomColumn(lowerBound, upperBound) {
lowerBound = lowerBound || this.randomStart;
upperBound = upperBound || this.totalColumns;
return this.rng.nextInt(lowerBound, upperBound);
}
}
exports.PatternType = void 0;
(function (PatternType) {
PatternType[PatternType["None"] = 0] = "None";
PatternType[PatternType["ForceStack"] = 1] = "ForceStack";
PatternType[PatternType["ForceNotStack"] = 2] = "ForceNotStack";
PatternType[PatternType["KeepSingle"] = 4] = "KeepSingle";
PatternType[PatternType["LowProbability"] = 8] = "LowProbability";
PatternType[PatternType["Alternate"] = 16] = "Alternate";
PatternType[PatternType["ForceSigSlider"] = 32] = "ForceSigSlider";
PatternType[PatternType["ForceNotSlider"] = 64] = "ForceNotSlider";
PatternType[PatternType["Gathered"] = 128] = "Gathered";
PatternType[PatternType["Mirror"] = 256] = "Mirror";
PatternType[PatternType["Reverse"] = 512] = "Reverse";
PatternType[PatternType["Cycle"] = 1024] = "Cycle";
PatternType[PatternType["Stair"] = 2048] = "Stair";
PatternType[PatternType["ReverseStair"] = 4096] = "ReverseStair";
})(exports.PatternType || (exports.PatternType = {}));
class Pattern {
constructor() {
this.hitObjects = [];
this.containedColumns = new Set();
}
columnHasObject(column) {
return this.containedColumns.has(column);
}
get columnsWithObjects() {
return this.containedColumns.size;
}
addHitObject(hitObject) {
this.hitObjects.push(hitObject);
this.containedColumns.add(hitObject.column);
}
addPatternHitObjects(other) {
this.hitObjects.push(...other.hitObjects);
other.hitObjects.forEach((h) => {
this.containedColumns.add(h.column);
});
}
clear() {
this.hitObjects.length = 0;
this.containedColumns.clear();
}
}
class DistanceObjectPatternGenerator extends PatternGenerator {
constructor(hitObject, beatmap, originalBeatmap, previousPattern, rng) {
super(hitObject, beatmap, originalBeatmap, previousPattern, rng);
this.convertType = exports.PatternType.None;
if (!beatmap.controlPoints.effectPointAt(hitObject.startTime).kiai) {
this.convertType = exports.PatternType.LowProbability;
}
const slider = hitObject;
const timingPoint = beatmap.controlPoints
.timingPointAt(hitObject.startTime);
const difficultyPoint = beatmap.controlPoints.difficultyPointAt(hitObject.startTime);
const beatLength = timingPoint.beatLength * difficultyPoint.bpmMultiplier;
this.spanCount = slider.repeats + 1 || 1;
this.startTime = osuClasses.RoundHelper.round(hitObject.startTime);
const sliderMultiplier = beatmap.difficulty.sliderMultiplier;
this.endTime = ((slider.path.distance || 0)
* beatLength * this.spanCount * 0.01) / sliderMultiplier;
this.endTime = Math.trunc(Math.floor(this.startTime + this.endTime));
const duration = this.endTime - this.startTime;
this.segmentDuration = (duration / this.spanCount) >> 0;
}
*generate() {
const originalPattern = this.generatePattern();
if (originalPattern.hitObjects.length === 1) {
return yield originalPattern;
}
const intermediatePattern = new Pattern();
const endTimePattern = new Pattern();
for (const hitObject of originalPattern.hitObjects) {
let endTime = hitObject.endTime;
endTime = endTime || hitObject.startTime;
if (this.endTime !== osuClasses.RoundHelper.round(endTime)) {
intermediatePattern.addHitObject(hitObject);
}
else {
endTimePattern.addHitObject(hitObject);
}
}
yield intermediatePattern;
yield endTimePattern;
}
generatePattern() {
if (this.totalColumns === 1) {
const pattern = new Pattern();
this.addToPattern(pattern, 0, this.startTime, this.endTime);
return pattern;
}
if (this.spanCount > 1) {
if (this.segmentDuration <= 90) {
return this.generateRandomHoldNotes(this.startTime, 1);
}
if (this.segmentDuration <= 120) {
this.convertType |= exports.PatternType.ForceNotStack;
return this.generateRandomNotes(this.startTime, this.spanCount + 1);
}
if (this.segmentDuration <= 160) {
return this.generateStair(this.startTime);
}
if (this.segmentDuration <= 200 && this.conversionDifficulty > 3) {
return this.generateRandomMultipleNotes(this.startTime);
}
if (this.endTime - this.startTime >= 4000) {
return this.generateNRandomNotes(this.startTime, 0.23, 0, 0);
}
const columns = this.totalColumns - 1 - this.randomStart;
if (this.segmentDuration > 400 && this.spanCount < columns) {
return this.generateTiledHoldNotes(this.startTime);
}
return this.generateHoldAndNormalNotes(this.startTime);
}
if (this.segmentDuration <= 110) {
if (this.previousPattern.columnsWithObjects < this.totalColumns) {
this.convertType |= exports.PatternType.ForceNotStack;
}
else {
this.convertType &= ~exports.PatternType.ForceNotStack;
}
const noteCount = this.segmentDuration < 80 ? 1 : 2;
return this.generateRandomNotes(this.startTime, noteCount);
}
if (this.conversionDifficulty > 6.5) {
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateNRandomNotes(this.startTime, 0.78, 0.3, 0);
}
return this.generateNRandomNotes(this.startTime, 0.85, 0.36, 0.03);
}
if (this.conversionDifficulty > 4) {
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateNRandomNotes(this.startTime, 0.43, 0.08, 0);
}
return this.generateNRandomNotes(this.startTime, 0.56, 0.18, 0);
}
if (this.conversionDifficulty > 2.5) {
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateNRandomNotes(this.startTime, 0.3, 0, 0);
}
return this.generateNRandomNotes(this.startTime, 0.37, 0.08, 0);
}
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateNRandomNotes(this.startTime, 0.17, 0, 0);
}
return this.generateNRandomNotes(this.startTime, 0.27, 0, 0);
}
generateRandomHoldNotes(startTime, noteCount) {
const pattern = new Pattern();
const usableColumns = this.totalColumns -
this.randomStart -
this.previousPattern.columnsWithObjects;
let column = this.getRandomColumn();
for (let i = 0, len = Math.min(usableColumns, noteCount); i < len; ++i) {
const options = {
patterns: [pattern, this.previousPattern],
};
column = this.findAvailableColumn(column, options);
this.addToPattern(pattern, column, startTime, this.endTime);
}
for (let i = 0, len = noteCount - usableColumns; i < len; ++i) {
const options = {
patterns: [pattern],
};
column = this.findAvailableColumn(column, options);
this.addToPattern(pattern, column, startTime, this.endTime);
}
return pattern;
}
generateRandomNotes(startTime, noteCount) {
const pattern = new Pattern();
const startX = this.hitObject.startX;
let column = this.getColumn(startX || 0, true);
const isForceNotStack = !!(this.convertType & exports.PatternType.ForceNotStack);
const lessThanTotalColumns = this.previousPattern.columnsWithObjects < this.totalColumns;
if (isForceNotStack && lessThanTotalColumns) {
const options = {
patterns: [this.previousPattern],
};
column = this.findAvailableColumn(column, options);
}
let lastColumn = column;
for (let i = 0; i < noteCount; ++i) {
this.addToPattern(pattern, column, startTime, startTime);
const options = {
validate: (c) => c !== lastColumn,
};
startTime += this.segmentDuration;
column = this.findAvailableColumn(column, options);
lastColumn = column;
}
return pattern;
}
generateStair(startTime) {
const pattern = new Pattern();
const startX = this.hitObject.startX;
let column = this.getColumn(startX || 0, true);
let increasing = this.rng.nextDouble() > 0.5;
for (let i = 0; i <= this.spanCount; ++i) {
this.addToPattern(pattern, column, startTime, startTime);
startTime += this.segmentDuration;
if (increasing) {
if (column >= this.totalColumns - 1) {
increasing = false;
--column;
}
else {
++column;
}
}
else {
if (column <= this.randomStart) {
increasing = true;
++column;
}
else {
--column;
}
}
}
return pattern;
}
generateRandomMultipleNotes(startTime) {
const pattern = new Pattern();
const legacy = this.totalColumns >= 4 && this.totalColumns <= 8;
const interval = this.rng.nextInt(1, this.totalColumns - (legacy ? 1 : 0));
const startX = this.hitObject.startX;
let column = this.getColumn(startX || 0, true);
for (let i = 0; i <= this.spanCount; ++i) {
this.addToPattern(pattern, column, startTime, startTime);
column += interval;
if (column >= this.totalColumns - this.randomStart) {
column =
column - this.totalColumns - this.randomStart + (legacy ? 1 : 0);
}
column += this.randomStart;
if (this.totalColumns > 2) {
this.addToPattern(pattern, column, startTime, startTime);
}
column = this.getRandomColumn();
startTime += this.segmentDuration;
}
return pattern;
}
generateNRandomNotes(startTime, p2, p3, p4) {
switch (this.totalColumns) {
case 2:
p2 = 0;
p3 = 0;
p4 = 0;
break;
case 3:
p2 = Math.min(p2, 0.1);
p3 = 0;
p4 = 0;
break;
case 4:
p2 = Math.min(p2, 0.3);
p3 = Math.min(p3, 0.04);
p4 = 0;
break;
case 5:
p2 = Math.min(p2, 0.34);
p3 = Math.min(p3, 0.1);
p4 = Math.min(p4, 0.03);
break;
}
const isDoubleAtObject = !!this.hitObject.samples.find(findDoubleSample);
const isDoubleAtTime = !!this.hitSamplesAt(this.startTime).find(findDoubleSample);
const isLowProbability = this.convertType & exports.PatternType.LowProbability;
const canGenerateTwoNotes = !isLowProbability && (isDoubleAtObject || isDoubleAtTime);
if (canGenerateTwoNotes)
p2 = 1;
const notes = this.getRandomNoteCount(p2, p3, p4);
return this.generateRandomHoldNotes(startTime, notes);
function findDoubleSample(sample) {
return (sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Clap] ||
sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Finish]);
}
}
generateTiledHoldNotes(startTime) {
const pattern = new Pattern();
const columnRepeat = Math.min(this.spanCount, this.totalColumns);
const endTime = startTime + this.segmentDuration * this.spanCount;
const startX = this.hitObject.startX;
let column = this.getColumn(startX || 0, true);
const isForceNotStack = !!(this.convertType & exports.PatternType.ForceNotStack);
const lessThanTotalColumns = this.previousPattern.columnsWithObjects < this.totalColumns;
if (isForceNotStack && lessThanTotalColumns) {
const options = {
patterns: [this.previousPattern],
};
column = this.findAvailableColumn(column, options);
}
for (let i = 0; i < columnRepeat; ++i) {
const options = {
patterns: [pattern],
};
column = this.findAvailableColumn(column, options);
this.addToPattern(pattern, column, startTime, endTime);
startTime += this.segmentDuration;
}
return pattern;
}
generateHoldAndNormalNotes(startTime) {
const pattern = new Pattern();
const startX = this.hitObject.startX;
let holdColumn = this.getColumn(startX || 0, true);
const isForceNotStack = !!(this.convertType & exports.PatternType.ForceNotStack);
const lessThanTotalColumns = this.previousPattern.columnsWithObjects < this.totalColumns;
if (isForceNotStack && lessThanTotalColumns) {
const options = {
patterns: [this.previousPattern],
};
holdColumn = this.findAvailableColumn(holdColumn, options);
}
this.addToPattern(pattern, holdColumn, startTime, this.endTime);
let column = this.getRandomColumn();
let noteCount = 0;
if (this.conversionDifficulty > 6.5) {
noteCount = this.getRandomNoteCount(0.63, 0);
}
else if (this.conversionDifficulty > 4) {
const p2 = this.totalColumns < 6 ? 0.12 : 0.45;
noteCount = this.getRandomNoteCount(p2, 0);
}
else if (this.conversionDifficulty > 2.5) {
const p2 = this.totalColumns < 6 ? 0 : 0.24;
noteCount = this.getRandomNoteCount(p2, 0);
}
noteCount = Math.min(this.totalColumns - 1, noteCount);
const ignoreHead = !this.hitSamplesAt(startTime).find((sample) => {
return (sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Whistle] ||
sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Finish] ||
sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Clap]);
});
const rowPattern = new Pattern();
for (let i = 0; i <= this.spanCount; ++i) {
if (!(ignoreHead && startTime === this.startTime)) {
for (let j = 0; j < noteCount; ++j) {
const options = {
validate: (c) => c !== holdColumn,
patterns: [rowPattern],
};
column = this.findAvailableColumn(column, options);
this.addToPattern(rowPattern, column, startTime, startTime);
}
}
pattern.addPatternHitObjects(rowPattern);
rowPattern.clear();
startTime += this.segmentDuration;
}
return pattern;
}
hitSamplesAt(time) {
const nodeSamples = this.nodeSamplesAt(time);
return nodeSamples[0] || this.hitObject.samples;
}
nodeSamplesAt(time) {
if (!(this.hitObject.hitType & osuClasses.HitType.Slider)) {
return [];
}
const slider = this.hitObject;
let index = 0;
if (this.segmentDuration) {
index = (time - this.startTime) / this.segmentDuration;
}
return index ? slider.nodeSamples.slice(index) : slider.nodeSamples;
}
addToPattern(pattern, column, startTime, endTime) {
var _a, _b;
if (startTime === endTime) {
const note = new Note();
const posData = this.hitObject;
note.startTime = startTime;
note.originalColumn = column;
note.hitType = osuClasses.HitType.Normal | (this.hitObject.hitType & osuClasses.HitType.NewCombo);
note.samples = this.hitSamplesAt(startTime);
note.startPosition = (_a = posData === null || posData === void 0 ? void 0 : posData.startPosition) !== null && _a !== void 0 ? _a : new osuClasses.Vector2(256, 192);
pattern.addHitObject(note);
}
else {
const hold = new Hold();
const posData = this.hitObject;
hold.startTime = startTime;
hold.endTime = endTime;
hold.originalColumn = column;
hold.hitType = osuClasses.HitType.Hold | (this.hitObject.hitType & osuClasses.HitType.NewCombo);
hold.samples = this.hitSamplesAt(startTime);
hold.nodeSamples = this.nodeSamplesAt(startTime);
hold.startPosition = (_b = posData === null || posData === void 0 ? void 0 : posData.startPosition) !== null && _b !== void 0 ? _b : new osuClasses.Vector2(256, 192);
pattern.addHitObject(hold);
}
}
}
DistanceObjectPatternGenerator.BASE_SCORING_DISTANCE = 100;
class EndTimeObjectPatternGenerator extends PatternGenerator {
constructor(hitObject, beatmap, originalBeatmap, previousPattern, rng) {
super(hitObject, beatmap, originalBeatmap, previousPattern, rng);
this.endTime = Math.trunc(hitObject.endTime || 0);
this.convertType =
previousPattern.columnsWithObjects === this.totalColumns
? exports.PatternType.None
: exports.PatternType.ForceNotStack;
}
*generate() {
const pattern = new Pattern();
const shouldGenerateHold = this.endTime - this.hitObject.startTime >= 100;
switch (this.totalColumns) {
case 8: {
const findFinish = (sample) => {
return sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Finish];
};
const hasFinish = !!this.hitObject.samples.find(findFinish);
if (hasFinish && this.endTime - this.hitObject.startTime < 1000) {
this.addToPattern(pattern, 0, shouldGenerateHold);
break;
}
this.addToPattern(pattern, this.getRandomColumn(), shouldGenerateHold);
break;
}
default:
this.addToPattern(pattern, this.getRandomColumn(0), shouldGenerateHold);
break;
}
yield pattern;
}
getRandomColumn(lowerBound) {
const column = super.getRandomColumn(lowerBound);
if (this.convertType & exports.PatternType.ForceNotStack) {
const options = {
lowerBound,
patterns: [this.previousPattern],
};
return this.findAvailableColumn(column, options);
}
return this.findAvailableColumn(column, { lowerBound });
}
addToPattern(pattern, column, isHoldNote) {
var _a, _b, _c;
if (isHoldNote) {
const hold = new Hold();
const posData = this.hitObject;
hold.startTime = this.hitObject.startTime;
hold.endTime = this.endTime;
hold.originalColumn = column;
hold.hitType = osuClasses.HitType.Hold | (this.hitObject.hitType & osuClasses.HitType.NewCombo);
hold.samples = this.hitObject.samples;
hold.nodeSamples = (_a = this.hitObject.nodeSamples) !== null && _a !== void 0 ? _a : [];
hold.startPosition = (_b = posData === null || posData === void 0 ? void 0 : posData.startPosition) !== null && _b !== void 0 ? _b : new osuClasses.Vector2(256, 192);
pattern.addHitObject(hold);
}
else {
const note = new Note();
const posData = this.hitObject;
note.startTime = this.hitObject.startTime;
note.originalColumn = column;
note.hitType = osuClasses.HitType.Normal | (this.hitObject.hitType & osuClasses.HitType.NewCombo);
note.samples = this.hitObject.samples;
note.startPosition = (_c = posData === null || posData === void 0 ? void 0 : posData.startPosition) !== null && _c !== void 0 ? _c : new osuClasses.Vector2(256, 192);
pattern.addHitObject(note);
}
}
}
class HitObjectPatternGenerator extends PatternGenerator {
constructor(hitObject, beatmap, originalBeatmap, previousPattern, rng, previousTime, previousPosition, density, lastStair) {
super(hitObject, beatmap, originalBeatmap, previousPattern, rng);
this.stairType = exports.PatternType.None;
this.convertType = exports.PatternType.None;
this.stairType = lastStair;
const timingPoint = beatmap.controlPoints
.timingPointAt(hitObject.startTime);
const effectPoint = beatmap.controlPoints
.effectPointAt(hitObject.startTime);
const startPosition = hitObject.startPosition;
const posSeparation = (startPosition || new osuClasses.Vector2(0, 0))
.fsubtract(previousPosition)
.flength();
const timeSeparation = hitObject.startTime - previousTime;
if (timeSeparation <= 80) {
this.convertType |= exports.PatternType.ForceNotStack | exports.PatternType.KeepSingle;
}
else if (timeSeparation <= 95) {
this.convertType |= exports.PatternType.ForceNotStack | exports.PatternType.KeepSingle | lastStair;
}
else if (timeSeparation <= 105) {
this.convertType |=
exports.PatternType.ForceNotStack | exports.PatternType.LowProbability;
}
else if (timeSeparation <= 125) {
this.convertType |= exports.PatternType.ForceNotStack;
}
else if (timeSeparation <= 135 && posSeparation < 20) {
this.convertType |= exports.PatternType.Cycle | exports.PatternType.KeepSingle;
}
else if (timeSeparation <= 150 && posSeparation < 20) {
this.convertType |= exports.PatternType.ForceStack | exports.PatternType.LowProbability;
}
else if (posSeparation < 20 && density >= timingPoint.beatLength / 2.5) {
this.convertType |= exports.PatternType.Reverse | exports.PatternType.LowProbability;
}
else if (density < timingPoint.beatLength / 2.5 || effectPoint.kiai) ;
else {
this.convertType |= exports.PatternType.LowProbability;
}
if (!(this.convertType & exports.PatternType.KeepSingle)) {
const isFinish = !!hitObject.samples.find((sample) => {
return sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Finish];
});
const isClap = !!hitObject.samples.find((sample) => {
return sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Clap];
});
if (isFinish && this.totalColumns !== 8) {
this.convertType |= exports.PatternType.Mirror;
}
else if (isClap) {
this.convertType |= exports.PatternType.Gathered;
}
}
}
*generate() {
const p = this._generateCore();
const isStair = !!(this.convertType & exports.PatternType.Stair);
const isReverseStair = !!(this.convertType & exports.PatternType.ReverseStair);
for (const hitObject of p.hitObjects) {
if (isStair && hitObject.column === this.totalColumns - 1) {
this.stairType = exports.PatternType.ReverseStair;
}
if (isReverseStair && hitObject.column === this.randomStart) {
this.stairType = exports.PatternType.Stair;
}
}
yield p;
}
_generateCore() {
const pattern = new Pattern();
if (this.totalColumns === 1) {
this.addToPattern(pattern, 0);
return pattern;
}
const lastColumn = this.previousPattern.hitObjects.length
? this.previousPattern.hitObjects[0].column
: 0;
const isReverse = !!(this.convertType & exports.PatternType.Reverse);
if (isReverse && this.previousPattern.hitObjects.length) {
for (let i = this.randomStart; i < this.totalColumns; ++i) {
if (this.previousPattern.columnHasObject(i)) {
const column = this.randomStart + this.totalColumns - i - 1;
this.addToPattern(pattern, column);
}
}
return pattern;
}
const isCycle = !!(this.convertType & exports.PatternType.Cycle);
const isSingleObject = this.previousPattern.hitObjects.length === 1;
const is7KPlus1 = this.totalColumns !== 8 || lastColumn !== 0;
const isNotCenter = this.totalColumns % 2 === 0
|| lastColumn !== this.totalColumns / 2;
if (isCycle && isSingleObject && is7KPlus1 && isNotCenter) {
const column = this.randomStart + this.totalColumns - lastColumn - 1;
this.addToPattern(pattern, column);
return pattern;
}
const isForceStack = !!(this.convertType & exports.PatternType.ForceStack);
if (isForceStack && this.previousPattern.hitObjects.length) {
for (let i = this.randomStart; i < this.totalColumns; ++i) {
if (this.previousPattern.columnHasObject(i)) {
this.addToPattern(pattern, i);
}
}
return pattern;
}
if (this.previousPattern.hitObjects.length === 1) {
if (this.convertType & exports.PatternType.Stair) {
let targetColumn = lastColumn + 1;
if (targetColumn === this.totalColumns) {
targetColumn = this.randomStart;
}
this.addToPattern(pattern, targetColumn);
return pattern;
}
if (this.convertType & exports.PatternType.ReverseStair) {
let targetColumn = lastColumn - 1;
if (targetColumn === this.randomStart - 1) {
targetColumn = this.totalColumns - 1;
}
this.addToPattern(pattern, targetColumn);
return pattern;
}
}
if (this.convertType & exports.PatternType.KeepSingle) {
return this.generateRandomNotes(1);
}
if (this.convertType & exports.PatternType.Mirror) {
if (this.conversionDifficulty > 6.5) {
return this.generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
}
if (this.conversionDifficulty > 4) {
return this.generateRandomPatternWithMirrored(0.12, 0.17, 0);
}
return this.generateRandomPatternWithMirrored(0.12, 0, 0);
}
if (this.conversionDifficulty > 6.5) {
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateRandomPattern(0.78, 0.42, 0, 0);
}
return this.generateRandomPattern(1, 0.62, 0, 0);
}
if (this.conversionDifficulty > 4) {
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateRandomPattern(0.35, 0.08, 0, 0);
}
return this.generateRandomPattern(0.52, 0.15, 0, 0);
}
if (this.conversionDifficulty > 2) {
if (this.convertType & exports.PatternType.LowProbability) {
return this.generateRandomPattern(0.18, 0, 0, 0);
}
return this.generateRandomPattern(0.45, 0, 0, 0);
}
return this.generateRandomPattern(0, 0, 0, 0);
}
generateRandomNotes(noteCount) {
const getNextColumn = (last) => {
if (this.convertType & exports.PatternType.Gathered) {
if (++last === this.totalColumns) {
last = this.randomStart;
}
}
else {
last = this.getRandomColumn();
}
return last;
};
const pattern = new Pattern();
const allowStacking = !(this.convertType & exports.PatternType.ForceNotStack);
if (!allowStacking) {
const count = this.totalColumns -
this.randomStart -
this.previousPattern.columnsWithObjects;
noteCount = Math.min(noteCount, count);
}
const startX = this.hitObject.startX;
let column = this.getColumn(startX || 0, true);
for (let i = 0; i < noteCount; ++i) {
if (allowStacking) {
const options = {
nextColumn: getNextColumn,
patterns: [pattern],
};
column = this.findAvailableColumn(column, options);
}
else {
const options = {
nextColumn: getNextColumn,
patterns: [pattern, this.previousPattern],
};
column = this.findAvailableColumn(column, options);
}
this.addToPattern(pattern, column);
}
return pattern;
}
hasSpecialColumn() {
const findClap = (sample) => sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Clap];
const findFinish = (sample) => sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Finish];
return (!!this.hitObject.samples.find(findClap) &&
!!this.hitObject.samples.find(findFinish));
}
generateRandomPattern(p2, p3, p4, p5) {
const pattern = new Pattern();
const noteCount = this.getRandomNoteCount(p2, p3, p4, p5);
const randomNotes = this.generateRandomNotes(noteCount);
pattern.addPatternHitObjects(randomNotes);
if (this.randomStart > 0 && this.hasSpecialColumn()) {
this.addToPattern(pattern, 0);
}
return pattern;
}
generateRandomPatternWithMirrored(centreProbability, p2, p3) {
if (this.convertType & exports.PatternType.ForceNotStack) {
return this.generateRandomPattern(1 / Math.fround(2) + p2 / 2, p2, (p2 + p3) / 2, p3);
}
const pattern = new Pattern();
const [noteCount, addToCentre] = this.getRandomNoteCountMirrored(centreProbability, p2, p3);
const columnLimit = Math.trunc((this.totalColumns % 2 ? this.totalColumns - 1 : this.totalColumns) / 2);
let column = this.getRandomColumn(this.randomStart, columnLimit);
const options = {
upperBound: columnLimit,
patterns: [pattern],
};
for (let i = 0; i < noteCount; ++i) {
column = this.findAvailableColumn(column, options);
const mirroredColumn = this.randomStart + this.totalColumns - column - 1;
this.addToPattern(pattern, column);
this.addToPattern(pattern, mirroredColumn);
}
if (addToCentre) {
this.addToPattern(pattern, Math.trunc(this.totalColumns / 2));
}
if (this.randomStart > 0 && this.hasSpecialColumn()) {
this.addToPattern(pattern, 0);
}
return pattern;
}
getRandomNoteCount(p2, p3, p4, p5) {
switch (this.totalColumns) {
case 2:
p2 = 0;
p3 = 0;
p4 = 0;
p5 = 0;
break;
case 3:
p2 = Math.min(p2, 0.1);
p3 = 0;
p4 = 0;
p5 = 0;
break;
case 4:
p2 = Math.min(p2, 0.23);
p3 = Math.min(p3, 0.04);
p4 = 0;
p5 = 0;
break;
case 5:
p3 = Math.min(p3, 0.15);
p4 = Math.min(p4, 0.03);
p5 = 0;
break;
}
const findClap = (sample) => sample.hitSound === osuClasses.HitSound[osuClasses.HitSound.Clap];
if (this.hitObject.samples.find(findClap)) {
p2 = 1;
}
return super.getRandomNoteCount(p2, p3, p4, p5);
}
getRandomNoteCountMirrored(centreProbability, p2, p3) {
switch (this.totalColumns) {
case 2:
centreProbability = 0;
p2 = 0;
p3 = 0;
break;
case 3:
centreProbability = Math.min(centreProbability, 0.03);
p2 = 0;
p3 = 0;
break;
case 4:
centreProbability = 0;
p2 = 1 - Math.max((1 - p2) * 2, 0.8);
p3 = 0;
break;
case 5:
centreProbability = Math.min(centreProbability, 0.03);
p3 = 0;
break;
case 6:
centreProbability = 0;
p2 = 1 - Math.max((1 - p2) * 2, 0.5);
p3 = 1 - Math.max((1 - p3) * 2, 0.85);
break;
}
p2 = Math.max(0, Math.min(p2, 1));
p3 = Math.max(0, Math.min(p3, 1));
const centreVal = this.rng.nextDouble();
const noteCount = super.getRandomNoteCount(p2, p3);
const addToCentre = this.totalColumns % 2 === 1
&& noteCount !== 3 && centreVal > 1 - centreProbability;
return [noteCount, addToCentre];
}
addToPattern(pattern, column) {
var _a;
const note = new Note();
const posData = this.hitObject;
note.s