Skip to content

Commit

Permalink
Add combo information to HitObject
Browse files Browse the repository at this point in the history
  • Loading branch information
Rian8337 committed Dec 14, 2024
1 parent 61c349d commit 625abf8
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 2 deletions.
6 changes: 5 additions & 1 deletion packages/osu-base/src/beatmap/Beatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ export class Beatmap {
}
});

const processor = new BeatmapProcessor(converted);

processor.preProcess();

// Compute default values for hit objects, including creating nested hit objects in-case they're needed.
converted.hitObjects.objects.forEach((hitObject) =>
hitObject.applyDefaults(
Expand Down Expand Up @@ -358,7 +362,7 @@ export class Beatmap {
}
});

new BeatmapProcessor(converted).postProcess(mode);
processor.postProcess(mode);

mods.forEach((mod) => {
if (mod.isApplicableToBeatmap()) {
Expand Down
23 changes: 23 additions & 0 deletions packages/osu-base/src/beatmap/BeatmapProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Modes } from "../constants/Modes";
import { CircleSizeCalculator } from "../utils/CircleSizeCalculator";
import { Beatmap } from "./Beatmap";
import { Circle } from "./hitobjects/Circle";
import { HitObject } from "./hitobjects/HitObject";
import { Slider } from "./hitobjects/Slider";
import { Spinner } from "./hitobjects/Spinner";

Expand All @@ -20,6 +21,28 @@ export class BeatmapProcessor {
this.beatmap = beatmap;
}

/**
* Processes the converted beatmap prior to `HitObject.applyDefaults` being invoked.
*
* Nested hitobjects generated during `HitObject.applyDefaults` will not be present by this point,
* and no mods will have been applied to the hitobjects.
*
* This can only be used to add alterations to hitobjects generated directly through the conversion process.
*/
preProcess() {
let last: HitObject | null = null;

for (const object of this.beatmap.hitObjects.objects) {
object.updateComboInformation(last);
last = object;
}

// Mark the last object in the beatmap as last in combo.
if (last) {
last.isLastInCombo = true;
}
}

/**
* Processes the converted beatmap after `HitObject.applyDefaults` has been invoked.
*
Expand Down
66 changes: 65 additions & 1 deletion packages/osu-base/src/beatmap/hitobjects/HitObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,49 @@ export abstract class HitObject {
readonly isNewCombo: boolean;

/**
* How many combo colors to skip, if this hitobject starts a new combo.
* When starting a new combo, the offset of the new combo relative to the current one.
*
* This is generally a setting provided by a beatmap creator to choreograph interesting color patterns
* which can only be achieved by skipping combo colors with per-hitobject level.
*
* It is exposed via `comboIndexWithOffsets`.
*/
readonly comboOffset: number;

private _indexInCurrentCombo = 0;

/**
* The index of this hitobject in the current combo.
*/
get indexInCurrentCombo(): number {
return this._indexInCurrentCombo;
}

private _comboIndex = 0;

/**
* The index of this hitobject's combo in relation to the beatmap.
*
* In other words, this is incremented by 1 each time an `isNewCombo` is reached.
*/
get comboIndex(): number {
return this._comboIndex;
}

private _comboIndexWithOffsets = 0;

/**
* The index of this hitobject's combo in relation to the beatmap, with all aggregates applied.
*/
get comboIndexWithOffsets(): number {
return this._comboIndexWithOffsets;
}

/**
* Whether this is the last hitobject in the current combo.
*/
isLastInCombo = false;

/**
* The samples to be played when this hitobject is hit.
*
Expand Down Expand Up @@ -280,6 +319,31 @@ export abstract class HitObject {
this.samples = this.samples.map((v) => sampleControlPoint.applyTo(v));
}

/**
* Given the previous hitobject in the beatmap, update relevant combo information.
*
* @param prev The previous hitobject in the beatmap.
*/
updateComboInformation(prev?: HitObject | null) {
this._comboIndex = prev?.comboIndex ?? 0;
this._comboIndexWithOffsets = prev?.comboIndexWithOffsets ?? 0;
this._indexInCurrentCombo = prev ? prev.indexInCurrentCombo + 1 : 0;

if (this.isNewCombo || !prev || prev.type & ObjectTypes.spinner) {
this._indexInCurrentCombo = 0;
++this._comboIndex;

if (!(this.type & ObjectTypes.spinner)) {
// Spinners do not affect combo color offsets.
this._comboIndexWithOffsets += this.comboOffset + 1;
}

if (prev) {
prev.isLastInCombo = true;
}
}
}

/**
* Evaluates the stack offset vector of the hitobject.
*
Expand Down

0 comments on commit 625abf8

Please sign in to comment.