From b18826ea07fc58dab03acc51e6c9ae84df7c4e84 Mon Sep 17 00:00:00 2001 From: kone <70107937+konekowo@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:55:58 -0400 Subject: [PATCH] finish storyboard parsing, start rendering storyboard --- src/Elements/RandomBackground/Background.ts | 11 +- .../RandomBackground/RandomBackground.ts | 2 + src/Elements/RandomBackground/StoryBoard.ts | 47 ++ .../Storyboard/Commands/StoryboardCommand.ts | 15 +- .../Storyboard/Commands/impl/ColorCommand.ts | 10 + .../Storyboard/Commands/impl/FadeCommand.ts | 9 +- .../Storyboard/Commands/impl/LoopCommand.ts | 12 + .../Storyboard/Commands/impl/MoveCommand.ts | 10 + .../Storyboard/Commands/impl/MoveXCommand.ts | 9 + .../Storyboard/Commands/impl/MoveYCommand.ts | 9 + .../Commands/impl/ParameterCommand.ts | 13 + .../Storyboard/Commands/impl/RotateCommand.ts | 15 + .../Storyboard/Commands/impl/ScaleCommand.ts | 9 + .../Commands/impl/TriggerCommand.ts | 8 + .../Commands/impl/VectorScaleCommand.ts | 10 + .../Sections/Events/Storyboard/EventSprite.ts | 2 +- src/Util/Beatmap/Parser/EventsParser.ts | 640 ++++++++++++------ src/Util/TweenWrapper/EasingFunction.ts | 79 +++ 18 files changed, 694 insertions(+), 216 deletions(-) create mode 100644 src/Elements/RandomBackground/StoryBoard.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ColorCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/LoopCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveXCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveYCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ParameterCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/RotateCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ScaleCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/TriggerCommand.ts create mode 100644 src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/VectorScaleCommand.ts diff --git a/src/Elements/RandomBackground/Background.ts b/src/Elements/RandomBackground/Background.ts index 9d2ad92..391baf4 100644 --- a/src/Elements/RandomBackground/Background.ts +++ b/src/Elements/RandomBackground/Background.ts @@ -12,7 +12,6 @@ export class Background extends PIXI.Sprite { this.texture = texture; this.visible = false; this.anchor.set(0.5, 0.5); - } public show() { @@ -31,6 +30,8 @@ export class Background extends PIXI.Sprite { } export class BackgroundContainer extends PIXI.Container { + public destroying = false; + public constructor() { super(); this.visible = false; @@ -40,16 +41,17 @@ export class BackgroundContainer extends PIXI.Container { this.visible = true; for (let i = 0; i < this.children?.length; i++) { let child = this.children[i]; - if (child instanceof Background) { + if (child instanceof Background || child instanceof BackgroundContainer) { child.show(); } } } public destroy(options?: DestroyOptions) { + this.destroying = true; for (let i = 0; i < this.children?.length; i++) { let child = this.children[i]; - if (child instanceof Background) { + if (child instanceof Background || child instanceof BackgroundContainer) { if (!child.destroyed && !child.destroying) { child.destroy(options); } @@ -57,9 +59,8 @@ export class BackgroundContainer extends PIXI.Container { } setTimeout(() => { super.destroy(options); + this.destroying = false; }, Background.fadeOutDuration); this.zIndex = 1; } - - } \ No newline at end of file diff --git a/src/Elements/RandomBackground/RandomBackground.ts b/src/Elements/RandomBackground/RandomBackground.ts index b77d30e..dc3e163 100644 --- a/src/Elements/RandomBackground/RandomBackground.ts +++ b/src/Elements/RandomBackground/RandomBackground.ts @@ -6,6 +6,7 @@ import {Background, BackgroundContainer} from "./Background"; import {EventTypes} from "../../Util/Beatmap/Data/Sections/Events/EventTypes"; import {EventVideo} from "../../Util/Beatmap/Data/Sections/Events/EventVideo"; import {EventBackground} from "../../Util/Beatmap/Data/Sections/Events/EventBackground"; +import {StoryBoard} from "./StoryBoard"; export class RandomBackground extends Screen { @@ -61,6 +62,7 @@ export class RandomBackground extends Screen { } } } + bgContainer.addChild(new StoryBoard(audio.beatmap)) if (background || backgroundVideo) { this.setBGContainer(bgContainer); } diff --git a/src/Elements/RandomBackground/StoryBoard.ts b/src/Elements/RandomBackground/StoryBoard.ts new file mode 100644 index 0000000..051379c --- /dev/null +++ b/src/Elements/RandomBackground/StoryBoard.ts @@ -0,0 +1,47 @@ +import * as PIXI from "pixi.js"; +import {BackgroundContainer} from "./Background"; +import {Main} from "../../main"; +import {BeatmapData} from "../../Util/Beatmap/Data/BeatmapData"; +import {DestroyOptions} from "pixi.js"; +import {EventSprite} from "../../Util/Beatmap/Data/Sections/Events/Storyboard/EventSprite"; +import {StoryboardCommand} from "../../Util/Beatmap/Data/Sections/Events/Storyboard/Commands/StoryboardCommand"; + +export class StoryBoard extends BackgroundContainer { + private beatmap: BeatmapData; + private startTime = Date.now(); + + public constructor(beatmap: BeatmapData) { + super(); + this.interactiveChildren = false; + this.interactive = false; + this.beatmap = beatmap; + Main.app.ticker.add(this.Update, this); + for (let i = 0; i < beatmap.Events.Events.length; i++) { + let event = beatmap.Events.Events[i]; + if (event instanceof EventSprite && event.texture){ + let sprite = PIXI.Sprite.from(event.texture); + sprite.visible = false; + event.sprite = sprite; + this.addChild(sprite); + } + } + } + + + public Update() { + let currentTime = Date.now() - this.startTime; + for (let i = 0; i < this.beatmap.Events.Events.length; i++) { + let event = this.beatmap.Events.Events[i]; + if (event instanceof StoryboardCommand) { + if (currentTime > event.startTime) { + + } + } + } + } + + public destroy(options?: DestroyOptions) { + Main.app.ticker.remove(this.Update, this); + super.destroy(options); + } +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/StoryboardCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/StoryboardCommand.ts index 1c3035f..a62bf76 100644 --- a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/StoryboardCommand.ts +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/StoryboardCommand.ts @@ -1,5 +1,18 @@ import {CommandType} from "./CommandType"; +import {EasingFunction} from "../../../../../../TweenWrapper/EasingFunction"; +import {EventStoryboard} from "../EventStoryboard"; +import {Event} from "../../Event"; -export abstract class StoryboardCommand { +export abstract class StoryboardCommand extends Event{ public abstract commandType: CommandType; + + public parentStoryboardObject!: EventStoryboard; + + /** + * indicates if the command should "accelerate". See Easing Functions Cheat Sheet. + */ + public easing: (amount: number) => number = EasingFunction.None; + + public endTime: number = 0; + } diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ColorCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ColorCommand.ts new file mode 100644 index 0000000..24c4aea --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ColorCommand.ts @@ -0,0 +1,10 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; +import * as PIXI from "pixi.js"; + +export class ColorCommand extends StoryboardCommand { + public commandType = CommandType.Color; + + public startColor: PIXI.Color = new PIXI.Color("white"); + public endColor: PIXI.Color = new PIXI.Color("white"); +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/FadeCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/FadeCommand.ts index 526e3b2..d5a72bf 100644 --- a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/FadeCommand.ts +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/FadeCommand.ts @@ -3,6 +3,13 @@ import {CommandType} from "../CommandType"; export class FadeCommand extends StoryboardCommand { public commandType = CommandType.Fade; - + /** + * the value at starttime + */ + public startOpacity = 1; + /** + * the value at endtime + */ + public endOpacity = 1; } \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/LoopCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/LoopCommand.ts new file mode 100644 index 0000000..a559c41 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/LoopCommand.ts @@ -0,0 +1,12 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +export class LoopCommand extends StoryboardCommand { + public commandType = CommandType.Loop; + + public readonly endTime = -1; + + public loopCount: number = 1; + + public childCommands: StoryboardCommand[] = []; +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveCommand.ts new file mode 100644 index 0000000..b03934c --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveCommand.ts @@ -0,0 +1,10 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; +import * as PIXI from "pixi.js"; + +export class MoveCommand extends StoryboardCommand { + public commandType = CommandType.Move; + + public startPos: PIXI.PointData = new PIXI.Point(0, 0); + public endPos: PIXI.PointData = new PIXI.Point(0, 0); +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveXCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveXCommand.ts new file mode 100644 index 0000000..d577037 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveXCommand.ts @@ -0,0 +1,9 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +export class MoveXCommand extends StoryboardCommand { + public commandType = CommandType.MoveX; + + public startX: number = 0; + public endX: number = 0; +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveYCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveYCommand.ts new file mode 100644 index 0000000..b3941b4 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/MoveYCommand.ts @@ -0,0 +1,9 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +export class MoveYCommand extends StoryboardCommand { + public commandType = CommandType.MoveY; + + public startY: number = 0; + public endY: number = 0; +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ParameterCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ParameterCommand.ts new file mode 100644 index 0000000..470a783 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ParameterCommand.ts @@ -0,0 +1,13 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +export class ParameterCommand extends StoryboardCommand { + public commandType = CommandType.Parameter; + public parameter!: ParameterCommandType; +} + +export enum ParameterCommandType{ + HorizontalFlip = "H", + VerticalFlip = "F", + UseAdditiveBlending = "A" +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/RotateCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/RotateCommand.ts new file mode 100644 index 0000000..61bef62 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/RotateCommand.ts @@ -0,0 +1,15 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +export class RotateCommand extends StoryboardCommand { + public commandType = CommandType.Rotate; + + /** + * rotation in radians + */ + public startRotation: number = 0; + /** + * rotation in radians + */ + public endRotation: number = 0; +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ScaleCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ScaleCommand.ts new file mode 100644 index 0000000..83baaf3 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/ScaleCommand.ts @@ -0,0 +1,9 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +export class ScaleCommand extends StoryboardCommand { + public commandType = CommandType.Scale; + + public startScale: number = 1; + public endScale: number = 1; +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/TriggerCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/TriggerCommand.ts new file mode 100644 index 0000000..0482fae --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/TriggerCommand.ts @@ -0,0 +1,8 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; + +// will not be supported for now +export class TriggerCommand extends StoryboardCommand { + public commandType = CommandType.Trigger; + public childCommands: StoryboardCommand[] = []; +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/VectorScaleCommand.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/VectorScaleCommand.ts new file mode 100644 index 0000000..973a612 --- /dev/null +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/Commands/impl/VectorScaleCommand.ts @@ -0,0 +1,10 @@ +import {StoryboardCommand} from "../StoryboardCommand"; +import {CommandType} from "../CommandType"; +import * as PIXI from "pixi.js"; + +export class VectorScaleCommand extends StoryboardCommand { + public commandType = CommandType.VectorScale; + + public startScale: PIXI.PointData = new PIXI.Point(1, 1); + public endScale: PIXI.PointData = new PIXI.Point(1, 1); +} \ No newline at end of file diff --git a/src/Util/Beatmap/Data/Sections/Events/Storyboard/EventSprite.ts b/src/Util/Beatmap/Data/Sections/Events/Storyboard/EventSprite.ts index fed426e..de8e56c 100644 --- a/src/Util/Beatmap/Data/Sections/Events/Storyboard/EventSprite.ts +++ b/src/Util/Beatmap/Data/Sections/Events/Storyboard/EventSprite.ts @@ -4,6 +4,6 @@ import * as PIXI from "pixi.js"; export class EventSprite extends EventStoryboard { public eventType = EventTypes.SPRITE; - + public sprite!: PIXI.Sprite; public texture?: PIXI.Texture; } diff --git a/src/Util/Beatmap/Parser/EventsParser.ts b/src/Util/Beatmap/Parser/EventsParser.ts index eec6da3..41138a9 100644 --- a/src/Util/Beatmap/Parser/EventsParser.ts +++ b/src/Util/Beatmap/Parser/EventsParser.ts @@ -14,127 +14,302 @@ import {convertToLoopType, LoopType} from "../Data/Sections/Events/Storyboard/Lo import {AudioEngine} from "../../../Audio/AudioEngine"; import {EventSample} from "../Data/Sections/Events/Storyboard/EventSample"; import {MathUtil} from "../../MathUtil"; +import {CommandType} from "../Data/Sections/Events/Storyboard/Commands/CommandType"; +import {LoopCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/LoopCommand"; +import {Event} from "../Data/Sections/Events/Event"; +import {EventStoryboard} from "../Data/Sections/Events/Storyboard/EventStoryboard"; +import {StoryboardCommand} from "../Data/Sections/Events/Storyboard/Commands/StoryboardCommand"; +import {EasingFunction} from "../../TweenWrapper/EasingFunction"; +import {FadeCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/FadeCommand"; +import {ScaleCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/ScaleCommand"; +import {VectorScaleCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/VectorScaleCommand"; +import {RotateCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/RotateCommand"; +import {MoveCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/MoveCommand"; +import {MoveXCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/MoveXCommand"; +import {MoveYCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/MoveYCommand"; +import {ColorCommand} from "../Data/Sections/Events/Storyboard/Commands/impl/ColorCommand"; +import { + ParameterCommand, + ParameterCommandType +} from "../Data/Sections/Events/Storyboard/Commands/impl/ParameterCommand"; export class EventsParser { public static aviTranscodedVideoCache: { beatmapSID: number, filePath: string, blob: Blob }[] = []; public static ParseEvents(beatmapData: BeatmapData, section: string[]) { - console.log(section); - let object; + let object: EventStoryboard | undefined; + let parentCommand: StoryboardCommand | undefined; section.forEach((str) => { + let depth = 0; + if (str.startsWith(" ") || str.startsWith("_")){ + depth = 1; + } + if (str.startsWith(" ") || str.startsWith("__")){ + depth = 2; + } let values = str.split(","); let event; - switch (values[0]) { - case EventTypes.BACKGROUND: - case "0": - event = new EventBackground(); - event.filename = values[2].replaceAll('"', "").replaceAll("\\", "/"); - event.xOffset = parseFloat(values[3]); - event.yOffset = parseFloat(values[4]); - if (isNaN(event.xOffset)) { - event.xOffset = 0; - } - if (isNaN(event.yOffset)) { - event.yOffset = 0; - } - object = event; - break; - case EventTypes.VIDEO: - case "1": - event = new EventVideo(); - event.startTime = parseInt(values[1]); - event.filename = values[2].replaceAll('"', "").replaceAll("\\", "/"); - event.xOffset = parseFloat(values[3]); - event.yOffset = parseFloat(values[4]); - if (isNaN(event.xOffset)) { - event.xOffset = 0; - } - if (isNaN(event.yOffset)) { - event.yOffset = 0; - } - object = event; - break; - case EventTypes.BREAK: - case "2": - event = new EventBreak(); - event.startTime = parseInt(values[1]); - event.endTime = parseInt(values[2]); - object = event; - break; - case EventTypes.COLOR: - case "3": - object = undefined; //ignore event for now - break; - case EventTypes.SPRITE: - case "4": - event = new EventSprite(); - event.layer = convertToLayer(values[1]); - event.origin = convertToOrigin(values[2]); - event.filepath = values[3].replaceAll('"', "").replaceAll("\\", "/"); - event.x = parseFloat(values[4]); - event.y = parseFloat(values[5]); - if (isNaN(event.x)) { - event.x = 0; - } - if (isNaN(event.y)) { - event.y = 0; - } - object = event; - break; - case EventTypes.SAMPLE: - case "5": - event = new EventSample(); - event.startTime = parseFloat(values[1]); - event.layer = convertToLayer(values[2]); - event.filepath = values[3].replaceAll('"', "").replaceAll("\\", "/"); - event.volume = values.length > 4 ? MathUtil.clamp01(parseFloat(values[4])/100) : 1; - object = event; - break; - case EventTypes.ANIMATION: - case "6": - event = new EventAnimation(); - event.layer = convertToLayer(values[1]); - event.origin = convertToOrigin(values[2]); - event.filepath = values[3].replaceAll('"', "").replaceAll("\\", "/"); - let filePathWithNoFileExtension = event.filepath.substring(0, event.filepath.lastIndexOf(".")); - for (let filePath of beatmapData.files.keys()) { - if (filePath.startsWith(filePathWithNoFileExtension)){ - event.filepaths.push(filePath); + if (depth == 0) { + switch (values[0]) { + case EventTypes.BACKGROUND: + case "0": + event = new EventBackground(); + event.filename = values[2].replaceAll('"', "").replaceAll("\\", "/"); + event.xOffset = parseFloat(values[3]); + event.yOffset = parseFloat(values[4]); + if (isNaN(event.xOffset)) { + event.xOffset = 0; } - } - event.filepaths.sort((a, b) => { - //@ts-ignore - let noExtA = parseInt(a.substring(filePathWithNoFileExtension.length, a.lastIndexOf("."))); - //@ts-ignore - let noExtB = parseInt(b.substring(filePathWithNoFileExtension.length, b.lastIndexOf("."))); - return noExtA - noExtB; - }); - event.x = parseFloat(values[4]); - event.y = parseFloat(values[5]); - if (isNaN(event.x)) { - event.x = 0; - } - if (isNaN(event.y)) { - event.y = 0; - } - event.frameCount = parseInt(values[6]); - event.frameDelay = parseFloat(values[7]); + if (isNaN(event.yOffset)) { + event.yOffset = 0; + } + object = undefined; + break; + case EventTypes.VIDEO: + case "1": + event = new EventVideo(); + event.startTime = parseInt(values[1]); + event.filename = values[2].replaceAll('"', "").replaceAll("\\", "/"); + event.xOffset = parseFloat(values[3]); + event.yOffset = parseFloat(values[4]); + if (isNaN(event.xOffset)) { + event.xOffset = 0; + } + if (isNaN(event.yOffset)) { + event.yOffset = 0; + } + object = undefined; + break; + case EventTypes.BREAK: + case "2": + event = new EventBreak(); + event.startTime = parseInt(values[1]); + event.endTime = parseInt(values[2]); + object = undefined; + break; + case EventTypes.COLOR: + case "3": + object = undefined; //ignore event for now + break; + case EventTypes.SPRITE: + case "4": + event = new EventSprite(); + event.layer = convertToLayer(values[1]); + event.origin = convertToOrigin(values[2]); + event.filepath = values[3].replaceAll('"', "").replaceAll("\\", "/"); + event.x = parseFloat(values[4]); + event.y = parseFloat(values[5]); + if (isNaN(event.x)) { + event.x = 0; + } + if (isNaN(event.y)) { + event.y = 0; + } + object = event; + break; + case EventTypes.SAMPLE: + case "5": + event = new EventSample(); + event.startTime = parseFloat(values[1]); + event.layer = convertToLayer(values[2]); + event.filepath = values[3].replaceAll('"', "").replaceAll("\\", "/"); + event.volume = values.length > 4 ? MathUtil.clamp01(parseFloat(values[4]) / 100) : 1; + object = event; + break; + case EventTypes.ANIMATION: + case "6": + event = new EventAnimation(); + event.layer = convertToLayer(values[1]); + event.origin = convertToOrigin(values[2]); + event.filepath = values[3].replaceAll('"', "").replaceAll("\\", "/"); + let filePathWithNoFileExtension = event.filepath.substring(0, event.filepath.lastIndexOf(".")); + for (let filePath of beatmapData.files.keys()) { + if (filePath.startsWith(filePathWithNoFileExtension)) { + event.filepaths.push(filePath); + } + } + event.filepaths.sort((a, b) => { + //@ts-ignore + let noExtA = parseInt(a.substring(filePathWithNoFileExtension.length, a.lastIndexOf("."))); + //@ts-ignore + let noExtB = parseInt(b.substring(filePathWithNoFileExtension.length, b.lastIndexOf("."))); + return noExtA - noExtB; + }); + event.x = parseFloat(values[4]); + event.y = parseFloat(values[5]); + if (isNaN(event.x)) { + event.x = 0; + } + if (isNaN(event.y)) { + event.y = 0; + } + event.frameCount = parseInt(values[6]); + event.frameDelay = parseFloat(values[7]); - if (beatmapData.formatVersion < 6) { - // i don't understand this at all, but looks like the developers behind osu! lazer don't either - // https://github.com/ppy/osu/blob/c46d787f1ef46827693dcfc334276e8b76b00d51/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs#L145 - event.frameDelay = Math.round(0.015 * event.frameDelay) * 1.186 * (1000 / 60); - } + if (beatmapData.formatVersion < 6) { + // i don't understand this at all, but looks like the developers behind osu! lazer don't either + // https://github.com/ppy/osu/blob/c46d787f1ef46827693dcfc334276e8b76b00d51/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs#L145 + event.frameDelay = Math.round(0.015 * event.frameDelay) * 1.186 * (1000 / 60); + } + + event.loopType = values.length > 8 ? convertToLoopType(values[8]) : LoopType.LoopForever; + object = event; + break; + } + } else { + if (object) { + switch (values[0].substring(depth, values[0].length)) { + case CommandType.Trigger: // skip trigger commands for now + if (depth == 2) break; + parentCommand = undefined; + break; + case CommandType.Loop: + if (depth == 2) break; + event = new LoopCommand(); + event.parentStoryboardObject = object; + event.startTime = parseFloat(values[1]); + event.loopCount = Math.max(0, parseInt(values[2]) - 1); + parentCommand = event; + break; + default: + if (values[3] == "") { + values[3] = values[2]; + } - event.loopType = values.length > 8 ? convertToLoopType(values[8]) : LoopType.LoopForever; - console.log(event); - object = event; - break; + let easing = EasingFunction.convertFromStoryBoardToEaseFunction(parseInt(values[1])); + let startTime = parseFloat(values[2]); + let endTime = parseFloat(values[3]); + let command; + switch (values[0].substring(depth, values[0].length)) { + case CommandType.Fade: + command = new FadeCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startOpacity = MathUtil.clamp01(parseFloat(values[4])); + command.endOpacity = values.length > 5 ? MathUtil.clamp01(parseFloat(values[5])) : command.startOpacity; + command.parentStoryboardObject = object; + break; + case CommandType.Scale: + command = new ScaleCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startScale = parseFloat(values[4]); + command.endScale = values.length > 5 ? parseFloat(values[5]) : command.startScale; + command.parentStoryboardObject = object; + break; + case CommandType.VectorScale: + command = new VectorScaleCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startScale = new PIXI.Point(parseFloat(values[4]), parseFloat(values[5])); + let endX = values.length > 6 ? parseFloat(values[6]) : command.startScale.x; + let endY = values.length > 7 ? parseFloat(values[7]) : command.startScale.y; + command.endScale = new PIXI.Point(endX, endY); + command.parentStoryboardObject = object; + break; + case CommandType.Rotate: + command = new RotateCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startRotation = parseFloat(values[4]); + command.endRotation = values.length > 5 ? parseFloat(values[5]) : command.startRotation; + command.parentStoryboardObject = object; + break; + case CommandType.Move: + command = new MoveCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startPos = new PIXI.Point(parseFloat(values[4]), parseFloat(values[5])); + let endX_ = values.length > 6 ? parseFloat(values[6]) : command.startPos.x; + let endY_ = values.length > 7 ? parseFloat(values[7]) : command.startPos.y; + command.endPos = new PIXI.Point(endX_, endY_); + command.parentStoryboardObject = object; + break; + case CommandType.MoveX: + command = new MoveXCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startX = parseFloat(values[4]); + command.endX = values.length > 5 ? parseFloat(values[5]) : command.startX; + command.parentStoryboardObject = object; + break; + case CommandType.MoveY: + command = new MoveYCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.startY = parseFloat(values[4]); + command.endY = values.length > 5 ? parseFloat(values[5]) : command.startY; + command.parentStoryboardObject = object; + break; + case CommandType.Color: + command = new ColorCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + let convertHexToNumber = (str: string) => { + let num = parseInt(str); + if (isNaN(num)) { + return parseInt(str, 16); + } + else { + return num; + } + } + let startR = convertHexToNumber(values[4])/255; + let startG = convertHexToNumber(values[5])/255; + let startB = convertHexToNumber(values[6])/255; + let endR = values.length > 7 ? (convertHexToNumber(values[7])/255) : (startR/255); + let endG = values.length > 8 ? (convertHexToNumber(values[8])/255) : (startG/255); + let endB = values.length > 9 ? (convertHexToNumber(values[9])/255) : (startB/255); + startR = MathUtil.clamp01(startR); + startG = MathUtil.clamp01(startG); + startB = MathUtil.clamp01(startB); + endR = MathUtil.clamp01(endR); + endG = MathUtil.clamp01(endG); + endB = MathUtil.clamp01(endB); + command.startColor = new PIXI.Color(new Float32Array([startR, startG, startB])); + command.endColor = new PIXI.Color(new Float32Array([endR, endG, endB])); + command.parentStoryboardObject = object; + break; + case CommandType.Parameter: + command = new ParameterCommand(); + command.startTime = startTime; + command.endTime = endTime; + command.easing = easing; + command.parameter = values[4] as ParameterCommandType; + command.parentStoryboardObject = object; + break; + } + if (command) { + if (depth == 2) { + if (parentCommand) { + if ("childCommands" in parentCommand && parentCommand.childCommands) { + // @ts-ignore + parentCommand.childCommands.push(command); + } + } + } else { + event = command; + } + } + break; + } + } } if (event) { - beatmapData.Events.Events.push(event); + beatmapData.Events.Events.push(event as Event); } }); + beatmapData.Events.Events.sort((a, b) => { + return a.startTime - b.startTime; + }); } public static async LoadFiles(beatmapData: BeatmapData, audioEngine: AudioEngine) { @@ -152,6 +327,7 @@ export class EventsParser { } } check(); // check because toLoad.length could be 0 + let textureCache: {filePath: string, texture: PIXI.Texture}[] = []; for (const event of toLoad) { for (let eventKey in event) { if (eventKey == "filename" || eventKey == "filepath") { @@ -163,95 +339,140 @@ export class EventsParser { if (file) { let url = URL.createObjectURL(file); if (event instanceof EventBackground) { - PIXI.Assets.load({src: url, loadParser: "loadTextures"}).then((texture) => { - event.texture = texture; - loaded++; - check(); - }).catch((e) => { - console.warn(e); + // @ts-ignore + if (!textureCache.find((texture) => {if (texture.filePath == event[eventKey]) {return texture;}})) { + PIXI.Assets.load({src: url, loadParser: "loadTextures"}).then((texture) => { + event.texture = texture; + // @ts-ignore + textureCache.push({filePath: event[eventKey], texture: texture}); + loaded++; + check(); + }).catch((e) => { + console.warn(e); + loaded++; + check(); + }); + } + else { + URL.revokeObjectURL(url); + // @ts-ignore + event.texture = textureCache.find((texture) => {if (texture.filePath == event[eventKey]) {return texture;}})!.texture; loaded++; check(); - }); + } } if (event instanceof EventVideo) { - let failed = false; // @ts-ignore - if (event[eventKey].endsWith(".avi")) { + if (!textureCache.find((texture) => {if (texture.filePath == event[eventKey]) {return texture;}})) { + let failed = false; // @ts-ignore - let cacheHit = this.aviTranscodedVideoCache.find( - (cache) => { - // @ts-ignore - if (cache.beatmapSID == beatmapData.Metadata.BeatmapSetID && cache.filePath == event[eventKey]) { - return cache; + if (event[eventKey].endsWith(".avi")) { + // @ts-ignore + let cacheHit = this.aviTranscodedVideoCache.find( + (cache) => { + // @ts-ignore + if (cache.beatmapSID == beatmapData.Metadata.BeatmapSetID && cache.filePath == event[eventKey]) { + return cache; + } } - } - ); - try { - if (!cacheHit) { - console.warn("AVI video is not natively supported! Transcoding to .mp4, this may take a few minutes!"); - let ffmpeg = new FFmpeg(); - ffmpeg.on('progress', ({progress, time}) => { - console.log(`.avi to .mp4 transcoding: ${Math.round(progress * 1000) / 10}% (transcoded time: ${Math.round(time / 100000) / 10}s)`); - }); - console.log("FFmpeg initialized"); - const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'; - let classWorker = Loader.Get("workers.ffmpeg"); - let core = Loader.Get("ffmpeg.core"); - let wasm = Loader.Get("ffmpeg.wasm"); - let worker = Loader.Get("ffmpeg.coreWorker"); - await ffmpeg.load({ - classWorkerURL: URL.createObjectURL(classWorker), - coreURL: URL.createObjectURL(core), - wasmURL: URL.createObjectURL(wasm), - workerURL: URL.createObjectURL(worker) - }); - console.log("FFmpeg loaded"); - await ffmpeg.writeFile('input.avi', new Uint8Array(await file.arrayBuffer())); - console.log("Written file to FFmpeg's vfs"); - await ffmpeg.exec(["-i", "input.avi", "-c:v", "libx264", "-preset", - "ultrafast", "-an", "-movflags", "faststart", "output.mp4"]); - console.log("Transcoding done"); - const data = await ffmpeg.readFile("output.mp4"); - ffmpeg.terminate(); - console.log("FFmpeg terminated"); - let newFile = new Blob([data], {type: 'video/mp4'}); - this.aviTranscodedVideoCache.push({ - beatmapSID: beatmapData.Metadata.BeatmapSetID, + ); + try { + if (!cacheHit) { + console.warn("AVI video is not natively supported! Transcoding to .mp4, this may take a few minutes!"); + let ffmpeg = new FFmpeg(); + ffmpeg.on('progress', ({progress, time}) => { + console.log(`.avi to .mp4 transcoding: ${Math.round(progress * 1000) / 10}% (transcoded time: ${Math.round(time / 100000) / 10}s)`); + }); + console.log("FFmpeg initialized"); + const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'; + let classWorker = Loader.Get("workers.ffmpeg"); + let core = Loader.Get("ffmpeg.core"); + let wasm = Loader.Get("ffmpeg.wasm"); + let worker = Loader.Get("ffmpeg.coreWorker"); + await ffmpeg.load({ + classWorkerURL: URL.createObjectURL(classWorker), + coreURL: URL.createObjectURL(core), + wasmURL: URL.createObjectURL(wasm), + workerURL: URL.createObjectURL(worker) + }); + console.log("FFmpeg loaded"); + await ffmpeg.writeFile('input.avi', new Uint8Array(await file.arrayBuffer())); + console.log("Written file to FFmpeg's vfs"); + await ffmpeg.exec(["-i", "input.avi", "-c:v", "libx264", "-preset", + "ultrafast", "-an", "-movflags", "faststart", "output.mp4"]); + console.log("Transcoding done"); + const data = await ffmpeg.readFile("output.mp4"); + ffmpeg.terminate(); + console.log("FFmpeg terminated"); + let newFile = new Blob([data], {type: 'video/mp4'}); + this.aviTranscodedVideoCache.push({ + beatmapSID: beatmapData.Metadata.BeatmapSetID, + // @ts-ignore + filePath: event[eventKey], + blob: newFile + }); // @ts-ignore - filePath: event[eventKey], - blob: newFile - }); - // @ts-ignore - beatmapData.files.delete(event[eventKey]); - // @ts-ignore - event[eventKey] = event[eventKey].substring(0, event[eventKey].length - 4) + ".mp4"; - // @ts-ignore - beatmapData.files.set(event[eventKey], newFile); - URL.revokeObjectURL(url); - url = URL.createObjectURL(newFile); - console.log("Successfully transcoded from .avi to .mp4!"); - } else { - // @ts-ignore - beatmapData.files.delete(event[eventKey]); - // @ts-ignore - event[eventKey] = event[eventKey].substring(0, event[eventKey].length - 4) + ".mp4"; - // @ts-ignore - beatmapData.files.set(event[eventKey], cacheHit.blob); - URL.revokeObjectURL(url); - url = URL.createObjectURL(cacheHit.blob); + beatmapData.files.delete(event[eventKey]); + // @ts-ignore + event[eventKey] = event[eventKey].substring(0, event[eventKey].length - 4) + ".mp4"; + // @ts-ignore + beatmapData.files.set(event[eventKey], newFile); + URL.revokeObjectURL(url); + url = URL.createObjectURL(newFile); + console.log("Successfully transcoded from .avi to .mp4!"); + } else { + // @ts-ignore + beatmapData.files.delete(event[eventKey]); + // @ts-ignore + event[eventKey] = event[eventKey].substring(0, event[eventKey].length - 4) + ".mp4"; + // @ts-ignore + beatmapData.files.set(event[eventKey], cacheHit.blob); + URL.revokeObjectURL(url); + url = URL.createObjectURL(cacheHit.blob); + } + } catch (e) { + console.warn(e); + failed = true; } - } catch (e) { - console.warn(e); - failed = true; + } + if (!failed) { + PIXI.Assets.load({src: url, loadParser: "loadVideo"}).then((texture) => { + event.texture = texture; + // @ts-ignore + textureCache.push({filePath: event[eventKey], texture: texture}); + if (event.texture) { + event.texture.source.resource.volume = 0; + event.texture.source.resource.pause(); + } + loaded++; + check(); + }).catch((e) => { + console.warn(e); + loaded++; + check(); + }); + } else { + loaded++; + check(); } } - if (!failed) { - PIXI.Assets.load({src: url, loadParser: "loadVideo"}).then((texture) => { + else { + URL.revokeObjectURL(url); + // @ts-ignore + event.texture = textureCache.find((texture) => {if (texture.filePath == event[eventKey]) {return texture;}})!.texture; + event.texture.source.resource.volume = 0; + event.texture.source.resource.pause(); + loaded++; + check(); + } + } + if (event instanceof EventSprite) { + // @ts-ignore + if (!textureCache.find((texture) => {if (texture.filePath == event[eventKey]) {return texture;}})) { + PIXI.Assets.load({src: url, loadParser: "loadTextures"}).then((texture) => { event.texture = texture; - if (event.texture) { - event.texture.source.resource.volume = 0; - event.texture.source.resource.pause(); - } + // @ts-ignore + textureCache.push({filePath: event[eventKey], texture: texture}); loaded++; check(); }).catch((e) => { @@ -259,21 +480,14 @@ export class EventsParser { loaded++; check(); }); - } else { - loaded++; - check(); } - } - if (event instanceof EventSprite) { - PIXI.Assets.load({src: url, loadParser: "loadTextures"}).then((texture) => { - event.texture = texture; - loaded++; - check(); - }).catch((e) => { - console.warn(e); + else { + URL.revokeObjectURL(url); + // @ts-ignore + event.texture = textureCache.find((texture) => {if (texture.filePath == event[eventKey]) {return texture;}})!.texture; loaded++; check(); - }); + } } if (event instanceof EventSample) { file.arrayBuffer().then((arrayBuffer) => { @@ -299,19 +513,29 @@ export class EventsParser { } } event.filepaths.forEach((filePath, i) => { - let file = beatmapData.files.get(filePath); - if (file) { - let url = URL.createObjectURL(file); - PIXI.Assets.load({src: url, loadParser: "loadTextures"}).then((texture) => { - event.textures[i] = texture; - loadedTextures++ - checkLoadedTextures(); - }).catch((e) => { - console.warn(e); - loadedTextures++; - checkLoadedTextures(); - }); - } + // @ts-ignore + if (!textureCache.find((texture) => {if (texture.filePath == filePath) {return texture;}})) { + let file = beatmapData.files.get(filePath); + if (file) { + let url = URL.createObjectURL(file); + PIXI.Assets.load({src: url, loadParser: "loadTextures"}).then((texture) => { + event.textures[i] = texture; + textureCache.push({filePath: filePath, texture: texture}); + loadedTextures++ + checkLoadedTextures(); + }).catch((e) => { + console.warn(e); + loadedTextures++; + checkLoadedTextures(); + }); + } + } + else { + event.textures[i] = textureCache.find((texture) => {if (texture.filePath == filePath) {return texture;}})!.texture; + loadedTextures++ + checkLoadedTextures(); + } + }); } } else { diff --git a/src/Util/TweenWrapper/EasingFunction.ts b/src/Util/TweenWrapper/EasingFunction.ts index adc8c71..54bb8f9 100644 --- a/src/Util/TweenWrapper/EasingFunction.ts +++ b/src/Util/TweenWrapper/EasingFunction.ts @@ -156,4 +156,83 @@ export class EasingFunction { public static readonly OutPow10 = (time: number) => { return --time * Math.pow(time, 10) + 1; } + + public static convertFromStoryBoardToEaseFunction(easing: number) { + switch (easing) { + case 0: + return this.None; + case 1: + return this.Out; + case 2: + return this.In; + case 3: + return this.InQuad; + case 4: + return this.OutQuad; + case 5: + return this.InOutQuad; + case 6: + return this.InCubic; + case 7: + return this.OutCubic; + case 8: + return this.InOutCubic; + case 9: + return this.InQuart; + case 10: + return this.OutQuart; + case 11: + return this.InOutQuart; + case 12: + return this.InQuint; + case 13: + return this.OutQuint; + case 14: + return this.InOutQuint; + case 15: + return this.InSine; + case 16: + return this.OutSine; + case 17: + return this.InOutSine; + case 18: + return this.InExpo; + case 19: + return this.OutExpo; + case 20: + return this.InOutExpo; + case 21: + return this.InCirc; + case 22: + return this.OutCirc; + case 23: + return this.InOutCirc; + case 24: + return this.InElastic; + case 25: + return this.OutElastic; + case 26: + return this.OutElasticHalf; + case 27: + return this.OutElasticQuarter; + case 28: + return this.InOutElastic; + case 29: + return this.InBack; + case 30: + return this.OutBack; + case 31: + return this.InOutBack; + case 32: + return this.InBounce; + case 33: + return this.OutBounce; + case 34: + return this.InOutBounce; + case 35: + return this.OutPow10; + default: + return this.None; + } + } } \ No newline at end of file