68 lines
1.9 KiB
TypeScript
68 lines
1.9 KiB
TypeScript
/**
|
||
* tosu (https://github.com/tosuapp/tosu) reads the osu! tourney client's
|
||
* memory and serves it over a websocket. We need each spectated player's
|
||
* live score, and how far through the map they are.
|
||
*/
|
||
|
||
export interface TosuClientScore {
|
||
osuId: number;
|
||
name: string;
|
||
score: number;
|
||
/** Percent. */
|
||
accuracy: number;
|
||
combo: number;
|
||
failed: boolean;
|
||
}
|
||
|
||
export interface TosuFrame {
|
||
/** 1 idle, 2 waiting for clients, 3 playing, 4 results (tosu's ipcState). */
|
||
ipcState: number | null;
|
||
beatmapId: number | null;
|
||
/** 0–1 through the map. */
|
||
progress: number | null;
|
||
clients: TosuClientScore[];
|
||
}
|
||
|
||
/** Picks what Vash needs out of one tosu v2 message. Returns null outside tourney mode. */
|
||
export function parseTosu(message: unknown): TosuFrame | null {
|
||
const root = message as {
|
||
tourney?: {
|
||
ipcState?: number;
|
||
clients?: {
|
||
user?: { id?: number; name?: string };
|
||
play?: {
|
||
score?: number;
|
||
accuracy?: number;
|
||
failed?: boolean;
|
||
combo?: { current?: number };
|
||
};
|
||
}[];
|
||
};
|
||
beatmap?: { id?: number; time?: { live?: number; firstObject?: number; lastObject?: number } };
|
||
};
|
||
if (!root?.tourney || !Array.isArray(root.tourney.clients)) return null;
|
||
const time = root.beatmap?.time;
|
||
const span =
|
||
time && time.lastObject != null && time.firstObject != null
|
||
? time.lastObject - time.firstObject
|
||
: 0;
|
||
return {
|
||
ipcState: root.tourney.ipcState ?? null,
|
||
beatmapId: root.beatmap?.id ?? null,
|
||
progress:
|
||
span > 0 && time?.live != null
|
||
? Math.max(0, Math.min(1, (time.live - time.firstObject!) / span))
|
||
: null,
|
||
clients: root.tourney.clients
|
||
.filter((client) => (client.user?.id ?? 0) > 0)
|
||
.map((client) => ({
|
||
osuId: client.user!.id!,
|
||
name: client.user?.name ?? '',
|
||
score: client.play?.score ?? 0,
|
||
accuracy: client.play?.accuracy ?? 0,
|
||
combo: client.play?.combo?.current ?? 0,
|
||
failed: client.play?.failed ?? false
|
||
}))
|
||
};
|
||
}
|