mirror of
https://github.com/project-slippi/slippi-js.git
synced 2025-10-06 00:32:40 +02:00
feat: parse stage events from 3.18.0 (#135)
This commit is contained in:
BIN
slp/FodPlatforms.slp
Normal file
BIN
slp/FodPlatforms.slp
Normal file
Binary file not shown.
BIN
slp/Whispy.slp
Normal file
BIN
slp/Whispy.slp
Normal file
Binary file not shown.
BIN
slp/stadiumTransformations.slp
Normal file
BIN
slp/stadiumTransformations.slp
Normal file
Binary file not shown.
55
src/types.ts
55
src/types.ts
@@ -9,6 +9,9 @@ export enum Command {
|
||||
ITEM_UPDATE = 0x3b,
|
||||
FRAME_BOOKEND = 0x3c,
|
||||
GECKO_LIST = 0x3d,
|
||||
FOD_PLATFORM = 0x3f,
|
||||
WHISPY = 0x40,
|
||||
STADIUM_TRANSFORMATION = 0x41,
|
||||
}
|
||||
|
||||
export type PlayerType = {
|
||||
@@ -274,6 +277,50 @@ export type GeckoCodeType = {
|
||||
contents: Uint8Array;
|
||||
};
|
||||
|
||||
export enum FodPlatformSide {
|
||||
RIGHT = 0,
|
||||
LEFT = 1,
|
||||
}
|
||||
|
||||
export type FodPlatformType = {
|
||||
frame: number | null;
|
||||
platform: FodPlatformSide | null;
|
||||
height: number | null;
|
||||
};
|
||||
|
||||
export enum WhispyBlowDirection {
|
||||
NONE = 0,
|
||||
LEFT = 1,
|
||||
RIGHT = 2,
|
||||
}
|
||||
|
||||
export type WhispyType = {
|
||||
frame: number | null;
|
||||
direction: WhispyBlowDirection | null;
|
||||
};
|
||||
|
||||
export enum StadiumTransformation {
|
||||
FIRE = 3,
|
||||
GRASS = 4,
|
||||
NORMAL = 5,
|
||||
ROCK = 6,
|
||||
WATER = 9,
|
||||
}
|
||||
|
||||
export enum StadiumTransformationEvent {
|
||||
INITIATE = 2,
|
||||
ON_MONITOR = 3,
|
||||
RECEDING = 4,
|
||||
RISING = 5,
|
||||
FINISH = 6,
|
||||
}
|
||||
|
||||
export type StadiumTransformationType = {
|
||||
frame: number | null;
|
||||
event: StadiumTransformationEvent | null;
|
||||
transformation: StadiumTransformation | null;
|
||||
};
|
||||
|
||||
export type MetadataType = {
|
||||
startAt?: string | null;
|
||||
playedOn?: string | null;
|
||||
@@ -300,7 +347,10 @@ export type EventPayloadTypes =
|
||||
| ItemUpdateType
|
||||
| FrameBookendType
|
||||
| GameEndType
|
||||
| GeckoListType;
|
||||
| GeckoListType
|
||||
| FodPlatformType
|
||||
| WhispyType
|
||||
| StadiumTransformationType;
|
||||
|
||||
export type EventCallbackFunc = (
|
||||
command: Command,
|
||||
@@ -308,6 +358,8 @@ export type EventCallbackFunc = (
|
||||
buffer?: Uint8Array | null,
|
||||
) => boolean;
|
||||
|
||||
export type StageEventTypes = FodPlatformType | WhispyType | StadiumTransformationType;
|
||||
|
||||
export type FrameEntryType = {
|
||||
frame: number;
|
||||
start?: FrameStartType;
|
||||
@@ -324,6 +376,7 @@ export type FrameEntryType = {
|
||||
} | null;
|
||||
};
|
||||
items?: ItemUpdateType[];
|
||||
stageEvents?: StageEventTypes[];
|
||||
};
|
||||
|
||||
export enum Frames {
|
||||
|
@@ -16,7 +16,11 @@ import type {
|
||||
ItemUpdateType,
|
||||
PostFrameUpdateType,
|
||||
PreFrameUpdateType,
|
||||
FodPlatformType,
|
||||
WhispyType,
|
||||
StadiumTransformationType,
|
||||
RollbackFrames,
|
||||
StageEventTypes,
|
||||
} from "../types";
|
||||
import { ItemSpawnType } from "../types";
|
||||
import { Command, Frames, GameMode } from "../types";
|
||||
@@ -91,6 +95,15 @@ export class SlpParser extends EventEmitter {
|
||||
case Command.GECKO_LIST:
|
||||
this._handleGeckoList(payload as GeckoListType);
|
||||
break;
|
||||
case Command.FOD_PLATFORM:
|
||||
this._handleStageEvent(payload as FodPlatformType);
|
||||
break;
|
||||
case Command.WHISPY:
|
||||
this._handleStageEvent(payload as WhispyType);
|
||||
break;
|
||||
case Command.STADIUM_TRANSFORMATION:
|
||||
this._handleStageEvent(payload as StadiumTransformationType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +306,15 @@ export class SlpParser extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleStageEvent(payload: StageEventTypes): void {
|
||||
const currentFrameNumber = payload.frame!;
|
||||
const stageEvents = this.frames[currentFrameNumber]?.stageEvents ?? [];
|
||||
stageEvents.push(payload);
|
||||
|
||||
// Set stageEvents with newest
|
||||
set(this.frames, [currentFrameNumber, "stageEvents"], stageEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires off the FINALIZED_FRAME event for frames up until a certain number
|
||||
* @param num The frame to finalize until
|
||||
|
@@ -583,6 +583,23 @@ export function parseMessage(command: Command, payload: Uint8Array): EventPayloa
|
||||
contents: payload.slice(1),
|
||||
codes: codes,
|
||||
};
|
||||
case Command.FOD_PLATFORM:
|
||||
return {
|
||||
frame: readInt32(view, 0x1),
|
||||
platform: readInt8(view, 0x5),
|
||||
height: readFloat(view, 0x6),
|
||||
};
|
||||
case Command.WHISPY:
|
||||
return {
|
||||
frame: readInt32(view, 0x1),
|
||||
direction: readInt8(view, 0x5),
|
||||
};
|
||||
case Command.STADIUM_TRANSFORMATION:
|
||||
return {
|
||||
frame: readInt32(view, 0x1),
|
||||
event: readUint16(view, 0x5),
|
||||
transformation: readUint16(view, 0x7),
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
75
test/stages.spec.ts
Normal file
75
test/stages.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Frames, SlippiGame, StadiumTransformationEvent, FodPlatformSide, WhispyBlowDirection } from "../src";
|
||||
|
||||
describe("when extracting stadium transformation information", () => {
|
||||
it("should properly increment event ids", () => {
|
||||
const game = new SlippiGame("slp/stadiumTransformations.slp");
|
||||
const frames = game.getFrames();
|
||||
|
||||
let lastEventId = -1;
|
||||
let lastTransformationId = -1;
|
||||
for (let frameNum = Frames.FIRST; frames[frameNum]; frameNum++) {
|
||||
const frame = frames[frameNum];
|
||||
if (frame.stageEvents) {
|
||||
frame.stageEvents.forEach((e) => {
|
||||
if (e.transformation != lastTransformationId) {
|
||||
expect(e.event).toBe(StadiumTransformationEvent.INITIATE);
|
||||
lastTransformationId = e.transformation;
|
||||
lastEventId = e.event;
|
||||
} else {
|
||||
expect(e.event).toBe((lastEventId + 1) % 7);
|
||||
lastEventId = e.event;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("when extracting FOD platform information", () => {
|
||||
it("should properly parse platform height", () => {
|
||||
const game = new SlippiGame("slp/FodPlatforms.slp");
|
||||
const frames = game.getFrames();
|
||||
|
||||
let prevHeightLeft = 20.0;
|
||||
let prevHeightRight = 28.0;
|
||||
for (let frameNum = Frames.FIRST; frames[frameNum]; frameNum++) {
|
||||
const frame = frames[frameNum];
|
||||
if (frame.stageEvents) {
|
||||
frame.stageEvents.forEach((e) => {
|
||||
if (e.platform == FodPlatformSide.LEFT) {
|
||||
expect(Math.abs(e.height - prevHeightLeft)).toBeLessThan(0.2);
|
||||
prevHeightLeft = e.height;
|
||||
} else {
|
||||
expect(Math.abs(e.height - prevHeightRight)).toBeLessThan(0.2);
|
||||
prevHeightRight = e.height;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("when extracting Dreamland Whispy information", () => {
|
||||
it("should properly parse blow directions", () => {
|
||||
const game = new SlippiGame("slp/Whispy.slp");
|
||||
const frames = game.getFrames();
|
||||
|
||||
let prevBlowDirection = WhispyBlowDirection.NONE;
|
||||
for (let frameNum = Frames.FIRST; frames[frameNum]; frameNum++) {
|
||||
const frame = frames[frameNum];
|
||||
if (frame.stageEvents) {
|
||||
frame.stageEvents.forEach((e) => {
|
||||
if (prevBlowDirection == WhispyBlowDirection.LEFT) {
|
||||
expect(e.direction).toBe(WhispyBlowDirection.NONE);
|
||||
} else if (prevBlowDirection == WhispyBlowDirection.RIGHT) {
|
||||
expect(e.direction).toBe(WhispyBlowDirection.NONE);
|
||||
} else {
|
||||
expect(e.direction).not.toBe(WhispyBlowDirection.NONE);
|
||||
}
|
||||
|
||||
prevBlowDirection = e.direction;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user