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.
143 lines
5.0 KiB
143 lines
5.0 KiB
import { BattlePhase } from 'db://assets/mx/module/battle';
|
|
import { getShieldAdMax, getShieldAdTriggerDist } from '../../bag/BagPrepConfig';
|
|
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):不播激励视频。
|
|
* 触发条件对齐玩家门神广告;是否发放读 Rank.aiusead;
|
|
* Const25 限制本局**投掷次数**(成败均消耗,避免条件反复满足时必中)。
|
|
*/
|
|
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,
|
|
_onBoardChanged?: () => void,
|
|
): 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}`
|
|
+ ` shieldMax=${this._shieldAdMax}`,
|
|
);
|
|
}
|
|
|
|
/** 释放引用与门神陷阱 */
|
|
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();
|
|
}
|
|
|
|
/** 解析当前机会并掷骰(对齐玩家门神 btnItem) */
|
|
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;
|
|
}
|
|
}
|
|
|