import type { BattleServices } from 'db://assets/mx/module/battle/combat/BattleServices'; import type { Cell } from 'db://assets/mx/module/battle/battlefield/Cell'; import type { EntityId } from 'db://assets/mx/module/battle/ecs/EntityId'; import type { UnitDef } from 'db://assets/mx/module/battle/config/BattleConfigTypes'; import { TransformComp, TeamComp, UnitMetaComp, HealthComp, CombatComp, AbilitySetComp, TagComp, MovementComp, GridOccupancyComp, } from '../ecs/Components'; import { BattleTeamId } from '../ecs/BattleTeamId'; import { traceCombatDamage } from 'db://assets/mx/tools/CombatDamageTrace'; import { finitePositiveHp } from 'db://assets/mx/tools/FiniteHp'; /** * 单位工厂:按 UnitDef 创建 ECS 实体并挂到 GridMap / SpatialGrid。 */ export class UnitFactory { /** * @param services 战斗服务 * @param def 单位定义 * @param ownerId 所属/进攻目标玩家 ID * @param anchor 锚点格 * @param cells 占地格 * @returns 实体 ID */ static create( services: BattleServices, def: UnitDef, ownerId: number, anchor: Cell, cells: Cell[], ): EntityId { const world = services.world; const id = world.spawn(); const teamId = def.team === 'player' ? ownerId : BattleTeamId.enemyForPlayer(ownerId); world.addComponent(id, new TransformComp(anchor.row, anchor.col)); world.addComponent(id, new TeamComp(teamId, ownerId)); world.addComponent(id, new UnitMetaComp(def.id, 1)); if (def.components.health) { const maxHp = finitePositiveHp(def.components.health.maxHp, 1); world.addComponent(id, new HealthComp(maxHp, maxHp)); traceCombatDamage('unitSpawnHealth', { entity: id, unitDefId: def.id, maxHp }); } if (def.components.combat) { const c = def.components.combat; world.addComponent(id, new CombatComp( c.attack ?? 10, c.attackSpeed ?? 1, c.range ?? 3, c.critRate ?? 0, c.critDamage ?? 1.5, )); } if (def.components.abilities?.length) { world.addComponent(id, new AbilitySetComp([...def.components.abilities])); } if (def.components.tags) { world.addComponent(id, new TagComp([...def.components.tags])); } if (def.components.movement) { const w = services.grid.cellToWorld(anchor); world.addComponent(id, new MovementComp( def.components.movement.speed, [], 0, w.x, w.y, false, )); } const blocksPath = def.components.occupancy?.blocksPath ?? false; world.addComponent(id, new GridOccupancyComp( anchor, cells.map(cell => ({ ...cell })), blocksPath, )); services.grid.occupy(cells, id); if (blocksPath) { let walkChanged = false; for (const cell of cells) { if (services.grid.isWalkable(cell)) walkChanged = true; services.grid.setWalkable(cell, false); } // 占地挡路改了可走性却未 invalidate 时,距离场会一直用旧障碍 if (walkChanged) services.pathfinder.invalidate(); } services.spatial.updateEntity(id, anchor); if (world.hasComponent(id, CombatComp)) { services.attributes.syncCombatToAttributes(id); } if (world.hasComponent(id, AbilitySetComp)) { services.abilities.bindEntity(id); } return id; } }