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.
 
 
 
 
AdLike2048/assets/script/ui/BattleGame/BattleGameItemAdPresenter.ts

431 lines
16 KiB

import { Label, Node } from 'cc';
import { BattlePhase } from 'db://assets/mx/module/battle';
import { GEvent } from '../../../mx/module/event/GEvent';
import { getShieldAdTriggerDist } from '../../battle/bag/BagPrepConfig';
import type { BattleBagSession } from '../../battle/bag/BattleBagSession';
import { BattleUiPauseGate } from '../../battle/BattleUiPauseGate';
import type { ProjectBattleHandle } from '../../battle/runtime/BattleRuntime';
import type { CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import type { BattleGameMenShenTrapController } from './BattleGameMenShenTrapController';
import { BattleGameItemAdBtnPulse } from './BattleGameItemAdBtnPulse';
/** 广告类型 */
enum ItemAdKind {
/** 无 */
None = 0,
/** 门神盾牌(优先级高) */
MenShen = 1,
/** 铲子 */
Shovel = 2,
}
/** 观看广告期间暂停 */
const AD_PAUSE_TOKEN = 'ad:battleItem';
/**
* BattleGame btnItem:铲子 / 门神盾牌广告入口。
* - 盾牌:怪首次离终点 Const23 格内触发,每局 1 次;放置陷阱后隐藏
* - 铲子:己方无可放置 Put + 背包有空位且无铲(同时满足);每局 Const10 次数上限
* - 门神已触发且铲子亦满足:Const23 内有怪优先门神,无怪则铲子顶掉门神;仅铲子时 Const23 有怪且门神可展示则切回门神
* - 无掘宝铲:固定 shovelIcon,藏 goldShovelIcon
*/
export class BattleGameItemAdPresenter {
private _btnItem: Node | null = null;
private _shovelIcon: Node | null = null;
private _goldShovelIcon: Node | null = null;
private _menIcon: Node | null = null;
private _videoIcon: Node | null = null;
private _numNode: Node | null = null;
private _numLabel: Label | null = null;
private _battle: ProjectBattleHandle | null = null;
private _bagView: CocosBagBoardView | null = null;
private _menShenTrap: BattleGameMenShenTrapController | null = null;
private _adPending = false;
private _activeKind = ItemAdKind.None;
private _shieldScanDone = false;
/** 盾牌广告已点击并放置陷阱 */
private _shieldBtnDismissed = false;
/** 飞铲动画进行中 */
private _shovelFlying = false;
/** 飞铲期间展示的赠送数量(避免首把入包后条件变化导致闪隐) */
private _shovelFlyingGrantCount = 0;
private _onBoardChanged: (() => void) | null = null;
private readonly _btnPulse = new BattleGameItemAdBtnPulse();
private _btnPulseActive = false;
/** 威胁扫描降频 */
private _threatScanAcc = 0;
/** 缓存:Const23 内是否有进攻怪 */
private _threatInZone = false;
/**
* @param btnItem ui_ready/btnItem 根节点
* @param bagView 背包视图(飞铲入包)
* @param menShenTrap 门神陷阱控制器
*/
bind(
battle: ProjectBattleHandle | null,
bagView: CocosBagBoardView | null,
btnItem: Node | null,
menShenTrap: BattleGameMenShenTrapController | null,
): void {
this.unbind();
this._battle = battle;
this._bagView = bagView;
this._menShenTrap = menShenTrap;
this._btnItem = btnItem;
if (!btnItem?.isValid) {
console.log('[BattleItemAd] btnItem 缺失');
return;
}
this._shovelIcon = btnItem.getChildByName('shovelIcon');
this._goldShovelIcon = btnItem.getChildByName('goldShovelIcon');
this._menIcon = btnItem.getChildByName('menIcon');
this._videoIcon = btnItem.getChildByName('video');
this._numNode = btnItem.getChildByName('num');
this._numLabel = this._numNode?.getComponent(Label) ?? null;
this._setNodeActive(this._goldShovelIcon, false);
btnItem.on(Node.EventType.TOUCH_END, this._onBtnClick, this);
this._onBoardChanged = () => this._refreshVisibility();
GEvent.Ins.on(GEvent.UpdateBestPlace, this._onBoardChanged, this);
this._refreshVisibility();
}
/** 解除绑定 */
unbind(): void {
if (this._btnItem?.isValid) {
this._btnItem.off(Node.EventType.TOUCH_END, this._onBtnClick, this);
}
if (this._onBoardChanged) {
GEvent.Ins.off(GEvent.UpdateBestPlace, this._onBoardChanged, this);
}
this._onBoardChanged = null;
if (this._adPending) {
this._adPending = false;
BattleUiPauseGate.release(AD_PAUSE_TOKEN);
}
this._btnItem = null;
this._shovelIcon = null;
this._goldShovelIcon = null;
this._menIcon = null;
this._videoIcon = null;
this._numNode = null;
this._numLabel = null;
this._battle = null;
this._bagView = null;
this._menShenTrap = null;
this._activeKind = ItemAdKind.None;
this._shieldScanDone = false;
this._shieldBtnDismissed = false;
this._shovelFlying = false;
this._shovelFlyingGrantCount = 0;
this._btnPulseActive = false;
this._threatScanAcc = 0;
this._threatInZone = false;
this._syncBtnPulse(false);
}
/** 帧更新:扫描盾牌触发条件并刷新显隐 */
tick(_dt: number): void {
this._threatScanAcc++;
if (this._threatScanAcc % 12 === 1) {
this._threatInZone = this._scanThreatInZone();
}
this._tickShieldOffer();
this._refreshVisibility();
}
/**
* GM 玩家 AI:当前有可点广告则免广告发放(不播激励视频)。
* @returns 是否已发起领取
*/
tryClaimForPlayerAi(): boolean {
if (this._adPending || this._shovelFlying) return false;
const kind = this._activeKind;
if (kind === ItemAdKind.MenShen) {
return this._grantMenShenWithoutAd();
}
if (kind === ItemAdKind.Shovel) {
return this._grantShovelWithoutAd();
}
return false;
}
/** 首次满足距离时触发盾牌广告 */
private _tickShieldOffer(): void {
const battle = this._battle;
const trap = this._menShenTrap;
if (!battle || !trap || battle.menShenAdConsumed) return;
if (battle.canShowMenShenAd()) {
this._shieldScanDone = true;
return;
}
if (this._shieldScanDone) return;
if (battle.session.phase !== BattlePhase.COMBAT || battle.session.ended) return;
const triggerDist = getShieldAdTriggerDist();
if (!(triggerDist > 0)) return;
if (!this._threatInZone) return;
battle.triggerMenShenAdOffer();
this._shieldScanDone = true;
}
/** 刷新 btnItem 显隐与图标 */
private _refreshVisibility(): void {
const kind = this._resolveActiveKind();
this._activeKind = kind;
const btn = this._btnItem;
if (!btn?.isValid) return;
const show = kind !== ItemAdKind.None;
if (btn.active !== show) btn.active = show;
this._syncBtnPulse(show);
if (!show) return;
const showMen = kind === ItemAdKind.MenShen;
const showShovel = kind === ItemAdKind.Shovel;
this._setNodeActive(this._menIcon, showMen);
this._setNodeActive(this._shovelIcon, showShovel);
this._setNodeActive(this._goldShovelIcon, false);
this._setNodeActive(this._videoIcon, true);
this._setNodeActive(this._numNode, showShovel);
if (showShovel) {
const grantCount = this._shovelFlying
? this._shovelFlyingGrantCount
: this._battle?.bagSession
? this._resolveShovelGrantCount(this._battle.bagSession)
: 0;
if (grantCount > 0 && this._numLabel) {
this._numLabel.string = `X${grantCount}`;
}
}
}
/** 解析当前应展示的广告类型(门神/铲子按 Const23 威胁动态切换) */
private _resolveActiveKind(): ItemAdKind {
const battle = this._battle;
if (!battle || battle.session.ended) return ItemAdKind.None;
const canMenShen = !this._shieldBtnDismissed && battle.canShowMenShenAd();
const canShovel = this._shovelFlying || this._canShowShovelAd();
const hasThreatInConst23 = this._hasMonsterInShieldTriggerZone();
if (canMenShen && hasThreatInConst23) return ItemAdKind.MenShen;
if (canMenShen && canShovel && !hasThreatInConst23) return ItemAdKind.Shovel;
if (canShovel) return ItemAdKind.Shovel;
if (canMenShen) return ItemAdKind.MenShen;
return ItemAdKind.None;
}
/** Const23 触发距离内是否存在进攻怪(用降频缓存) */
private _hasMonsterInShieldTriggerZone(): boolean {
return this._threatInZone;
}
/** 扫描 Const23 内进攻怪 */
private _scanThreatInZone(): boolean {
const trap = this._menShenTrap;
if (!trap || !this._battle) return false;
const triggerDist = getShieldAdTriggerDist();
if (!(triggerDist > 0)) return false;
return trap.shouldOfferShieldAd(triggerDist);
}
/** 铲子广告:① 己方无可放置 Put ② 背包有空位且无铲(同时满足) */
private _canShowShovelAd(): boolean {
const battle = this._battle;
const session = battle?.bagSession;
if (!battle || !session) return false;
if (battle.getPrepShovelAdRemaining() <= 0) return false;
if (session.hasLocalPlaceablePutGrid()) return false;
if (this._countEmptyBagSlots(session) <= 0) return false;
if (session.countLocalShovelTiles() > 0) return false;
return true;
}
/** 本次广告可获得的铲数量(背包空位 >1 给 2,否则 1) */
private _resolveShovelGrantCount(session: BattleBagSession): number {
return this._countEmptyBagSlots(session) > 1 ? 2 : 1;
}
/** 背包空槽数量 */
private _countEmptyBagSlots(session: BattleBagSession): number {
const slots = session.getBagSlots();
let n = 0;
for (let i = 0; i < slots.length; i++) {
if (!slots[i]) n++;
}
return n;
}
private readonly _onBtnClick = (): void => {
if (this._adPending || this._shovelFlying) return;
const kind = this._activeKind;
if (kind === ItemAdKind.MenShen) {
this._playMenShenAd();
return;
}
if (kind === ItemAdKind.Shovel) {
this._playShovelAd();
}
};
/** 免广告发放门神盾牌 */
private _grantMenShenWithoutAd(): boolean {
const battle = this._battle;
const trap = this._menShenTrap;
if (!battle?.canShowMenShenAd() || !trap) return false;
battle.IsWatchAd = true;
battle.consumeMenShenAd();
if (!trap.tryPlaceTraps()) {
console.log('[BattleItemAd] 玩家AI门神陷阱放置失败');
}
this._shieldBtnDismissed = true;
this._refreshVisibility();
console.log('[BattleItemAd] 玩家AI免广告领取门神盾牌');
return true;
}
/** 免广告发放铲子 */
private _grantShovelWithoutAd(): boolean {
const battle = this._battle;
if (!battle || battle.getPrepShovelAdRemaining() <= 0) return false;
if (!this._canShowShovelAd()) return false;
const bagView = this._bagView;
const btn = this._btnItem;
const session = battle.bagSession;
if (!session || !bagView || !btn?.isValid) {
console.log('[BattleItemAd] 玩家AI铲奖励失败:bag 未就绪');
return false;
}
battle.IsWatchAd = true;
battle.consumePrepShovelAd();
const shovelCount = this._resolveShovelGrantCount(session);
const fromWorld = btn.worldPosition.clone();
this._shovelFlying = true;
this._shovelFlyingGrantCount = shovelCount;
bagView.playItemAdShovelFlights({
fromWorld,
count: shovelCount,
onAllComplete: (granted) => {
this._shovelFlying = false;
this._shovelFlyingGrantCount = 0;
if (granted <= 0) {
console.log('[BattleItemAd] 玩家AI铲飞入背包失败');
}
this._refreshVisibility();
},
});
this._refreshVisibility();
console.log('[BattleItemAd] 玩家AI免广告领取铲子', shovelCount);
return true;
}
/** 观看门神广告并布置陷阱 */
private _playMenShenAd(): void {
const battle = this._battle;
const trap = this._menShenTrap;
if (!battle?.canShowMenShenAd() || !trap) return;
this._adPending = true;
BattleUiPauseGate.acquire(AD_PAUSE_TOKEN);
gg.sdk.showVideoAd((res) => {
if (!this._adPending) return;
this._adPending = false;
BattleUiPauseGate.release(AD_PAUSE_TOKEN);
if (!res) {
gg.ui.showToast('观看视频失败');
return;
}
const b = this._battle;
const t = this._menShenTrap;
if (!b?.canShowMenShenAd() || !t) return;
b.IsWatchAd = true;
b.consumeMenShenAd();
if (!t.tryPlaceTraps()) {
console.log('[BattleItemAd] 门神陷阱放置失败');
}
const curWaveNum = gg.game.CurentBattle.CurentWaveNum;
gg.sdk.reportDY('inLevel', `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-AD-广告门神盾牌`);
this._shieldBtnDismissed = true;
this._refreshVisibility();
});
}
/** 观看铲子广告并按背包空位数量发放 */
private _playShovelAd(): void {
const battle = this._battle;
if (!battle || battle.getPrepShovelAdRemaining() <= 0) return;
if (!this._canShowShovelAd()) return;
this._adPending = true;
BattleUiPauseGate.acquire(AD_PAUSE_TOKEN);
gg.sdk.showVideoAd((res) => {
if (!this._adPending) return;
this._adPending = false;
BattleUiPauseGate.release(AD_PAUSE_TOKEN);
if (!res) {
gg.ui.showToast('观看视频失败');
return;
}
const b = this._battle;
const bagView = this._bagView;
const btn = this._btnItem;
if (!b || b.getPrepShovelAdRemaining() <= 0) return;
const session = b.bagSession;
if (!session || !bagView || !btn?.isValid) {
console.log('[BattleItemAd] 铲奖励失败:bag 未就绪');
return;
}
if (!this._canShowShovelAd()) return;
b.IsWatchAd = true;
b.consumePrepShovelAd();
const shovelCount = this._resolveShovelGrantCount(session);
const fromWorld = btn.worldPosition.clone();
this._shovelFlying = true;
this._shovelFlyingGrantCount = shovelCount;
bagView.playItemAdShovelFlights({
fromWorld,
count: shovelCount,
onAllComplete: (granted) => {
this._shovelFlying = false;
this._shovelFlyingGrantCount = 0;
if (granted <= 0) {
console.log('[BattleItemAd] 铲飞入背包失败');
}
const curWaveNum = gg.game.CurentBattle.CurentWaveNum;
gg.sdk.reportDY('inLevel', `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-AD-广告铲子`);
this._refreshVisibility();
},
});
this._refreshVisibility();
});
}
private _setNodeActive(node: Node | null, active: boolean): void {
if (node?.isValid && node.active !== active) node.active = active;
}
/** btnItem 显示时循环跳动,隐藏时停止 */
private _syncBtnPulse(active: boolean): void {
if (active === this._btnPulseActive) return;
this._btnPulseActive = active;
if (active) {
this._btnPulse.bind(this._btnItem);
} else {
this._btnPulse.unbind();
}
}
}