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/script/battle/map/importTopologyFromMapManage...

165 lines
5.8 KiB

import type { BattleSession, Cell, MapTopologySnapshot } from 'db://assets/mx/module/battle';
import type { CampAreaPreset } from 'db://assets/mx/module/battle';
import type { MapManager } from './legacy/MapManager';
import type { MapGrid } from './legacy/MapGrid';
import type { Node } from 'cc';
import {
collectAndValidateMapCampPoints,
readCampAreaByCell,
} from './campAreaMapValidation';
export interface TopologyBuildResult {
ok: boolean;
snapshot?: MapTopologySnapshot;
errors: string[];
}
/**
* 从 Cocos 地图预制体导入中性拓扑快照(含阵营区域;校验失败不生成 snapshot)
* 前提:MapManager.mapRows/mapCols/mapGridSize 与 session.grid 一致(由预制体探测写入 LevelDef)
*/
export function buildTopologySnapshotFromMapManager(
map: MapManager,
logicRows: number,
logicCols: number,
enablePatrolLoop = false,
): TopologyBuildResult {
const errors: string[] = [];
const mapName = map.node?.name ?? 'MapManager';
if (!map.gridsInitialized) {
errors.push(`${mapName}: 地图格子未初始化,无法导入拓扑`);
return { ok: false, errors };
}
if (map.mapRows !== logicRows || map.mapCols !== logicCols) {
console.error(`[buildTopologySnapshotFromMapManager] map/session grid mismatch map=${map.mapRows}x${map.mapCols} session=${logicRows}x${logicCols}`);
}
const isPerimeter = enablePatrolLoop;
const { spawnBindings, attackBindings, errors: pointErrors } = collectAndValidateMapCampPoints(map, {
requireAttackPerCampArea: !isPerimeter,
});
errors.push(...pointErrors);
if (errors.length > 0) {
return { ok: false, errors };
}
const size = logicRows * logicCols;
const walkable: boolean[] = new Array(size).fill(false);
const buildable: boolean[] = new Array(size).fill(false);
const campAreaByCell = readCampAreaByCell(map, logicRows, logicCols);
for (let row = 0; row < logicRows; row++) {
for (let col = 0; col < logicCols; col++) {
const idx = row * logicCols + col;
const mg = map.getMapGrid(row, col);
if (!mg) continue;
walkable[idx] = mg.isCanWalk ?? false;
buildable[idx] = mg.isCanPutText ?? mg.isCanPutGrid ?? false;
}
}
const spawnPorts: Record<number, Cell> = {};
const spawnPortCampArea: Record<number, number> = {};
for (const b of spawnBindings) {
spawnPorts[b.port] = { row: b.grid.Row, col: b.grid.Col };
spawnPortCampArea[b.port] = b.campAreaId;
}
const attackTargets: Cell[] = [];
const attackTargetCampArea: number[] = [];
const attackTargetWorlds: { x: number; y: number }[] = [];
for (const b of attackBindings) {
const node = map.allAttackPointGrids.get(b.standGrid);
if (!node) {
errors.push(`${mapName}: 攻击点 ${b.nodeName} 内部映射丢失`);
continue;
}
attackTargets.push({ row: b.standGrid.Row, col: b.standGrid.Col });
attackTargetCampArea.push(b.campAreaId);
attackTargetWorlds.push(attackNodeToLogicalWorld(b.standGrid, node, map.mapGridSize));
}
if (errors.length > 0) {
return { ok: false, errors };
}
return {
ok: true,
snapshot: {
walkable,
buildable,
campAreaByCell,
spawnPorts,
spawnPortCampArea,
attackTargets,
attackTargetCampArea,
attackTargetWorlds,
enablePatrolLoop,
},
errors: [],
};
}
/**
* 攻击目标节点 Cocos 世界坐标 → 逻辑世界坐标(与 GridMap.cellToWorld 同系)
* 以寻路站立格心为锚,X 同向、Y 翻转(Cocos↑ / 逻辑↓)
*/
function attackNodeToLogicalWorld(standGrid: MapGrid, attackNode: Node, cellSize: number): { x: number; y: number } {
const s = cellSize;
const logicalCenterX = (standGrid.Col + 0.5) * s;
const logicalCenterY = (standGrid.Row + 0.5) * s;
const cellWp = standGrid.node.worldPosition;
const atkWp = attackNode.worldPosition;
return {
x: logicalCenterX + (atkWp.x - cellWp.x),
y: logicalCenterY - (atkWp.y - cellWp.y),
};
}
/**
* 将拓扑导入战斗会话;校验失败时打印全部错误且不 apply。
* @returns 是否成功导入
*/
export function applyMapTopologyToSession(
session: BattleSession,
map: MapManager,
campPreset?: CampAreaPreset,
): boolean {
if (!map.gridsInitialized) {
console.error('[applyMapTopologyToSession] map not initialized');
return false;
}
const logicRows = session.grid.rows;
const logicCols = session.grid.cols;
if (map.mapRows !== logicRows || map.mapCols !== logicCols) {
console.error(`[applyMapTopologyToSession] map vs session grid mismatch — deploy/寻敌会异常 map=${map.mapRows}x${map.mapCols} session=${logicRows}x${logicCols}`);
return false;
}
if (map.mapGridSize !== session.grid.cellSize) {
console.error(`[applyMapTopologyToSession] cell size mismatch map=${map.mapGridSize} session=${session.grid.cellSize}`);
return false;
}
const result = buildTopologySnapshotFromMapManager(
map,
logicRows,
logicCols,
isPerimeterBattle(),
);
if (!result.ok || !result.snapshot) {
for (const err of result.errors) {
console.error(String(err));
}
console.error('[applyMapTopologyToSession] 拓扑导入失败,共', result.errors.length, '项错误');
return false;
}
session.applyTopology(result.snapshot, campPreset);
return true;
}
/** 当前是否为循环圈地图(巡逻环走,无攻击目标) */
function isPerimeterBattle(): boolean {
return !!gg.game.CurentBattle?.session?.topology?.enablePatrolLoop;
}