/** * State Machine Plugin for ECSpresso * * Provides ECS-native finite state machines with guard-based transitions, * event-driven transitions, and lifecycle hooks (onEnter, onExit, onUpdate). * * Each entity gets a `stateMachine` component referencing a shared definition. * One system processes all state machine entities each tick. */ import { type BasePluginOptions } from 'ecspresso'; import type { BaseWorld } from 'ecspresso'; /** BaseWorld narrowed to state-machine components for typed access in helpers. */ type StateMachineWorld = BaseWorld; /** * Configuration for a single state in a state machine definition. * * @template S - Union of state name strings * @template W - World interface type for hooks/guards (default: StateMachineWorld) */ export interface StateConfig = StateMachineWorld> { /** Called when entering this state */ onEnter?(ctx: { ecs: W; entityId: number; }): void; /** Called when exiting this state */ onExit?(ctx: { ecs: W; entityId: number; }): void; /** Called each tick while in this state */ onUpdate?(ctx: { ecs: W; entityId: number; dt: number; }): void; /** Guard-based transitions evaluated each tick. First passing guard wins. */ transitions?: ReadonlyArray<{ target: S; guard(ctx: { ecs: W; entityId: number; }): boolean; }>; /** Event-based transition map: eventName → target state or guarded transition */ on?: Record; } /** * Immutable definition of a state machine. Shared across entities. * * @template S - Union of state name strings */ export interface StateMachineDefinition { readonly id: string; readonly initial: S; readonly states: { readonly [K in S]: StateConfig; }; } /** * Runtime state machine component stored on each entity. * * @template S - Union of state name strings (default: string) */ export interface StateMachine { readonly definition: StateMachineDefinition; current: S; previous: S | null; stateTime: number; } /** * Component types provided by the state machine plugin. * * @template S - Union of state name strings (default: string) */ export interface StateMachineComponentTypes { stateMachine: StateMachine; } /** * Event published on every state transition. * * @template S - Union of state name strings (default: string) */ export interface StateTransitionEvent { entityId: number; from: S; to: S; definitionId: string; } /** * Event types provided by the state machine plugin. * * @template S - Union of state name strings (default: string) */ export interface StateMachineEventTypes { stateTransition: StateTransitionEvent; } /** * Extract the state name union from a StateMachineDefinition. * * @example * ```typescript * const enemyFSM = defineStateMachine('enemy', { initial: 'idle', states: { idle: {}, chase: {} } }); * type EnemyStates = StatesOf; // 'idle' | 'chase' * type AllStates = StatesOf | StatesOf; * ``` */ export type StatesOf = D extends StateMachineDefinition ? S : never; /** * Configuration options for the state machine plugin. */ export interface StateMachinePluginOptions extends BasePluginOptions { } /** * Define a state machine with type-safe state names. * * @template S - Union of state name strings, inferred from `states` keys * @param id - Unique identifier for this definition * @param config - Initial state and state configurations * @returns A frozen StateMachineDefinition * * @example * ```typescript * const enemyFSM = defineStateMachine('enemy', { * initial: 'idle', * states: { * idle: { * onEnter: ({ ecs, entityId }) => { ... }, * transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }], * }, * chase: { * onUpdate: ({ ecs, entityId, dt }) => { ... }, * on: { playerLost: 'idle' }, * }, * }, * }); * ``` */ export declare function defineStateMachine(id: string, config: { initial: NoInfer; states: Record>>; }): StateMachineDefinition; /** * Create a stateMachine component from a definition. * * @param definition - The state machine definition to use * @param options - Optional overrides (e.g., initial state) * @returns Component object suitable for spreading into spawn() * * @example * ```typescript * ecs.spawn({ * ...createStateMachine(enemyFSM), * position: { x: 100, y: 200 }, * }); * ``` */ export declare function createStateMachine(definition: StateMachineDefinition, options?: { initial?: S; }): Pick, 'stateMachine'>; /** * Directly transition an entity's state machine to a target state. * Fires onExit, onEnter hooks and publishes stateTransition event. * * @param ecs - ECS instance (structural typing) * @param entityId - Entity to transition * @param targetState - State to transition to * @returns true if transition succeeded, false if entity has no stateMachine or target state doesn't exist */ export declare function transitionTo(ecs: StateMachineWorld, entityId: number, targetState: string): boolean; /** * Send a named event to an entity's state machine. * Checks the current state's `on` handlers for a matching event. * * @param ecs - ECS instance (structural typing) * @param entityId - Entity to send event to * @param eventName - Event name to match against `on` handlers * @returns true if a transition occurred, false otherwise */ export declare function sendEvent(ecs: StateMachineWorld, entityId: number, eventName: string): boolean; /** * Get the current state of an entity's state machine. * * @param ecs - ECS instance (structural typing) * @param entityId - Entity to query * @returns The current state string, or undefined if entity has no stateMachine */ export declare function getStateMachineState(ecs: StateMachineWorld, entityId: number): string | undefined; /** * Typed helpers for the state machine plugin. * Creates helpers that validate hook parameters against the world type W. * Call after .build() using typeof ecs. */ export interface StateMachineHelpers> { defineStateMachine: (id: string, config: { initial: NoInfer; states: Record, W>>; }) => StateMachineDefinition; } export declare function createStateMachineHelpers = StateMachineWorld>(_world?: W): StateMachineHelpers; /** * Create a state machine plugin for ECSpresso. * * Provides: * - Lifecycle hooks (onEnter, onExit, onUpdate) per state * - Guard-based automatic transitions evaluated each tick * - Event-based transitions via `sendEvent()` * - Direct transitions via `transitionTo()` * - stateTransition events published on every transition * * @example * ```typescript * const ecs = ECSpresso.create() * .withPlugin(createStateMachinePlugin()) * .build(); * * const fsm = defineStateMachine('enemy', { * initial: 'idle', * states: { * idle: { * transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }], * }, * chase: { * onUpdate: ({ ecs, entityId, dt }) => moveTowardPlayer(ecs, entityId, dt), * on: { playerLost: 'idle' }, * }, * }, * }); * * ecs.spawn({ * ...createStateMachine(fsm), * position: { x: 0, y: 0 }, * }); * ``` */ export declare function createStateMachinePlugin(options?: StateMachinePluginOptions): import("ecspresso").Plugin>, StateMachineEventTypes>, import("ecspresso").EmptyConfig, "state-machine-update", G, never, never>; export {};