import { Trigger, ControlsDirective } from 'canvasengine'; import { AbstractWebsocket } from './services/AbstractSocket'; import { I18nParams, RpgRoomDescriptor, RpgActionInput, RpgActionName, RpgDashInput, RpgMovementInput } from '@rpgjs/common'; import { EventComponentConfig } from './RpgClient'; import { RpgClientEvent } from './Game/Event'; import { RpgClientMap } from './Game/Map'; import { AnimationManager } from './Game/AnimationManager'; import { Observable } from 'rxjs'; import { ProjectileManager } from './Game/ProjectileManager'; import { ClientVisualRegistry, ClientVisualHandler, ClientVisualMap, ClientVisualPacket } from './Game/ClientVisuals'; import { ClientPointerContext } from './services/pointerContext'; import { RpgClientInteractions } from './services/interactions'; import { EventComponentResolver } from './Game/EventComponentResolver'; import { CameraFollowSmoothMove } from './services/cameraFollow'; import { RpgMusicManager } from './Game/MusicManager'; import { RpgClientRoom } from './services/gameplayRooms'; import { RpgAudioChannel, RpgPlaySoundOptions } from './Game/AudioManager'; import * as PIXI from "pixi.js"; export type { CameraFollowEase, CameraFollowSmoothMove, CameraFollowSmoothMoveOptions, } from './services/cameraFollow'; type ConfigurableTrigger = Omit, "start"> & { start(config?: T): Promise; }; type MapShakeOptions = { intensity?: number; duration?: number; frequency?: number; direction?: string; }; export declare class RpgClientEngine { context: any; /** Runtime defaults used by modules that specialize the built-in dash. */ dashDefaults: Partial; private guiService; private webSocket; private loadMapService; private hooks; private sceneMap; /** Synchronized state for the active non-map gameplay room. */ readonly sceneRoom: RpgClientRoom>; /** Authoritative descriptor of the active room. */ readonly activeRoom: import('canvasengine').WritableSignal; /** Active room kind; `map` keeps the historical map pipeline enabled. */ readonly activeSceneKind: import('canvasengine').WritableSignal; /** CanvasEngine component selected for a custom gameplay room. */ readonly activeRoomSceneComponent: import('canvasengine').WritableArraySignal; private readonly clientSceneRegistry; private activeClientScene?; private selector; globalConfig: T; sceneComponent: any; sceneMapComponent: any; stopProcessingInput: boolean; width: import('canvasengine').WritableSignal; height: import('canvasengine').WritableSignal; spritesheets: Map; private spritesheetPromises; sounds: Map; /** Internal mixer behind the established sound API. */ private readonly audio; /** Client-only controller for temporary looping music and map BGM crossfades. */ music: RpgMusicManager; componentAnimations: any[]; /** Custom weather components by id, registered with the `weathers` module option. */ weatherComponents: Map; clientVisuals: ClientVisualRegistry; projectiles: ProjectileManager; /** * Read the latest pointer position tracked by the client canvas. World * coordinates are suitable for action payloads and map interactions. * * @title pointer * @prop pointer: ClientPointerContext * @memberof RpgClientEngine * @example * ```ts * const target = engine.pointer.world() * if (target) engine.processAction('projectile:shoot', { target }) * ``` */ pointer: ClientPointerContext; /** * Register client-only pointer behaviors for map sprites. Interactions remain * local unless a behavior explicitly sends an action to the server. * * See the [client interactions guide](../../guide/interactions.md) for hover, * selection, hit testing, drag-and-drop, overlays, and network rules. * * @title interactions * @prop interactions: RpgClientInteractions * @memberof RpgClientEngine * @example * ```ts * engine.interactions.use('Guard', { * cursor: 'pointer', * click(ctx) { * ctx.action('guard:talk', { eventId: ctx.target.id }) * } * }) * ``` */ interactions: RpgClientInteractions; private spritesheetResolver?; private soundResolver?; particleSettings: { emitters: any[]; }; renderer: PIXI.Renderer; tick: Observable; private canvasApp?; private canvasElement?; playerIdSignal: import('canvasengine').WritableSignal; spriteComponentsBehind: import('canvasengine').WritableArraySignal; spriteComponentsInFront: import('canvasengine').WritableArraySignal; spriteComponents: Map; private eventComponentResolvers; /** ID of the sprite that the camera should follow. null means follow the current player */ cameraFollowTargetId: import('canvasengine').WritableSignal; /** Camera follow transition options used by character components when the target changes */ cameraFollowSmoothMove: CameraFollowSmoothMove; /** Render the camera on whole pixels so tilemaps do not shimmer with a fractional camera. Set to false for a fully fluid camera. */ cameraPixelSnap: boolean; /** Incremented for each camera follow command so repeated commands on the same target are applied */ cameraFollowRevision: import('canvasengine').WritableSignal; /** Trigger for map shake animation */ mapShakeTrigger: ConfigurableTrigger; controlsReady: import('canvasengine').WritableSignal; activeKeyboardControls: import('canvasengine').WritableSignal; gamePause: import('canvasengine').WritableSignal; /** * Freezes map rendering for short presentation-only beats such as combat * hit-stop. It is deliberately separate from menu/gameplay pause ownership. */ visualPause: import('canvasengine').WritableSignal; private predictionEnabled; private prediction?; private readonly SERVER_CORRECTION_THRESHOLD; private localMovementAuthority; private lastLocalMovementInputAt; private readonly LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS; private inputFrameCounter; private pendingPredictionFrames; private lastClientPhysicsStepAt; private frameOffset; private serverTick; private dashLockedUntil; private rtt; private pingInterval; private readonly PING_INTERVAL_MS; private lastInputTime; private latestDirectionalInput?; private pendingMapTransferInput?; private movePath; private mapLoadCompleted$; private playerIdReceived$; private playersReceived$; private eventsReceived$; private onAfterLoadingSubscription?; private sceneResetQueued; private mapTransitionInProgress; private currentMapRoomId?; private activeMapStreamController?; private socketListenersInitialized; private clientReadyForMapChanges; private pendingMapChanges; private pendingRoomChanges; private tickSubscriptions; private resizeHandler?; private pointerMoveHandler?; private pointerUpHandler?; private pointerCancelHandler?; private pointerCanvas?; private pendingSyncPackets; private notificationManager; private i18nService; private locale; private localePreferences; private localeConnected; private connectionPromise?; constructor(context: any); private resolveLocalMovementAuthority; /** * Select and persist a game language, or `auto` for browser negotiation. * Updates client labels and the connected player's future translations. * @title setLocale * @method setLocale * @param locale - Registered game locale or `auto`. * @returns Nothing. * @memberof RpgClientEngine * @example client.setLocale('fr') */ setLocale(locale: string): void; /** * Read the resolved client locale, reactively inside CanvasEngine computations. * Works in standalone RPG and MMORPG modes. * @title getLocale * @method getLocale * @returns The resolved game locale, never `auto`. * @memberof RpgClientEngine * @example client.getLocale() // 'fr' */ getLocale(): string; /** Game catalogue locales only, in both standalone and MMORPG mode. * @title getAvailableLocales * @method getAvailableLocales * @returns Registered game locales; defaultLocale if there are no catalogues. * @memberof RpgClientEngine * @example client.getAvailableLocales() // ['en', 'fr'] */ getAvailableLocales(): string[]; /** Current client preference in standalone RPG and MMORPG modes. * @title getLocalePreference * @method getLocalePreference * @returns Locale preference, reactive in client components. * @memberof RpgClientEngine * @example client.getLocalePreference() // 'auto' */ getLocalePreference(): string; t(key: string, params?: I18nParams): string; i18n(): { locale: string; t: (key: string, params?: I18nParams) => string; }; /** * Registers the current player's live CanvasEngine controls on the client. * * Used automatically when the player component mounts in standalone RPG and * MMORPG modes. Destroyed directives are ignored so a retiring component cannot * overwrite replacement controls during streamed map updates. Input handling * remains client-side; this does not change server movement authority. * * @title setKeyboardControls * @method setKeyboardControls * @param controlInstance - The live CanvasEngine controls directive to register. * @returns Nothing. * @memberof RpgClientEngine * @example * ```ts * // Inside the current player's CanvasEngine mount callback: * client.setKeyboardControls(element.directives.controls) * ``` */ setKeyboardControls(controlInstance: ControlsDirective): void; start(): Promise; /** * Open the initial MMORPG connection after a deferred account flow. * Concurrent calls share the same attempt. Standalone and normal MMORPG starts * call this automatically. * * @title connect * @method connect * @returns A promise resolved after the RPGJS server accepts the connection. * @memberof RpgClientEngine */ connect(): Promise; /** * Close the current physical connection without destroying the rendered client. * Account modules can return to a pre-connection GUI and call `connect()` again. * * @title disconnect * @method disconnect * @returns Nothing. * @memberof RpgClientEngine */ disconnect(): void; private resolveSceneMapComponent; private setupPointerTracking; updatePointerFromInteractionEvent(event: any): void; private findViewportInstance; private clearCameraFollowViewportPlugins; private prepareSyncPayload; private shouldPreserveLocalPlayerPosition; private shouldKeepLocalPlayerMovement; private initListeners; private beginMapTransfer; private resetSceneForMapTransfer; private clearComponentAnimations; private shouldProcessProjectilePacket; private callConnectError; private flushPendingSyncPackets; private flushPendingMapChanges; private flushPendingRoomChanges; private handleChangeMap; private handleChangeRoom; private leaveActiveClientScene; private applySyncPacket; /** * Start periodic ping/pong for client-server synchronization * * Sends ping requests to the server to measure round-trip time (RTT) and * calculate the frame offset between client and server ticks. * * ## Design * * - Sends ping every 5 seconds * - Measures RTT for latency compensation * - Calculates frame offset to map client frames to server ticks * - Used for accurate server reconciliation * * @example * ```ts * // Called automatically when connection opens * this.startPingPong(); * ``` */ private startPingPong; /** * Stop periodic ping/pong * * Stops the ping interval when disconnecting or changing maps. * * @example * ```ts * // Called automatically when connection closes * this.stopPingPong(); * ``` */ private stopPingPong; /** * Send a ping request to the server * * Sends current client time and frame counter to the server, * which will respond with its server tick for synchronization. * * @example * ```ts * // Send a ping to measure RTT * this.sendPing(); * ``` */ private sendPing; private loadScene; private resumeMapTransferMovement; addSpriteSheet(spritesheetClass: any, id?: string): any; /** * Set a resolver function for spritesheets * * The resolver is called when a spritesheet is requested but not found in the cache. * It can be synchronous (returns directly) or asynchronous (returns a Promise). * The resolved spritesheet is automatically cached for future use. * * @param resolver - Function that takes a spritesheet ID and returns a spritesheet or Promise of spritesheet * * @example * ```ts * // Synchronous resolver * engine.setSpritesheetResolver((id) => { * if (id === 'dynamic-sprite') { * return { id: 'dynamic-sprite', image: 'path/to/image.png', framesWidth: 32, framesHeight: 32 }; * } * return undefined; * }); * * // Asynchronous resolver (loading from API) * engine.setSpritesheetResolver(async (id) => { * const response = await fetch(`/api/spritesheets/${id}`); * const data = await response.json(); * return data; * }); * ``` */ setSpritesheetResolver(resolver: (id: string | number) => any | Promise): void; /** * Get a spritesheet by ID, using resolver if not found in cache * * This method first checks if the spritesheet exists in the cache. * If not found and a resolver is set, it calls the resolver to create the spritesheet. * The resolved spritesheet is automatically cached for future use. * * @param id - The spritesheet ID or legacy tile ID to retrieve * @returns The spritesheet if found or created, or undefined if not found and no resolver * @returns Promise if the resolver is asynchronous * * @example * ```ts * // Synchronous usage * const spritesheet = engine.getSpriteSheet('my-sprite'); * * // Asynchronous usage (when resolver returns Promise) * const spritesheet = await engine.getSpriteSheet('dynamic-sprite'); * ``` */ getSpriteSheet(id: string | number): any | Promise; /** * Add a sound to the engine * * Adds a sound to the engine's sound cache. The sound can be: * - A simple object with `id` and `src` properties * - A Howler instance * - An object with a `play()` method * * If the sound has a `src` property, a Howler instance will be created automatically. * * @param sound - The sound object or Howler instance * @param id - Optional sound ID (if not provided, uses sound.id) * @returns The added sound * * @example * ```ts * // Simple sound object * engine.addSound({ id: 'click', src: 'click.mp3' }); * * // With explicit ID * engine.addSound({ src: 'music.mp3' }, 'background-music'); * ``` */ addSound(sound: any, id?: string): any; /** * Set a resolver function for sounds * * The resolver is called when a sound is requested but not found in the cache. * It can be synchronous (returns directly) or asynchronous (returns a Promise). * The resolved sound is automatically cached for future use. * * @param resolver - Function that takes a sound ID and returns a sound or Promise of sound * * @example * ```ts * // Synchronous resolver * engine.setSoundResolver((id) => { * if (id === 'dynamic-sound') { * return { id: 'dynamic-sound', src: 'path/to/sound.mp3' }; * } * return undefined; * }); * * // Asynchronous resolver (loading from API) * engine.setSoundResolver(async (id) => { * const response = await fetch(`/api/sounds/${id}`); * const data = await response.json(); * return data; * }); * ``` */ setSoundResolver(resolver: (id: string) => any | Promise): void; /** * Get a sound by ID, using resolver if not found in cache * * This method first checks if the sound exists in the cache. * If not found and a resolver is set, it calls the resolver to create the sound. * The resolved sound is automatically cached for future use. * * @param id - The sound ID to retrieve * @returns The sound if found or created, or undefined if not found and no resolver * @returns Promise if the resolver is asynchronous * * @example * ```ts * // Synchronous usage * const sound = engine.getSound('my-sound'); * * // Asynchronous usage (when resolver returns Promise) * const sound = await engine.getSound('dynamic-sound'); * ``` */ getSound(id: string): any | Promise; /** * Set the persisted volume of one sound channel for the current project. * Master volume is applied through Howler, including sounds controlled through * the legacy `RpgSound.global` facade. This client-owned preference behaves the * same in standalone and MMORPG games and never changes server state. * * @title setSoundVolume * @method setSoundVolume(channel: RpgAudioChannel, value: number): void * @param {RpgAudioChannel} channel - Channel to update. * @param {number} value - Volume between 0 and 1; out-of-range values are clamped. * @returns {void} * @memberof RpgClientEngine * @example * ```ts * engine.setSoundVolume('music', 0.6) * engine.setSoundVolume('master', 0.8) * ``` */ setSoundVolume(channel: RpgAudioChannel, value: number): void; /** * Read the persisted volume of one sound channel for the current project. * Calls made inside a CanvasEngine computed value remain reactive. * * @title getSoundVolume * @method getSoundVolume(channel: RpgAudioChannel): number * @param {RpgAudioChannel} channel - Channel to read. * @returns {number} Volume between 0 and 1. * @memberof RpgClientEngine * @example * ```ts * const musicVolume = engine.getSoundVolume('music') * ``` */ getSoundVolume(channel: RpgAudioChannel): number; /** * Play a sound by its ID * * This method retrieves a sound from the cache or resolver and plays it. * If the sound is not found, it will attempt to resolve it using the soundResolver. * Uses Howler.js for audio playback instead of native Audio elements. * * The existing API remains the single entry point for ordinary, channel-aware, * and spatial sounds. Playback is client-owned in both standalone and MMORPG * games; server calls only ask the receiving client to play a registered ID. * * @example * ```ts * // Play a sound synchronously * engine.playSound('item-pickup'); * * // Play a sound with volume and loop * engine.playSound('background-music', { volume: 0.5, loop: true }); * * // Play a sound asynchronously (when resolver returns Promise) * await engine.playSound('dynamic-sound', { volume: 0.8 }); * * // Play a spatial sound without exposing CanvasEngine signals * await engine.playSound('enemy-hit', { * channel: 'sfx', * position: { x: enemy.x(), y: enemy.y() }, * listener: { x: player.x(), y: player.y() }, * }); * ``` * @title playSound * @method playSound(soundId: string, options?: RpgPlaySoundOptions): Promise * @param {string} soundId - Registered sound ID or ID handled by the sound resolver. * @param {RpgPlaySoundOptions} [options] - Playback, channel, and spatial options. * @returns {Promise} Resolves after the sound has been resolved and started when available. * @memberof RpgClientEngine */ playSound(soundId: string, options?: RpgPlaySoundOptions): Promise; /** * Stop a sound that is currently playing * * This method stops a sound that was previously started with `playSound()`. * * @param soundId - The sound ID to stop * * @example * ```ts * // Start a looping sound * engine.playSound('background-music', { loop: true }); * * // Later, stop it * engine.stopSound('background-music'); * ``` */ stopSound(soundId: string): void; /** * Stop all currently playing sounds * * This method stops all sounds that are currently playing. * Useful when changing maps to prevent sound overlap. * * @example * ```ts * // Stop all sounds * engine.stopAllSounds(); * ``` */ stopAllSounds(): void; /** * Set the camera to follow a specific sprite * * This method changes which sprite the camera viewport should follow. * The camera can smoothly animate to the target sprite before continuous follow starts. * * ## Design * * The camera follow target is stored in a signal that is read by sprite components. * Each sprite checks if it should be followed by comparing its ID with the target ID. * When smoothMove options are provided, the transition is handled by pixi-viewport's * animation plugin, then continuous follow is handled by CanvasEngine's viewport system. * * @param targetId - The ID of the sprite to follow. Set to null to follow the current player * @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease * @param smoothMove.time - Duration of the animation in milliseconds (optional) * @param smoothMove.ease - Easing function name from https://easings.net (optional) * @param smoothMove.speed - Continuous follow speed after the transition (optional) * @param smoothMove.acceleration - Continuous follow acceleration after the transition (optional) * @param smoothMove.radius - Center radius where the target can move without moving the viewport (optional) * * @example * ```ts * // Follow another player with default smooth animation * engine.setCameraFollow(otherPlayerId, true); * * // Follow an event with custom smooth animation * engine.setCameraFollow(eventId, { * time: 1000, * ease: "easeInOutQuad" * }); * * // Follow without animation (instant) * engine.setCameraFollow(targetId, false); * * // Return to following current player * engine.setCameraFollow(null); * ``` */ setCameraFollow(targetId: string | null, smoothMove?: CameraFollowSmoothMove): void; private resetCameraFollow; addParticle(particle: any): any; /** * Add a component to render behind sprites * Components added with this method will be displayed with a lower z-index than the sprite * * Supports multiple formats: * 1. Direct component: `ShadowComponent` * 2. Configuration object: `{ component: LightHalo, props: {...} }` * 3. With dynamic props: `{ component: LightHalo, props: (object) => {...} }` * 4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }` * * Components with dependencies will only be displayed when all dependencies are resolved (!= undefined). * The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution. * * @param component - The component to add behind sprites, or a configuration object * @param component.component - The component function to render * @param component.props - Static props object or function that receives the sprite object and returns props * @param component.dependencies - Function that receives the sprite object and returns an array of Signals * @returns The added component or configuration * * @example * ```ts * // Add a shadow component behind all sprites * engine.addSpriteComponentBehind(ShadowComponent); * * // Add a component with static props * engine.addSpriteComponentBehind({ * component: LightHalo, * props: { radius: 30 } * }); * * // Add a component with dynamic props and dependencies * engine.addSpriteComponentBehind({ * component: HealthBar, * props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }), * dependencies: (object) => [object.hp, object.param.maxHp] * }); * ``` */ addSpriteComponentBehind(component: any): any; /** * Add a component to render in front of sprites * Components added with this method will be displayed with a higher z-index than the sprite * * Supports multiple formats: * 1. Direct component: `HealthBarComponent` * 2. Configuration object: `{ component: StatusIndicator, props: {...} }` * 3. With dynamic props: `{ component: HealthBar, props: (object) => {...} }` * 4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }` * * Components with dependencies will only be displayed when all dependencies are resolved (!= undefined). * The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution. * * @param component - The component to add in front of sprites, or a configuration object * @param component.component - The component function to render * @param component.props - Static props object or function that receives the sprite object and returns props * @param component.dependencies - Function that receives the sprite object and returns an array of Signals * @returns The added component or configuration * * @example * ```ts * // Add a health bar component in front of all sprites * engine.addSpriteComponentInFront(HealthBarComponent); * * // Add a component with static props * engine.addSpriteComponentInFront({ * component: StatusIndicator, * props: { type: 'poison' } * }); * * // Add a component with dynamic props and dependencies * engine.addSpriteComponentInFront({ * component: HealthBar, * props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }), * dependencies: (object) => [object.hp, object.param.maxHp] * }); * ``` */ addSpriteComponentInFront(component: any | { component: any; props: (object: any) => any; dependencies?: (object: any) => any[]; }): any; /** * Register a reusable sprite component that can be addressed by the server. * * Server-side component definitions only carry the component id and * serializable props. The client registry maps that id to the CanvasEngine * component that performs the actual rendering. * * @param id - Stable component id used by server component definitions * @param component - CanvasEngine component to render for this id * @returns The registered component * * @example * ```ts * engine.registerSpriteComponent('guildBadge', GuildBadgeComponent); * ``` */ registerSpriteComponent(id: string, component: any): any; /** * Get a reusable sprite component by id. * * @param id - Component id registered on the client * @returns The CanvasEngine component, or undefined when missing */ getSpriteComponent(id: string): any; /** * Register a custom event component resolver. * * The last resolver returning a component wins. This lets later modules * override earlier defaults without replacing the whole map scene. * * @param resolver - Function receiving the synced event object * @returns The registered resolver */ addEventComponentResolver(resolver: EventComponentResolver): EventComponentResolver; /** * Resolve the custom CanvasEngine component for an event, if any. * * @param event - Synced client event object * @returns The component/config returned by the last matching resolver */ resolveEventComponent(event: RpgClientEvent): EventComponentConfig | null; registerProjectileComponent(type: string, component: any): any; getProjectileComponent(type: string): any; /** * Register a named client visual macro. * * Client visuals are small client-side functions that group existing visual * primitives such as flash, sound, component animations, sprite animation, or * map shake. The server sends only the visual name and a serializable payload. * * @param name - Stable visual name sent by the server * @param handler - Client-side visual handler * @returns The registered handler */ registerClientVisual(name: string, handler: ClientVisualHandler): ClientVisualHandler; /** * Register several named client visual macros. * * @param visuals - Map of visual names to client-side handlers */ registerClientVisuals(visuals: ClientVisualMap): void; /** * Play a registered client visual locally. * * This is also used by the websocket listener when the server calls * `player.clientVisual()` or `map.clientVisual()`. * * @param packet - Visual name and serializable payload */ playClientVisual(packet: ClientVisualPacket): Promise; /** * Add a component animation to the engine * * Component animations are temporary visual effects that can be displayed * on sprites or objects, such as hit indicators, spell effects, or status animations. * * @param componentAnimation - The component animation configuration * @param componentAnimation.id - Unique identifier for the animation * @param componentAnimation.component - The component function to render * @returns The added component animation configuration * * @example * ```ts * // Add a hit animation component * engine.addComponentAnimation({ * id: 'hit', * component: HitComponent * }); * * // Add an explosion effect component * engine.addComponentAnimation({ * id: 'explosion', * component: ExplosionComponent * }); * ``` */ addComponentAnimation(componentAnimation: { component: any; id: string; }): { component: any; id: string; }; /** * Register a custom weather component. * * When the server sets a weather whose `effect` is this id, the scene renders * the component instead of CanvasEngine's ``. A custom id also * replaces a built-in effect of the same name. * * @param weather - The weather id and its component * @example * ```ts * engine.addWeather({ id: 'aurora', component: AuroraWeather }) * ``` */ addWeather(weather: { id: string; component: any; }): { id: string; component: any; }; /** * Get a component animation by its ID * * Retrieves the EffectManager instance for a specific component animation, * which can be used to display the animation on sprites or objects. * * @param id - The unique identifier of the component animation * @returns The EffectManager instance for the animation * @throws Error if the component animation is not found * * @example * ```ts * // Get the hit animation and display it * const hitAnimation = engine.getComponentAnimation('hit'); * hitAnimation.displayEffect({ text: "Critical!" }, player); * ``` */ getComponentAnimation(id: string): AnimationManager; /** * Start a transition * * Convenience method to display a transition by its ID using the GUI system. * * @param id - The unique identifier of the transition to start * @param props - Props to pass to the transition component * * @example * ```ts * // Start a fade transition * engine.startTransition('fade', { duration: 1000, color: 'black' }); * * // Start with onFinish callback * engine.startTransition('fade', { * duration: 1000, * onFinish: () => console.log('Fade complete') * }); * * // Wait until the transition component calls onFinish * await engine.startTransition('fade', { duration: 1000 }); * ``` */ startTransition(id: string, props?: any): Promise; processInput({ input }: { input: RpgMovementInput; }): Promise; /** * Start a predicted dash for the current player and send it through the * authoritative movement channel. * * @title processDash * @method processDash(input?: Partial): Promise * @param input - Optional direction, speed, duration, and cooldown overrides. * @returns A promise resolved after the dash input has been processed locally. * @memberof RpgClientEngine * @example * ```ts * await engine.processDash({ * direction: { x: 1, y: 0 }, * additionalSpeed: 10, * duration: 220, * cooldown: 600, * }) * ``` */ processDash(input?: Partial): Promise; /** * Send an action intent to the authoritative server. Client-provided data * must be validated by the receiving player input handler or action. * * @title processAction * @method processAction(action: RpgActionName | RpgActionInput, data?: any): void * @param action - Action name/control value, or a normalized action object. * @param data - Optional serializable context sent with an action name. * @returns Nothing. * @memberof RpgClientEngine * @example * ```ts * engine.processAction('projectile:shoot', { * target: engine.pointer.world(), * source: 'map-click', * }) * ``` */ processAction(action: RpgActionName, data?: any): void; processAction(action: RpgActionInput): void; get PIXI(): typeof PIXI; get socket(): AbstractWebsocket; get playerId(): string | null; get scene(): RpgClientMap; getObjectById(id: string): any; private getPhysicsTick; private getPhysicsTickDurationMs; private ensureCurrentPlayerBody; private stepClientPhysicsTick; private flushPendingPredictedStates; private emitMovePacket; private flushPendingMovePath; private applyPredictedMovementInput; private getLocalPlayerState; private resolveHitboxDimension; private resolveObjectHitboxSize; private applyAuthoritativeState; private initializePredictionController; getCurrentPlayer(): import('.').RpgClientPlayer | undefined; /** Return the active map scene or synchronized custom gameplay room. */ getCurrentRoom(): RpgClientMap | RpgClientRoom; emitSceneMapHook(hookName: string, ...args: any[]): void; /** * Setup RxJS observer to wait for all conditions before calling onAfterLoading hook * * This method uses RxJS `combineLatest` to wait for all conditions to be met, * regardless of the order in which they arrive: * 1. The map loading is completed (loadMapService.load is finished) * 2. We received a player ID (pId) * 3. Players array has at least one element * 4. Events property is present in the sync data * * Once all conditions are met, it uses `switchMap` to call the onAfterLoading hook once. * * ## Design * * Uses BehaviorSubjects to track each condition state, allowing events to arrive * in any order. The `combineLatest` operator waits until all observables emit `true`, * then `take(1)` ensures the hook is called only once, and `switchMap` handles * the hook execution. * * @example * ```ts * // Called automatically in loadScene to setup the observer * this.setupOnAfterLoadingObserver(); * ``` */ private setupOnAfterLoadingObserver; /** * Clear client prediction states for cleanup * * Removes old prediction states and input history to prevent memory leaks. * Should be called when changing maps or disconnecting. * * @example * ```ts * // Clear prediction states when changing maps * engine.clearClientPredictionStates(); * ``` */ clearClientPredictionStates(): void; private clearMapTransferPredictionStates; /** * Stop local movement immediately and discard pending predicted movement. * * Use this before a blocking action such as an A-RPG attack, dialog, dash * startup, or any client-side state where already buffered movement inputs * must not be replayed after server reconciliation. * * @param player - Player object to stop. Defaults to the current player. * @returns `true` when a player was found and interrupted. * * @example * ```ts * engine.interruptCurrentPlayerMovement(); * ``` */ interruptCurrentPlayerMovement(player?: any): boolean; /** * Trigger a flash animation on a sprite * * This method allows you to trigger a flash effect on any sprite from client-side code. * The flash can be configured with various options including type (alpha, tint, or both), * duration, cycles, and color. * * ## Design * * The flash is applied directly to the sprite object using its flash trigger. * This is useful for client-side visual feedback, UI interactions, or local effects * that don't need to be synchronized with the server. * * @param spriteId - The ID of the sprite to flash. If not provided, flashes the current player * @param options - Flash configuration options * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha') * @param options.duration - Duration of the flash animation in milliseconds (default: 300) * @param options.cycles - Number of flash cycles (flash on/off) (default: 1) * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3) * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white) * * @example * ```ts * // Flash the current player with default settings * engine.flash(); * * // Flash a specific sprite with red tint * engine.flash('sprite-id', { type: 'tint', tint: 0xff0000 }); * * // Flash with both alpha and tint for dramatic effect * engine.flash(undefined, { * type: 'both', * alpha: 0.5, * tint: 0xff0000, * duration: 200, * cycles: 2 * }); * * // Quick damage flash on current player * engine.flash(undefined, { * type: 'tint', * tint: 'red', * duration: 150, * cycles: 1 * }); * ``` */ flash(spriteId?: string, options?: { type?: 'alpha' | 'tint' | 'both'; duration?: number; cycles?: number; alpha?: number; tint?: number | string; }): void; private applyServerAck; private reconcilePrediction; /** * Replay unacknowledged inputs from a given frame to resimulate client prediction * after applying server authority at a certain frame. * * @param startFrame - The last server-acknowledged frame * * @example * ```ts * // After applying a server correction at frame N * this.replayUnackedInputsFromFrame(N); * ``` */ private replayUnackedInputsFromFrame; /** * Clear all client resources and reset state * * This method should be called to clean up all client-side resources when * shutting down or resetting the client engine. It: * - Destroys the PIXI renderer * - Stops all sounds * - Cleans up subscriptions and event listeners * - Resets scene map * - Stops ping/pong interval * - Clears prediction states * * ## Design * * This method is used primarily in testing environments to ensure clean * state between tests. In production, the client engine typically persists * for the lifetime of the application. * * @example * ```ts * // In test cleanup * afterEach(() => { * clientEngine.clear(); * }); * ``` */ clear(): void; }