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/manager/HeadIconManager.ts

285 lines
11 KiB

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<number> | null = null;
/** 新开一局战斗时重置局内合成计数 */
static resetBattleSession(): void {
this._battleComposeCounted = createMembershipSet<number>();
}
/** Headicon 全表(id 升序) */
static getHeadIconList(): ITableHeadicon[] {
const list = gg.data.table.getTableList<ITableHeadicon>(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<ITableHeadicon>(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<number>();
}
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<number[]> {
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<ITableHeadicon>(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<ITableHeadicon>(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<ITableHeadicon>(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<void> {
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 };
}
}