import { Node } from 'cc'; import { TableNames } from '../../game/ConfigTableData'; /** ui_textList 格子状态 */ export enum TextListCellState { /** 开启,可按背包规则拖动 */ Open = 1, /** 开启但不能拖;其他字拖入后解锁下标 +1 */ OpenNoDrag = 2, /** 未解锁 */ Locked = 3, } /** 格子节点上的开启底 */ const OPEN_NODE = '背包格开启'; /** 格子节点上的未开启底 */ const LOCKED_NODE = '背包格未开启'; /** 模板节点,不参与状态 */ const TEMP_NODE = 'temp'; /** * 按关卡 normalland / tolockland 显示 ui_textList 格子,并处理待解锁格的拖入解锁。 * normalland 为开启;tolockland 为开启但不能拖;其余为未解锁。名为 temp 的节点跳过。 */ export class TextListLandHost { /** ui_textList */ private _list: Node | null = null; /** 已按关卡刷过;未配置时全部视为开启 */ private _useTable = false; /** 开启下标(含拖入后新解锁) */ private readonly _open: number[] = []; /** 待解锁下标(开启但不能拖) */ private readonly _gate: number[] = []; /** * 读取关卡并刷新格子底图。 * @param levelId Level.id * @param list ui_textList */ bind(levelId: number, list: Node | null): void { this.clear(); if (!list?.isValid) { console.log('[TextListLand] ui_textList 缺失'); return; } this._list = list; const row = gg.data.table.getTableData(TableNames.Level, levelId); if (!row) { console.log('[TextListLand] Level 缺失', levelId); this._applyAll(TextListCellState.Open); return; } const normal = row.normalland; const gate = row.tolockland; if (!normal?.length && !gate?.length) { console.log('[TextListLand] 关卡未配置地块,全部开启', levelId); this._applyAll(TextListCellState.Open); return; } this._useTable = true; this._fill(this._open, normal, 'normalland'); this._fill(this._gate, gate, 'tolockland'); for (let i = this._gate.length - 1; i >= 0; i--) { if (!this._has(this._open, this._gate[i])) continue; console.log('[TextListLand] 下标同时在开启与待解锁,按开启', this._gate[i]); this._gate.splice(i, 1); } this._applyFromTable(); } /** 解除引用 */ clear(): void { this._list = null; this._useTable = false; this._open.length = 0; this._gate.length = 0; } /** * 节点对应的格子下标。temp 与无法解析的名字返回 null。 * @param node ui_textList 子节点 */ static indexOfNode(node: Node | null | undefined): number | null { if (!node?.isValid) return null; if (node.name === TEMP_NODE) return null; if (node.name === 'uigrid') return 0; const matched = /^uigrid-(\d+)$/.exec(node.name); if (!matched) { console.log('[TextListLand] 无法解析格子下标', node.name); return null; } const index = parseInt(matched[1], 10); if (!Number.isFinite(index) || index < 0) { console.log('[TextListLand] 格子下标非法', node.name); return null; } return index; } /** * 当前下标状态。未走配表时一律开启。 * @param index 格子下标 */ stateOf(index: number): TextListCellState { if (!this._useTable) return TextListCellState.Open; if (this._has(this._open, index)) return TextListCellState.Open; if (this._has(this._gate, index)) return TextListCellState.OpenNoDrag; return TextListCellState.Locked; } /** * 待解锁格禁止把字拖走。 * @param index 格子下标 */ isNoDrag(index: number): boolean { return this.stateOf(index) === TextListCellState.OpenNoDrag; } /** * 其他字拖到待解锁格:把下标 +1 的未解锁格改为开启。 * @param gateIndex 待解锁格下标 */ unlockNext(gateIndex: number): void { if (!this.isNoDrag(gateIndex)) { console.log('[TextListLand] 非待解锁格', gateIndex); return; } const next = gateIndex + 1; const node = this._nodeAt(next); if (!node) { console.log('[TextListLand] 下标+1 没有可解锁格子', gateIndex, next); return; } if (this.stateOf(next) === TextListCellState.Open) return; if (this.stateOf(next) !== TextListCellState.Locked) { console.log('[TextListLand] 下标+1 已不是未解锁', next, this.stateOf(next)); return; } this._open.push(next); this._applyNode(node, TextListCellState.Open); } /** * 写入一组下标,非法项打日志。 * @param out 目标数组 * @param raw 配表数组 * @param label 字段名 */ private _fill(out: number[], raw: readonly number[] | undefined, label: string): void { if (!raw?.length) return; for (let i = 0; i < raw.length; i++) { const index = Math.floor(Number(raw[i])); if (!Number.isFinite(index) || index < 0) { console.log('[TextListLand] 非法下标', label, raw[i]); continue; } if (this._has(out, index)) continue; out.push(index); } } /** 按当前开启/待解锁刷新全部格子 */ private _applyFromTable(): void { const list = this._list; if (!list?.isValid) return; const children = list.children; for (let i = 0; i < children.length; i++) { const node = children[i]; const index = TextListLandHost.indexOfNode(node); if (index == null) continue; this._applyNode(node, this.stateOf(index)); } this._warnMissing(this._open); this._warnMissing(this._gate); } /** * 配表下标在列表里没有对应格子时打日志。 * @param indices 下标 */ private _warnMissing(indices: readonly number[]): void { for (let i = 0; i < indices.length; i++) { if (this._nodeAt(indices[i])) continue; console.log('[TextListLand] 下标没有可显示格子', indices[i]); } } /** * 未配置地块时统一刷一种状态。 * @param state 状态 */ private _applyAll(state: TextListCellState): void { const list = this._list; if (!list?.isValid) return; const children = list.children; for (let i = 0; i < children.length; i++) { const node = children[i]; if (TextListLandHost.indexOfNode(node) == null) continue; this._applyNode(node, state); } } /** * 按下标找格子节点。 * @param index 格子下标 */ private _nodeAt(index: number): Node | null { const list = this._list; if (!list?.isValid) return null; const children = list.children; for (let i = 0; i < children.length; i++) { if (TextListLandHost.indexOfNode(children[i]) === index) return children[i]; } return null; } /** * 开启显示背包格开启,未解锁显示背包格未开启。 * @param node 格子节点 * @param state 状态 */ private _applyNode(node: Node, state: TextListCellState): void { const openNode = node.getChildByName(OPEN_NODE); const lockedNode = node.getChildByName(LOCKED_NODE); if (!openNode?.isValid || !lockedNode?.isValid) { console.log('[TextListLand] 缺少开关节点', node.name); return; } const opened = state !== TextListCellState.Locked; openNode.active = opened; lockedNode.active = !opened; } /** * 数组是否包含下标。 * @param list 下标列表 * @param index 下标 */ private _has(list: readonly number[], index: number): boolean { for (let i = 0; i < list.length; i++) { if (list[i] === index) return true; } return false; } }