import { TableNames } from '../../../game/ConfigTableData'; import { aiLog, aiOp, aiWarn } from './AiLog'; import { RankDataManager } from '../../../manager/RankDataManager'; import type { AiHeroWeaponRecord } from '../types/AiHeroWeaponTypes'; import { AI_WEAPON_QUALITY_COUNT } from '../types/AiHeroWeaponTypes'; import type { AiHeroWeaponRegistry } from './AiHeroWeaponRegistry'; /** * 需求 5.6:Rank.aiweapon 品质加权掷骰 + Hero.weapon 约束下解析 Weapon.id。 * 各武将独立抽取,结果可重复;已入档的 heroId 直接复用。 */ export class AiHeroWeaponAssigner { /** * 获取或分配本局 AI 武将武器。 * @param registry 局内存档 * @param heroId Hero.id * @param opponentRankId 对手段位(读 aiweapon) * @param seed 随机种子(子种子含 heroId) */ resolveOrAssign(registry: AiHeroWeaponRegistry, heroId: number, opponentRankId: number, seed: number): AiHeroWeaponRecord | null { const hid = Math.floor(Number(heroId) || 0); if (hid <= 0) { aiLog('[AiHeroWeaponAssigner] invalid heroId', heroId); return null; } const cached = registry.getWeaponId(hid); if (cached != null) { return { heroId: hid, weaponId: cached, quality: this._readWeaponQuality(cached) }; } const hero = gg.data.table.getTableData(TableNames.Hero, hid); if (!hero) { aiLog('[AiHeroWeaponAssigner] Hero missing', hid); return null; } if (Number(hero.herotype) !== 2) { aiWarn(`[AiHeroWeapon] skip: herotype!=2 heroId=${hid} type=${hero.herotype}`); return null; } const rank = RankDataManager.getRankConfig(opponentRankId); if (!rank) { aiWarn(`[AiHeroWeapon] skip: Rank missing rankId=${opponentRankId} heroId=${hid}`); return null; } const weights = this._parseAiWeaponWeights(rank.aiweapon); const quality = this._rollQuality(weights, seed, hid); if (quality == null) { aiWarn(`[AiHeroWeapon] skip: aiweapon roll failed heroId=${hid} weights=${rank.aiweapon}`); return null; } const weaponId = this._pickWeaponId(hero, quality); if (weaponId <= 0) { aiWarn(`[AiHeroWeapon] skip: no weapon heroId=${hid} quality=${quality}`); return null; } const record: AiHeroWeaponRecord = { heroId: hid, weaponId, quality }; registry.put(record); const weaponRow = gg.data.table.getTableData(TableNames.Weapon, weaponId); const weaponName = (weaponRow?.name ?? '').trim() || `id=${weaponId}`; const heroName = (hero.name ?? '').trim() || `id=${hid}`; const qLabel = this._qualityLabel(quality); // 首次分配始终打印,便于核对 aiweapon + Hero.weapon aiOp( `[AiHeroWeapon] assign hero=${heroName}(${hid}) weaponType=${Math.floor(Number(hero.weapon) || 0)}` + ` -> ${weaponName}(${weaponId}) quality=${qLabel}(${quality}) rank=${opponentRankId}`, ); return record; } /** 解析 Rank.aiweapon 五档权重 */ private _parseAiWeaponWeights(raw: string): number[] { const parts = (raw ?? '').split(','); const out: number[] = []; for (let i = 0; i < AI_WEAPON_QUALITY_COUNT; i++) { out.push(Math.max(0, Math.floor(Number(parts[i]) || 0))); } if (parts.length !== AI_WEAPON_QUALITY_COUNT) { aiLog('[AiHeroWeaponAssigner] aiweapon length invalid'); } return out; } /** 加权随机品质 0~4(白/绿/蓝/紫/橙) */ private _rollQuality(weights: number[], seed: number, heroId: number): number | null { let total = 0; for (let i = 0; i < weights.length; i++) total += weights[i]; if (total <= 0) { aiLog('[AiHeroWeaponAssigner] aiweapon total weight <= 0'); return null; } let roll = this._seededUnit(seed, heroId) * total; for (let i = 0; i < weights.length; i++) { roll -= weights[i]; if (roll < 0) return i; } return AI_WEAPON_QUALITY_COUNT - 1; } /** 按 Hero.weapon 与品质选 Weapon.id */ private _pickWeaponId(hero: ITableHero, quality: number): number { const q = Math.floor(Number(quality) || 0); const weaponType = Math.floor(Number(hero.weapon) || 0); const personal = Math.floor(Number(hero.personalweapon) || 0); if (personal > 0) { const pe = gg.data.table.getTableData(TableNames.Weapon, personal); if (pe && Number(pe.type) === weaponType && Number(pe.quality) === q) return personal; } const list = gg.data.table.getTableList(TableNames.Weapon) ?? []; let best = 0; for (let i = 0; i < list.length; i++) { const row = list[i]; if (Number(row.type) !== weaponType) continue; if (Number(row.quality) !== q) continue; const id = Math.floor(Number(row.id) || 0); if (id <= 0) continue; if (best <= 0 || id < best) best = id; } if (best > 0) return best; const fallback = Math.floor(Number(hero.startweapon) || 0); if (fallback <= 0) { aiLog('[AiHeroWeaponAssigner] fallback startweapon missing', hero.id, q); return 0; } aiLog('[AiHeroWeaponAssigner] no weapon for quality, fallback startweapon', hero.id, q, fallback); return fallback; } private _readWeaponQuality(weaponId: number): number { const row = gg.data.table.getTableData(TableNames.Weapon, weaponId); return Math.floor(Number(row?.quality) || 0); } /** * 品质档可读名(与 Rank.aiweapon 五档一致)。 * @param quality 0~4 */ private _qualityLabel(quality: number): string { const q = Math.floor(Number(quality) || 0); if (q === 0) return '白'; if (q === 1) return '绿'; if (q === 2) return '蓝'; if (q === 3) return '紫'; if (q === 4) return '橙'; return `q${q}`; } /** seed + heroId 子随机 [0,1) */ private _seededUnit(seed: number, heroId: number): number { let x = (Math.floor(Number(seed) || 0) ^ (Math.floor(Number(heroId) || 0) * 2654435761)) >>> 0; x ^= x << 13; x ^= x >>> 17; x ^= x << 5; return (x >>> 0) / 4294967296; } }