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/view/BoardGridRegistry.ts

269 lines
9.2 KiB

3 weeks ago
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<number, TextGrid>();
private _mapGrids = new Map<number, TextGrid>();
private _flightGrids: TextGrid[] = [];
/** 背包槽索引表(可变引用,供 BagSlotEntranceAnimator 等直接读写) */
get slotGrids(): Map<number, TextGrid> {
return this._slotGrids;
}
/** 地图格索引表(可变引用) */
get mapGrids(): Map<number, TextGrid> {
return this._mapGrids;
}
/** 飞行中字块列表 */
get flightGrids(): readonly TextGrid[] {
return this._flightGrids;
}
/** 全量替换背包索引(rebuild 批次切换) */
replaceSlotGrids(next: Map<number, TextGrid>): void {
this._slotGrids = next;
}
/** 全量替换地图索引(rebuild 批次切换) */
replaceMapGrids(next: Map<number, TextGrid>): 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);
}
2 weeks ago
/**
* 背包重建前:收集所有须保护的地图字块(含专用层与格子残留)。
* @param textLayer ui_textGrids
*/
3 weeks ago
collectMapProtected(
out: MembershipSet<TextGrid>,
map: MapManager | null,
prevMap?: ReadonlyMap<number, TextGrid>,
hasTile?: (gridIndex: number) => boolean,
2 weeks ago
textLayer?: import('cc').Node | null,
3 weeks ago
): 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);
}
}
2 weeks ago
this._collectTextGridsOnNode(textLayer, out);
3 weeks ago
const cells = map?.mapGrids;
if (!cells?.length) return;
for (let i = 0; i < cells.length; i++) {
2 weeks ago
this._collectTextGridsOnNode(cells[i]?.node, out);
3 weeks ago
}
}
/**
2 weeks ago
* 背包重建后:按 session 地图数据 + 专用层/格子对齐 _mapGrids。
* @param textLayer ui_textGrids
3 weeks ago
*/
reconcileMapFromScene(
map: MapManager,
hasTile: (gridIndex: number) => boolean,
2 weeks ago
isNodeOnMapLayer: (node: import('cc').Node | null | undefined) => boolean,
textLayer?: import('cc').Node | null,
3 weeks ago
): 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);
2 weeks ago
} else if (!g?.node?.isValid || !isNodeOnMapLayer(g.node)) {
3 weeks ago
this._mapGrids.delete(idx);
}
}
2 weeks ago
this._indexLayerMapGrids(textLayer, hasTile);
3 weeks ago
const cells = map.mapGrids;
if (!cells?.length) return;
for (let i = 0; i < cells.length; i++) {
if (!hasTile(i)) continue;
2 weeks ago
if (this._mapGrids.get(i)?.node?.isValid) continue;
3 weeks ago
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) {
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 = [];
}
2 weeks ago
private _collectTextGridsOnNode(host: import('cc').Node | null | undefined, out: MembershipSet<TextGrid>): void {
if (!host?.isValid) return;
for (const child of host.children) {
if (child.name === '__bagDragGhost') continue;
const tg = child.getComponent(TextGrid);
if (tg?.isValid) membershipAdd(out, tg);
}
}
/**
* 按 location 把专用层上的地图字写入索引。
* @param textLayer ui_textGrids
* @param hasTile 该格是否仍有字
*/
private _indexLayerMapGrids(textLayer: import('cc').Node | null | undefined, hasTile: (gridIndex: number) => boolean): void {
if (!textLayer?.isValid) return;
for (const child of textLayer.children) {
if (child.name === '__bagDragGhost') continue;
const tg = child.getComponent(TextGrid);
if (!tg?.isValid) continue;
const loc = tg.location;
if (loc?.kind !== 'map') continue;
if (!hasTile(loc.gridIndex)) continue;
if (!this._mapGrids.get(loc.gridIndex)?.node?.isValid) {
this._mapGrids.set(loc.gridIndex, tg);
}
}
}
3 weeks ago
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);
}
}