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.

144 lines
5.0 KiB

3 weeks ago
import { BattlePhase } from 'db://assets/mx/module/battle';
2 weeks ago
import { getShieldAdMax, getShieldAdTriggerDist } from '../../bag/BagPrepConfig';
3 weeks ago
import type { MapManager } from '../../map/legacy/MapManager';
import type { ProjectBattleHandle } from '../../runtime/BattleRuntime';
import { RankDataManager } from '../../../manager/RankDataManager';
import { BattleGameMenShenTrapController } from '../../../ui/BattleGame/BattleGameMenShenTrapController';
import { AiHeadlessBagHost } from './AiHeadlessBagHost';
import { aiOp, aiWarn } from './AiLog';
/** 万分比上限 */
const WAN_DENOM = 10000;
/**
* AI 广告效果模拟(§8.2):不播激励视频。
2 weeks ago
* 触发条件对齐玩家门神广告;是否发放读 Rank.aiusead;
* Const25 限制本局**投掷次数**(成败均消耗,避免条件反复满足时必中)。
3 weeks ago
*/
export class AiAdHost {
private _bagHost: AiHeadlessBagHost | null = null;
private _battle: ProjectBattleHandle | null = null;
private _map: MapManager | null = null;
private _opponentRankId = 0;
private _aiUseAdWan = 0;
private _shieldAdMax = 0;
/** 盾牌已投掷次数(成败均计) */
private _shieldAdUsed = 0;
/** 盾牌机会已掷过 */
private _shieldLatched = false;
private readonly _menShenTrap = new BattleGameMenShenTrapController();
/**
* 绑定本局 AI 广告模拟。
* @param bagHost 无头背包
* @param battle 战斗
* @param map 地图
* @param opponentRankId 对手段位(读 aiusead)
*/
bind(
bagHost: AiHeadlessBagHost,
battle: ProjectBattleHandle,
map: MapManager,
opponentRankId: number,
2 weeks ago
_onBoardChanged?: () => void,
3 weeks ago
): void {
this.dispose();
const rank = RankDataManager.getRankConfig(opponentRankId);
if (!rank) {
aiWarn(`[AiAd] Rank 缺失 rankId=${opponentRankId},本局不模拟广告`);
return;
}
const wan = Math.floor(Number(rank.aiusead) || 0);
if (!(wan >= 0)) {
aiWarn(`[AiAd] aiusead 非法 rankId=${opponentRankId} raw=${rank.aiusead}`);
return;
}
this._bagHost = bagHost;
this._battle = battle;
this._map = map;
this._opponentRankId = opponentRankId;
this._aiUseAdWan = wan;
this._shieldAdMax = getShieldAdMax();
this._shieldAdUsed = 0;
this._shieldLatched = false;
this._menShenTrap.bind(battle, map, AiHeadlessBagHost.AI_PLAYER_ID);
aiOp(
`AI 广告就绪 rank=${opponentRankId} aiusead=${wan}`
2 weeks ago
+ ` shieldMax=${this._shieldAdMax}`,
3 weeks ago
);
}
/** 释放引用与门神陷阱 */
dispose(): void {
this._menShenTrap.unbind();
this._bagHost = null;
this._battle = null;
this._map = null;
this._opponentRankId = 0;
this._aiUseAdWan = 0;
this._shieldAdMax = 0;
this._shieldAdUsed = 0;
this._shieldLatched = false;
}
/**
* 每帧:门神陷阱推进 + 条件满足时掷骰发效果。
* @param dtSec 帧间隔
*/
tick(dtSec: number): void {
if (!this._battle || !this._bagHost || !this._map) return;
this._menShenTrap.tick(dtSec);
this._tickOffers();
}
2 weeks ago
/** 解析当前机会并掷骰(对齐玩家门神 btnItem) */
3 weeks ago
private _tickOffers(): void {
const battle = this._battle;
if (!battle || battle.session.ended) return;
const canShield = this._canOfferShield();
if (!canShield) this._shieldLatched = false;
if (canShield) {
this._tryRollShield();
}
}
/** 盾牌:战斗中、次数未满、尚无陷阱 */
private _canOfferShield(): boolean {
const battle = this._battle;
if (!battle) return false;
if (battle.session.phase !== BattlePhase.COMBAT) return false;
if (this._shieldAdUsed >= this._shieldAdMax) return false;
if (this._menShenTrap.hasTrap()) return false;
return this._hasShieldThreat();
}
/** Const23 内是否有进攻 AI 城墙的怪 */
private _hasShieldThreat(): boolean {
const dist = getShieldAdTriggerDist();
if (!(dist > 0)) return false;
return this._menShenTrap.shouldOfferShieldAd(dist);
}
/** 本机会掷盾牌广告(无论成败都消耗一次 Const25) */
private _tryRollShield(): void {
if (this._shieldLatched || !this._canOfferShield()) return;
this._shieldLatched = true;
this._shieldAdUsed++;
const ok = this._rollAiUseAd();
if (!ok) return;
if (!this._menShenTrap.tryPlaceTraps()) return;
}
/** 按 Rank.aiusead 万分比判定是否发放 */
private _rollAiUseAd(): boolean {
const battle = this._battle;
if (!battle) return false;
const wan = this._aiUseAdWan;
if (wan <= 0) return false;
const roll = battle.session.rng.int(0, WAN_DENOM - 1);
return roll < wan;
}
}