You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/mx/module/battle/deploy/DeploySystem.ts

112 lines
4.2 KiB

3 weeks ago
import type { World, EntityId } from 'db://assets/mx/module/ecs';
6 hours ago
import type { Cell } from '../battlefield/Cell';
import type { UnitDef } from '../config/BattleConfigTypes';
import { BattleEventFactory } from '../events/BattleEvents';
import type { BattleEventBus } from '../events/BattleEventBus';
import { UnitFactory } from '../unit/UnitFactory';
import { UnitMetaComp, TeamComp } from '../ecs/Components';
3 weeks ago
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;
}
}