UNPKG

@game-vir/multiplayer

Version:

Client side definitions and types for @game-vir/multiplayer-server

343 lines (342 loc) 14.5 kB
import { type JsonCompatibleValue, type MaybePromise, type Uuid } from '@augment-vir/common'; import { type mapServiceDevPort } from '@rest-vir/define-service'; import { type AnyDuration } from 'date-vir'; import { ListenTarget } from 'typed-event-target'; import { type RoomInput } from '../webrtc/webrtc-multiplayer-controller.js'; import { RoomRejectionError } from './errors.js'; import { LockStepGameStateController } from './lock-step-controller.js'; import { type MultiplayerApi } from './multiplayer-api.js'; /** * Connection state for {@link MultiplayerController}. * * @category Internal */ export declare enum MultiplayerConnectionState { Connecting = "connecting", Connected = "connected", /** The connection has not been started or has been gracefully terminated. */ Disconnected = "disconnected" } /** * Service and room connection state for {@link MultiplayerController}. * * @category Internal */ export type ServiceAndRoomConnectionState = { service: MultiplayerConnectionState | Error; room: MultiplayerConnectionState | Error; }; /** * Empty or totally disconnected state for {@link ServiceAndRoomConnectionState}. * * @category Internal */ export declare const emptyServiceAndRoomConnectionState: Readonly<ServiceAndRoomConnectionState>; /** * Constructor parameters for {@link MultiplayerController}. * * @category Internal */ export type MultiplayerControllerParams<Action extends JsonCompatibleValue> = { /** * Unique string id that represents your game. Your backend will need to know this game id and * match it to your frontend's origin. */ gameId: string; /** * This is fired when a WebRTC peer attempts to connect to the host client (this will only be * fired if your client is the host). Return `true` to accept the connection. Return `false` to * reject it. * * @default accept all connections */ acceptConnection?: ((connectingClientId: Uuid, controller: MultiplayerController<Action>) => MaybePromise<boolean>) | undefined; /** * The duration between each frame. This should probably always be smaller than your supported * render frame duration (1/FPS). If this is set to `undefined`, you'll need to manually trigger * frames with `MultiplayerController.runFrame`. */ frameDuration?: AnyDuration | undefined; }; /** * Multiplayer mode parameters for {@link MultiplayerController}. * * @category Internal */ export type MultiplayerParams = { /** * Set to `undefined` or `false` to disable port scanning. Set to `true` to enable port * scanning. Set to an options object to configure port scanning. * * It is useful to enable this so that clients can find the port that your multiplayer server is * running on in case it must change. Note that port scanning will not be active if your * `serviceOrigin` does not contain a port. * * @default undefined */ portScanOptions?: undefined | Parameters<typeof mapServiceDevPort>[1] | boolean; /** * The origin of the server running the multiplayer connection service. * * @example 'http://localhost:3000' */ backendOrigin: string; /** * How long to wait before fetching the list of rooms again. * * @default {seconds: 10} */ roomUpdateInterval?: AnyDuration | undefined; /** * Optional stun server URLs to help with routing WebRTC connections. This is entirely optional, * but might help with clients attempting to establish connections to each other. */ stunServerUrls?: ReadonlyArray<string> | undefined; }; declare const ControllerFrameEvent_base: (new (eventInitDict: { bubbles?: boolean; cancelable?: boolean; composed?: boolean; detail: any; }) => import("typed-event-target").TypedCustomEvent<any, "controller-frame">) & Pick<{ new (type: string, eventInitDict?: EventInit): Event; prototype: Event; readonly NONE: 0; readonly CAPTURING_PHASE: 1; readonly AT_TARGET: 2; readonly BUBBLING_PHASE: 3; }, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<any, "controller-frame">, "type">; /** * This is fired whenever a new frame is received from the host client. * * @category Events */ export declare class ControllerFrameEvent<Action extends JsonCompatibleValue> extends ControllerFrameEvent_base { detail: ReadonlyArray<Action>; } declare const ControllerRoomListEvent_base: (new (eventInitDict: { bubbles?: boolean; cancelable?: boolean; composed?: boolean; detail: Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, { roomName: string; roomId: `${string}-${string}-${string}-${string}-${string}`; clientCount: number; hasRoomPassword: boolean; }>>>; }) => import("typed-event-target").TypedCustomEvent<Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, { roomName: string; roomId: `${string}-${string}-${string}-${string}-${string}`; clientCount: number; hasRoomPassword: boolean; }>>>, "controller-room-list">) & Pick<{ new (type: string, eventInitDict?: EventInit): Event; prototype: Event; readonly NONE: 0; readonly CAPTURING_PHASE: 1; readonly AT_TARGET: 2; readonly BUBBLING_PHASE: 3; }, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, { roomName: string; roomId: `${string}-${string}-${string}-${string}-${string}`; clientCount: number; hasRoomPassword: boolean; }>>>, "controller-room-list">, "type">; /** * This is called whenever the room list updates, even if there were no changes to the room list. * Note that room list updates are paused while the controller is connected to an actual room. * * @category Events */ export declare class ControllerRoomListEvent extends ControllerRoomListEvent_base { } declare const ControllerClientEvent_base: (new (eventInitDict: { bubbles?: boolean; cancelable?: boolean; composed?: boolean; detail: Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{ newHost: Uuid; newMember: Uuid; lostHost: Uuid; lostMember: Uuid; }, "newHost" | "newMember" | "lostHost" | "lostMember">>; }) => import("typed-event-target").TypedCustomEvent<Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{ newHost: Uuid; newMember: Uuid; lostHost: Uuid; lostMember: Uuid; }, "newHost" | "newMember" | "lostHost" | "lostMember">>, "controller-client">) & Pick<{ new (type: string, eventInitDict?: EventInit): Event; prototype: Event; readonly NONE: 0; readonly CAPTURING_PHASE: 1; readonly AT_TARGET: 2; readonly BUBBLING_PHASE: 3; }, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{ newHost: Uuid; newMember: Uuid; lostHost: Uuid; lostMember: Uuid; }, "newHost" | "newMember" | "lostHost" | "lostMember">>, "controller-client">, "type">; /** * This is fired in the following situations: * * - A new host for the room was selected * - The room host was lost * - A new room client was added (only fired on the host client) * - A room client was lost (only fired on the host client) * * @category Events */ export declare class ControllerClientEvent extends ControllerClientEvent_base { } declare const ControllerConnectionEvent_base: (new (eventInitDict: { bubbles?: boolean; cancelable?: boolean; composed?: boolean; detail: ServiceAndRoomConnectionState; }) => import("typed-event-target").TypedCustomEvent<ServiceAndRoomConnectionState, "controller-connection">) & Pick<{ new (type: string, eventInitDict?: EventInit): Event; prototype: Event; readonly NONE: 0; readonly CAPTURING_PHASE: 1; readonly AT_TARGET: 2; readonly BUBBLING_PHASE: 3; }, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<ServiceAndRoomConnectionState, "controller-connection">, "type">; /** * Fires when the controller's connection state is updated. * * @category Events */ export declare class ControllerConnectionEvent extends ControllerConnectionEvent_base { } /** * All events emitted by this controller. * * @category Internal */ export type AllMultiplayerControllerEvents<Action extends JsonCompatibleValue> = ControllerFrameEvent<Action> | ControllerRoomListEvent | ControllerClientEvent | ControllerConnectionEvent; /** * An all-in-one controller for singleplayer or lock-step multiplayer game state. Singleplayer mode * requires no servers. Multiplayer mode requires a backend service running the * {@link MultiplayerApi}. * * @category Main */ export declare class MultiplayerController<Action extends JsonCompatibleValue = any> extends ListenTarget<AllMultiplayerControllerEvents<Action>> { protected readonly params: MultiplayerControllerParams<Action>; /** All events emitted by this controller. */ static readonly events: { ControllerFrameEvent: typeof ControllerFrameEvent; ControllerRoomListEvent: typeof ControllerRoomListEvent; ControllerClientEvent: typeof ControllerClientEvent; ControllerConnectionEvent: typeof ControllerConnectionEvent; }; /** All events emitted by this controller. */ readonly events: { ControllerFrameEvent: typeof ControllerFrameEvent; ControllerRoomListEvent: typeof ControllerRoomListEvent; ControllerClientEvent: typeof ControllerClientEvent; ControllerConnectionEvent: typeof ControllerConnectionEvent; }; static readonly knownErrors: { RoomRejectionError: typeof RoomRejectionError; }; readonly knownErrors: { RoomRejectionError: typeof RoomRejectionError; }; /** * Set to `false` to disable room updates, even when still not connected to a room in * multiplayer mode. */ enableRoomUpdates: boolean; /** Currently joined room id. If a room has not been joined yet, this will be empty. */ readonly roomId: Uuid | undefined; /** The current connection state of the controller's connection to a backend service. */ readonly serviceConnectionState: ServiceAndRoomConnectionState['service']; /** The current connection state of the controller's connection to a multiplayer room. */ readonly roomConnectionState: ServiceAndRoomConnectionState['room']; /** * Current WebRTC lock step connection with the room host (when not the host) or all room * participants (when the host). This will only be initialized after calling * {@link MultiplayerController.joinOrCreateRoom}. */ currentConnection: LockStepGameStateController | undefined; /** * Rooms that have rejected the current player, so the player doesn't keep trying to connect to * them. */ protected rejectedRoomIds: Set<`${string}-${string}-${string}-${string}-${string}`>; /** The current MultiplayerApi. This will be `undefined` if playing in single player. */ multiplayerApi: Promise<MultiplayerApi> | undefined; /** * Used to keep track of the room update interval. This will be set when the controller is * constructed in multiplayer mode or when a room is left. This will be cleared when a room is * joined or if the controller is destroyed. */ protected roomUpdateIntervalId: ReturnType<typeof globalThis.setInterval> | undefined; /** This is populated if `.startMultiplayer` is called. */ protected multiplayerParams: Readonly<MultiplayerParams> | undefined; /** * Get the current client's WebRTC client id. This will return `undefined` if there is no * current connection. */ getClientId(): Uuid | undefined; /** * Get all connected client ids. * * - For host clients, this will indicate how many member clients are connected to the host * client, _not_ including the host itself. * - For non-host clients, this will only list the host's client. * * For host clients, this does ont include the host client id whereas * {@link MultiplayerController.getAllClientIds} does. */ getConnectedClientIds(): Uuid[]; /** * Get all room client ids. * * - For host clients, this will indicate how many clients are connected to the room, including * the host client itself. * - For non-host clients, this will only list the host's client. * * For host clients, this includes the host client id whereas * {@link MultiplayerController.getConnectedClientIds} does not. */ getAllClientIds(): Uuid[]; constructor(params: MultiplayerControllerParams<Action>); /** * Start multiplayer mode. This initializes {@link MultiplayerController.multiplayerApi} and * {@link MultiplayerController.roomUpdateIntervalId}. */ startMultiplayer(params: Readonly<MultiplayerParams>): void; /** Start singleplayer mode. */ startSingleplayer(): void; /** * Manually run the next frame. * * @throws Error if `frameDuration` has been set. */ runFrame(actions?: ReadonlyArray<Action> | undefined): void; /** The current FPS of the data flow. */ getFps(): number; /** Fire an action. This will be sent to all clients in the room so they can process it. */ act(actions: Action | ReadonlyArray<Action>): void; /** Detects if this controller is the room host or not. */ isHost(): boolean; /** Cleanup everything. */ destroy(): void; /** * Join or create a room. * * @throws `Error` if this controller is already connected to a room. */ joinOrCreateRoom(room: Readonly<RoomInput>): Promise<void>; /** Leave the current room or single player connection. */ leaveRoom(): void; /** Set the current connection state and fire listeners. */ protected updateConnectionState(state: Partial<ServiceAndRoomConnectionState>): void; /** Starts polling the multiplayer server for room updates and fires listeners. */ protected startRoomInterval(): void; } export {};