From 82d1644cbb90921ec97ee8987170c2f7d974b563 Mon Sep 17 00:00:00 2001 From: humingxing <930621236@qq.com> Date: Sat, 10 Oct 2026 17:36:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAI=E6=96=B9=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E7=9A=84buff=E6=8A=80=E8=83=BD=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../battle/ai/decision/AiActionPlanner.ts | 10 ++- .../script/battle/ai/decision/AiBoardProbe.ts | 89 +++++++++++++++++++ docs/方案-Battlebuff新表实现.md | 1 + 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/assets/script/battle/ai/decision/AiActionPlanner.ts b/assets/script/battle/ai/decision/AiActionPlanner.ts index 5184568..4ad4d06 100644 --- a/assets/script/battle/ai/decision/AiActionPlanner.ts +++ b/assets/script/battle/ai/decision/AiActionPlanner.ts @@ -5,7 +5,7 @@ import { AiBoardProbe } from './AiBoardProbe'; import { aiOp } from '../runtime/AiLog'; /** - * AI 决策:解锁 → 五行合成 → 上阵。 + * AI 决策:解锁 → 产卡使用 → 五行合成 → 上阵。 */ export class AiActionPlanner { private readonly _probe = new AiBoardProbe(); @@ -32,6 +32,12 @@ export class AiActionPlanner { return this._logAction('解锁地块', { kind: AiActionKind.Place, merge: unlock }, emptyPut, dig); } + const card = this._probe.findBattleCardAction(ctx.bagSession, ctx.map); + if (card) { + // Place:勿用 Upgrade,避免 onBagMergedAt 误推进待开启地块 + return this._logAction('产卡使用', { kind: AiActionKind.Place, merge: card }, emptyPut, dig); + } + const upgrade = this._probe.findUpgradeAction(ctx.bagSession, ctx.map); if (upgrade) { return this._logAction('数字合成', { kind: AiActionKind.Upgrade, merge: upgrade }, emptyPut, dig); @@ -70,7 +76,7 @@ export class AiActionPlanner { const dst = m.target.kind === 'bag' ? `bag[${m.target.slotIndex}]` : `map[${m.target.gridIndex}]`; detail += ` ${src}->${dst}`; } - if (label === '解锁地块' || label === '数字合成' || label === '上阵') { + if (label === '解锁地块' || label === '产卡使用' || label === '数字合成' || label === '上阵') { console.warn('[AI操作]', `决策 ${label} | ${detail} | emptyPut=${emptyPut} dig=${dig}`); } else { aiOp(`决策 ${label} | ${detail} | emptyPut=${emptyPut} dig=${dig}`); diff --git a/assets/script/battle/ai/decision/AiBoardProbe.ts b/assets/script/battle/ai/decision/AiBoardProbe.ts index 1f7e3b0..be2d343 100644 --- a/assets/script/battle/ai/decision/AiBoardProbe.ts +++ b/assets/script/battle/ai/decision/AiBoardProbe.ts @@ -1,4 +1,5 @@ import { + BattleBagCardKind, MapCellKind, Weapon2TileType, isElementGlyphType, @@ -188,6 +189,94 @@ export class AiBoardProbe { return bestMap ? { source: bestMap.source, target: bestMap.target } : null; } + /** + * 倍率卡/属性卡:拖到己方可拖开启格或地图五行数字(优先场上、高等级)。 + * @param session 会话 + * @param map 地图 + */ + findBattleCardAction(session: BattleBagSession, map: MapManager): { source: DragSource; target: DropTarget } | null { + WeaponComposeConfigProvider.instance.load(); + const provider = WeaponComposeConfigProvider.instance; + const slots = session.getBagSlots(); + let best: { source: DragSource; target: DropTarget; score: number } | null = null; + + for (let s = 0; s < slots.length; s++) { + const card = slots[s]; + if (!card) continue; + const kind = Math.floor(Number(card.battleCardKind) || 0); + if (kind !== BattleBagCardKind.Multiplier && kind !== BattleBagCardKind.Attr) continue; + if (!this._canDragBagSlot(session, s)) continue; + + for (let t = 0; t < slots.length; t++) { + if (t === s) continue; + const dst = slots[t]; + if (!dst || dst.battleCardKind) continue; + const land = session.landStateOf?.(t) ?? TextListCellState.Open; + if (land !== TextListCellState.Open) continue; + if (!this._isBattleCardDigitTarget(provider, dst.weapon2Id, dst.level ?? 1, kind)) continue; + const score = this._scoreBattleCardTarget(provider, dst.weapon2Id, dst.level ?? 1, false, t); + if (!best || score > best.score) { + best = { + source: { kind: 'bag', slotIndex: s }, + target: { kind: 'bag', slotIndex: t }, + score, + }; + } + } + + const grids = this._listActiveGrids(map); + for (let g = 0; g < grids.length; g++) { + const mg = grids[g]; + const tile = session.merge.map.getTile(mg.Index); + if (!tile || tile.battleCardKind) continue; + if (!session.validator.isAllowedCampForPlace(mg.Index)) continue; + if (!this._isBattleCardDigitTarget(provider, tile.weapon2Id, tile.level ?? 1, kind)) continue; + const score = this._scoreBattleCardTarget(provider, tile.weapon2Id, tile.level ?? 1, true, mg.Index); + if (!best || score > best.score) { + best = { + source: { kind: 'bag', slotIndex: s }, + target: { kind: 'map', gridIndex: mg.Index }, + score, + }; + } + } + } + + return best ? { source: best.source, target: best.target } : null; + } + + /** + * 产卡目标是否为可生效五行数字。 + * @param provider 配表 + * @param weapon2Id 目标字 + * @param level 显示等级 + * @param kind 卡种类 + */ + private _isBattleCardDigitTarget(provider: WeaponComposeConfigProvider, weapon2Id: number, level: number, kind: number): boolean { + const w2Type = Math.floor(provider.getWeapon2Type(weapon2Id)); + if (!isElementGlyphType(w2Type)) return false; + const lv = Math.max(1, Math.floor(Number(level) || 1)); + if (kind === BattleBagCardKind.Multiplier) { + return lv < provider.getElementMaxLevel(w2Type); + } + return true; + } + + /** + * 产卡落点评分:场上优先、等级高优先、下标稳定。 + * @param provider 配表 + * @param weapon2Id 目标字 + * @param level 显示等级 + * @param onMap 是否地图 + * @param index 槽/格下标 + */ + private _scoreBattleCardTarget(provider: WeaponComposeConfigProvider, weapon2Id: number, level: number, onMap: boolean, index: number): number { + const lv = Math.max(1, Math.floor(Number(level) || 1)); + const w2Type = Math.floor(provider.getWeapon2Type(weapon2Id)); + const maxLv = isElementGlyphType(w2Type) ? provider.getElementMaxLevel(w2Type) : lv; + return (onMap ? 100000 : 0) + lv * 1000 + (maxLv - lv) + (1000 - (index % 1000)); + } + /** * 空 Put 格是否可落五行字。 * @param session 会话 diff --git a/docs/方案-Battlebuff新表实现.md b/docs/方案-Battlebuff新表实现.md index 180f3d9..ae41632 100644 --- a/docs/方案-Battlebuff新表实现.md +++ b/docs/方案-Battlebuff新表实现.md @@ -36,6 +36,7 @@ | 能量石 | `comm/prefab/power` | | 金币飞 | `battleGame/prefab/coinFly` | | 倍率卡/属性卡实体 | `battleHero/prefab/buff/倍率卡`、`属性卡`(内部美术后换) | +| AI 产卡使用 | `AiBoardProbe.findBattleCardAction`:解锁后优先卡→己方数字(`dropLogical`) | ## 3. 清除范围