osbtools
Version:
A set of tools for working with osu! storyboards
131 lines (106 loc) • 4.03 kB
text/typescript
import { ESbElementProperty, ESbElementType, ESbLayer } from "../types/enums";
import { TSbLayerData, TSbLayersBuilder } from "../types/types";
import { parseElementGroupProperty, parseElementProperty, parseElementTitle } from "../utils/parsers";
import SbEmptyElement from "./sbEmptyElement";
import StoryboardElement from "./storyboardElement";
const layerData = (title: string): TSbLayerData<StoryboardElement> => ({
title,
elements: []
});
type TLayers = TSbLayersBuilder<ESbLayer, StoryboardElement>;
class Storyboard {
readonly #elements: StoryboardElement[] = [];
readonly #layers: TLayers = {
[ESbLayer.Background]: layerData("//Storyboard Layer 0 (Background)"),
[ESbLayer.Fail]: layerData("//Storyboard Layer 1 (Fail)"),
[ESbLayer.Pass]: layerData("//Storyboard Layer 2 (Pass)"),
[ESbLayer.Foreground]: layerData("//Storyboard Layer 3 (Foreground)"),
[ESbLayer.Overlay]: layerData("//Storyboard Layer 4 (Overlay)"),
[ESbLayer.Sound]: layerData("//Storyboard Sound Samples")
} as const;
constructor(osb?: string) {
if (!osb) return;
this.parse(osb, false);
}
addElement(element: StoryboardElement): Storyboard {
this.#elements.push(element);
this.#layers[element.getData().layer].elements.push(element);
return this;
}
parse(osb: string, createNew: boolean = true): Storyboard | undefined {
const sb = new Storyboard();
let currentPropertyType: ESbElementProperty | null = null;
let currentPropertyTrimmedLine: string | null = null;
let currentGroupElement = new SbEmptyElement();
let currentElement: StoryboardElement | null = null;
const lines = osb.split("\n");
lines.forEach((line, index) => {
if (line.startsWith("//") || line.startsWith("[")) return;
const isElementTitle =
line.startsWith(ESbElementType.Sprite) ||
line.startsWith(ESbElementType.Animation) ||
line.startsWith(ESbElementType.Sample);
if (isElementTitle) {
currentElement = null;
}
const isElementProperty = line.startsWith(" ");
const isElementPropertyGroup = line.startsWith(" ");
if (currentElement) {
const trimmedLine = line.trim();
const type = trimmedLine.substring(0, trimmedLine[1] !== "," ? 2 : 1) as ESbElementProperty;
if (isElementProperty && !isElementPropertyGroup) {
currentPropertyType = type;
currentPropertyTrimmedLine = trimmedLine;
parseElementProperty(trimmedLine, type, currentElement);
} else if (isElementPropertyGroup) {
const isLastElementPropertyGroup =
(typeof lines[index + 1] === "string" && !lines[index + 1].startsWith(" ")) || !lines[index + 1];
parseElementProperty(trimmedLine, type, currentGroupElement);
if (isLastElementPropertyGroup && currentPropertyType && currentPropertyTrimmedLine) {
parseElementGroupProperty(
currentPropertyTrimmedLine,
currentPropertyType,
currentElement,
currentGroupElement
);
currentGroupElement = new SbEmptyElement();
}
}
}
parseElementTitle(line, ESbElementType.Sprite, (element) => {
currentElement = element;
});
parseElementTitle(line, ESbElementType.Animation, (element) => {
currentElement = element;
});
parseElementTitle(line, ESbElementType.Sample, (element) => {
currentElement = element;
});
if (currentElement && isElementTitle) {
if (!createNew) this.addElement(currentElement);
else sb.addElement(currentElement);
}
});
if (createNew) return sb;
}
getElements(): StoryboardElement[] {
return this.#elements;
}
getElement(index: number): StoryboardElement | null {
return this.#elements[index] || null;
}
getLayers(): TLayers {
return this.#layers;
}
toString(): string {
return [
"[Events]",
"//Background and Video events",
...Object.entries(this.getLayers()).map(([, layerData]) => {
const { title, elements } = layerData ?? {};
return [title, ...elements.map((element) => element.toString())].join("\n");
})
].join("\n");
}
}
export default Storyboard;