126 lines
3.0 KiB
TypeScript
126 lines
3.0 KiB
TypeScript
export enum HitObjectType {
|
|
HitCircle = 1,
|
|
Slider = 2,
|
|
Spinner = 8
|
|
}
|
|
|
|
export type HitObject = {
|
|
x: number;
|
|
y: number;
|
|
time: number;
|
|
type: number;
|
|
hitSound: number;
|
|
objectParams: string;
|
|
hitSample: string;
|
|
currentCombo: number;
|
|
};
|
|
|
|
export interface BeatmapDifficulty {
|
|
hpDrainRate: number;
|
|
circleSize: number;
|
|
overralDifficulty: number;
|
|
approachRate: number;
|
|
sliderMultiplier: number;
|
|
sliderTickRate: number;
|
|
}
|
|
|
|
export function parseBeatmapDifficulty(beatmap: string): BeatmapDifficulty {
|
|
const lines = beatmap.split('\n');
|
|
let recording = false;
|
|
const difficulty: Partial<BeatmapDifficulty> = {};
|
|
|
|
for (const line of lines) {
|
|
if (line.trim() === '[Difficulty]') {
|
|
recording = true;
|
|
continue;
|
|
}
|
|
|
|
if (!recording) continue;
|
|
|
|
if (line.startsWith('[')) break;
|
|
|
|
const parts = line.split(':');
|
|
if (parts.length < 2) continue;
|
|
|
|
switch (parts[0]) {
|
|
case 'HPDrainRate':
|
|
difficulty.hpDrainRate = parseFloat(parts[1]);
|
|
break;
|
|
case 'CircleSize':
|
|
difficulty.circleSize = parseFloat(parts[1]);
|
|
break;
|
|
case 'OverallDifficulty':
|
|
difficulty.overralDifficulty = parseFloat(parts[1]);
|
|
break;
|
|
case 'ApproachRate':
|
|
difficulty.approachRate = parseFloat(parts[1]);
|
|
break;
|
|
case 'SliderMultiplier':
|
|
difficulty.sliderMultiplier = parseFloat(parts[1]);
|
|
break;
|
|
case 'SliderTickRate':
|
|
difficulty.sliderTickRate = parseFloat(parts[1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return difficulty as BeatmapDifficulty;
|
|
}
|
|
|
|
export function parseHitObjects(beatmap: string): any[] {
|
|
const lines = beatmap.split('\n');
|
|
let recording = false;
|
|
const hitObjects = [];
|
|
let currentCombo = 1;
|
|
|
|
for (const line of lines) {
|
|
if (line.trim() === '[HitObjects]') {
|
|
recording = true;
|
|
continue;
|
|
}
|
|
|
|
if (!recording) continue;
|
|
|
|
if (line.startsWith('[')) break;
|
|
|
|
const parts = line.split(',');
|
|
if (parts.length < 5) continue;
|
|
|
|
const type = parseInt(parts[3], 10);
|
|
const isNewCombo = type & (1 << 2); // Bit at index 2 for new combo
|
|
if (isNewCombo) {
|
|
currentCombo = 1; // Reset combo
|
|
} else {
|
|
// If not the start of a new combo, increment the current combo
|
|
currentCombo++;
|
|
}
|
|
|
|
const hitObject = {
|
|
x: parseInt(parts[0], 10),
|
|
y: parseInt(parts[1], 10),
|
|
time: parseInt(parts[2], 10),
|
|
type: getTypeFromFlag(type),
|
|
hitSound: parseInt(parts[4], 10),
|
|
objectParams: parts[5],
|
|
hitSample: parts.length > 6 ? parts[6] : '0:0:0:0:',
|
|
currentCombo: currentCombo
|
|
};
|
|
|
|
if (isNewCombo) {
|
|
// Reset currentCombo after assigning to hitObject if it's the start of a new combo
|
|
currentCombo = 1;
|
|
}
|
|
|
|
hitObjects.push(hitObject);
|
|
}
|
|
|
|
return hitObjects;
|
|
}
|
|
|
|
function getTypeFromFlag(flag: number): HitObjectType | null {
|
|
if (flag & HitObjectType.HitCircle) return HitObjectType.HitCircle;
|
|
if (flag & HitObjectType.Slider) return HitObjectType.Slider;
|
|
if (flag & HitObjectType.Spinner) return HitObjectType.Spinner;
|
|
return null;
|
|
}
|