import { type ECSWorld } from "./World"; import { type ComponentMap } from "./components"; import { type EntityId, type Stats, type EnemyAIState, type ActorType } from "../../core/types"; import { GAME_CONFIG } from "../../core/config/GameConfig"; /** * Fluent builder for creating ECS entities with components. * Makes entity creation declarative and easy to extend. * * @example * // Create a simple trap * EntityBuilder.create(world) * .withPosition(5, 10) * .asTrap(15) * .build(); * * @example * // Create an enemy * EntityBuilder.create(world) * .withPosition(3, 7) * .asEnemy("rat") * .build(); */ export class EntityBuilder { private world: ECSWorld; private entityId: EntityId; private components: Partial<{ [K in keyof ComponentMap]: ComponentMap[K] }> = {}; private constructor(world: ECSWorld) { this.world = world; this.entityId = world.createEntity(); } /** * Start building a new entity. */ static create(world: ECSWorld): EntityBuilder { return new EntityBuilder(world); } /** * Add a position component. */ withPosition(x: number, y: number): this { this.components.position = { x, y }; return this; } /** * Add a name component. */ withName(name: string): this { this.components.name = { name }; return this; } /** * Add a sprite component. */ withSprite(texture: string, index: number): this { this.components.sprite = { texture, index }; return this; } /** * Add stats component with partial stats (fills defaults). */ withStats(stats: Partial): this { const defaultStats: Stats = { maxHp: 10, hp: 10, maxMana: 0, mana: 0, attack: 1, defense: 0, level: 1, exp: 0, expToNextLevel: 10, critChance: 0, critMultiplier: 100, accuracy: 100, lifesteal: 0, evasion: 0, blockChance: 0, luck: 0, statPoints: 0, skillPoints: 0, strength: 10, dexterity: 10, intelligence: 10, passiveNodes: [] }; this.components.stats = { ...defaultStats, ...stats }; return this; } /** * Add energy component for turn scheduling. */ withEnergy(speed: number, current: number = 0): this { this.components.energy = { current, speed }; return this; } /** * Add AI component for enemy behavior. */ withAI(state: EnemyAIState = "wandering"): this { this.components.ai = { state }; return this; } /** * Add player tag component. */ asPlayer(): this { this.components.player = {}; this.components.actorType = { type: "player" }; return this; } /** * Configure as an enemy with stats from GameConfig. */ asEnemy(type: ActorType): this { if (type === "player") { throw new Error("Use asPlayer() for player entities"); } this.components.actorType = { type }; this.withAI("wandering"); // Apply enemy stats from config const config = GAME_CONFIG.enemies[type as keyof typeof GAME_CONFIG.enemies]; if (config) { const speed = config.minSpeed + Math.random() * (config.maxSpeed - config.minSpeed); this.withStats({ maxHp: config.baseHp, hp: config.baseHp, attack: config.baseAttack, defense: config.baseDefense }); this.withEnergy(speed); } return this; } /** * Configure as a trap that deals damage when stepped on. */ asTrap(damage: number, oneShot: boolean = false): this { this.components.trigger = { onEnter: true, oneShot, damage }; return this; } /** * Configure as a trigger zone (pressure plate, etc). */ asTrigger(options: { onEnter?: boolean; onExit?: boolean; oneShot?: boolean; effect?: string; effectDuration?: number; }): this { this.components.trigger = { onEnter: options.onEnter ?? true, onExit: options.onExit, oneShot: options.oneShot, effect: options.effect, effectDuration: options.effectDuration }; return this; } /** * Configure as a destructible object. */ asDestructible(hp: number, maxHp?: number, options?: { destroyedTile?: number; lootTable?: string; }): this { this.components.destructible = { hp, maxHp: maxHp ?? hp, destroyedTile: options?.destroyedTile, lootTable: options?.lootTable }; return this; } /** * Configure as a collectible (exp orb, etc). */ asCollectible(type: "exp_orb", amount: number): this { this.components.collectible = { type, amount }; return this; } /** * Configure as an item on the ground. */ asGroundItem(itemId: string, quantity: number = 1): this { this.components.groundItem = { itemId, quantity }; return this; } /** * Add initial status effects. */ withStatusEffects(effects: ComponentMap["statusEffects"]["effects"]): this { this.components.statusEffects = { effects }; return this; } /** * Add combat tracking component. */ withCombat(): this { this.components.combat = {}; return this; } /** * Add a raw component directly. */ with(type: K, data: ComponentMap[K]): this { this.components[type] = data; return this; } /** * Finalize and register all components with the ECS world. * @returns The created entity ID */ build(): EntityId { for (const [type, data] of Object.entries(this.components)) { if (data !== undefined) { this.world.addComponent(this.entityId, type as keyof ComponentMap, data as any); } } return this.entityId; } /** * Get the entity ID (even before build is called). */ getId(): EntityId { return this.entityId; } }