From b9c7d56e862cede1ba488cd58a28d50092c04f2f Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 9 Sep 2020 13:54:10 +1000 Subject: [PATCH 01/26] Move connection to console connection --- .../{connection.ts => consoleConnection.ts} | 36 +--------------- src/console/index.ts | 3 +- src/console/types.ts | 42 +++++++++++++++++++ 3 files changed, 46 insertions(+), 35 deletions(-) rename src/console/{connection.ts => consoleConnection.ts} (94%) create mode 100644 src/console/types.ts diff --git a/src/console/connection.ts b/src/console/consoleConnection.ts similarity index 94% rename from src/console/connection.ts rename to src/console/consoleConnection.ts index 5d240a54..4ccd3a8f 100644 --- a/src/console/connection.ts +++ b/src/console/consoleConnection.ts @@ -4,44 +4,12 @@ import { EventEmitter } from "events"; import inject from "reconnect-core"; import { ConsoleCommunication, CommunicationType, CommunicationMessage } from "./communication"; +import { ConnectionDetails, Connection, ConnectionStatus, Ports, ConnectionSettings, ConnectionEvent } from "./types"; export const NETWORK_MESSAGE = "HELO\0"; const DEFAULT_CONNECTION_TIMEOUT_MS = 20000; -export enum ConnectionEvent { - HANDSHAKE = "handshake", - STATUS_CHANGE = "statusChange", - DATA = "data", - INFO = "loginfo", - WARN = "logwarn", -} - -export enum ConnectionStatus { - DISCONNECTED = 0, - CONNECTING = 1, - CONNECTED = 2, - RECONNECT_WAIT = 3, -} - -export enum Ports { - DEFAULT = 51441, - LEGACY = 666, - RELAY_START = 53741, -} - -export interface ConnectionDetails { - consoleNick: string; - gameDataCursor: Uint8Array; - version: string; - clientToken: number; -} - -export interface ConnectionSettings { - ipAddress: string; - port: number; -} - enum CommunicationState { INITIAL = "initial", LEGACY = "legacy", @@ -77,7 +45,7 @@ const defaultConnectionDetails: ConnectionDetails = { * }); * ``` */ -export class ConsoleConnection extends EventEmitter { +export class ConsoleConnection extends EventEmitter implements Connection { private ipAddress: string; private port: number; private connectionStatus = ConnectionStatus.DISCONNECTED; diff --git a/src/console/index.ts b/src/console/index.ts index 4de10ebf..f24427fd 100644 --- a/src/console/index.ts +++ b/src/console/index.ts @@ -1 +1,2 @@ -export * from "./connection"; +export * from "./types"; +export * from "./consoleConnection"; diff --git a/src/console/types.ts b/src/console/types.ts new file mode 100644 index 00000000..a30d93b7 --- /dev/null +++ b/src/console/types.ts @@ -0,0 +1,42 @@ +import { EventEmitter } from "events"; + +export enum ConnectionEvent { + HANDSHAKE = "handshake", + STATUS_CHANGE = "statusChange", + DATA = "data", + INFO = "loginfo", + WARN = "logwarn", +} + +export enum ConnectionStatus { + DISCONNECTED = 0, + CONNECTING = 1, + CONNECTED = 2, + RECONNECT_WAIT = 3, +} + +export enum Ports { + DEFAULT = 51441, + LEGACY = 666, + RELAY_START = 53741, +} + +export interface ConnectionDetails { + consoleNick: string; + gameDataCursor: Uint8Array; + version: string; + clientToken: number; +} + +export interface ConnectionSettings { + ipAddress: string; + port: number; +} + +export interface Connection extends EventEmitter { + getStatus(): ConnectionStatus; + getSettings(): ConnectionSettings; + getDetails(): ConnectionDetails; + connect(ip: string, port: number, timeout: number): void; + disconnect(): void; +} From c580f32a431d661269e4e415108b9627e4f8f5d6 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 9 Sep 2020 17:37:54 +1000 Subject: [PATCH 02/26] Add enet package and some basic type declarations --- package.json | 1 + src/declarations.d.ts | 24 ++++++++++++++++++++++++ yarn.lock | 5 +++++ 3 files changed, 30 insertions(+) create mode 100644 src/declarations.d.ts diff --git a/package.json b/package.json index eb03ee74..ae674c69 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ ], "dependencies": { "@shelacek/ubjson": "^1.0.1", + "enet": "^0.2.9", "iconv-lite": "^0.6.2", "lodash": "^4.17.19", "moment": "^2.27.0", diff --git a/src/declarations.d.ts b/src/declarations.d.ts new file mode 100644 index 00000000..233c2b30 --- /dev/null +++ b/src/declarations.d.ts @@ -0,0 +1,24 @@ +declare module "enet" { + import { EventEmitter } from "events"; + + export const PACKET_FLAG: any; + export class Packet { + constructor(data: string | Buffer, flag: any); + data(): Buffer; + } + export interface Peer extends EventEmitter { + ping(): void; + send(channel: number, packet: Packet): boolean; + } + interface ClientArguments { + peers: number; + channels: number; + down: number; + up: number; + } + export interface Host extends Record { + connect(args: any, channels: number, data: any, callback: (err: Error, peer: Peer) => void): Peer; + destroy(): void; + } + export function createClient(args: ClientArguments, callback: (err: Error, client: Host) => void): Host; +} diff --git a/yarn.lock b/yarn.lock index 71199fdb..0f567e07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1280,6 +1280,11 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +enet@^0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/enet/-/enet-0.2.9.tgz#d7fd68a0bf8bb3891408ea465e26ed6955822a1a" + integrity sha1-1/1ooL+Ls4kUCOpGXibtaVWCKho= + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" From e1f7cc018425a635dcc5534dff2f92447d7d8253 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 9 Sep 2020 17:38:57 +1000 Subject: [PATCH 03/26] Update typings --- src/console/consoleConnection.ts | 4 ++-- src/console/index.ts | 1 + src/console/types.ts | 8 +++----- src/utils/slpStream.ts | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/console/consoleConnection.ts b/src/console/consoleConnection.ts index 4ccd3a8f..3a96afb6 100644 --- a/src/console/consoleConnection.ts +++ b/src/console/consoleConnection.ts @@ -191,7 +191,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { }); const handshakeMsgOut = consoleComms.genHandshakeOut( - this.connDetails.gameDataCursor, + this.connDetails.gameDataCursor as Uint8Array, this.connDetails.clientToken, ); @@ -278,7 +278,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { break; case CommunicationType.REPLAY: const readPos = Uint8Array.from(message.payload.pos); - const cmp = Buffer.compare(this.connDetails.gameDataCursor, readPos); + const cmp = Buffer.compare(this.connDetails.gameDataCursor as Uint8Array, readPos); if (!message.payload.forcePos && cmp !== 0) { console.warn( "Position of received data is not what was expected. Expected, Received:", diff --git a/src/console/index.ts b/src/console/index.ts index f24427fd..55dc6e49 100644 --- a/src/console/index.ts +++ b/src/console/index.ts @@ -1,2 +1,3 @@ export * from "./types"; export * from "./consoleConnection"; +export * from "./dolphinConnection"; diff --git a/src/console/types.ts b/src/console/types.ts index a30d93b7..d910d7e9 100644 --- a/src/console/types.ts +++ b/src/console/types.ts @@ -4,8 +4,6 @@ export enum ConnectionEvent { HANDSHAKE = "handshake", STATUS_CHANGE = "statusChange", DATA = "data", - INFO = "loginfo", - WARN = "logwarn", } export enum ConnectionStatus { @@ -23,9 +21,9 @@ export enum Ports { export interface ConnectionDetails { consoleNick: string; - gameDataCursor: Uint8Array; + gameDataCursor: number | Uint8Array; version: string; - clientToken: number; + clientToken?: number; } export interface ConnectionSettings { @@ -37,6 +35,6 @@ export interface Connection extends EventEmitter { getStatus(): ConnectionStatus; getSettings(): ConnectionSettings; getDetails(): ConnectionDetails; - connect(ip: string, port: number, timeout: number): void; + connect(ip: string, port: number): void; disconnect(): void; } diff --git a/src/utils/slpStream.ts b/src/utils/slpStream.ts index 3c4af6d4..e5cd2f05 100644 --- a/src/utils/slpStream.ts +++ b/src/utils/slpStream.ts @@ -1,7 +1,7 @@ import { Writable, WritableOptions } from "stream"; import { Command, EventPayloadTypes } from "../types"; import { parseMessage } from "./slpReader"; -import { NETWORK_MESSAGE } from "../console/connection"; +import { NETWORK_MESSAGE } from "../console"; export enum SlpStreamMode { AUTO = "AUTO", // Always reading data, but errors on invalid command From 3c905fdb029ce7dc2854da30d354b602847bb661 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 9 Sep 2020 17:40:27 +1000 Subject: [PATCH 04/26] Initial commit for dolphin connection --- src/console/dolphinConnection.ts | 146 +++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/console/dolphinConnection.ts diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts new file mode 100644 index 00000000..4eaee23f --- /dev/null +++ b/src/console/dolphinConnection.ts @@ -0,0 +1,146 @@ +import enet from "enet"; + +import { EventEmitter } from "events"; +import { Connection, ConnectionStatus, ConnectionSettings, ConnectionDetails, Ports, ConnectionEvent } from "./types"; + +const MAX_PEERS = 32; + +enum MessageType { + CONNECT_REPLY = "connect_reply", + GAME_EVENT = "game_event", +} + +export class DolphinConnection extends EventEmitter implements Connection { + private ipAddress: string; + private port: number; + private connectionStatus = ConnectionStatus.DISCONNECTED; + private gameCursor = 0; + private nickname = "unknown"; + private version = ""; + private client: enet.Host; + private peer: enet.Peer | null = null; + + public constructor() { + super(); + this.ipAddress = "0.0.0.0"; + this.port = Ports.DEFAULT; + + // Create the enet client + this.client = enet.createClient({ peers: MAX_PEERS, channels: 3, down: 0, up: 0 }, (err) => { + if (err) { + console.error(err); + return; + } + }); + } + + /** + * @returns The current connection status. + */ + public getStatus(): ConnectionStatus { + return this.connectionStatus; + } + + /** + * @returns The IP address and port of the current connection. + */ + public getSettings(): ConnectionSettings { + return { + ipAddress: this.ipAddress, + port: this.port, + }; + } + + public getDetails(): ConnectionDetails { + return { + consoleNick: this.nickname, + gameDataCursor: this.gameCursor, + version: this.version, + }; + } + + public connect(ip: string, port: number): void { + console.log(`Connecting to: ${ip}:${port}`); + this.ipAddress = ip; + this.port = port; + + this.peer = this.client.connect( + { + address: this.ipAddress, + port: this.port, + }, + 3, + 1337, // Data to send, not sure what this is or what this represents + (err, newPeer) => { + if (err) { + console.error(err); + return; + } + + newPeer.ping(); + this._setStatus(ConnectionStatus.CONNECTED); + }, + ); + + this.peer.on("connect", () => { + // Reset the game cursor to the beginning of the game. Do we need to do this or + // should it just continue from where it left off? + this.gameCursor = 0; + + const request = { + type: "connect_request", + cursor: this.gameCursor, + }; + const packet = new enet.Packet(JSON.stringify(request), enet.PACKET_FLAG.RELIABLE); + this.peer.send(0, packet); + }); + + this.peer.on("message", (packet: enet.Packet, _channel: any) => { + const data = packet.data(); + if (data.length === 0) { + return; + } + + const message = JSON.parse(data.toString("ascii")); + switch (message.type) { + case MessageType.CONNECT_REPLY: + this.connectionStatus = ConnectionStatus.CONNECTED; + this.gameCursor = message.cursor; + this.nickname = message.nick; + this.version = message.version; + this.emit(ConnectionEvent.HANDSHAKE, this.getDetails()); + break; + case MessageType.GAME_EVENT: + const cursor = message.cursor; + if (this.gameCursor !== cursor) { + throw new Error(`Unexpected game data cursor. Expected: ${this.gameCursor} but got: ${cursor}`); + } + + this.gameCursor = message.next_cursor; + const gameData = Buffer.from(message.payload, "base64"); + this._handleReplayData(gameData); + break; + } + }); + + this.peer.on("disconnect", () => { + this.disconnect(); + }); + + this._setStatus(ConnectionStatus.CONNECTING); + } + + public disconnect(): void { + this._setStatus(ConnectionStatus.DISCONNECTED); + this.peer = null; + } + + private _handleReplayData(data: Uint8Array): void { + this.emit(ConnectionEvent.DATA, data); + } + + private _setStatus(status: ConnectionStatus): void { + this.connectionStatus = status; + this.emit(ConnectionEvent.STATUS_CHANGE, this.connectionStatus); + } +} From 9cc6e77531e38ce6aacce09da5aa2d29094bcd24 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 9 Sep 2020 18:29:13 +1000 Subject: [PATCH 05/26] Return the cloned connection details object --- src/console/consoleConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console/consoleConnection.ts b/src/console/consoleConnection.ts index 3a96afb6..d841e459 100644 --- a/src/console/consoleConnection.ts +++ b/src/console/consoleConnection.ts @@ -82,7 +82,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { * @returns The specific details about the connected console. */ public getDetails(): ConnectionDetails { - return this.connDetails; + return { ...this.connDetails }; } /** From 2a723710f3157d780a42f29a91688649afd828d2 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Thu, 10 Sep 2020 11:43:36 +1000 Subject: [PATCH 06/26] Remove unused function parameter --- src/console/dolphinConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index 4eaee23f..4ad44edc 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -95,7 +95,7 @@ export class DolphinConnection extends EventEmitter implements Connection { this.peer.send(0, packet); }); - this.peer.on("message", (packet: enet.Packet, _channel: any) => { + this.peer.on("message", (packet: enet.Packet) => { const data = packet.data(); if (data.length === 0) { return; From 442cb66c5228f107da8d7ddad25af10bb8ac2499 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Thu, 10 Sep 2020 11:44:09 +1000 Subject: [PATCH 07/26] Correctly handle manual game disconnection requests --- src/console/dolphinConnection.ts | 17 +++++++++++++---- src/declarations.d.ts | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index 4ad44edc..cc317f20 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -111,13 +111,19 @@ export class DolphinConnection extends EventEmitter implements Connection { this.emit(ConnectionEvent.HANDSHAKE, this.getDetails()); break; case MessageType.GAME_EVENT: - const cursor = message.cursor; + const { payload, cursor, next_cursor } = message; + if (!payload) { + // We got a disconnection request + this.disconnect(); + return; + } + if (this.gameCursor !== cursor) { throw new Error(`Unexpected game data cursor. Expected: ${this.gameCursor} but got: ${cursor}`); } - this.gameCursor = message.next_cursor; - const gameData = Buffer.from(message.payload, "base64"); + const gameData = Buffer.from(payload, "base64"); + this.gameCursor = next_cursor; this._handleReplayData(gameData); break; } @@ -131,8 +137,11 @@ export class DolphinConnection extends EventEmitter implements Connection { } public disconnect(): void { + if (this.peer) { + this.peer.disconnect(); + this.peer = null; + } this._setStatus(ConnectionStatus.DISCONNECTED); - this.peer = null; } private _handleReplayData(data: Uint8Array): void { diff --git a/src/declarations.d.ts b/src/declarations.d.ts index 233c2b30..c37949b8 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -8,6 +8,7 @@ declare module "enet" { } export interface Peer extends EventEmitter { ping(): void; + disconnect(data?: any): void; send(channel: number, packet: Packet): boolean; } interface ClientArguments { From 3dadf6c8339a807430ce52aed049438b177a9f2a Mon Sep 17 00:00:00 2001 From: Vince Au Date: Thu, 10 Sep 2020 11:46:29 +1000 Subject: [PATCH 08/26] Prevent the status event from firing when the status hasn't changed --- src/console/consoleConnection.ts | 7 +++++-- src/console/dolphinConnection.ts | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/console/consoleConnection.ts b/src/console/consoleConnection.ts index d841e459..90150230 100644 --- a/src/console/consoleConnection.ts +++ b/src/console/consoleConnection.ts @@ -323,7 +323,10 @@ export class ConsoleConnection extends EventEmitter implements Connection { } private _setStatus(status: ConnectionStatus): void { - this.connectionStatus = status; - this.emit(ConnectionEvent.STATUS_CHANGE, this.connectionStatus); + // Don't fire the event if the status hasn't actually changed + if (this.connectionStatus !== status) { + this.connectionStatus = status; + this.emit(ConnectionEvent.STATUS_CHANGE, this.connectionStatus); + } } } diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index cc317f20..e52ca5e1 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -149,7 +149,10 @@ export class DolphinConnection extends EventEmitter implements Connection { } private _setStatus(status: ConnectionStatus): void { - this.connectionStatus = status; - this.emit(ConnectionEvent.STATUS_CHANGE, this.connectionStatus); + // Don't fire the event if the status hasn't actually changed + if (this.connectionStatus !== status) { + this.connectionStatus = status; + this.emit(ConnectionEvent.STATUS_CHANGE, this.connectionStatus); + } } } From 4bc83e3a1f405af3734802d18ac713ef88a4404d Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 22 Sep 2020 16:17:19 +1000 Subject: [PATCH 09/26] Add auto-reconnection option to ConsoleConnection --- src/console/consoleConnection.ts | 80 ++++++++++++++------------------ 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/src/console/consoleConnection.ts b/src/console/consoleConnection.ts index 90150230..b686cd9c 100644 --- a/src/console/consoleConnection.ts +++ b/src/console/consoleConnection.ts @@ -23,6 +23,12 @@ const defaultConnectionDetails: ConnectionDetails = { clientToken: 0, }; +const consoleConnectionOptions = { + autoReconnect: true, +}; + +export type ConsoleConnectionOptions = typeof consoleConnectionOptions; + /** * Responsible for maintaining connection to a Slippi relay connection or Wii connection. * Events are emitted whenever data is received. @@ -50,15 +56,16 @@ export class ConsoleConnection extends EventEmitter implements Connection { private port: number; private connectionStatus = ConnectionStatus.DISCONNECTED; private connDetails: ConnectionDetails = { ...defaultConnectionDetails }; - private clientsByPort: Array; - private connectionsByPort: Array>; + private client: net.Socket | null = null; + private connection: inject.Instance | null = null; + private options: ConsoleConnectionOptions; + private shouldReconnect = false; - public constructor() { + public constructor(options?: Partial) { super(); this.ipAddress = "0.0.0.0"; this.port = Ports.DEFAULT; - this.clientsByPort = []; - this.connectionsByPort = []; + this.options = Object.assign({}, consoleConnectionOptions, options); } /** @@ -95,23 +102,14 @@ export class ConsoleConnection extends EventEmitter implements Connection { public connect(ip: string, port: number, timeout = DEFAULT_CONNECTION_TIMEOUT_MS): void { this.ipAddress = ip; this.port = port; - - if (port === Ports.LEGACY || port === Ports.DEFAULT) { - // Connect to both legacy and default in case somebody accidentally set it - // and they would encounter issues with the new Nintendont - this._connectOnPort(Ports.DEFAULT, timeout); - this._connectOnPort(Ports.LEGACY, timeout); - } else { - // If port is manually set, use that port. - this._connectOnPort(port, timeout); - } + this._connectOnPort(ip, port, timeout); } - private _connectOnPort(port: number, timeout: number): void { + private _connectOnPort(ip: string, port: number, timeout: number): void { // set up reconnect const reconnect = inject(() => net.connect({ - host: this.ipAddress, + host: ip, port: port, timeout: timeout, }), @@ -133,13 +131,15 @@ export class ConsoleConnection extends EventEmitter implements Connection { failAfter: Infinity, }, (client) => { - this.clientsByPort[port] = client; + // We successfully connected so turn on auto-reconnect + this.shouldReconnect = this.options.autoReconnect; + this.client = client; let commState: CommunicationState = CommunicationState.INITIAL; client.on("data", (data) => { if (commState === CommunicationState.INITIAL) { commState = this._getInitialCommState(data); - console.log(`Connected to ${this.ipAddress}:${this.port} with type: ${commState}`); + console.log(`Connected to ${ip}:${port} with type: ${commState}`); this._setStatus(ConnectionStatus.CONNECTED); console.log(data.toString("hex")); } @@ -177,13 +177,15 @@ export class ConsoleConnection extends EventEmitter implements Connection { client.on("timeout", () => { // const previouslyConnected = this.connectionStatus === ConnectionStatus.CONNECTED; - console.warn(`Attempted connection to ${this.ipAddress}:${this.port} timed out after ${timeout}ms`); + console.warn(`Attempted connection to ${ip}:${port} timed out after ${timeout}ms`); client.destroy(); }); client.on("end", () => { console.log("disconnect"); - client.destroy(); + if (!this.shouldReconnect) { + client.destroy(); + } }); client.on("close", () => { @@ -208,18 +210,11 @@ export class ConsoleConnection extends EventEmitter implements Connection { connection.on("reconnect", setConnectingStatus); connection.on("disconnect", () => { - // If one of the connections was successful, we no longer need to try connecting this one - this.connectionsByPort.forEach((iConn, iPort) => { - if (iPort === port || !iConn.connected) { - // Only disconnect if a different connection was connected - return; - } - - // Prevent reconnections and disconnect + if (!this.shouldReconnect) { connection.reconnect = false; connection.disconnect(); - }); - + this._setStatus(ConnectionStatus.DISCONNECTED); + } // TODO: Figure out how to set RECONNECT_WAIT state here. Currently it will stay on // TODO: Connecting... forever }); @@ -228,8 +223,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { console.error(`Connection on port ${port} encountered an error.`, error); }); - this.connectionsByPort[port] = connection; - console.log("Starting connection"); + this.connection = connection; connection.connect(port); } @@ -237,18 +231,16 @@ export class ConsoleConnection extends EventEmitter implements Connection { * Terminate the current connection. */ public disconnect(): void { - console.log("Disconnect request"); - - this.connectionsByPort.forEach((connection) => { - // Prevent reconnections and disconnect - connection.reconnect = false; // eslint-disable-line - connection.disconnect(); - }); + // Prevent reconnections and disconnect + if (this.connection) { + this.connection.reconnect = false; + this.connection.disconnect(); + this.connection = null; + } - this.clientsByPort.forEach((client) => { - client.destroy(); - }); - this._setStatus(ConnectionStatus.DISCONNECTED); + if (this.client) { + this.client.destroy(); + } } private _getInitialCommState(data: Buffer): CommunicationState { From fc989b957fcb254253e2a347c16baeaa3ee4593b Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 22 Sep 2020 18:53:43 +1000 Subject: [PATCH 10/26] Add connect and message events --- src/console/consoleConnection.ts | 6 ++++-- src/console/types.ts | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/console/consoleConnection.ts b/src/console/consoleConnection.ts index b686cd9c..6fc9ee02 100644 --- a/src/console/consoleConnection.ts +++ b/src/console/consoleConnection.ts @@ -97,7 +97,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { * @param ip The IP address of the Wii or Slippi relay. * @param port The port to connect to. * @param timeout Optional. The timeout in milliseconds when attempting to connect - * to the Wii or relay. Default: 5000. + * to the Wii or relay. */ public connect(ip: string, port: number, timeout = DEFAULT_CONNECTION_TIMEOUT_MS): void { this.ipAddress = ip; @@ -131,6 +131,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { failAfter: Infinity, }, (client) => { + this.emit(ConnectionEvent.CONNECT); // We successfully connected so turn on auto-reconnect this.shouldReconnect = this.options.autoReconnect; this.client = client; @@ -203,7 +204,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { const setConnectingStatus = (): void => { // Indicate we are connecting - this._setStatus(ConnectionStatus.CONNECTING); + this._setStatus(this.shouldReconnect ? ConnectionStatus.RECONNECT_WAIT : ConnectionStatus.CONNECTING); }; connection.on("connect", setConnectingStatus); @@ -256,6 +257,7 @@ export class ConsoleConnection extends EventEmitter implements Connection { } private _processMessage(message: CommunicationMessage): void { + this.emit(ConnectionEvent.MESSAGE, message); switch (message.type) { case CommunicationType.KEEP_ALIVE: // console.log("Keep alive message received"); diff --git a/src/console/types.ts b/src/console/types.ts index d910d7e9..c7053c30 100644 --- a/src/console/types.ts +++ b/src/console/types.ts @@ -1,6 +1,8 @@ import { EventEmitter } from "events"; export enum ConnectionEvent { + CONNECT = "connect", + MESSAGE = "message", HANDSHAKE = "handshake", STATUS_CHANGE = "statusChange", DATA = "data", From 6d802b417b86ef534aa35c6578d517086f312426 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 22 Sep 2020 19:07:46 +1000 Subject: [PATCH 11/26] Add connect and message events to DolphinConnection --- src/console/dolphinConnection.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index e52ca5e1..02660ae1 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -78,6 +78,7 @@ export class DolphinConnection extends EventEmitter implements Connection { } newPeer.ping(); + this.emit(ConnectionEvent.CONNECT); this._setStatus(ConnectionStatus.CONNECTED); }, ); @@ -102,6 +103,7 @@ export class DolphinConnection extends EventEmitter implements Connection { } const message = JSON.parse(data.toString("ascii")); + this.emit(ConnectionEvent.MESSAGE, message); switch (message.type) { case MessageType.CONNECT_REPLY: this.connectionStatus = ConnectionStatus.CONNECTED; From af0467f049b69cbf421e6f3d9b058ec0c3ddc6fe Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 22 Sep 2020 20:28:30 +1000 Subject: [PATCH 12/26] Export Dolphin message types --- src/console/dolphinConnection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index 02660ae1..574fc029 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -5,7 +5,7 @@ import { Connection, ConnectionStatus, ConnectionSettings, ConnectionDetails, Po const MAX_PEERS = 32; -enum MessageType { +export enum DolphinMessageType { CONNECT_REPLY = "connect_reply", GAME_EVENT = "game_event", } @@ -105,14 +105,14 @@ export class DolphinConnection extends EventEmitter implements Connection { const message = JSON.parse(data.toString("ascii")); this.emit(ConnectionEvent.MESSAGE, message); switch (message.type) { - case MessageType.CONNECT_REPLY: + case DolphinMessageType.CONNECT_REPLY: this.connectionStatus = ConnectionStatus.CONNECTED; this.gameCursor = message.cursor; this.nickname = message.nick; this.version = message.version; this.emit(ConnectionEvent.HANDSHAKE, this.getDetails()); break; - case MessageType.GAME_EVENT: + case DolphinMessageType.GAME_EVENT: const { payload, cursor, next_cursor } = message; if (!payload) { // We got a disconnection request From b2374fb45acea66bb53fa25e2655a914681662dc Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 30 Sep 2020 09:35:37 +1000 Subject: [PATCH 13/26] Fix lint issues --- src/console/dolphinConnection.ts | 4 ++-- src/declarations.d.ts | 8 ++++++-- src/utils/slpFile.ts | 4 ++++ src/utils/slpParser.ts | 1 + src/utils/slpStream.ts | 3 ++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index 574fc029..0d60bc4e 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -113,7 +113,7 @@ export class DolphinConnection extends EventEmitter implements Connection { this.emit(ConnectionEvent.HANDSHAKE, this.getDetails()); break; case DolphinMessageType.GAME_EVENT: - const { payload, cursor, next_cursor } = message; + const { payload, cursor } = message; if (!payload) { // We got a disconnection request this.disconnect(); @@ -125,7 +125,7 @@ export class DolphinConnection extends EventEmitter implements Connection { } const gameData = Buffer.from(payload, "base64"); - this.gameCursor = next_cursor; + this.gameCursor = message.next_cursor; this._handleReplayData(gameData); break; } diff --git a/src/declarations.d.ts b/src/declarations.d.ts index c37949b8..8c050785 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -1,10 +1,12 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + declare module "enet" { import { EventEmitter } from "events"; export const PACKET_FLAG: any; export class Packet { - constructor(data: string | Buffer, flag: any); - data(): Buffer; + public constructor(data: string | Buffer, flag: any); + public data(): Buffer; } export interface Peer extends EventEmitter { ping(): void; @@ -23,3 +25,5 @@ declare module "enet" { } export function createClient(args: ClientArguments, callback: (err: Error, client: Host) => void): Host; } + +/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/src/utils/slpFile.ts b/src/utils/slpFile.ts index 8ab004b4..47d821d3 100644 --- a/src/utils/slpFile.ts +++ b/src/utils/slpFile.ts @@ -135,7 +135,11 @@ export class SlpFile extends Writable { this.on("finish", () => { // Update file with bytes written const fd = fs.openSync(this.filePath, "r+"); + + // Not sure why writeSync isn't defined on fs so just ignore the lint warning for now + // eslint-disable-next-line @typescript-eslint/no-explicit-any (fs as any).writeSync(fd, createUInt32Buffer(this.rawDataLength), 0, "binary", 11); + fs.closeSync(fd); // Unsubscribe from the stream diff --git a/src/utils/slpParser.ts b/src/utils/slpParser.ts index b7f57433..3ff40e30 100644 --- a/src/utils/slpParser.ts +++ b/src/utils/slpParser.ts @@ -49,6 +49,7 @@ export class SlpParser extends EventEmitter { this.options = Object.assign({}, defaultSlpParserOptions, options); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public handleCommand(command: Command, payload: any): void { switch (command) { case Command.GAME_START: diff --git a/src/utils/slpStream.ts b/src/utils/slpStream.ts index e5cd2f05..c924f2f6 100644 --- a/src/utils/slpStream.ts +++ b/src/utils/slpStream.ts @@ -65,6 +65,7 @@ export class SlpStream extends Writable { this.payloadSizes = null; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public _write(newData: Buffer, encoding: string, callback: (error?: Error | null, data?: any) => void): void { if (encoding !== "buffer") { throw new Error(`Unsupported stream encoding. Expected 'buffer' got '${encoding}'.`); @@ -154,7 +155,7 @@ export class SlpStream extends Writable { // Fetch the payload and parse it let payload: Uint8Array; - let parsedPayload: any; + let parsedPayload: EventPayloadTypes | null = null; if (payloadSize > 0) { payload = this._writeCommand(command, entirePayload, payloadSize); parsedPayload = parseMessage(command, payload); From d7eab86ccac627a6e0adbcb8531c36143e4d55a4 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 30 Sep 2020 09:35:51 +1000 Subject: [PATCH 14/26] 5.1.0-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae674c69..2cc236a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slippi/slippi-js", - "version": "5.0.5", + "version": "5.1.0-beta.0", "description": "Official Project Slippi Javascript SDK", "license": "LGPL-3.0-or-later", "repository": "project-slippi/slippi-js", From d51d5ed50f663a737c3a6bb42eed418437d52553 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 6 Oct 2020 17:30:26 +1100 Subject: [PATCH 15/26] Add start and end game events --- src/console/dolphinConnection.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index 0d60bc4e..2b93478f 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -8,6 +8,8 @@ const MAX_PEERS = 32; export enum DolphinMessageType { CONNECT_REPLY = "connect_reply", GAME_EVENT = "game_event", + START_GAME = "start_game", + END_GAME = "end_game", } export class DolphinConnection extends EventEmitter implements Connection { From 457ec0230d29fcab7fbf927052e4fd6a7e431c14 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 6 Oct 2020 17:39:29 +1100 Subject: [PATCH 16/26] Allow forcefully ending SLP files in the SlpFileWriter --- src/utils/slpFileWriter.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils/slpFileWriter.ts b/src/utils/slpFileWriter.ts index 420b4f66..14a21a7b 100644 --- a/src/utils/slpFileWriter.ts +++ b/src/utils/slpFileWriter.ts @@ -101,6 +101,16 @@ export class SlpFileWriter extends SlpStream { return null; } + /** + * Ends the current file being written to. + * + * @returns {(string | null)} + * @memberof SlpFileWriter + */ + public endCurrentFile(): void { + this._handleEndGame(); + } + /** * Updates the settings to be the desired ones passed in. * From a335eca9326ce3d48a43474cad4ead853f329a7b Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 6 Oct 2020 23:13:27 +1100 Subject: [PATCH 17/26] Remove payload sizes check when starting new games. Though technically it's more "correct" to have this check, it's not actually necessary. But by removing this check, it's more fault-tolerant since it allows us to better handle games which don't have game end events. Since not having game end events is more common now (e.g. when players force kill their Dolphin), we're removing this check for better fault tolerance, potentially at the expense of correctness. --- src/utils/slpStream.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/utils/slpStream.ts b/src/utils/slpStream.ts index c924f2f6..8926efb5 100644 --- a/src/utils/slpStream.ts +++ b/src/utils/slpStream.ts @@ -138,7 +138,7 @@ export class SlpStream extends Writable { private _processCommand(command: Command, entirePayload: Uint8Array, dataView: DataView): number { // Handle the message size command - if (command === Command.MESSAGE_SIZES && this.payloadSizes === null) { + if (command === Command.MESSAGE_SIZES) { const payloadSize = dataView.getUint8(0); // Set the payload sizes this.payloadSizes = processReceiveCommands(dataView); @@ -169,9 +169,6 @@ export class SlpStream extends Writable { // Stop parsing data until we manually restart the stream if (this.settings.mode === SlpStreamMode.MANUAL) { this.gameEnded = true; - } else { - // We're in auto-mode so reset the payload sizes for the next game - this.payloadSizes = null; } break; } From 500442ea349add2ab6cb8e1cad6747e6e23f4294 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Tue, 6 Oct 2020 23:22:01 +1100 Subject: [PATCH 18/26] 5.1.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2cc236a2..2fb7b5ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slippi/slippi-js", - "version": "5.1.0-beta.0", + "version": "5.1.0-beta.1", "description": "Official Project Slippi Javascript SDK", "license": "LGPL-3.0-or-later", "repository": "project-slippi/slippi-js", From e8f472a79ab992b42005e475fa07ff15ac145117 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 7 Oct 2020 10:15:10 +1100 Subject: [PATCH 19/26] Emit error events in DolphinConnection --- src/console/dolphinConnection.ts | 9 +++++++-- src/console/types.ts | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/console/dolphinConnection.ts b/src/console/dolphinConnection.ts index 2b93478f..6da8428c 100644 --- a/src/console/dolphinConnection.ts +++ b/src/console/dolphinConnection.ts @@ -104,7 +104,8 @@ export class DolphinConnection extends EventEmitter implements Connection { return; } - const message = JSON.parse(data.toString("ascii")); + const dataString = data.toString("ascii"); + const message = JSON.parse(dataString); this.emit(ConnectionEvent.MESSAGE, message); switch (message.type) { case DolphinMessageType.CONNECT_REPLY: @@ -123,7 +124,11 @@ export class DolphinConnection extends EventEmitter implements Connection { } if (this.gameCursor !== cursor) { - throw new Error(`Unexpected game data cursor. Expected: ${this.gameCursor} but got: ${cursor}`); + const err = new Error( + `Unexpected game data cursor. Expected: ${this.gameCursor} but got: ${cursor}. Payload: ${dataString}`, + ); + console.error(err); + this.emit(ConnectionEvent.ERROR, err); } const gameData = Buffer.from(payload, "base64"); diff --git a/src/console/types.ts b/src/console/types.ts index c7053c30..f0ffa1c3 100644 --- a/src/console/types.ts +++ b/src/console/types.ts @@ -6,6 +6,7 @@ export enum ConnectionEvent { HANDSHAKE = "handshake", STATUS_CHANGE = "statusChange", DATA = "data", + ERROR = "error", } export enum ConnectionStatus { From 3a0050a98e481b9d34f086372134065e3202bce9 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 7 Oct 2020 10:25:53 +1100 Subject: [PATCH 20/26] Utilise the error event for console connections too --- src/console/consoleConnection.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/console/consoleConnection.ts b/src/console/consoleConnection.ts index 6fc9ee02..cfeafce2 100644 --- a/src/console/consoleConnection.ts +++ b/src/console/consoleConnection.ts @@ -155,13 +155,13 @@ export class ConsoleConnection extends EventEmitter implements Connection { try { consoleComms.receive(data); } catch (err) { - console.warn("Failed to process new data from server...", { + console.error("Failed to process new data from server...", { error: err, prevDataBuf: consoleComms.getReceiveBuffer(), rcvData: data, }); client.destroy(); - + this.emit(ConnectionEvent.ERROR, err); return; } const messages = consoleComms.getMessages(); @@ -171,8 +171,9 @@ export class ConsoleConnection extends EventEmitter implements Connection { messages.forEach((message) => this._processMessage(message)); } catch (err) { // Disconnect client to send another handshake message - client.destroy(); console.error(err); + client.destroy(); + this.emit(ConnectionEvent.ERROR, err); } }); @@ -274,14 +275,10 @@ export class ConsoleConnection extends EventEmitter implements Connection { const readPos = Uint8Array.from(message.payload.pos); const cmp = Buffer.compare(this.connDetails.gameDataCursor as Uint8Array, readPos); if (!message.payload.forcePos && cmp !== 0) { - console.warn( - "Position of received data is not what was expected. Expected, Received:", - this.connDetails.gameDataCursor, - readPos, - ); - // The readPos is not the one we are waiting on, throw error - throw new Error("Position of received data is incorrect."); + throw new Error( + `Position of received data is incorrect. Expected: ${this.connDetails.gameDataCursor.toString()}, Received: ${readPos.toString()}`, + ); } if (message.payload.forcePos) { From 2f426c7b8a2a526794fd3bfc1fe2705562bd2d3f Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 7 Oct 2020 10:26:46 +1100 Subject: [PATCH 21/26] 5.1.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fb7b5ee..b5fdb765 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slippi/slippi-js", - "version": "5.1.0-beta.1", + "version": "5.1.0-beta.2", "description": "Official Project Slippi Javascript SDK", "license": "LGPL-3.0-or-later", "repository": "project-slippi/slippi-js", From 7529f80034759069df2cd729d446f3018022a1c8 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 7 Oct 2020 17:36:22 +1100 Subject: [PATCH 22/26] Consolidate SlpFileWriterOptions --- src/utils/slpFileWriter.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/utils/slpFileWriter.ts b/src/utils/slpFileWriter.ts index 14a21a7b..2d549c08 100644 --- a/src/utils/slpFileWriter.ts +++ b/src/utils/slpFileWriter.ts @@ -13,7 +13,7 @@ function getNewFilePath(folder: string, m: Moment): string { return path.join(folder, `Game_${m.format("YYYYMMDD")}T${m.format("HHmmss")}.slp`); } -export interface SlpFileWriterOptions { +export interface SlpFileWriterOptions extends Partial { outputFiles: boolean; folderPath: string; consoleNickname: string; @@ -49,12 +49,8 @@ export class SlpFileWriter extends SlpStream { /** * Creates an instance of SlpFileWriter. */ - public constructor( - options?: Partial, - slpOptions?: Partial, - opts?: WritableOptions, - ) { - super(slpOptions, opts); + public constructor(options?: Partial, opts?: WritableOptions) { + super(options, opts); this.options = Object.assign({}, defaultSettings, options); this._setupListeners(); } From d961f917c296f07b02f13584996a1684bd87d801 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Wed, 7 Oct 2020 17:44:06 +1100 Subject: [PATCH 23/26] 5.1.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5fdb765..9431599a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slippi/slippi-js", - "version": "5.1.0-beta.2", + "version": "5.1.0-beta.3", "description": "Official Project Slippi Javascript SDK", "license": "LGPL-3.0-or-later", "repository": "project-slippi/slippi-js", From 8c1465eac92cf92b085eef5edb6de6803310b1e3 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Thu, 22 Oct 2020 18:53:59 +1100 Subject: [PATCH 24/26] 5.1.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e22f4af..2d265188 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slippi/slippi-js", - "version": "5.1.0-beta.3", + "version": "5.1.0-beta.4", "description": "Official Project Slippi Javascript SDK", "license": "LGPL-3.0-or-later", "repository": "project-slippi/slippi-js", From 95b76a8c246bc8b908307b3a69afb58aec0b4d89 Mon Sep 17 00:00:00 2001 From: Vince Au Date: Thu, 26 Nov 2020 15:54:39 +1100 Subject: [PATCH 25/26] Add melee enum types --- src/melee/characters.ts | 54 ++++++++-------- src/melee/index.ts | 2 + src/melee/stages.ts | 135 +++++++++++++++++++--------------------- src/melee/types.ts | 61 ++++++++++++++++++ 4 files changed, 156 insertions(+), 96 deletions(-) create mode 100644 src/melee/types.ts diff --git a/src/melee/characters.ts b/src/melee/characters.ts index 64770b76..668f96b3 100644 --- a/src/melee/characters.ts +++ b/src/melee/characters.ts @@ -1,3 +1,5 @@ +import { Character } from "./types"; + export type CharacterColor = string; export interface CharacterInfo { @@ -9,157 +11,157 @@ export interface CharacterInfo { const externalCharacters: CharacterInfo[] = [ { - id: 0, + id: Character.CAPTAIN_FALCON, name: "Captain Falcon", shortName: "Falcon", colors: ["Default", "Black", "Red", "White", "Green", "Blue"], }, { - id: 1, + id: Character.DONKEY_KONG, name: "Donkey Kong", shortName: "DK", colors: ["Default", "Black", "Red", "Blue", "Green"], }, { - id: 2, + id: Character.FOX, name: "Fox", shortName: "Fox", colors: ["Default", "Red", "Blue", "Green"], }, { - id: 3, + id: Character.GAME_AND_WATCH, name: "Mr. Game & Watch", shortName: "G&W", colors: ["Default", "Red", "Blue", "Green"], }, { - id: 4, + id: Character.KIRBY, name: "Kirby", shortName: "Kirby", colors: ["Default", "Yellow", "Blue", "Red", "Green", "White"], }, { - id: 5, + id: Character.BOWSER, name: "Bowser", shortName: "Bowser", colors: ["Default", "Red", "Blue", "Black"], }, { - id: 6, + id: Character.LINK, name: "Link", shortName: "Link", colors: ["Default", "Red", "Blue", "Black", "White"], }, { - id: 7, + id: Character.LUIGI, name: "Luigi", shortName: "Luigi", colors: ["Default", "White", "Blue", "Red"], }, { - id: 8, + id: Character.MARIO, name: "Mario", shortName: "Mario", colors: ["Default", "Yellow", "Black", "Blue", "Green"], }, { - id: 9, + id: Character.MARTH, name: "Marth", shortName: "Marth", colors: ["Default", "Red", "Green", "Black", "White"], }, { - id: 10, + id: Character.MEWTWO, name: "Mewtwo", shortName: "Mewtwo", colors: ["Default", "Red", "Blue", "Green"], }, { - id: 11, + id: Character.NESS, name: "Ness", shortName: "Ness", colors: ["Default", "Yellow", "Blue", "Green"], }, { - id: 12, + id: Character.PEACH, name: "Peach", shortName: "Peach", colors: ["Default", "Daisy", "White", "Blue", "Green"], }, { - id: 13, + id: Character.PIKACHU, name: "Pikachu", shortName: "Pikachu", colors: ["Default", "Red", "Party Hat", "Cowboy Hat"], }, { - id: 14, + id: Character.ICE_CLIMBERS, name: "Ice Climbers", shortName: "ICs", colors: ["Default", "Green", "Orange", "Red"], }, { - id: 15, + id: Character.JIGGLYPUFF, name: "Jigglypuff", shortName: "Puff", colors: ["Default", "Red", "Blue", "Headband", "Crown"], }, { - id: 16, + id: Character.SAMUS, name: "Samus", shortName: "Samus", colors: ["Default", "Pink", "Black", "Green", "Purple"], }, { - id: 17, + id: Character.YOSHI, name: "Yoshi", shortName: "Yoshi", colors: ["Default", "Red", "Blue", "Yellow", "Pink", "Cyan"], }, { - id: 18, + id: Character.ZELDA, name: "Zelda", shortName: "Zelda", colors: ["Default", "Red", "Blue", "Green", "White"], }, { - id: 19, + id: Character.SHEIK, name: "Sheik", shortName: "Sheik", colors: ["Default", "Red", "Blue", "Green", "White"], }, { - id: 20, + id: Character.FALCO, name: "Falco", shortName: "Falco", colors: ["Default", "Red", "Blue", "Green"], }, { - id: 21, + id: Character.YOUNG_LINK, name: "Young Link", shortName: "YLink", colors: ["Default", "Red", "Blue", "White", "Black"], }, { - id: 22, + id: Character.DR_MARIO, name: "Dr. Mario", shortName: "Doc", colors: ["Default", "Red", "Blue", "Green", "Black"], }, { - id: 23, + id: Character.ROY, name: "Roy", shortName: "Roy", colors: ["Default", "Red", "Blue", "Green", "Yellow"], }, { - id: 24, + id: Character.PICHU, name: "Pichu", shortName: "Pichu", colors: ["Default", "Red", "Blue", "Green"], }, { - id: 25, + id: Character.GANONDORF, name: "Ganondorf", shortName: "Ganon", colors: ["Default", "Red", "Blue", "Green", "Purple"], diff --git a/src/melee/index.ts b/src/melee/index.ts index 0f036fe4..cf9825ce 100644 --- a/src/melee/index.ts +++ b/src/melee/index.ts @@ -3,4 +3,6 @@ import * as characters from "./characters"; import * as moves from "./moves"; import * as stages from "./stages"; +export * from "./types"; + export { animations, characters, moves, stages }; diff --git a/src/melee/stages.ts b/src/melee/stages.ts index 818571f9..ab0b7a4e 100644 --- a/src/melee/stages.ts +++ b/src/melee/stages.ts @@ -1,139 +1,134 @@ -export interface Stage { +import { Stage } from "./types"; + +export interface StageInfo { id: number; name: string; } -const stages: { [id: number]: Stage } = { - 2: { - id: 2, +const stages: { [id: number]: StageInfo } = { + [Stage.FOUNTAIN_OF_DREAMS]: { + id: Stage.FOUNTAIN_OF_DREAMS, name: "Fountain of Dreams", }, - 3: { - id: 3, + [Stage.POKEMON_STADIUM]: { + id: Stage.POKEMON_STADIUM, name: "Pokémon Stadium", }, - 4: { - id: 4, + [Stage.PEACHS_CASTLE]: { + id: Stage.PEACHS_CASTLE, name: "Princess Peach's Castle", }, - 5: { - id: 5, + [Stage.KONGO_JUNGLE]: { + id: Stage.KONGO_JUNGLE, name: "Kongo Jungle", }, - 6: { - id: 6, + [Stage.BRINSTAR]: { + id: Stage.BRINSTAR, name: "Brinstar", }, - 7: { - id: 7, + [Stage.CORNERIA]: { + id: Stage.CORNERIA, name: "Corneria", }, - 8: { - id: 8, + [Stage.YOSHIS_STORY]: { + id: Stage.YOSHIS_STORY, name: "Yoshi's Story", }, - 9: { - id: 9, + [Stage.ONETT]: { + id: Stage.ONETT, name: "Onett", }, - 10: { - id: 10, + [Stage.MUTE_CITY]: { + id: Stage.MUTE_CITY, name: "Mute City", }, - 11: { - id: 11, + [Stage.RAINBOW_CRUISE]: { + id: Stage.RAINBOW_CRUISE, name: "Rainbow Cruise", }, - 12: { - id: 12, + [Stage.JUNGLE_JAPES]: { + id: Stage.JUNGLE_JAPES, name: "Jungle Japes", }, - 13: { - id: 13, + [Stage.GREAT_BAY]: { + id: Stage.GREAT_BAY, name: "Great Bay", }, - 14: { - id: 14, + [Stage.HYRULE_TEMPLE]: { + id: Stage.HYRULE_TEMPLE, name: "Hyrule Temple", }, - 15: { - id: 15, + [Stage.BRINSTAR_DEPTHS]: { + id: Stage.BRINSTAR_DEPTHS, name: "Brinstar Depths", }, - 16: { - id: 16, + [Stage.YOSHIS_ISLAND]: { + id: Stage.YOSHIS_ISLAND, name: "Yoshi's Island", }, - 17: { - id: 17, + [Stage.GREEN_GREENS]: { + id: Stage.GREEN_GREENS, name: "Green Greens", }, - 18: { - id: 18, + [Stage.FOURSIDE]: { + id: Stage.FOURSIDE, name: "Fourside", }, - 19: { - id: 19, + [Stage.MUSHROOM_KINGDOM]: { + id: Stage.MUSHROOM_KINGDOM, name: "Mushroom Kingdom I", }, - 20: { - id: 20, + [Stage.MUSHROOM_KINGDOM_2]: { + id: Stage.MUSHROOM_KINGDOM_2, name: "Mushroom Kingdom II", }, - 22: { - id: 22, + [Stage.VENOM]: { + id: Stage.VENOM, name: "Venom", }, - 23: { - id: 23, + [Stage.POKE_FLOATS]: { + id: Stage.POKE_FLOATS, name: "Poké Floats", }, - 24: { - id: 24, + [Stage.BIG_BLUE]: { + id: Stage.BIG_BLUE, name: "Big Blue", }, - 25: { - id: 25, + [Stage.ICICLE_MOUNTAIN]: { + id: Stage.ICICLE_MOUNTAIN, name: "Icicle Mountain", }, - 26: { - id: 26, + [Stage.ICETOP]: { + id: Stage.ICETOP, name: "Icetop", }, - 27: { - id: 27, + [Stage.FLAT_ZONE]: { + id: Stage.FLAT_ZONE, name: "Flat Zone", }, - 28: { - id: 28, + [Stage.DREAMLAND]: { + id: Stage.DREAMLAND, name: "Dream Land N64", }, - 29: { - id: 29, + [Stage.YOSHIS_ISLAND_N64]: { + id: Stage.YOSHIS_ISLAND_N64, name: "Yoshi's Island N64", }, - 30: { - id: 30, + [Stage.KONGO_JUNGLE_N64]: { + id: Stage.KONGO_JUNGLE_N64, name: "Kongo Jungle N64", }, - 31: { - id: 31, + [Stage.BATTLEFIELD]: { + id: Stage.BATTLEFIELD, name: "Battlefield", }, - 32: { - id: 32, + [Stage.FINAL_DESTINATION]: { + id: Stage.FINAL_DESTINATION, name: "Final Destination", }, }; -export const STAGE_FOD = 2; -export const STAGE_POKEMON = 3; -export const STAGE_YOSHIS = 8; -export const STAGE_DREAM_LAND = 28; -export const STAGE_BATTLEFIELD = 31; -export const STAGE_FD = 32; - -export function getStageInfo(stageId: number): Stage { +export function getStageInfo(stageId: number): StageInfo { const s = stages[stageId]; if (!s) { throw new Error(`Invalid stage with id ${stageId}`); diff --git a/src/melee/types.ts b/src/melee/types.ts new file mode 100644 index 00000000..9373b09d --- /dev/null +++ b/src/melee/types.ts @@ -0,0 +1,61 @@ +export enum Character { + CAPTAIN_FALCON = 0, + DONKEY_KONG = 1, + FOX = 2, + GAME_AND_WATCH = 3, + KIRBY = 4, + BOWSER = 5, + LINK = 6, + LUIGI = 7, + MARIO = 8, + MARTH = 9, + MEWTWO = 10, + NESS = 11, + PEACH = 12, + PIKACHU = 13, + ICE_CLIMBERS = 14, + JIGGLYPUFF = 15, + SAMUS = 16, + YOSHI = 17, + ZELDA = 18, + SHEIK = 19, + FALCO = 20, + YOUNG_LINK = 21, + DR_MARIO = 22, + ROY = 23, + PICHU = 24, + GANONDORF = 25, +} + +export enum Stage { + FOUNTAIN_OF_DREAMS = 2, + POKEMON_STADIUM = 3, + PEACHS_CASTLE = 4, + KONGO_JUNGLE = 5, + BRINSTAR = 6, + CORNERIA = 7, + YOSHIS_STORY = 8, + ONETT = 9, + MUTE_CITY = 10, + RAINBOW_CRUISE = 11, + JUNGLE_JAPES = 12, + GREAT_BAY = 13, + HYRULE_TEMPLE = 14, + BRINSTAR_DEPTHS = 15, + YOSHIS_ISLAND = 16, + GREEN_GREENS = 17, + FOURSIDE = 18, + MUSHROOM_KINGDOM = 19, + MUSHROOM_KINGDOM_2 = 20, + VENOM = 22, + POKE_FLOATS = 23, + BIG_BLUE = 24, + ICICLE_MOUNTAIN = 25, + ICETOP = 26, + FLAT_ZONE = 27, + DREAMLAND = 28, + YOSHIS_ISLAND_N64 = 29, + KONGO_JUNGLE_N64 = 30, + BATTLEFIELD = 31, + FINAL_DESTINATION = 32, +} From 58faf756a7661285fc6b3a18240d5fe19988166b Mon Sep 17 00:00:00 2001 From: Vince Au Date: Thu, 26 Nov 2020 15:55:39 +1100 Subject: [PATCH 26/26] 5.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d265188..58074f57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slippi/slippi-js", - "version": "5.1.0-beta.4", + "version": "5.1.0", "description": "Official Project Slippi Javascript SDK", "license": "LGPL-3.0-or-later", "repository": "project-slippi/slippi-js",