import type { IComposeConfigProvider } from 'db://assets/mx/module/merge'; import { createMembershipSet, membershipAdd, membershipHas, mapValuesToArray, type MembershipSet, } from '../../../mx/tools/MembershipSet'; import { isElementGlyphType, Weapon2TileType } from 'db://assets/mx/module/merge'; import { TableNames } from '../../game/ConfigTableData'; import { PEACH_TREE_MAX_LEVEL, PEACH_TREE_WEAPON2_ID } from './BagPrepConfig'; /** 归一化 Weapon2 数值字段(抖音/热更 JSON 常见字符串数字) */ function normalizeWeapon2Row(w: ITableText): ITableText | null { const id = Number(w.id); if (!Number.isFinite(id)) return null; return { ...w, id, type: Number(w.type), weight: Number(w.weight), }; } /** Weapon1 / Weapon2 配表 → MX 合成配置 */ export class WeaponComposeConfigProvider implements IComposeConfigProvider { private static _instance: WeaponComposeConfigProvider | null = null; private _weapon1List: ITableHero[] = []; private _weapon2Map = new Map(); private _groupIndex = new Map(); private _maxLevel = new Map(); private _loaded = false; static get instance(): WeaponComposeConfigProvider { if (!this._instance) { this._instance = new WeaponComposeConfigProvider(); } return this._instance; } /** 配表热更后调用,强制下次 load 重读 */ invalidate(): void { this._loaded = false; this._weapon2Map.clear(); this._weapon1List = []; this._groupIndex.clear(); this._maxLevel.clear(); } load(): void { if (this._loaded) return; const w1List = gg.data.table.getTableList(TableNames.Hero) ?? []; const w2List = gg.data.table.getTableList(TableNames.Text) ?? []; // Weapon2 是字块 type 的权威来源;H5 可能先到 Weapon1,此时绝不能 _loaded=true 锁死空表 if (w2List.length === 0) { return; } this._weapon2Map.clear(); for (const w of w2List) { const row = normalizeWeapon2Row(w); if (!row) continue; this._weapon2Map.set(row.id, row); } if (this._weapon2Map.size === 0) { return; } this._weapon1List = w1List; this._groupIndex.clear(); for (const w1 of this._weapon1List) { const group = w1.text; if (!Array.isArray(group)) continue; const key = group.join(','); this._groupIndex.set(key, Number(w1.id)); } this._maxLevel.clear(); for (const w1 of this._weapon1List) { if (Number(w1.level_limit) > 0) { this._maxLevel.set(Number(w1.id), Number(w1.level_limit)); } } this._loaded = true; } /** Weapon2 解锁章节(新产品无 unlock 字段,默认第 1 章可用) */ getWeapon2UnlockChapter(_weapon2: ITableText): number { return 1; } /** 背包刷新候选:weight > 0、当前章节已解锁;单字/组合字须属于局外上阵武器的 text */ getWeapon2RefreshCandidates(chapterId: number): ITableText[] { this.load(); const equippedWeapon2Ids = this._getEquippedWeapon2IdSet(); const result: ITableText[] = []; for (const w2 of this._weapon2Map.values()) { const w2Type = Number(w2.type); if (Number(w2.weight) <= 0) continue; if (this.getWeapon2UnlockChapter(w2) > chapterId) continue; if (w2Type === Weapon2TileType.Grid) continue; if (!membershipHas(equippedWeapon2Ids, Number(w2.id))) continue; result.push(w2); } return result; } /** 局外上阵武器 text:基础上阵单字(herotype===1)+ 英雄组合字(herotype===2) */ private _getEquippedWeapon2IdSet(): MembershipSet { const ids = createMembershipSet(); for (const weapon of gg.data.project.getEquippedWeaponDataList()) { for (const weapon2Id of weapon.text ?? []) { const id = Number(weapon2Id); if (Number.isFinite(id) && id > 0) membershipAdd(ids, id); } } return ids; } getAllWeapon2Configs(): readonly ITableText[] { this.load(); return mapValuesToArray(this._weapon2Map); } getWeapon2Type(weapon2Id: number): number { this.load(); const id = Number(weapon2Id); const row = this._weapon2Map.get(id); const t = Number(row?.type); return Number.isFinite(t) ? t : 0; } getWeapon1Group(weapon1Id: number): readonly number[] { this.load(); const w1 = this._weapon1List.find((w) => Number(w.id) === Number(weapon1Id)); return w1?.text ?? []; } getWeapon1ByGroupSequence(sequence: readonly number[]): number | null { this.load(); const key = sequence.join(','); return this._groupIndex.get(key) ?? null; } getWeapon2IdForWeapon1(weapon1Id: number): number | null { const group = this.getWeapon1Group(weapon1Id); return group.length > 0 ? group[0] : null; } getMaxWeaponLevel(weapon1Id: number): number { this.load(); const id = Number(weapon1Id); const cached = this._maxLevel.get(id); if (cached != null) return cached; const row = this._weapon1List.find((w) => Number(w.id) === id); if (!row) { console.log('[WeaponCompose] Hero 缺失,等级上限按 1', id); return 1; } const ht = Math.floor(Number(row.herotype)); if (ht >= 1 && ht <= 5) { const max = this.getElementMaxLevel(ht); this._maxLevel.set(id, max); return max; } console.log('[WeaponCompose] Hero.level_limit 未配置,等级上限按 1', id); return 1; } isSingleCellWeapon1(weapon1Id: number): boolean { return this.getWeapon1Group(weapon1Id).length === 1; } /** 被动 buff 可在 Dig 格产出的 Weapon2(单字/组合字/蟠桃树) */ allowsPassiveDigSpawn(weapon2Id: number): boolean { this.load(); const id = Number(weapon2Id); if (isElementGlyphType(this.getWeapon2Type(id))) return true; return id === PEACH_TREE_WEAPON2_ID; } /** 蟠桃树(102):空 Dig/Put 可落,落到 Put 后转 Dig,可拖回背包 */ passiveSpawnKeepsDigCell(weapon2Id: number): boolean { this.load(); return Number(weapon2Id) === PEACH_TREE_WEAPON2_ID; } /** * 五行显示等级上限:同 herotype 的 Hero.level 最大值。 * @param textType Text.type(1–5 金木水火土) */ getElementMaxLevel(textType: number): number { this.load(); const type = Math.floor(Number(textType)); let max = 0; for (let i = 0; i < this._weapon1List.length; i++) { const hero = this._weapon1List[i]; if (Math.floor(Number(hero.herotype)) !== type) continue; const level = Math.floor(Number(hero.level)); if (level > max) max = level; } if (!(max > 0)) { console.log('[WeaponCompose] 五行没有等级行', type); return 1; } return max; } /** * 五行 Hero.id:herotype 与显示等级对齐。 * @param textType Text.type(1–5) * @param level 显示等级 */ getElementHeroId(textType: number, level: number): number | null { this.load(); const type = Math.floor(Number(textType)); const lv = Math.floor(Number(level)); if (type < 1 || type > 5 || !(lv > 0)) return null; for (let i = 0; i < this._weapon1List.length; i++) { const hero = this._weapon1List[i]; if (Math.floor(Number(hero.herotype)) !== type) continue; if (Math.floor(Number(hero.level)) !== lv) continue; return Number(hero.id); } console.log('[WeaponCompose] 无五行英雄', type, lv); return null; } /** 蟠桃树合成等级上限(读 Hero.level_limit);其它 weapon2 返回 1 */ getKeepDigTileMaxLevel(weapon2Id: number): number { this.load(); if (!this.passiveSpawnKeepsDigCell(weapon2Id)) return 1; const w1 = this.getWeapon1ByGroupSequence([Number(weapon2Id)]); if (w1 == null) { console.log('[WeaponCompose] 蟠桃树无对应 Hero,等级上限用常量', weapon2Id, PEACH_TREE_MAX_LEVEL); return PEACH_TREE_MAX_LEVEL; } return this.getMaxWeaponLevel(w1); } getWeapon2Config(weapon2Id: number): ITableText | null { this.load(); return this._weapon2Map.get(Number(weapon2Id)) ?? null; } }