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.
111 lines
4.2 KiB
111 lines
4.2 KiB
import type { World, EntityId } from 'db://assets/mx/module/ecs';
|
|
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';
|
|
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;
|
|
}
|
|
}
|
|
|