feat: Add traps
This commit is contained in:
152
src/engine/ecs/EventBus.ts
Normal file
152
src/engine/ecs/EventBus.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
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 };
|
||||
|
||||
export type GameEventType = GameEvent["type"];
|
||||
|
||||
type EventHandler<T extends GameEvent = GameEvent> = (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<string, Set<EventHandler>> = new Map();
|
||||
private onceListeners: Map<string, Set<EventHandler>> = new Map();
|
||||
private eventQueue: GameEvent[] = [];
|
||||
|
||||
/**
|
||||
* Subscribe to events of a specific type.
|
||||
* @returns Unsubscribe function
|
||||
*/
|
||||
on<T extends GameEventType>(
|
||||
eventType: T,
|
||||
handler: EventHandler<Extract<GameEvent, { type: T }>>
|
||||
): () => 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<T extends GameEventType>(
|
||||
eventType: T,
|
||||
handler: EventHandler<Extract<GameEvent, { type: T }>>
|
||||
): 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;
|
||||
}
|
||||
Reference in New Issue
Block a user