import { Node } from 'cc'; import { GBundle, GPath } from '../game/ConfigRes'; import { TableNames } from '../game/ConfigTableData'; import { OtherDataType } from '../game/GameDataEnums'; import { GEvent } from '../../mx/module/event/GEvent'; import { SpriteLoad } from '../../mx/components/SpriteLoad'; import { createMembershipSet, membershipAdd, membershipFromArray, membershipHas, membershipToArray, type MembershipSet, } from '../../mx/tools/MembershipSet'; /** Headicon.unlocktype:无需解锁 */ const UNLOCK_TYPE_NONE = 0; /** Headicon.unlocktype:段位星数 */ const UNLOCK_TYPE_RANK_STARS = 1; /** Headicon.unlocktype:合成英雄次数 */ const UNLOCK_TYPE_HERO_COMPOSE = 2; /** Headicon.unlocktype:获得装备 */ const UNLOCK_TYPE_EQUIP = 3; /** 合成英雄次数存档 para 解析结果 */ type HeroComposeUnlockPara = { /** 英雄 id */ heroId: number; /** 所需次数 */ count: number; }; /** Headicon 表头像:设置页选择、首页展示、解锁进度 */ export class HeadIconManager { /** 本局已计入合成次数的英雄 id(每局每英雄只计 1 次) */ private static _battleComposeCounted: MembershipSet | null = null; /** 新开一局战斗时重置局内合成计数 */ static resetBattleSession(): void { this._battleComposeCounted = createMembershipSet(); } /** Headicon 全表(id 升序) */ static getHeadIconList(): ITableHeadicon[] { const list = gg.data.table.getTableList(TableNames.Headicon) ?? []; return list.slice().sort((a, b) => (Number(a.id) || 0) - (Number(b.id) || 0)); } /** 解析 Headicon.icon;空则回退 name */ static resolveIconName(row: ITableHeadicon | null | undefined): string { if (!row) return ''; const icon = String(row.icon ?? '').trim(); if (icon) return icon; const name = String(row.name ?? '').trim(); if (!name) { console.log('[HeadIconManager] Headicon 缺少 icon/name', row.id); return ''; } return name; } /** 解析 Headicon.unlocktype(空 / 0 视为未配置) */ static resolveUnlockType(row: ITableHeadicon | null | undefined): number { if (!row) return UNLOCK_TYPE_NONE; const raw = row.unlocktype as unknown; if (raw === null || raw === undefined) return UNLOCK_TYPE_NONE; const text = String(raw).trim(); if (!text) return UNLOCK_TYPE_NONE; const n = Math.floor(Number(text)); return Number.isFinite(n) ? n : UNLOCK_TYPE_NONE; } /** 是否配置了解锁条件(仅 1/2/3 有效) */ static needsUnlock(row: ITableHeadicon | null | undefined): boolean { const t = this.resolveUnlockType(row); return t === UNLOCK_TYPE_RANK_STARS || t === UNLOCK_TYPE_HERO_COMPOSE || t === UNLOCK_TYPE_EQUIP; } /** 设置页是否显示 lock 遮罩 */ static shouldShowLock(row: ITableHeadicon | null | undefined): boolean { if (!this.needsUnlock(row)) return false; return !this.isUnlocked(row); } /** 已解锁头像 id 列表(解锁头像 id 存档) */ static getUnlockedHeadIconIds(): number[] { return gg.data.getArrayDataInt(OtherDataType.UnlockedHeadIconIds); } /** 头像是否已解锁(unlocktype=0 视为默认解锁) */ static isUnlocked(rowOrId: ITableHeadicon | number): boolean { const row = typeof rowOrId === 'number' ? gg.data.table.getTableData(TableNames.Headicon, rowOrId) : rowOrId; if (!row) return false; if (!this.needsUnlock(row)) return true; return membershipHas(membershipFromArray(this.getUnlockedHeadIconIds()), row.id); } /** 某英雄累计合成次数 */ static getHeroComposeCount(heroId: number): number { const id = Math.floor(Number(heroId) || 0); if (id <= 0) return 0; const v = gg.data.getkeyKeyValue(OtherDataType.HeroComposeCount, id); return v === '' ? 0 : Math.max(0, Math.floor(Number(v) || 0)); } /** 记录一次英雄合成并尝试解锁头像(同局同英雄只计 1 次) */ static onHeroComposed(heroId: number): void { const id = Math.floor(Number(heroId) || 0); if (id <= 0) return; if (!this._battleComposeCounted) { this._battleComposeCounted = createMembershipSet(); } if (membershipHas(this._battleComposeCounted, id)) return; membershipAdd(this._battleComposeCounted, id); const next = this.getHeroComposeCount(id) + 1; gg.data.setkeyKeyValue(OtherDataType.HeroComposeCount, id, next); const newly = this._syncUnlocksFromProgress(); if (newly.length <= 0) gg.data.saveLocal(); } /** 获得装备后尝试解锁头像 */ static onEquipGained(equipId: number): void { const id = Math.floor(Number(equipId) || 0); if (id <= 0) return; this._syncUnlocksFromProgress(); } /** 段位星数变化后尝试解锁头像 */ static onRankStarsChanged(): void { this._syncUnlocksFromProgress(); } /** 读档/进大厅后按当前进度补解锁(异步加载 Headicon 表) */ static async syncUnlocksFromProgressAsync(): Promise { await gg.data.table.ensureTableLoaded(TableNames.Headicon); return this._syncUnlocksFromProgress(); } /** 存档头像 id;0 或未设置时取首个可用头像 */ static getPlayerHeadIconId(): number { const saved = Math.floor(Number(gg.data.doc.headIconId) || 0); if (saved > 0 && this.isUnlocked(saved)) { const row = gg.data.table.getTableData(TableNames.Headicon, saved); if (row) return saved; console.log('[HeadIconManager] headIconId 无效', saved); } const list = this.getHeadIconList(); for (let i = 0; i < list.length; i++) { if (this.isUnlocked(list[i])) return list[i].id; } if (list.length <= 0) { console.log('[HeadIconManager] Headicon 表为空'); return 0; } return list[0].id; } /** 写入首页头像 id(须已解锁) */ static setPlayerHeadIconId(id: number): void { const headId = Math.floor(Number(id) || 0); const row = gg.data.table.getTableData(TableNames.Headicon, headId); if (!row) { console.log('[HeadIconManager] 设置头像失败,无效 id', headId); return; } if (!this.isUnlocked(row)) { console.log('[HeadIconManager] 设置头像失败,未解锁', headId); return; } gg.data.doc.headIconId = headId; gg.data.saveLocal(); } /** 按 Headicon 行绑定 head 节点(需已挂 SpriteLoad) */ static applyRowToNode(headNode: Node | null, row: ITableHeadicon | null | undefined): void { if (!headNode?.isValid) return; const iconName = this.resolveIconName(row); if (!iconName) return; const loader = headNode.getComponent(SpriteLoad); if (!loader) { console.error('[HeadIconManager] 节点缺少 SpriteLoad', headNode.name); return; } loader.setSprite(GPath.HeadIcon(iconName), GBundle.Items); } /** 按存档或指定 id 绑定 head 节点 */ static applyToNode(headNode: Node | null, iconId?: number): void { const id = iconId ?? this.getPlayerHeadIconId(); if (id <= 0) return; const row = gg.data.table.getTableData(TableNames.Headicon, id); if (!row) { console.log('[HeadIconManager] 未找到 Headicon', id); return; } this.applyRowToNode(headNode, row); } /** 刷新 ui_head 头像区(headMask/head,隐藏 man/woman) */ static async refreshUiHeadAsync(uiHead: Node | null): Promise { if (!uiHead?.isValid) return; await gg.data.table.ensureTableLoaded(TableNames.Headicon); this.refreshUiHead(uiHead); } /** 刷新 ui_head 头像区(headMask/head,隐藏 man/woman) */ static refreshUiHead(uiHead: Node | null): void { if (!uiHead?.isValid) return; const headMask = uiHead.getChildByName('headMask'); const headNode = headMask?.getChildByName('head') ?? uiHead.find('headMask/head'); if (!headNode?.isValid) return; this.applyToNode(headNode); if (headMask) headMask.active = true; } /** 按当前段位/合成次数/装备补写解锁头像 id */ private static _syncUnlocksFromProgress(): number[] { const list = this.getHeadIconList(); const unlocked = membershipFromArray(this.getUnlockedHeadIconIds()); const newly: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i]; if (!this.needsUnlock(row)) continue; if (membershipHas(unlocked, row.id)) continue; if (!this._meetsUnlockCondition(row)) continue; membershipAdd(unlocked, row.id); newly.push(row.id); } if (newly.length <= 0) return newly; gg.data.setArrayData(OtherDataType.UnlockedHeadIconIds, membershipToArray(unlocked)); gg.data.saveLocal(); GEvent.Ins.emit(GEvent.UpdateHeadIcon); return newly; } /** 判定 Headicon 解锁条件是否已满足 */ private static _meetsUnlockCondition(row: ITableHeadicon): boolean { const unlockType = this.resolveUnlockType(row); if (unlockType === UNLOCK_TYPE_RANK_STARS) { const needStars = Math.floor(Number(row.para) || 0); if (needStars <= 0) { console.log('[HeadIconManager] 段位解锁 para 无效', row.id, row.para); return false; } const stars = Math.floor(Number(gg.data.doc.rankStars) || 0); return stars >= 1 && stars >= needStars; } if (unlockType === UNLOCK_TYPE_HERO_COMPOSE) { const para = this._parseHeroComposePara(row.para); if (!para) { console.log('[HeadIconManager] 合成解锁 para 无效', row.id, row.para); return false; } return this.getHeroComposeCount(para.heroId) >= para.count; } if (unlockType === UNLOCK_TYPE_EQUIP) { const equipId = Math.floor(Number(row.para) || 0); if (equipId <= 0) { console.log('[HeadIconManager] 装备解锁 para 无效', row.id, row.para); return false; } return gg.equip.getHaveEquipCount(equipId) >= 1; } if (unlockType !== UNLOCK_TYPE_NONE) { console.log('[HeadIconManager] 未知 unlocktype', row.id, unlockType); } return false; } /** 解析 unlocktype=2 的 para(heroId,count) */ private static _parseHeroComposePara(para: string | number): HeroComposeUnlockPara | null { const raw = String(para ?? '').trim(); if (!raw) return null; const parts = raw.split(','); if (parts.length < 2) return null; const heroId = Math.floor(Number(parts[0]) || 0); const count = Math.floor(Number(parts[1]) || 0); if (heroId <= 0 || count <= 0) return null; return { heroId, count }; } }