import type { World, EntityId } from 'db://assets/mx/module/ecs'; import type { Cell } from 'db://assets/mx/module/battle/battlefield/Cell'; import type { UnitDef } from 'db://assets/mx/module/battle/config/BattleConfigTypes'; import { BattleEventFactory } from 'db://assets/mx/module/battle/events/BattleEvents'; import type { BattleEventBus } from 'db://assets/mx/module/battle/events/BattleEventBus'; import { UnitFactory } from 'db://assets/mx/module/battle/unit/UnitFactory'; import { UnitMetaComp, TeamComp } from 'db://assets/mx/module/battle/ecs/Components'; import type { DeployResult } from './DeployTypes'; import { createDefaultDeployRules, type DeployRule } from './DeployValidator'; import type { IConfigProvider } from '../config/IConfigProvider'; import type { GridMap } from '../battlefield/GridMap'; import type { SpatialGrid } from '../battlefield/SpatialGrid'; import type { PlayerSlot } from '../player/PlayerSlot'; import type { AbilitySystem } from '../combat/AbilitySystem'; import type { BattleServices } from '../combat/BattleServices'; /** 单位放置系统(本项目:塔由背包摆放 + DeploySync 同步,不涉及 deploy 扣金) */ export class DeploySystem { private _rules: DeployRule[]; constructor( private readonly _world: World, private readonly _config: IConfigProvider, private readonly _grid: GridMap, private readonly _spatial: SpatialGrid, private readonly _abilities: AbilitySystem, private readonly _events: BattleEventBus, private readonly _players: PlayerSlot[], private readonly _services: BattleServices, rules: DeployRule[] = createDefaultDeployRules(), ) { this._rules = rules; } /** 替换部署规则链 */ setRules(rules: DeployRule[]): void { this._rules = rules; } /** * @param unitDef 单位定义 * @param anchor 锚点格子 * @returns 占地格子列表 */ resolveCells(unitDef: UnitDef, anchor: Cell): Cell[] { const shape = unitDef.components.occupancy?.shape ?? [[0, 0]]; return shape.map(([dr, dc]) => ({ row: anchor.row + dr, col: anchor.col + dc })); } /** * @param playerId 玩家 ID * @param unitDefId 单位配置 ID * @param anchor 锚点格子 * @returns 是否可部署及原因 */ canPlace(playerId: number, unitDefId: string, anchor: Cell): DeployResult { const def = this._config.getUnit(unitDefId); if (!def) return { ok: false, reason: 'unknown_unit' }; if (def.team !== 'player') return { ok: false, reason: 'not_deployable' }; const cells = this.resolveCells(def, anchor); for (const rule of this._rules) { const r = rule.check(this._services, playerId, unitDefId, cells); if (!r.ok) return r; } return { ok: true, cells }; } /** * @param playerId 玩家 ID * @param unitDefId 单位配置 ID * @param anchor 锚点格子 * @returns 部署成功返回实体 ID,否则 null */ place(playerId: number, unitDefId: string, anchor: Cell): EntityId | null { const check = this.canPlace(playerId, unitDefId, anchor); if (!check.ok || !check.cells) return null; const def = this._config.getUnit(unitDefId)!; const player = this._players[playerId]; const entity = UnitFactory.create(this._services, def, playerId, anchor, check.cells); player.deployedUnits.push(entity); this._abilities.fireTrigger(entity, 'OnDeploy'); this._events.emit(BattleEventFactory.unitPlaced(entity, playerId, anchor)); return entity; } /** * @param playerId 玩家 ID * @param entity 待出售实体 ID * @returns 是否出售成功 */ sell(playerId: number, entity: EntityId): boolean { const player = this._players[playerId]; const world = this._world; const meta = world.getComponent(entity, UnitMetaComp); const team = world.getComponent(entity, TeamComp); if (!meta || team?.ownerId !== playerId) return false; this._grid.clearOccupant(entity); this._spatial.removeEntity(entity); this._abilities.unbindEntity(entity); this._world.destroy(entity); player.deployedUnits = player.deployedUnits.filter(id => id !== entity); this._events.emit(BattleEventFactory.unitSold(entity, playerId)); return true; } }