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.
371 lines
14 KiB
371 lines
14 KiB
|
3 weeks ago
|
import { game, 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 { MapManager } from '../../battle/map/legacy/MapManager';
|
||
|
|
import { hasCarriedTreasureShovel } from '../../manager/battleBuff/BattleBuffPassiveSkillUtil';
|
||
|
|
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 + 背包有空位且无铲(同时满足);每局 Const22 次数上限
|
||
|
|
* - 门神已触发且铲子亦满足:Const23 内有怪优先门神,无怪则铲子顶掉门神;仅铲子时 Const23 有怪且门神可展示则切回门神
|
||
|
|
*/
|
||
|
|
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 _map: MapManager | 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 menShenTrap 门神陷阱控制器
|
||
|
|
*/
|
||
|
|
bind(
|
||
|
|
battle: ProjectBattleHandle | null,
|
||
|
|
map: MapManager | null,
|
||
|
|
bagView: CocosBagBoardView | null,
|
||
|
|
btnItem: Node | null,
|
||
|
|
menShenTrap: BattleGameMenShenTrapController | null,
|
||
|
|
): void {
|
||
|
|
this.unbind();
|
||
|
|
this._battle = battle;
|
||
|
|
this._map = map;
|
||
|
|
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;
|
||
|
|
|
||
|
|
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._map = 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();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 首次满足距离时触发盾牌广告 */
|
||
|
|
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;
|
||
|
|
const gold = showShovel && hasCarriedTreasureShovel();
|
||
|
|
|
||
|
|
this._setNodeActive(this._menIcon, showMen);
|
||
|
|
this._setNodeActive(this._shovelIcon, showShovel && !gold);
|
||
|
|
this._setNodeActive(this._goldShovelIcon, showShovel && gold);
|
||
|
|
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 _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 (game.isPaused()) game.resume();
|
||
|
|
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 (game.isPaused()) game.resume();
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|