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.
182 lines
7.0 KiB
182 lines
7.0 KiB
import { Weapon2TileType, isElementGlyphType, type MapTileRecord } from 'db://assets/mx/module/merge';
|
|
import type { BattleBagSession } from '../../bag/BattleBagSession';
|
|
import { WeaponComposeConfigProvider } from '../../bag/WeaponComposeConfigProvider';
|
|
import { heroForTextRow, isElementHero } from '../../config/BattleHeroTable';
|
|
import { TableNames } from '../../../game/ConfigTableData';
|
|
|
|
/** 主动技落点(己方字/武将) */
|
|
export type AiActiveSkillTileTarget =
|
|
| { kind: 'map'; gridIndex: number }
|
|
| { kind: 'bag'; slotIndex: number };
|
|
|
|
/** 紫字品质(赌橙合成) */
|
|
const QUALITY_PURPLE = 3;
|
|
/** 橙字品质(已可合成,文昌笔垫底) */
|
|
const QUALITY_ORANGE = 4;
|
|
|
|
/** 候选打分条目 */
|
|
type ScoredTarget = {
|
|
/** 落点 */
|
|
target: AiActiveSkillTileTarget;
|
|
/** 是否合成武将 */
|
|
isHero: boolean;
|
|
/** 等级 */
|
|
level: number;
|
|
/** Hero.quality(由 Text 映射) */
|
|
quality: number;
|
|
};
|
|
|
|
/**
|
|
* 主动道具目标选择(需求 §8.1)。
|
|
* 默认:合成武将 → 普通字,同档等级高优先。
|
|
* 文昌笔:紫字赌橙优先,其余等级低优先,橙字垫底。
|
|
*/
|
|
export class AiActiveSkillTargetPicker {
|
|
/**
|
|
* 在己方盘面按默认优先级选落点;`canUse` 过滤不可施放目标。
|
|
* @param session AI bag
|
|
* @param canUse 是否可对本落点施放
|
|
*/
|
|
pick(session: BattleBagSession, canUse: (target: AiActiveSkillTileTarget) => boolean): AiActiveSkillTileTarget | null {
|
|
const scored = this._collect(session);
|
|
scored.sort((a, b) => {
|
|
if (a.isHero !== b.isHero) return a.isHero ? -1 : 1;
|
|
if (a.level !== b.level) return b.level - a.level;
|
|
return this._targetStableKey(a.target) - this._targetStableKey(b.target);
|
|
});
|
|
return this._firstUsable(scored, canUse);
|
|
}
|
|
|
|
/**
|
|
* 文昌笔:紫字(赌变橙可合成)优先;其余等级低优先;橙字垫底。
|
|
* @param session AI bag
|
|
* @param canUse 是否可对本落点施放
|
|
*/
|
|
pickRewrite(session: BattleBagSession, canUse: (target: AiActiveSkillTileTarget) => boolean): AiActiveSkillTileTarget | null {
|
|
const scored = this._collect(session);
|
|
scored.sort((a, b) => {
|
|
const ta = this._rewriteTier(a.quality);
|
|
const tb = this._rewriteTier(b.quality);
|
|
if (ta !== tb) return ta - tb;
|
|
if (a.level !== b.level) return a.level - b.level;
|
|
return this._targetStableKey(a.target) - this._targetStableKey(b.target);
|
|
});
|
|
return this._firstUsable(scored, canUse);
|
|
}
|
|
|
|
/**
|
|
* 文昌笔品质档:紫=0,普通=1,橙=2。
|
|
* @param quality Hero.quality
|
|
*/
|
|
private _rewriteTier(quality: number): number {
|
|
if (quality === QUALITY_PURPLE) return 0;
|
|
if (quality === QUALITY_ORANGE) return 2;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 按排序取首个可施放目标。
|
|
* @param scored 已排序候选
|
|
* @param canUse 过滤
|
|
*/
|
|
private _firstUsable(scored: ScoredTarget[], canUse: (target: AiActiveSkillTileTarget) => boolean): AiActiveSkillTileTarget | null {
|
|
for (let i = 0; i < scored.length; i++) {
|
|
if (canUse(scored[i].target)) return scored[i].target;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 收集己方地图+背包字块候选。
|
|
* @param session AI bag
|
|
*/
|
|
private _collect(session: BattleBagSession): ScoredTarget[] {
|
|
WeaponComposeConfigProvider.instance.load();
|
|
const provider = WeaponComposeConfigProvider.instance;
|
|
const out: ScoredTarget[] = [];
|
|
const coveredGrids: number[] = [];
|
|
|
|
const compositions = session.merge.compositions;
|
|
for (let i = 0; i < compositions.length; i++) {
|
|
const compose = compositions[i];
|
|
if (!compose.gridIndices || compose.gridIndices.length <= 0) continue;
|
|
const anchor = compose.gridIndices[0];
|
|
if (!session.validator.isAllowedCampForPlace(anchor)) continue;
|
|
for (let g = 0; g < compose.gridIndices.length; g++) {
|
|
coveredGrids.push(compose.gridIndices[g]);
|
|
}
|
|
const level = Math.max(1, Math.floor(Number(compose.level) || 1));
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, compose.weapon1Id);
|
|
const isHero = isElementHero(hero);
|
|
const tile = session.merge.map.getTile(anchor);
|
|
const weapon2Id = tile ? tile.weapon2Id : 0;
|
|
out.push({
|
|
target: { kind: 'map', gridIndex: anchor },
|
|
isHero,
|
|
level,
|
|
quality: this._readQuality(weapon2Id),
|
|
});
|
|
}
|
|
|
|
session.merge.map.forEachTile((gridIndex, tile) => {
|
|
if (coveredGrids.indexOf(gridIndex) >= 0) return;
|
|
this._pushLooseMapTile(session, gridIndex, tile, out);
|
|
});
|
|
|
|
const slots = session.getBagSlots();
|
|
for (let i = 0; i < slots.length; i++) {
|
|
const slot = slots[i];
|
|
if (!slot) continue;
|
|
const w2Type = Number(provider.getWeapon2Type(slot.weapon2Id)) as Weapon2TileType;
|
|
if (w2Type === Weapon2TileType.Grid) continue;
|
|
out.push({
|
|
target: { kind: 'bag', slotIndex: i },
|
|
isHero: isElementGlyphType(w2Type),
|
|
level: Math.max(1, Math.floor(Number(slot.level) || 1)),
|
|
quality: this._readQuality(slot.weapon2Id),
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* 推入未计入合成的五行字。
|
|
* @param session 会话
|
|
* @param gridIndex 格
|
|
* @param tile 字
|
|
* @param out 输出
|
|
*/
|
|
private _pushLooseMapTile(session: BattleBagSession, gridIndex: number, tile: MapTileRecord, out: ScoredTarget[]): void {
|
|
if (!session.validator.isAllowedCampForPlace(gridIndex)) return;
|
|
WeaponComposeConfigProvider.instance.load();
|
|
const provider = WeaponComposeConfigProvider.instance;
|
|
const w2Type = Number(provider.getWeapon2Type(tile.weapon2Id)) as Weapon2TileType;
|
|
if (w2Type === Weapon2TileType.Grid) return;
|
|
out.push({
|
|
target: { kind: 'map', gridIndex },
|
|
isHero: isElementGlyphType(w2Type),
|
|
level: Math.max(1, Math.floor(Number(tile.level) || 1)),
|
|
quality: this._readQuality(tile.weapon2Id),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 读 Hero.quality(Text → Hero)。
|
|
* @param weapon2Id 字 id
|
|
*/
|
|
private _readQuality(weapon2Id: number): number {
|
|
WeaponComposeConfigProvider.instance.load();
|
|
const text = WeaponComposeConfigProvider.instance.getWeapon2Config(weapon2Id);
|
|
const hero = heroForTextRow(text);
|
|
return Math.floor(Number(hero?.quality) || 0);
|
|
}
|
|
|
|
/**
|
|
* 稳定排序键。
|
|
* @param target 落点
|
|
*/
|
|
private _targetStableKey(target: AiActiveSkillTileTarget): number {
|
|
if (target.kind === 'map') return target.gridIndex;
|
|
return 100000 + target.slotIndex;
|
|
}
|
|
}
|
|
|