import { type EntityId } from "../../core/types"; import { type ComponentType } from "./components"; /** * Game events for cross-system communication. * Systems can emit and subscribe to these events to react to gameplay changes. */ export type GameEvent = // Combat events | { type: "damage"; entityId: EntityId; amount: number; source?: EntityId } | { type: "heal"; entityId: EntityId; amount: number; source?: EntityId } | { type: "death"; entityId: EntityId; killedBy?: EntityId } // Component lifecycle events | { type: "component_added"; entityId: EntityId; componentType: ComponentType } | { type: "component_removed"; entityId: EntityId; componentType: ComponentType } | { type: "entity_created"; entityId: EntityId } | { type: "entity_destroyed"; entityId: EntityId } // Movement & trigger events | { type: "stepped_on"; entityId: EntityId; x: number; y: number } | { type: "trigger_activated"; triggerId: EntityId; activatorId: EntityId } // Status effect events | { type: "status_applied"; entityId: EntityId; status: string; duration: number } | { type: "status_expired"; entityId: EntityId; status: string } | { type: "status_tick"; entityId: EntityId; status: string; remaining: number } // World events | { type: "tile_changed"; x: number; y: number }; export type GameEventType = GameEvent["type"]; type EventHandler = (event: T) => void; /** * Lightweight event bus for cross-system communication. * Enables reactive gameplay like status effects, triggers, and combat feedback. */ export class EventBus { private listeners: Map> = new Map(); private onceListeners: Map> = new Map(); private eventQueue: GameEvent[] = []; /** * Subscribe to events of a specific type. * @returns Unsubscribe function */ on( eventType: T, handler: EventHandler> ): () => void { if (!this.listeners.has(eventType)) { this.listeners.set(eventType, new Set()); } this.listeners.get(eventType)!.add(handler as EventHandler); // Return unsubscribe function return () => { this.listeners.get(eventType)?.delete(handler as EventHandler); }; } /** * Subscribe to a single occurrence of an event type. * The handler is automatically removed after being called once. */ once( eventType: T, handler: EventHandler> ): void { if (!this.onceListeners.has(eventType)) { this.onceListeners.set(eventType, new Set()); } this.onceListeners.get(eventType)!.add(handler as EventHandler); } /** * Emit an event to all registered listeners. */ emit(event: GameEvent): void { const eventType = event.type; // Call regular listeners const handlers = this.listeners.get(eventType); if (handlers) { for (const handler of handlers) { handler(event); } } // Call once listeners and remove them const onceHandlers = this.onceListeners.get(eventType); if (onceHandlers) { for (const handler of onceHandlers) { handler(event); } this.onceListeners.delete(eventType); } // Add to queue for drain() this.eventQueue.push(event); } /** * Drain all queued events and return them. * Clears the internal queue. */ drain(): GameEvent[] { const events = [...this.eventQueue]; this.eventQueue = []; return events; } /** * Remove all listeners for a specific event type. */ off(eventType: GameEventType): void { this.listeners.delete(eventType); this.onceListeners.delete(eventType); } /** * Remove all listeners for all event types. */ clear(): void { this.listeners.clear(); this.onceListeners.clear(); } /** * Check if there are any listeners for a specific event type. */ hasListeners(eventType: GameEventType): boolean { return ( (this.listeners.get(eventType)?.size ?? 0) > 0 || (this.onceListeners.get(eventType)?.size ?? 0) > 0 ); } } // Singleton instance for global event bus (optional - can also create instances per world) let globalEventBus: EventBus | null = null; export function getEventBus(): EventBus { if (!globalEventBus) { globalEventBus = new EventBus(); } return globalEventBus; } export function resetEventBus(): void { globalEventBus?.clear(); globalEventBus = null; }