修复AI使用道具卡的一些bug

master
humingxing 5 hours ago
parent 70d37eea4d
commit 4fb30e60bb
  1. 45
      assets/mx/module/merge/core/MergeSession.ts
  2. 18
      assets/script/battle/ai/runtime/AiHeadlessBagHost.ts
  3. 12
      assets/script/battle/ai/runtime/AiMapTextHost.ts
  4. 14
      assets/script/battle/combat/TowerQueryService.ts
  5. 46
      assets/script/battle/view/CocosBagBoardView.ts
  6. 43
      assets/script/battle/view/TextGrid.ts
  7. 13
      assets/script/ui/BattleGame/UIBattleGame.ts

@ -151,9 +151,13 @@ export class MergeSession {
const w2 = Math.floor(Number(weapon2Id) || 0);
const lv = Math.max(1, Math.floor(Number(level) || 0));
if (w2 <= 0) return PlaceRejectReason.InvalidMerge;
this.map.placeTile(gridIndex, { ...tile, weapon2Id: w2, level: lv });
this._recompose();
const heroId = this._resolveElementHeroId(w2, lv) ?? 0;
if (!(heroId > 0)) {
console.warn('[MergeSession] applyBattleCardToMapTile 无 Hero', w2, lv);
return PlaceRejectReason.InvalidMerge;
}
this.map.placeTile(gridIndex, { ...tile, weapon2Id: w2, level: lv, weapon1Id: heroId });
this._recompose();
this.emit(MergeEventFactory.mapUpgraded(gridIndex, heroId, lv));
return null;
}
@ -171,8 +175,12 @@ export class MergeSession {
const w2 = Math.floor(Number(weapon2Id) || 0);
const lv = Math.max(1, Math.floor(Number(level) || 0));
if (w2 <= 0) return PlaceRejectReason.InvalidMerge;
this.bag.setSlot(slotIndex, { ...dst, weapon2Id: w2, level: lv });
const heroId = this._resolveElementHeroId(w2, lv) ?? 0;
if (!(heroId > 0)) {
console.warn('[MergeSession] applyBattleCardToBagSlot 无 Hero', w2, lv);
return PlaceRejectReason.InvalidMerge;
}
this.bag.setSlot(slotIndex, { ...dst, weapon2Id: w2, level: lv, weapon1Id: heroId });
this.emit(MergeEventFactory.bagMerged(slotIndex, heroId, lv));
return null;
}
@ -397,9 +405,13 @@ export class MergeSession {
const max = this._config.getElementMaxLevel(srcType);
if (srcLevel >= max) return PlaceRejectReason.MaxLevel;
const newLevel = srcLevel + 1;
clearSource();
this.bag.setSlot(toSlot, { ...target, level: newLevel });
const heroId = this._config.getElementHeroId(srcType, newLevel) ?? 0;
if (!(heroId > 0)) {
console.warn('[MergeSession] 背包五行合成无 Hero', srcType, newLevel);
return PlaceRejectReason.InvalidMerge;
}
clearSource();
this.bag.setSlot(toSlot, { ...target, level: newLevel, weapon1Id: heroId });
this.emit(MergeEventFactory.bagMerged(toSlot, heroId, newLevel));
return null;
}
@ -628,14 +640,22 @@ export class MergeSession {
return null;
}
/** 组合内所有字块设为同一等级,并同步绑定快照等级 */
/** 组合内所有字块设为同一等级,并同步 Hero.id / 绑定快照 */
private _applyLevelToCompositionTiles(compose: ComposedUnit, level: number): void {
const w1 = Math.floor(Number(compose.weapon1Id) || 0);
for (const idx of compose.gridIndices) {
const tile = this.map.getTile(idx);
if (!tile) continue;
if ((tile.level ?? 1) === level && tile.heroBind?.level === level) continue;
const sameLv = (tile.level ?? 1) === level && tile.heroBind?.level === level;
const sameW1 = (tile.weapon1Id ?? 0) === w1;
if (sameLv && sameW1) continue;
const rec: MapTileRecord = { ...tile, level };
if (tile.heroBind) rec.heroBind = { ...this._cloneHeroBind(tile.heroBind), level };
if (w1 > 0) rec.weapon1Id = w1;
if (tile.heroBind) {
const bind = { ...this._cloneHeroBind(tile.heroBind), level };
if (w1 > 0) bind.weapon1Id = w1;
rec.heroBind = bind;
}
this.map.placeTile(idx, rec);
}
}
@ -1008,10 +1028,15 @@ export class MergeSession {
const max = this._config.getElementMaxLevel(srcType);
if (srcLevel >= max) return PlaceRejectReason.MaxLevel;
const newLevel = srcLevel + 1;
const heroId = this._config.getElementHeroId(srcType, newLevel) ?? 0;
if (!(heroId > 0)) {
console.warn('[MergeSession] 五行合成无 Hero', srcType, newLevel);
return PlaceRejectReason.InvalidMerge;
}
clearSource();
this.map.placeTile(toGrid, { ...target, level: newLevel });
this.map.placeTile(toGrid, { ...target, level: newLevel, weapon1Id: heroId });
this._recompose();
this.emit(MergeEventFactory.mapUpgraded(toGrid, target.weapon1Id ?? 0, newLevel));
this.emit(MergeEventFactory.mapUpgraded(toGrid, heroId, newLevel));
return null;
}

@ -7,6 +7,7 @@ import type { MapManager } from '../../map/legacy/MapManager';
import type { AiMatchProfile } from '../match/AiMatchProfile';
import { AI_PLACE_CAMP, AI_PLAYER_ID, AI_VIEW_LOCAL_CAMP } from './AiCampArea';
import { WeaponComposeConfigProvider } from '../../bag/WeaponComposeConfigProvider';
import { isBagToolTextType, isShovelTextType } from 'db://assets/mx/module/merge';
import { textDisplayName } from '../../config/BattleHeroTable';
import { TextListCellState, TextListLandHost } from '../../view/TextListLandHost';
import { TableNames } from '../../../game/ConfigTableData';
@ -121,11 +122,24 @@ export class AiHeadlessBagHost {
const slots = bag.getBagSlots();
let best = -1;
let bestLv = 1e9;
let bestIsTool = false;
for (let i = 0; i < slots.length; i++) {
const slot = slots[i];
if (!slot || !(slot.weapon2Id > 0)) continue;
if (this._land.stateOf(i) !== TextListCellState.Open) continue;
const lv = Math.floor(Number(slot.level) || 0);
const w2Type = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(slot.weapon2Id));
const isTool = isBagToolTextType(w2Type) || isShovelTextType(w2Type) || Math.floor(Number(slot.battleCardKind) || 0) > 0;
const lv = Math.max(0, Math.floor(Number(slot.level) || 0));
// 功能符 level=0:优先丢掉,避免卡死开启格
if (isTool) {
if (!bestIsTool || i < best || best < 0) {
bestIsTool = true;
bestLv = lv;
best = i;
}
continue;
}
if (bestIsTool) continue;
if (!(lv > 0)) continue;
if (lv < bestLv || (lv === bestLv && i < best)) {
bestLv = lv;
@ -134,7 +148,7 @@ export class AiHeadlessBagHost {
}
if (best < 0) return false;
if (!bag.discardBagSlot(best)) return false;
aiOp(`卡死丢弃 bag[${best}] Lv${bestLv} 腾召唤位`);
aiOp(`卡死丢弃 bag[${best}] Lv${bestLv}${bestIsTool ? ' 工具' : ''} 腾召唤位`);
return true;
}

@ -1,5 +1,5 @@
import { Director, director, Tween, tween, v3, Vec3, type Node } from 'cc';
import { canDragMapTile, Weapon2TileType, isElementGlyphType, type ComposedUnit, type DragSource, type DropTarget, type MapTileRecord } from 'db://assets/mx/module/merge';
import { canDragMapTile, Weapon2TileType, isBagToolTextType, isElementGlyphType, type ComposedUnit, type DragSource, type DropTarget, type MapTileRecord } from 'db://assets/mx/module/merge';
import { aiLog, aiWarn } from './AiLog';
import type { BattleBagSession } from '../../bag/BattleBagSession';
import type { MapManager } from '../../map/legacy/MapManager';
@ -157,6 +157,16 @@ export class AiMapTextHost {
return false;
}
if (source.kind === 'bag' && target.kind === 'bag') return false;
// 功能符/产卡:瞬移生效,避免飞向已占格(含对方)时误藏目标格 HeroActor
if (source.kind === 'bag') {
const slot = bag.merge.bag.getSlot(source.slotIndex);
const cardKind = Math.floor(Number(slot?.battleCardKind) || 0);
if (cardKind > 0) return false;
if (slot && slot.weapon2Id > 0) {
const w2Type = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(slot.weapon2Id));
if (isBagToolTextType(w2Type)) return false;
}
}
const bagOrigin = this._resolveBagOriginWorld();
if (!bagOrigin) {

@ -388,15 +388,19 @@ export class TowerQueryService {
composition: ComposedUnit | null,
tileType: Weapon2TileType,
): number {
if (record.weapon1Id != null && record.weapon1Id > 0) return record.weapon1Id;
if (composition) return composition.weapon1Id;
if (record.weapon2Id <= 0) return 0;
const provider = WeaponComposeConfigProvider.instance;
// 五行数字:按 type + 显示等级解析 Hero
// 五行:以 type+显示等级为准,避免升/降级后 tile.weapon1Id 陈旧盖住 composition
if (isElementGlyphType(Number(tileType))) {
const level = Math.max(1, Math.floor(Number(record.level) || 1));
return provider.getElementHeroId(Number(tileType), level) ?? 0;
const fromLevel = provider.getElementHeroId(Number(tileType), level) ?? 0;
if (fromLevel > 0) return fromLevel;
if (composition && composition.weapon1Id > 0) return composition.weapon1Id;
if (record.weapon1Id != null && record.weapon1Id > 0) return record.weapon1Id;
return 0;
}
if (record.weapon1Id != null && record.weapon1Id > 0) return record.weapon1Id;
if (composition) return composition.weapon1Id;
if (record.weapon2Id <= 0) return 0;
// 蟠桃等非五行工具字:单 id 解析
return provider.getWeapon1ByGroupSequence([record.weapon2Id]) ?? 0;
}

@ -940,9 +940,10 @@ export class CocosBagBoardView {
if (this._bagSession?.merge.map.hasTile(mapGrid)) {
const tile = this._bagSession.merge.map.getTile(mapGrid);
const level = tile?.level ?? 1;
const w1 = Number(tile?.weapon1Id) || 0;
const grid = this._mapGrids.get(mapGrid);
if (grid?.isValid) {
grid.applyMapLevel(level);
grid.applyMapLevel(level, w1);
grid.enableDrag(this._canDrag());
this._bindDrag(grid);
}
@ -964,7 +965,7 @@ export class CocosBagBoardView {
const record = this._bagSession.getBagSlots()[bagSlot];
if (grid?.isValid && record) {
this._grids.setBagGrid(bagSlot, grid);
grid.applyMapLevel(record.level ?? 1);
grid.applyMapLevel(record.level ?? 1, Number(record.weapon1Id) || 0);
}
this._sanitizeBagSlotAfterMerge(bagSlot);
this._playBagUpgradeEffect(bagSlot);
@ -4593,7 +4594,7 @@ export class CocosBagBoardView {
this._deferredBagUpgradeSlot = event.slotIndex;
break;
}
if (this._applyBagUpgradeInPlace(event.slotIndex, event.level)) {
if (this._applyBagUpgradeInPlace(event.slotIndex, event.level, event.weapon1Id)) {
this._sanitizeBagSlotAfterMerge(event.slotIndex);
if (!this._suppressDefaultUpgradeFx) {
this._playBagUpgradeEffect(event.slotIndex);
@ -4614,7 +4615,7 @@ export class CocosBagBoardView {
}
const prevMapLv = Math.floor(Number(this._grids.getMapGrid(event.gridIndex)?.level) || 0);
const mapLevelDown = event.level < prevMapLv;
if (this._applyMapUpgradeInPlace(event.gridIndex, event.level)) {
if (this._applyMapUpgradeInPlace(event.gridIndex, event.level, event.weapon1Id)) {
this._refreshHeroLevelAtGrid(event.gridIndex, event.level);
if (this._deploySync && this._map.gridsInitialized) {
this._deploySync.reconcileFromMerge('mapUpgraded');
@ -5471,9 +5472,12 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
/**
* 地图字块仅等级变化:原地刷新 TextGrid,避免全量 rebuild 叠字。
* @param gridIndex 地图格
* @param newLevel 新等级
* @param weapon1Id 新 Hero.id(0 表示仅改等级)
* @returns 是否已在原节点上更新成功
*/
private _applyMapUpgradeInPlace(gridIndex: number, newLevel: number): boolean {
private _applyMapUpgradeInPlace(gridIndex: number, newLevel: number, weapon1Id = 0): boolean {
if (!this._bagSession || !this._map) return false;
if (!this._bagSession.merge.map.hasTile(gridIndex)) return false;
@ -5483,13 +5487,14 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
const indices = composition?.gridIndices.length
? composition.gridIndices.slice()
: [gridIndex];
const w1 = Math.floor(Number(weapon1Id) || 0) || (composition?.weapon1Id ?? 0);
let applied = 0;
for (let i = 0; i < indices.length; i++) {
const idx = indices[i];
const grid = this._resolveMapTextGridForUpgrade(idx);
if (!grid?.isValid) continue;
grid.applyMapLevel(newLevel);
grid.applyMapLevel(newLevel, w1);
this._grids.setMapGrid(idx, grid);
applied++;
}
@ -5497,8 +5502,13 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
return applied > 0;
}
/** 背包字块仅等级变化:原地刷新 TextGrid */
private _applyBagUpgradeInPlace(slotIndex: number, newLevel: number): boolean {
/**
* 背包字块仅等级变化:原地刷新 TextGrid。
* @param slotIndex 槽
* @param newLevel 新等级
* @param weapon1Id 新 Hero.id
*/
private _applyBagUpgradeInPlace(slotIndex: number, newLevel: number, weapon1Id = 0): boolean {
if (!this._bagSession || !this._bagList) return false;
if (!this._bagSession.getBagSlots()[slotIndex]) return false;
const slotNode = this._bagSlotNodeAt(slotIndex);
@ -5506,7 +5516,9 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
const grid = this._resolveBagTextGridForUpgrade(slotIndex, slotNode);
if (!grid?.isValid) return false;
grid.applyMapLevel(newLevel);
const slot = this._bagSession.getBagSlots()[slotIndex];
const w1 = Math.floor(Number(weapon1Id) || 0) || (Number(slot?.weapon1Id) || 0);
grid.applyMapLevel(newLevel, w1);
this._grids.setBagGrid(slotIndex, grid);
return true;
}
@ -6912,12 +6924,13 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
return loc?.kind === 'map' && this._isEnemyPlaceCampCell(loc.gridIndex);
}
/** 字块完整快照(敌方格补 ECS runtime) */
private _resolveTextGridData(grid: TextGrid): TextGridData {
/** 字块完整快照(敌方格补 ECS runtime);无实体时 null */
private _resolveTextGridData(grid: TextGrid): TextGridData | null {
const deploySync = this._isEnemyTextGrid(grid)
? AiBattleBridge.inst.aiDeploySync
: this._deploySync;
const data = grid.getData(deploySync);
if (!data) return null;
if (!this._isEnemyTextGrid(grid)) return data;
const loc = grid.location;
if (loc?.kind !== 'map') return data;
@ -6981,7 +6994,7 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
/** 字块占格合成体(含敌方) */
private _resolveGridComposition(grid: TextGrid, gridIndex: number): ComposedUnit | null {
const fromEntity = this._resolveTextGridData(grid).composition;
const fromEntity = this._resolveTextGridData(grid)?.composition;
if (fromEntity) return fromEntity;
if (this._isEnemyPlaceCampCell(gridIndex)) {
const enemyPlayer = this._battle?.getPlayer(this._resolveEnemyPlayerId());
@ -7004,6 +7017,11 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
this._textInteractDedupKey = dedupKey;
const data = this._resolveTextGridData(grid);
if (!data) {
console.warn('[BagBoard] 字块交互无快照', target.kind, target.index, grid.weapon2Id);
this._clearTextSelect();
return;
}
const isPeachTree = grid.isPeachTreeTile();
const isBagItem = this._isBagItemEntity(data);
// 五行数字与旧 Single=1 编号重叠,数字格子一律 Hero,勿再开 Desc1
@ -7136,6 +7154,7 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
): Pick<BattleWeaponDescHeroPanelData, 'entity' | 'runtime' | 'weaponId'> | null {
if (!grid?.node?.isValid) return null;
let data = this._resolveTextGridData(grid);
if (!data) return null;
if (target.kind === 'map') {
const comp = this._resolveGridComposition(grid, target.index);
if (comp) {
@ -7215,6 +7234,7 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
private _gridCanShowWeaponDesc(grid: TextGrid): boolean {
const data = this._resolveTextGridData(grid);
if (!data) return false;
if (grid.isPeachTreeTile()) return !!data.tile;
if (this._isBagItemEntity(data)) return !!data.tile;
return !!(data.tower && data.weapon1Id > 0);
@ -7249,6 +7269,7 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
/** 字块攻击范围(像素):已部署优先读 runtime */
private _resolveGridRangePx(grid: TextGrid): number {
const data = this._resolveTextGridData(grid);
if (!data) return 0;
const px = data.runtime?.stats.range ?? data.tower?.stats.range ?? 0;
return px > 0 ? px : 0;
}
@ -7437,6 +7458,7 @@ const w2Type = WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id) as
private _canShowType1Range(grid: TextGrid | null): boolean {
if (!grid?.node?.isValid) return false;
const data = this._resolveTextGridData(grid);
if (!data) return false;
if (this._isCampGridText(data)) return false;
if (!data.tower && !data.runtime) return false;
const rangePx = this._resolveGridRangePx(grid);

@ -793,11 +793,18 @@ export class TextGrid extends Component {
this.location = loc;
}
/** 完整快照:实体走战斗层查询,视图层只补 UI 字段 */
getData(deploySync?: DeploySync | null): TextGridData {
/**
* 完整快照:实体走战斗层查询,视图层只补 UI 字段。
* @returns 无实体/location 时 null(点选勿崩)
*/
getData(deploySync?: DeploySync | null): TextGridData | null {
const entity = this._aiMapLite && this._entity
? this._entity
: this._queryEntity();
if (!entity?.location) {
console.warn('[TextGrid] getData 无实体或 location', this.weapon2Id, this.node?.name);
return null;
}
const runtime = entity.location.kind === 'map' && !this._aiMapLite
? TowerQueryService.instance.getRuntimeByGridIndex(entity.location.gridIndex, undefined, deploySync ?? null)
: null;
@ -815,9 +822,10 @@ export class TextGrid extends Component {
}
onClickText(e: EventTouch): void {
const data = this.getData();
const range = data.runtime?.stats.range ?? data.tower?.stats.range;
if (!this.location && !this._entity?.location) {
console.warn('[TextGrid] onClick 无 location,忽略', this.weapon2Id);
return;
}
if (this.location?.kind === 'map' || this.location?.kind === 'bag') {
if (this._mapClickHandler) {
e.propagationStopped = true;
@ -1078,21 +1086,23 @@ export class TextGrid extends Component {
this.node.removeDragEvent?.();
}
private _queryEntity(): TileEntitySnapshot {
private _queryEntity(): TileEntitySnapshot | null {
const location = this.location ?? this._entity?.location;
if (!location) return this._entity!;
if (!location) return this._entity;
const composition = this._findComposition() ?? this._entity?.composition ?? null;
const level = composition?.level ?? this.level;
const fromCompose = composition && composition.weapon1Id > 0 ? composition.weapon1Id : 0;
const weapon1Id = fromCompose > 0 ? fromCompose : (this.weapon1Id > 0 ? this.weapon1Id : undefined);
return TowerQueryService.instance.buildTileEntity(
location,
{
weapon2Id: this.weapon2Id,
weapon1Id: this.weapon1Id > 0 ? this.weapon1Id : undefined,
weapon1Id,
level,
source: this._entity?.source ?? undefined,
},
{ composition, player: gg.game.localPlayer },
) ?? this._entity!;
) ?? this._entity;
}
private _findComposition(): ComposedUnit | null {
@ -2405,16 +2415,23 @@ export class TextGrid extends Component {
return this.level;
}
/** 地图字块等级变更后刷新数字皮(免全量 setData) */
applyMapLevel(level: number): void {
/**
* 地图字块等级变更后刷新数字皮(免全量 setData)。
* @param level 新显示等级
* @param weapon1Id 新 Hero.id;>0 时同步,避免升/降级后陈旧
*/
applyMapLevel(level: number, weapon1Id = 0): void {
if (!this.isValid) return;
const lv = Math.max(1, Math.floor(level) || 1);
this.level = lv;
const w1 = Math.floor(Number(weapon1Id) || 0);
if (w1 > 0) this.weapon1Id = w1;
if (this._entity) {
const nextW1 = w1 > 0 ? w1 : (Number(this._entity.weapon1Id) || this.weapon1Id);
const comp = this._entity.composition
? { ...this._entity.composition, level: lv }
? { ...this._entity.composition, level: lv, weapon1Id: nextW1 > 0 ? nextW1 : this._entity.composition.weapon1Id }
: null;
this._entity = { ...this._entity, level: lv, composition: comp };
this._entity = { ...this._entity, level: lv, weapon1Id: nextW1, composition: comp };
}
this._updateLevelLabel();
this._syncSkillActiveState();

@ -991,16 +991,23 @@ export class UIBattleGame extends Auto_BattleGame {
* @param gridIndex 地图格
*/
private readonly _onPeerMapToolApplied = (peerOwnerPlayerId: number, gridIndex: number): void => {
if (!(gridIndex >= 0)) {
console.warn('[UIBattleGame] 衰弱符对端格无效', peerOwnerPlayerId, gridIndex);
return;
}
if (peerOwnerPlayerId === AiHeadlessBagHost.AI_PLAYER_ID) {
const bridge = AiBattleBridge.inst;
bridge.aiDeploySync?.reconcileFromMerge('bagToolWeaken');
bridge.notifyAiBoardChanged();
return;
}
// 本机盘面由 peer.merge 的 MapUpgraded 驱动 CocosBagBoardView
if (!(gridIndex >= 0)) {
console.warn('[UIBattleGame] 衰弱符对端格无效', peerOwnerPlayerId, gridIndex);
// AI 衰弱本机:MapUpgraded 已改视图等级;补 DeploySync,避免 Hero/装载仍绑旧等级
const battle = this._battle;
if (!battle?.deploySync) {
console.warn('[UIBattleGame] 衰弱本机缺 DeploySync', peerOwnerPlayerId, gridIndex);
return;
}
battle.deploySync.reconcileFromMerge('bagToolWeakenLocal');
};
/**

Loading…
Cancel
Save