import type { IComposeConfigProvider } from 'db://assets/mx/module/merge'; import { mapValuesToArray } 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 _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._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._maxLevel.clear(); this._loaded = true; } /** Weapon2 解锁章节(新产品无 unlock 字段,默认第 1 章可用) */ getWeapon2UnlockChapter(_weapon2: ITableText): number { return 1; } /** 背包刷新候选:Text.weight>0 且 type 为五行(1–5) */ getWeapon2RefreshCandidates(chapterId: number): ITableText[] { this.load(); const result: ITableText[] = []; for (const w2 of this._weapon2Map.values()) { const w2Type = Number(w2.type); if (Number(w2.weight) <= 0) continue; if (!isElementGlyphType(w2Type)) continue; if (this.getWeapon2UnlockChapter(w2) > chapterId) continue; if (w2Type === Weapon2TileType.Grid) continue; result.push(w2); } return result; } 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)); if (!w1) return []; const ht = Math.floor(Number(w1.herotype)); if (ht >= 1 && ht <= 5) { const lv = Math.floor(Number(w1.level)); for (const w2 of this._weapon2Map.values()) { if (Math.floor(Number(w2.type)) !== ht) continue; if (Math.floor(Number(w2.level)) !== lv) continue; const id = Math.floor(Number(w2.id)); if (id > 0) return [id]; } console.log('[WeaponCompose] Hero 无对应 Text', weapon1Id, ht, lv); return []; } if (Math.floor(Number(w1.id)) === PEACH_TREE_WEAPON2_ID) { return [PEACH_TREE_WEAPON2_ID]; } return []; } getWeapon1ByGroupSequence(sequence: readonly number[]): number | null { this.load(); if (!sequence.length) return null; if (sequence.length > 1) { console.log('[WeaponCompose] 多字组合索引已下线', sequence.join(',')); return null; } const w2Id = Math.floor(Number(sequence[0]) || 0); if (!(w2Id > 0)) return null; if (w2Id === PEACH_TREE_WEAPON2_ID) { return PEACH_TREE_WEAPON2_ID; } const row = this._weapon2Map.get(w2Id); if (!row) return null; const type = Math.floor(Number(row.type)); if (!isElementGlyphType(type)) return null; const lv = Math.max(1, Math.floor(Number(row.level) || 1)); return this.getElementHeroId(type, lv); } 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; } if (id === PEACH_TREE_WEAPON2_ID) { this._maxLevel.set(id, PEACH_TREE_MAX_LEVEL); return PEACH_TREE_MAX_LEVEL; } console.log('[WeaponCompose] 非五行 Hero 等级上限按 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; } /** 蟠桃树合成等级上限;其它非五行 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; } }