AI镶嵌宝石

master
humingxing 14 hours ago
parent a63e605506
commit 61f71f8f08
  1. 122
      assets/bundles/first/config/Rank.json
  2. 48
      assets/script/battle/ai/bridge/AiBattleBridge.ts
  3. 7
      assets/script/battle/ai/index.ts
  4. 163
      assets/script/battle/ai/runtime/AiGemSocketAssigner.ts
  5. 9
      assets/script/battle/ai/runtime/AiGemSocketAssigner.ts.meta
  6. 66
      assets/script/battle/ai/runtime/AiGemSocketStore.ts
  7. 9
      assets/script/battle/ai/runtime/AiGemSocketStore.ts.meta
  8. 6
      assets/script/battle/ai/types/AiHeroWeaponTypes.ts
  9. 21
      assets/script/battle/combat/GemBattleEffect.ts
  10. 3
      assets/script/battle/combat/TowerLoadoutService.ts
  11. 2
      assets/script/interface/AutoTable.ts

@ -11,7 +11,7 @@
"levelvalue": 0,
"diffcult": 0,
"weapondrop": 0,
"aiweapon": "100,0,0,0,0",
"aiweapon": "100,0,0,0,0,0",
"aiusead": 3300,
"aiusechanzi": [
5,
@ -35,7 +35,7 @@
"levelvalue": 0,
"diffcult": 0,
"weapondrop": 0,
"aiweapon": "90,10,0,0,0",
"aiweapon": "90,10,0,0,0,0",
"aiusead": 3350,
"aiusechanzi": [
5,
@ -59,7 +59,7 @@
"levelvalue": 0,
"diffcult": 0,
"weapondrop": 0,
"aiweapon": "80,20,0,0,0",
"aiweapon": "80,20,0,0,0,0",
"aiusead": 3400,
"aiusechanzi": [
5,
@ -83,7 +83,7 @@
"levelvalue": 0,
"diffcult": 0,
"weapondrop": 0,
"aiweapon": "70,30,0,0,0",
"aiweapon": "70,30,0,0,0,0",
"aiusead": 3450,
"aiusechanzi": [
5,
@ -107,7 +107,7 @@
"levelvalue": 0,
"diffcult": 1,
"weapondrop": 5,
"aiweapon": "60,40,0,0,0",
"aiweapon": "60,40,0,0,0,0",
"aiusead": 3500,
"aiusechanzi": [
5,
@ -131,7 +131,7 @@
"levelvalue": 0,
"diffcult": 1,
"weapondrop": 6,
"aiweapon": "50,50,0,0,0",
"aiweapon": "50,50,0,0,0,0",
"aiusead": 3550,
"aiusechanzi": [
5,
@ -155,7 +155,7 @@
"levelvalue": 0,
"diffcult": 1,
"weapondrop": 7,
"aiweapon": "40,60,0,0,0",
"aiweapon": "40,60,0,0,0,0",
"aiusead": 3600,
"aiusechanzi": [
5,
@ -179,7 +179,7 @@
"levelvalue": 0,
"diffcult": 1,
"weapondrop": 8,
"aiweapon": "30,70,0,0,0",
"aiweapon": "30,70,0,0,0,0",
"aiusead": 3650,
"aiusechanzi": [
5,
@ -203,7 +203,7 @@
"levelvalue": 1,
"diffcult": 2,
"weapondrop": 9,
"aiweapon": "20,80,0,0,0",
"aiweapon": "20,80,0,0,0,0",
"aiusead": 3700,
"aiusechanzi": [
5,
@ -227,7 +227,7 @@
"levelvalue": 1,
"diffcult": 2,
"weapondrop": 10,
"aiweapon": "10,90,0,0,0",
"aiweapon": "10,90,0,0,0,0",
"aiusead": 3750,
"aiusechanzi": [
5,
@ -251,7 +251,7 @@
"levelvalue": 1,
"diffcult": 2,
"weapondrop": 11,
"aiweapon": "0,90,10,0,0",
"aiweapon": "0,90,10,0,0,0",
"aiusead": 3800,
"aiusechanzi": [
5,
@ -275,7 +275,7 @@
"levelvalue": 1,
"diffcult": 2,
"weapondrop": 12,
"aiweapon": "0,85,15,0,0",
"aiweapon": "0,85,15,0,0,0",
"aiusead": 3850,
"aiusechanzi": [
5,
@ -299,7 +299,7 @@
"levelvalue": 1,
"diffcult": 3,
"weapondrop": 13,
"aiweapon": "0,80,20,0,0",
"aiweapon": "0,80,20,0,0,0",
"aiusead": 3900,
"aiusechanzi": [
5,
@ -323,7 +323,7 @@
"levelvalue": 1,
"diffcult": 3,
"weapondrop": 14,
"aiweapon": "0,75,25,0,0",
"aiweapon": "0,75,25,0,0,0",
"aiusead": 3950,
"aiusechanzi": [
5,
@ -347,7 +347,7 @@
"levelvalue": 1,
"diffcult": 3,
"weapondrop": 15,
"aiweapon": "0,70,30,0,0",
"aiweapon": "0,70,30,0,0,0",
"aiusead": 4000,
"aiusechanzi": [
5,
@ -371,7 +371,7 @@
"levelvalue": 1,
"diffcult": 3,
"weapondrop": 16,
"aiweapon": "0,65,30,5,0",
"aiweapon": "0,65,30,5,0,0",
"aiusead": 4050,
"aiusechanzi": [
5,
@ -395,7 +395,7 @@
"levelvalue": 2,
"diffcult": 4,
"weapondrop": 17,
"aiweapon": "0,60,30,10,0",
"aiweapon": "0,60,30,10,0,0",
"aiusead": 4100,
"aiusechanzi": [
5,
@ -419,7 +419,7 @@
"levelvalue": 2,
"diffcult": 4,
"weapondrop": 18,
"aiweapon": "0,55,35,10,0",
"aiweapon": "0,55,35,10,0,0",
"aiusead": 4150,
"aiusechanzi": [
5,
@ -443,7 +443,7 @@
"levelvalue": 2,
"diffcult": 4,
"weapondrop": 19,
"aiweapon": "0,50,35,15,0",
"aiweapon": "0,50,35,15,0,0",
"aiusead": 4200,
"aiusechanzi": [
5,
@ -467,7 +467,7 @@
"levelvalue": 2,
"diffcult": 4,
"weapondrop": 20,
"aiweapon": "0,45,40,15,0",
"aiweapon": "0,45,40,15,0,0",
"aiusead": 4250,
"aiusechanzi": [
5,
@ -491,7 +491,7 @@
"levelvalue": 2,
"diffcult": 5,
"weapondrop": 21,
"aiweapon": "0,40,40,20,0",
"aiweapon": "0,40,40,20,0,0",
"aiusead": 4300,
"aiusechanzi": [
5,
@ -515,7 +515,7 @@
"levelvalue": 2,
"diffcult": 5,
"weapondrop": 22,
"aiweapon": "0,35,45,20,0",
"aiweapon": "0,35,45,20,0,0",
"aiusead": 4350,
"aiusechanzi": [
5,
@ -539,7 +539,7 @@
"levelvalue": 2,
"diffcult": 5,
"weapondrop": 23,
"aiweapon": "0,30,45,25,0",
"aiweapon": "0,30,45,25,0,0",
"aiusead": 4400,
"aiusechanzi": [
5,
@ -563,7 +563,7 @@
"levelvalue": 2,
"diffcult": 5,
"weapondrop": 24,
"aiweapon": "0,25,50,25,0",
"aiweapon": "0,25,50,25,0,0",
"aiusead": 4450,
"aiusechanzi": [
5,
@ -587,7 +587,7 @@
"levelvalue": 3,
"diffcult": 6,
"weapondrop": 25,
"aiweapon": "0,20,50,25,0",
"aiweapon": "0,20,50,25,0,0",
"aiusead": 4500,
"aiusechanzi": [
5,
@ -611,7 +611,7 @@
"levelvalue": 3,
"diffcult": 6,
"weapondrop": 26,
"aiweapon": "0,15,50,30,5",
"aiweapon": "0,15,50,30,5,0",
"aiusead": 4550,
"aiusechanzi": [
5,
@ -635,7 +635,7 @@
"levelvalue": 3,
"diffcult": 6,
"weapondrop": 27,
"aiweapon": "0,10,50,35,5",
"aiweapon": "0,10,50,35,5,0",
"aiusead": 4600,
"aiusechanzi": [
5,
@ -659,7 +659,7 @@
"levelvalue": 3,
"diffcult": 6,
"weapondrop": 28,
"aiweapon": "0,5,50,35,10",
"aiweapon": "0,5,50,35,10,0",
"aiusead": 4650,
"aiusechanzi": [
5,
@ -683,7 +683,7 @@
"levelvalue": 3,
"diffcult": 7,
"weapondrop": 29,
"aiweapon": "0,0,50,40,10",
"aiweapon": "0,0,50,40,10,0",
"aiusead": 4700,
"aiusechanzi": [
5,
@ -707,7 +707,7 @@
"levelvalue": 3,
"diffcult": 7,
"weapondrop": 30,
"aiweapon": "0,0,45,40,15",
"aiweapon": "0,0,45,40,15,0",
"aiusead": 4750,
"aiusechanzi": [
5,
@ -731,7 +731,7 @@
"levelvalue": 3,
"diffcult": 7,
"weapondrop": 31,
"aiweapon": "0,0,40,45,15",
"aiweapon": "0,0,40,45,15,0",
"aiusead": 4800,
"aiusechanzi": [
5,
@ -755,7 +755,7 @@
"levelvalue": 3,
"diffcult": 7,
"weapondrop": 32,
"aiweapon": "0,0,35,45,20",
"aiweapon": "0,0,35,45,20,0",
"aiusead": 4850,
"aiusechanzi": [
5,
@ -779,7 +779,7 @@
"levelvalue": 4,
"diffcult": 8,
"weapondrop": 33,
"aiweapon": "0,0,30,50,20",
"aiweapon": "0,0,30,50,20,0",
"aiusead": 4900,
"aiusechanzi": [
5,
@ -803,7 +803,7 @@
"levelvalue": 4,
"diffcult": 8,
"weapondrop": 34,
"aiweapon": "0,0,25,50,25",
"aiweapon": "0,0,25,50,25,0",
"aiusead": 4950,
"aiusechanzi": [
5,
@ -827,7 +827,7 @@
"levelvalue": 4,
"diffcult": 8,
"weapondrop": 35,
"aiweapon": "0,0,20,55,25",
"aiweapon": "0,0,20,55,25,0",
"aiusead": 5000,
"aiusechanzi": [
5,
@ -851,7 +851,7 @@
"levelvalue": 4,
"diffcult": 8,
"weapondrop": 36,
"aiweapon": "0,0,15,55,30",
"aiweapon": "0,0,15,55,30,0",
"aiusead": 5050,
"aiusechanzi": [
5,
@ -875,7 +875,7 @@
"levelvalue": 4,
"diffcult": 9,
"weapondrop": 37,
"aiweapon": "0,0,10,60,30",
"aiweapon": "0,0,10,60,30,0",
"aiusead": 5100,
"aiusechanzi": [
5,
@ -899,7 +899,7 @@
"levelvalue": 4,
"diffcult": 9,
"weapondrop": 38,
"aiweapon": "0,0,5,60,35",
"aiweapon": "0,0,5,60,35,0",
"aiusead": 5150,
"aiusechanzi": [
5,
@ -923,7 +923,7 @@
"levelvalue": 4,
"diffcult": 9,
"weapondrop": 39,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,60,40,0",
"aiusead": 5200,
"aiusechanzi": [
5,
@ -947,7 +947,7 @@
"levelvalue": 4,
"diffcult": 9,
"weapondrop": 40,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,60,40,0",
"aiusead": 5250,
"aiusechanzi": [
5,
@ -971,7 +971,7 @@
"levelvalue": 4,
"diffcult": 10,
"weapondrop": 41,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,60,40,0",
"aiusead": 5300,
"aiusechanzi": [
5,
@ -995,7 +995,7 @@
"levelvalue": 4,
"diffcult": 10,
"weapondrop": 42,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,60,40,0",
"aiusead": 5350,
"aiusechanzi": [
5,
@ -1019,7 +1019,7 @@
"levelvalue": 4,
"diffcult": 10,
"weapondrop": 43,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,55,40,5",
"aiusead": 5400,
"aiusechanzi": [
5,
@ -1043,7 +1043,7 @@
"levelvalue": 4,
"diffcult": 10,
"weapondrop": 44,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,55,40,5",
"aiusead": 5450,
"aiusechanzi": [
5,
@ -1067,7 +1067,7 @@
"levelvalue": 4,
"diffcult": 11,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,55,40,5",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1091,7 +1091,7 @@
"levelvalue": 4,
"diffcult": 11,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,50,40,10",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1115,7 +1115,7 @@
"levelvalue": 4,
"diffcult": 11,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,50,40,10",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1139,7 +1139,7 @@
"levelvalue": 4,
"diffcult": 11,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,50,40,10",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1163,7 +1163,7 @@
"levelvalue": 4,
"diffcult": 12,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,50,40,10",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1187,7 +1187,7 @@
"levelvalue": 4,
"diffcult": 12,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,45,40,15",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1211,7 +1211,7 @@
"levelvalue": 4,
"diffcult": 12,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,45,40,15",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1235,7 +1235,7 @@
"levelvalue": 4,
"diffcult": 12,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,45,40,15",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1259,7 +1259,7 @@
"levelvalue": 4,
"diffcult": 13,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,45,40,15",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1283,7 +1283,7 @@
"levelvalue": 4,
"diffcult": 13,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,45,40,15",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1307,7 +1307,7 @@
"levelvalue": 5,
"diffcult": 13,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,40,40,20",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1331,7 +1331,7 @@
"levelvalue": 5,
"diffcult": 13,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,40,40,20",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1355,7 +1355,7 @@
"levelvalue": 5,
"diffcult": 14,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,40,40,20",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1379,7 +1379,7 @@
"levelvalue": 5,
"diffcult": 14,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,40,40,20",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1403,7 +1403,7 @@
"levelvalue": 5,
"diffcult": 14,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,35,40,25",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1427,7 +1427,7 @@
"levelvalue": 5,
"diffcult": 14,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,30,40,30",
"aiusead": 5500,
"aiusechanzi": [
5,
@ -1451,7 +1451,7 @@
"levelvalue": 5,
"diffcult": 15,
"weapondrop": 50,
"aiweapon": "0,0,0,60,40",
"aiweapon": "0,0,0,25,40,35",
"aiusead": 5500,
"aiusechanzi": [
5,

@ -1,5 +1,5 @@
import type { Node } from 'cc';
import { aiLog, aiOp, aiWarn } from '../runtime/AiLog';
import { aiOp } from '../runtime/AiLog';
import type { ProjectBattleHandle } from '../../runtime/BattleRuntime';
import type { MapManager } from '../../map/legacy/MapManager';
import type { AiMatchProfile } from '../match/AiMatchProfile';
@ -13,15 +13,15 @@ import {
} from '../runtime/AiBattlePersonality';
import { AiHeadlessBagHost } from '../runtime/AiHeadlessBagHost';
import { AiOpponentController } from '../runtime/AiOpponentController';
import { AiHeroWeaponRegistry } from '../runtime/AiHeroWeaponRegistry';
import { AiHeroWeaponAssigner } from '../runtime/AiHeroWeaponAssigner';
import { AiGemSocketStore } from '../runtime/AiGemSocketStore';
import { AiGemSocketAssigner } from '../runtime/AiGemSocketAssigner';
import { AiSkillHost } from '../runtime/AiSkillHost';
import { AiMapTextHost } from '../runtime/AiMapTextHost';
import { AiPassiveCdHost, type AiPassiveCdUiSlot } from '../runtime/AiPassiveCdHost';
import { AiActiveSkillHost, type AiActiveSkillUiSlot, type AiActiveSkillCastFxHandler } from '../runtime/AiActiveSkillHost';
import { AiAdHost } from '../runtime/AiAdHost';
import type { AiMatchBuildInput } from '../types/AiTypes';
import { setAiHeroWeaponResolver } from '../../combat/TowerLoadoutService';
import { setAiGemSlotsResolver } from '../../combat/GemBattleEffect';
import { SkillAtkBuffBoardViewBind } from '../../combat/SkillAtkBuffBoardBridge';
import { AiMatchPlayerBuffCounts } from '../match/AiMatchPlayerBuffCounts';
import { BattleBuffCarryStore } from '../../../manager/battleBuff/BattleBuffCarryStore';
@ -38,8 +38,8 @@ export class AiBattleBridge {
private readonly _personalityFactory = new AiBattlePersonalityFactory();
private readonly _bagHost = new AiHeadlessBagHost();
private readonly _controller = new AiOpponentController();
private readonly _weaponRegistry = new AiHeroWeaponRegistry();
private readonly _weaponAssigner = new AiHeroWeaponAssigner();
private readonly _gemStore = new AiGemSocketStore();
private readonly _gemAssigner = new AiGemSocketAssigner();
private readonly _skillHost = new AiSkillHost();
private readonly _mapText = new AiMapTextHost();
private readonly _passiveCd = new AiPassiveCdHost();
@ -307,7 +307,8 @@ export class AiBattleBridge {
console.error('[AiBattleBridge] start aborted: headless bag bind failed');
return false;
}
this._weaponRegistry.clear();
this._gemAssigner.fill(this._gemStore, resolved.opponentRankId, resolved.seed);
setAiGemSlotsResolver(this._resolveAiGemSlots);
this._battle = battle;
this._map = map;
this._profile = resolved;
@ -330,7 +331,6 @@ export class AiBattleBridge {
this._passiveCd.bind(this._bagHost, battle, this._onBoardChanged);
this._adHost.bind(this._bagHost, battle, map, resolved.opponentRankId, this._onBoardChanged);
this._onBoardChanged();
setAiHeroWeaponResolver(this._resolveAiWeaponForLoadout);
SkillAtkBuffBoardViewBind.bindEnemyNotify(this._onBoardChanged);
console.warn(
'[AiBattleBridge] 就绪',
@ -338,6 +338,7 @@ export class AiBattleBridge {
'gold=' + this._bagHost.aiGold,
'nextCost=' + this._bagHost.nextRefreshCost,
'text=' + personality.textPolicy,
'gemFilled=' + this._gemStore.countFilled(),
);
return true;
}
@ -391,16 +392,16 @@ export class AiBattleBridge {
};
/**
* 供 TowerLoadout 注册的 AI 武器解析(避免循环 require)。
* @param heroId Hero.id
* 供 GemBattleEffect 读 AI 五行槽。
* @param element 五行 1~5
*/
private readonly _resolveAiWeaponForLoadout = (heroId: number): number | null => {
return this.getHeroBattleWeaponId(heroId);
private readonly _resolveAiGemSlots = (element: number): readonly number[] | null => {
return this._gemStore.getElementSlots(element);
};
/** 战斗结束或切场景时停止 AI */
stop(): void {
setAiHeroWeaponResolver(null);
setAiGemSlotsResolver(null);
SkillAtkBuffBoardViewBind.bindEnemyNotify(null);
this._controller.stop();
this._passiveCd.dispose();
@ -412,10 +413,11 @@ export class AiBattleBridge {
this._mapText.clear();
this._skillHost.dispose();
this._bagHost.dispose();
this._weaponRegistry.clear();
this._gemStore.clear();
BattleBuffCarryStore.clear();
this._battle = null;
this._map = null;
this._profile = null;
}
/**
@ -451,21 +453,11 @@ export class AiBattleBridge {
}
/**
* AI 战斗武器 id(旧 aiweapon 分配已下线,非 null 时亦可能为 0)。
* @param heroId Hero.id
* 本局 AI 某五行宝石槽(只读副本)。
* @param element 五行 1~5
*/
getHeroBattleWeaponId(heroId: number): number | null {
if (!this._profile) {
aiWarn(`[AiHeroWeapon] skip: profile missing heroId=${heroId}`);
return null;
}
const record = this._weaponAssigner.resolveOrAssign(
this._weaponRegistry,
heroId,
this._profile.opponentRankId,
this._profile.seed,
);
return record != null ? record.weaponId : null;
getAiElementGemSlots(element: number): number[] {
return this._gemStore.getElementSlots(element);
}
/**

@ -13,8 +13,8 @@ export {
} from './runtime/AiBattlePersonality';
export { AiHeadlessBagHost } from './runtime/AiHeadlessBagHost';
export { AiOpponentController } from './runtime/AiOpponentController';
export { AiHeroWeaponRegistry } from './runtime/AiHeroWeaponRegistry';
export { AiHeroWeaponAssigner } from './runtime/AiHeroWeaponAssigner';
export { AiGemSocketStore } from './runtime/AiGemSocketStore';
export { AiGemSocketAssigner, AI_GEM_WEIGHT_COUNT } from './runtime/AiGemSocketAssigner';
export { AiSkillHost } from './runtime/AiSkillHost';
export { AiMapTextHost } from './runtime/AiMapTextHost';
export { AiPassiveCdHost } from './runtime/AiPassiveCdHost';
@ -46,6 +46,3 @@ export {
type AiPlannedAction,
type AiMatchBuildInput,
} from './types/AiTypes';
export type { AiHeroWeaponRecord } from './types/AiHeroWeaponTypes';
export { AI_WEAPON_QUALITY_COUNT } from './types/AiHeroWeaponTypes';

@ -0,0 +1,163 @@
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;
}
}

@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "9d5c95c8-e3be-4a32-8d68-07af92bb5a26",
"files": [],
"subMetas": {},
"userData": {}
}

@ -0,0 +1,66 @@
import { GEM_SLOT_COUNT } from '../../../game/GameDataEnums';
/** 五行数量 */
const ELEMENT_COUNT = 5;
/**
* 本局 AI 宝石镶嵌存档:五行 × 4 槽。
* 开战灌入,局内只读,结束清空。
*/
export class AiGemSocketStore {
/** element(1~5) → 长度 4 的宝石 id */
private readonly _slots: number[][] = [];
constructor() {
this.clear();
}
/** 清空全部槽 */
clear(): void {
this._slots.length = 0;
for (let e = 0; e <= ELEMENT_COUNT; e++) {
this._slots[e] = [0, 0, 0, 0];
}
}
/**
* 读某五行 4 槽(副本)。
* @param element Hero.herotype 1~5
*/
getElementSlots(element: number): number[] {
const e = Math.floor(Number(element) || 0);
const out = [0, 0, 0, 0];
if (e < 1 || e > ELEMENT_COUNT) return out;
const src = this._slots[e];
for (let i = 0; i < GEM_SLOT_COUNT; i++) {
out[i] = Math.max(0, Math.floor(Number(src[i]) || 0));
}
return out;
}
/**
* 写入单槽。
* @param element 五行 1~5
* @param slot 槽 0~3
* @param gemId Weapon.id;0=空
*/
setSlot(element: number, slot: number, gemId: number): boolean {
const e = Math.floor(Number(element) || 0);
const s = Math.floor(Number(slot) || 0);
if (e < 1 || e > ELEMENT_COUNT || s < 0 || s >= GEM_SLOT_COUNT) return false;
this._slots[e][s] = Math.max(0, Math.floor(Number(gemId) || 0));
return true;
}
/** 已镶嵌槽数(gemId>0) */
countFilled(): number {
let n = 0;
for (let e = 1; e <= ELEMENT_COUNT; e++) {
const row = this._slots[e];
for (let s = 0; s < GEM_SLOT_COUNT; s++) {
if (row[s] > 0) n++;
}
}
return n;
}
}

@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "987914d6-2b9b-4799-a189-de1f6765bc51",
"files": [],
"subMetas": {},
"userData": {}
}

@ -8,5 +8,7 @@ export interface AiHeroWeaponRecord {
quality: number;
}
/** 武器品质档位数(Rank.aiweapon 五档:白绿蓝紫橙,quality 0~4) */
export const AI_WEAPON_QUALITY_COUNT = 5;
/**
* @deprecated 已改为宝石六档,见 `AI_GEM_WEIGHT_COUNT`(无/绿/蓝/紫/橙/红)。
*/
export const AI_WEAPON_QUALITY_COUNT = 6;

@ -9,6 +9,17 @@ const LOG = '[GemBattle]';
/** modifier.source 前缀,刷新时整批清除 */
const SOURCE_PREFIX = 'gem_';
/** AI 五行槽读取(AiBattleBridge 开战注册) */
let _aiGemSlotsResolver: ((element: number) => readonly number[] | null) | null = null;
/**
* 注册/清除 AI 宝石槽解析。
* @param resolver element→4 槽;null 清除
*/
export function setAiGemSlotsResolver(resolver: ((element: number) => readonly number[] | null) | null): void {
_aiGemSlotsResolver = resolver;
}
/** 五行镶嵌解析结果(局内用) */
export interface IGemBattleResolve {
/** 提供技能的宝石 id(0=无) */
@ -33,7 +44,7 @@ export interface IGemBattleAttr {
/**
* 局内宝石生效:读镶嵌 → 属性 modifier → 技能宝石 id。
* 仅本机玩家;AI 返回空。
* 本机读 `gg.gem`;AI 读开战灌入的槽。
*/
export class GemBattleEffect {
/**
@ -43,17 +54,23 @@ export class GemBattleEffect {
*/
static resolve(element: number, player: Player | null | undefined): IGemBattleResolve {
const empty: IGemBattleResolve = { skillGemId: 0, skillId: 0, attrs: [] };
if (!player || !player.isLocal) return empty;
if (!player) return empty;
const e = Math.floor(Number(element) || 0);
if (e < 1 || e > 5) return empty;
let slots: number[];
if (player.isLocal) {
try {
slots = gg.gem.getElementSlots(e);
} catch (err) {
console.warn(LOG, 'getElementSlots 失败', e, err);
return empty;
}
} else {
const aiSlots = _aiGemSlotsResolver?.(e) ?? null;
if (!aiSlots || aiSlots.length < 4) return empty;
slots = [aiSlots[0] | 0, aiSlots[1] | 0, aiSlots[2] | 0, aiSlots[3] | 0];
}
const attrs: IGemBattleAttr[] = [];
let skillGemId = 0;

@ -45,11 +45,8 @@ export function setAiHeroWeaponResolver(resolver: ((heroId: number) => number |
export function resolveHeroBattleWeaponId(heroId: number, player?: Player | null): number {
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, heroId);
if (isElementHero(hero)) {
if (player?.isLocal) {
return GemBattleEffect.resolve(Math.floor(Number(hero!.herotype)), player).skillGemId;
}
return 0;
}
if (player && !player.isLocal) {
if (_aiHeroWeaponResolver) {
const aiId = _aiHeroWeaponResolver(heroId);

@ -33,7 +33,7 @@ declare global {
/** 装备掉落概率加值,万分比 */
readonly weapondrop: number;
/** ai武将装备品质
白,绿,蓝,紫,橙 */
无,绿,蓝,紫,橙,红 */
readonly aiweapon: string;
/** ai广告使用几率 */
readonly aiusead: number;

Loading…
Cancel
Save