import { membershipAdd, mapKeysToArray, type MembershipSet } from '../../../mx/tools/MembershipSet'; import type { MapManager } from '../map/legacy/MapManager'; import { TextGrid } from './TextGrid'; /** 字块起飞前的原归属格 */ export type GridDetachSource = | { kind: 'map'; gridIndex: number } | { kind: 'bag'; slotIndex: number }; /** * 背包槽 / 地图格 / 飞行中字块的唯一归属索引。 * 视图层不得再直接维护多份 Map;所有挂接变更经此类提交。 */ export class BoardGridRegistry { private _slotGrids = new Map(); private _mapGrids = new Map(); private _flightGrids: TextGrid[] = []; /** 背包槽索引表(可变引用,供 BagSlotEntranceAnimator 等直接读写) */ get slotGrids(): Map { return this._slotGrids; } /** 地图格索引表(可变引用) */ get mapGrids(): Map { return this._mapGrids; } /** 飞行中字块列表 */ get flightGrids(): readonly TextGrid[] { return this._flightGrids; } /** 全量替换背包索引(rebuild 批次切换) */ replaceSlotGrids(next: Map): void { this._slotGrids = next; } /** 全量替换地图索引(rebuild 批次切换) */ replaceMapGrids(next: Map): void { this._mapGrids = next; } /** 取出并清空飞行列表(全量 rebuild 纳入 prev 复用) */ takeFlightGrids(): TextGrid[] { const out = this._flightGrids; this._flightGrids = []; return out; } /** 背包槽字块 */ getBagGrid(slotIndex: number): TextGrid | undefined { return this._slotGrids.get(slotIndex); } /** 地图格字块 */ getMapGrid(gridIndex: number): TextGrid | undefined { return this._mapGrids.get(gridIndex); } /** 登记背包槽归属 */ setBagGrid(slotIndex: number, grid: TextGrid): void { this._purgeGridFromIndices(grid); this._slotGrids.set(slotIndex, grid); this._removeFromFlight(grid); } /** 登记地图格归属 */ setMapGrid(gridIndex: number, grid: TextGrid): void { this._purgeGridFromIndices(grid); this._mapGrids.set(gridIndex, grid); this._removeFromFlight(grid); } /** 删除背包槽登记 */ deleteBagSlot(slotIndex: number): void { this._slotGrids.delete(slotIndex); } /** 删除地图格登记 */ deleteMapCell(gridIndex: number): void { this._mapGrids.delete(gridIndex); } /** 交换飞行:从槽/地图摘下并记入飞行列表 */ detachForFlight(grid: TextGrid, from: GridDetachSource): void { if (from.kind === 'map') { if (this._mapGrids.get(from.gridIndex) === grid) { this._mapGrids.delete(from.gridIndex); } } else if (this._slotGrids.get(from.slotIndex) === grid) { this._slotGrids.delete(from.slotIndex); } if (this._flightGrids.indexOf(grid) < 0) { this._flightGrids.push(grid); } } /** 从飞行列表移除 */ removeFromFlight(grid: TextGrid): void { const i = this._flightGrids.indexOf(grid); if (i >= 0) this._flightGrids.splice(i, 1); } /** 从所有索引与飞行列表移除(回收前) */ purgeGridFromIndices(grid: TextGrid): void { this._purgeGridFromIndices(grid); this._removeFromFlight(grid); } /** 是否已登记槽/格/飞行(不含父节点兜底) */ isIndexed(grid: TextGrid): boolean { for (const g of this._slotGrids.values()) { if (g === grid) return true; } for (const g of this._mapGrids.values()) { if (g === grid) return true; } return this._flightGrids.indexOf(grid) >= 0; } /** * 字块是否已被本注册表跟踪。 * @param mapHostedCheck 父节点在地图格上但索引未登记时的兜底 */ isTracked(grid: TextGrid, mapHostedCheck: (g: TextGrid) => boolean): boolean { if (this.isIndexed(grid)) return true; return mapHostedCheck(grid); } /** 背包重建前:收集所有须保护的地图字块(含场景扫描) */ collectMapProtected( out: MembershipSet, map: MapManager | null, prevMap?: ReadonlyMap, hasTile?: (gridIndex: number) => boolean, ): void { for (const g of this._mapGrids.values()) { if (g?.isValid) membershipAdd(out, g); } if (prevMap) { for (const [idx, g] of prevMap) { if (g?.isValid && (!hasTile || hasTile(idx))) membershipAdd(out, g); } } const cells = map?.mapGrids; if (!cells?.length) return; for (let i = 0; i < cells.length; i++) { const parent = cells[i]?.node; if (!parent?.isValid) continue; for (const child of parent.children) { if (child.name === '__bagDragGhost') continue; const tg = child.getComponent(TextGrid); if (tg?.isValid) membershipAdd(out, tg); } } } /** * 背包重建后:按 session 地图数据 + 场景父节点对齐 _mapGrids。 */ reconcileMapFromScene( map: MapManager, hasTile: (gridIndex: number) => boolean, isNodeUnderMapCell: (node: import('cc').Node | null | undefined) => boolean, ): void { const mapIdx = mapKeysToArray(this._mapGrids); for (let i = 0; i < mapIdx.length; i++) { const idx = mapIdx[i]; const g = this._mapGrids.get(idx); if (!hasTile(idx)) { this._mapGrids.delete(idx); } else if (!g?.node?.isValid || !isNodeUnderMapCell(g.node)) { this._mapGrids.delete(idx); } } const cells = map.mapGrids; if (!cells?.length) return; for (let i = 0; i < cells.length; i++) { if (!hasTile(i)) continue; const parent = cells[i]?.node; if (!parent?.isValid) continue; const tracked = this._mapGrids.get(i); if (tracked?.node?.isValid && tracked.node.parent === parent) continue; for (const child of parent.children) { if (child.name === '__bagDragGhost') continue; const tg = child.getComponent(TextGrid); if (tg?.isValid) { this._mapGrids.set(i, tg); break; } } } } /** 在场景父节点下查找地图格上的字块 */ findMapGridOnCell(parent: import('cc').Node | null | undefined): TextGrid | null { if (!parent?.isValid) return null; for (const child of parent.children) { if (child.name === '__bagDragGhost') continue; const tg = child.getComponent(TextGrid); if (tg?.isValid) return tg; } return null; } /** 父节点上除 exclude 外的第一块 TextGrid */ findOtherGridOnCell(parent: import('cc').Node | null | undefined, exclude: TextGrid): TextGrid | null { if (!parent?.isValid) return null; for (const child of parent.children) { if (child.name === '__bagDragGhost') continue; const tg = child.getComponent(TextGrid); if (tg?.isValid && tg !== exclude) return tg; } return null; } /** 清空全部登记 */ clear(): void { this._slotGrids.clear(); this._mapGrids.clear(); this._flightGrids = []; } private _purgeGridFromIndices(grid: TextGrid): void { for (const [idx, g] of this._slotGrids) { if (g === grid) this._slotGrids.delete(idx); } for (const [idx, g] of this._mapGrids) { if (g === grid) this._mapGrids.delete(idx); } } private _removeFromFlight(grid: TextGrid): void { const i = this._flightGrids.indexOf(grid); if (i >= 0) this._flightGrids.splice(i, 1); } }