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/battle/ai/runtime/AiGemSocketAssigner.ts

164 lines
6.0 KiB

13 hours ago
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<ITableWeapon>(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<ITableWeapon>(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;
}
}