import { TableNames } from '../../../game/ConfigTableData'; import { GEM_SLOT_COUNT } from '../../../game/GameDataEnums'; import { RankDataManager } from '../../../manager/RankDataManager'; import { aiLog, aiOp, aiWarn } from './AiLog'; import type { AiGemSocketStore } from './AiGemSocketStore'; /** Rank.aiweapon 六档:无 / 绿 / 蓝 / 紫 / 橙 / 红 */ export const AI_GEM_WEIGHT_COUNT = 6; /** 五行数量 */ const ELEMENT_COUNT = 5; /** * 开战按对手段位 `Rank.aiweapon` 一次性镶嵌 AI 宝石。 * 20 槽各自独立掷骰:是否镶嵌 + 镶嵌何物;遵守「每五行最多 1 颗专属」。 */ export class AiGemSocketAssigner { /** * 灌满 store。 * @param store 局内存档 * @param opponentRankId 对手段位 * @param seed 匹配种子 */ fill(store: AiGemSocketStore, opponentRankId: number, seed: number): void { store.clear(); const rank = RankDataManager.getRankConfig(opponentRankId); if (!rank) { aiWarn(`[AiGem] Rank 缺失 rankId=${opponentRankId},本局 AI 不镶嵌`); return; } const weights = this._parseWeights(rank.aiweapon); if (!weights) { aiWarn(`[AiGem] aiweapon 无效 rankId=${opponentRankId} raw=${rank.aiweapon}`); return; } const baseSeed = (Math.floor(Number(seed) || 0) >>> 0) || 1; let filled = 0; for (let element = 1; element <= ELEMENT_COUNT; element++) { let hasExclusive = false; for (let slot = 0; slot < GEM_SLOT_COUNT; slot++) { const salt = element * 100 + slot; const band = this._rollBand(weights, baseSeed, salt); if (band == null) { aiWarn(`[AiGem] 掷骰失败 element=${element} slot=${slot}`); continue; } if (band === 0) { store.setSlot(element, slot, 0); continue; } const quality = band; const gemId = this._pickGemId(element, quality, hasExclusive, baseSeed, salt ^ 0x9e3779b9); if (gemId <= 0) { store.setSlot(element, slot, 0); continue; } const row = gg.data.table.getTableData(TableNames.Weapon, gemId); const gemType = Math.floor(Number(row?.type) || 0); if (gemType >= 2 && gemType <= 6) hasExclusive = true; store.setSlot(element, slot, gemId); filled += 1; } } aiOp( `[AiGem] 开战镶嵌完成 rank=${opponentRankId} filled=${filled}/20 weights=${rank.aiweapon}`, ); } /** * 解析六档权重。 * @param raw Rank.aiweapon */ private _parseWeights(raw: string): number[] | null { const parts = String(raw ?? '').split(','); if (parts.length !== AI_GEM_WEIGHT_COUNT) { aiLog('[AiGem] aiweapon 长度须为 6', raw); return null; } const out: number[] = []; let total = 0; for (let i = 0; i < AI_GEM_WEIGHT_COUNT; i++) { const v = Math.max(0, Math.floor(Number(parts[i]) || 0)); out.push(v); total += v; } if (total <= 0) { aiLog('[AiGem] aiweapon 权重和为 0', raw); return null; } return out; } /** * 加权抽档:0=空,1~5=品质绿~红。 * @param weights 六档 * @param seed 局种子 * @param salt 槽盐 */ private _rollBand(weights: number[], seed: number, salt: number): number | null { let total = 0; for (let i = 0; i < weights.length; i++) total += weights[i]; if (total <= 0) return null; let roll = this._unit(seed, salt) * total; for (let i = 0; i < weights.length; i++) { roll -= weights[i]; if (roll < 0) return i; } return AI_GEM_WEIGHT_COUNT - 1; } /** * 在合法候选中抽一颗(通用 + 未占用专属)。 * @param element 五行 * @param quality Weapon.quality 1~5 * @param hasExclusive 本五行是否已有专属 * @param seed 局种子 * @param salt 抽选盐 */ private _pickGemId(element: number, quality: number, hasExclusive: boolean, seed: number, salt: number): number { const q = Math.floor(Number(quality) || 0); if (q < 1 || q > 5) { aiWarn(`[AiGem] 品质越界 q=${q} element=${element}`); return 0; } const needExclusive = element + 1; const list = gg.data.table.getTableList(TableNames.Weapon) ?? []; const candidates: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i]; const id = Math.floor(Number(row?.id) || 0); if (id <= 0) continue; if (Math.floor(Number(row.quality) || 0) !== q) continue; const t = Math.floor(Number(row.type) || 0); if (t === 1) { candidates.push(id); continue; } if (t === needExclusive && !hasExclusive) { candidates.push(id); } } if (candidates.length <= 0) { aiWarn(`[AiGem] 无候选宝石 element=${element} quality=${q} exclusiveUsed=${hasExclusive}`); return 0; } candidates.sort((a, b) => a - b); const idx = Math.floor(this._unit(seed, salt) * candidates.length); const pick = candidates[Math.min(candidates.length - 1, Math.max(0, idx))]; return pick; } /** * 种子均匀 [0,1)。 * @param seed 局种子 * @param salt 扰动 */ private _unit(seed: number, salt: number): number { let x = ((Math.floor(Number(seed) || 0) ^ Math.floor(Number(salt) || 0)) >>> 0) || 1; x ^= x << 13; x ^= x >>> 17; x ^= x << 5; return (x >>> 0) / 4294967296; } }