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.
72 lines
2.8 KiB
72 lines
2.8 KiB
|
3 weeks ago
|
import type { BattleServices } from '../combat/BattleServices';
|
||
|
|
import type { Cell } from '../battlefield/Cell';
|
||
|
|
import type { DeployResult, DeployRule } from './DeployTypes';
|
||
|
|
import { BattlePhase } from '../core/BattlePhase';
|
||
|
|
|
||
|
|
/** 仅允许 PREP 阶段部署(连续模式放宽) */
|
||
|
|
export class PrepPhaseOnlyRule implements DeployRule {
|
||
|
|
check(services: BattleServices, _playerId: number, _unitDefId: string, cells: Cell[]): DeployResult {
|
||
|
|
if (services.phase !== BattlePhase.PREP && !services.isContinuousFlow) {
|
||
|
|
return { ok: false, reason: 'not_prep_phase' };
|
||
|
|
}
|
||
|
|
return { ok: true, cells };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 格子必须在玩家部署区域内 */
|
||
|
|
export class InPlayerRegionRule implements DeployRule {
|
||
|
|
check(services: BattleServices, playerId: number, _unitDefId: string, cells: Cell[]): DeployResult {
|
||
|
|
const player = services.players[playerId];
|
||
|
|
if (!player) return { ok: false, reason: 'invalid_player' };
|
||
|
|
for (const c of cells) {
|
||
|
|
if (!player.region.contains(c)) return { ok: false, reason: 'out_of_region' };
|
||
|
|
}
|
||
|
|
return { ok: true, cells };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 格子必须未被 ECS 占用(合成格由 DeploySync 在部署前同步 buildable) */
|
||
|
|
export class CellEmptyRule implements DeployRule {
|
||
|
|
check(services: BattleServices, _playerId: number, _unitDefId: string, cells: Cell[]): DeployResult {
|
||
|
|
for (const c of cells) {
|
||
|
|
const s = services.grid.getCell(c);
|
||
|
|
if (!s) return { ok: false, reason: 'out_of_bounds' };
|
||
|
|
if (s.occupant != null) return { ok: false, reason: 'cell_occupied' };
|
||
|
|
}
|
||
|
|
return { ok: true, cells };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 玩家金币必须足够 */
|
||
|
|
export class EnoughGoldRule implements DeployRule {
|
||
|
|
check(services: BattleServices, playerId: number, unitDefId: string, _cells: Cell[]): DeployResult {
|
||
|
|
const def = services.config.getUnit(unitDefId);
|
||
|
|
const cost = def?.deploy?.cost ?? 0;
|
||
|
|
const player = services.players[playerId];
|
||
|
|
if (!player || player.gold < cost) return { ok: false, reason: 'not_enough_gold' };
|
||
|
|
return { ok: true };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 占地形状必须在地图范围内 */
|
||
|
|
export class ShapeInBoundsRule implements DeployRule {
|
||
|
|
check(services: BattleServices, _playerId: number, _unitDefId: string, cells: Cell[]): DeployResult {
|
||
|
|
for (const c of cells) {
|
||
|
|
if (!services.grid.inBounds(c)) return { ok: false, reason: 'out_of_bounds' };
|
||
|
|
}
|
||
|
|
return { ok: true, cells };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @returns 默认部署规则链 */
|
||
|
|
export function createDefaultDeployRules(): DeployRule[] {
|
||
|
|
return [
|
||
|
|
new PrepPhaseOnlyRule(),
|
||
|
|
new ShapeInBoundsRule(),
|
||
|
|
new InPlayerRegionRule(),
|
||
|
|
new CellEmptyRule(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
export type { DeployRule };
|