//********************* // create by 流云 // time: Thu Sep 25 2025 // desc: //********************* import { _decorator, Label, Layout, Node, Sprite, tween, Tween, Vec3 } from 'cc'; import { Auto_BattleFail } from './Auto_BattleFail'; import { BattleSuccessCoinFlyPresenter } from '../BattleSuccess/BattleSuccessCoinFlyPresenter'; import { OtherDataType, StatisticsType, ItemNumType } from '../../game/GameData'; import { ItemData } from '../../game/ConfigProjectData'; import { UIID, GAudioPath } from '../../game/ConfigRes'; import { MatchWinStats } from '../../manager/MatchWinStats'; import { RankDataManager } from '../../manager/RankDataManager'; import { VivoAd } from '../../sdk/functions/VivoAd'; import { DailyShopDataManager } from '../../manager/DailyShopDataManager'; import { WeaponDropView } from '../BattleGame/BattleWeaponDropPresenter'; import { WeaponDropService, type WeaponDropRecord } from '../../battle/combat/WeaponDropService'; const { ccclass } = _decorator; /** 降星 fillRange 动画时长(秒) */ const STAR_DROP_FILL_SEC = 1.5; /** 展示变化前星级后再播动画的延迟(秒) */ const STAR_DROP_ANIM_DELAY_SEC = 0.45; @ccclass('UIBattleFail') export class UIBattleFail extends Auto_BattleFail { /** 领取倍率(广告双倍为 2) */ addNum = 1; /** 本局待领金币(不含倍率) */ private _pendingCoin = 0; /** 结算奖励列表(领取时入账,不含金币) */ private _dataList: ItemData[] = []; /** ui_content 宝石展示节点 */ private readonly _lootWeaponNodes: Node[] = []; /** 金币飞行 */ private _coinFly: BattleSuccessCoinFlyPresenter | null = null; /** 领取流程中 */ private _claiming = false; /** 降星动画目标 index(star1~star5) */ private _starDropIndex = 0; /** 降星动画结束后星条点亮数 */ private _starDropAfterCount = 0; onLoad(): void { super.onLoad(); this._hideGemTemp(); this._coinFly = new BattleSuccessCoinFlyPresenter(); this._coinFly.bind(this.ui_center); } /** 隐藏结算宝石模板,避免占位节点显示在面板上 */ private _hideGemTemp(): void { if (!this.ui_gemTemp?.isValid) { console.log('[UIBattleFail] ui_gemTemp 缺失'); return; } this.ui_gemTemp.active = false; } onEnable(): void { super.onEnable(); gg.sdk.pfTriggerGC(); this._coinFly?.bind(this.ui_center); } onDisable(): void { this._stopStarDropAnim(); this.unschedule(this._onStarDropAnimDelay); this._recycleWeaponLoot(); this._coinFly?.unbind(); VivoAd.hideNative(); super.onDisable(); } onInit(): void { this.addNum = 1; this._claiming = false; this.btnGet.active = true; this.btnADGet.active = true; gg.audio.playEffect({ name: '游戏失败', path: GAudioPath.SfxUi }); const starsBefore = RankDataManager.getPlayerRankStars(); MatchWinStats.record(false, gg.game.CurentBattle?.opponentRankId ?? 0); gg.sdk.reportDY('inLevel', `${gg.game.getReportDYRankLabel()}-失败`); void this._refreshRankUi(starsBefore); if (gg.game.CurentBattle.LevelConfigId == gg.game.CurentSelectLevelId) { gg.data.setStatistics(StatisticsType.SuccessContinuousCount, 0); } // let value = gg.data.getkeyKeyValue(OtherDataType.FailReportData, gg.game.CurentBattle.getReportDYLevelId()); // let times = 1; // if (value != '' && value != '0') { // times = Number(value) + 1; // } //gg.data.setkeyKeyValue(OtherDataType.FailReportData, gg.game.CurentBattle.getReportDYLevelId(), times); const curWaveNum = gg.game.CurentBattle.CurentWaveNum; gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-过关失败`); const levelId = gg.sdk.get30rLevelId(); gg.sdk.report30r('LEVEL_LOSE', { level_id: levelId, stage_type: gg.sdk.get30rStageType(), ad_cnt: gg.sdk.get30rAdCount(), action_param: levelId, }); this.showReward(); this._refreshMoneyDisplay(); this._refreshCoinRewardDisplay(); if (!gg.game.CurentBattle.IsWatchAd && gg.game.CurentSelectLevelId >= 3) { gg.sdk.showInterstitialAd(() => { }, () => { }, true); } } /** 结算段位与星条:先展示降星前,再播当前星 fillRange 1→0 */ private _refreshRankUi(starsBefore: number): void { void RankDataManager.ensureTable().then(() => { if (!this.isValid) return; const starsAfter = RankDataManager.getPlayerRankStars(); const beforeCount = RankDataManager.getStarModCount(starsBefore); const afterCount = RankDataManager.getStarModCount(starsAfter); const starDropped = starsAfter < starsBefore; RankDataManager.applyRankLevelIconToNode( this.ui_rankLevel, RankDataManager.resolveTierByStars(starsAfter)?.id, ); this._stopStarDropAnim(); this.unschedule(this._onStarDropAnimDelay); if (!starDropped) { this._applyStarDisplay(afterCount); return; } this._starDropIndex = beforeCount; this._starDropAfterCount = afterCount; this._applyStarDisplay(beforeCount); this.scheduleOnce(this._onStarDropAnimDelay, STAR_DROP_ANIM_DELAY_SEC); }); } private _onStarDropAnimDelay = (): void => { this._playStarDropAnim(this._starDropIndex, this._starDropAfterCount); }; /** ui_star:star1~star5,内层 star 控制点亮与 fillRange */ private _applyStarDisplay(litCount: number): void { if (!this.ui_star?.isValid) { console.log('[BattleFail] ui_star 缺失'); return; } const count = Math.max(0, Math.min(5, Math.floor(Number(litCount) || 0))); for (let i = 1; i <= 5; i++) { const slot = this.ui_star.getChildByName(`star${i}`); if (!slot?.isValid) continue; slot.active = true; const icon = slot.getChildByName('star'); if (!icon?.isValid) continue; Tween.stopAllByTarget(icon); const sprite = icon.getComponent(Sprite); if (sprite) { Tween.stopAllByTarget(sprite); sprite.fillRange = 1; } icon.active = i <= count; } } /** 当前降掉的那颗星:fillRange 1→0,结束后刷新为降星后星条 */ private _playStarDropAnim(loseStarIndex: number, afterCount: number): void { if (!this.isValid) return; const slot = this.ui_star?.getChildByName(`star${loseStarIndex}`); const icon = slot?.getChildByName('star'); const sprite = icon?.getComponent(Sprite); if (!slot?.isValid || !icon?.isValid || !sprite) { console.log('[BattleFail] 降星动画节点缺失', loseStarIndex); this._applyStarDisplay(afterCount); return; } icon.active = true; Tween.stopAllByTarget(sprite); sprite.fillRange = 1; tween(sprite) .to(STAR_DROP_FILL_SEC, { fillRange: 0 }) .call(() => { if (!this.isValid) return; this._applyStarDisplay(afterCount); }) .start(); } /** 停止降星 fillRange 动画 */ private _stopStarDropAnim(): void { this.unschedule(this._onStarDropAnimDelay); if (!this.ui_star?.isValid) return; for (let i = 1; i <= 5; i++) { const slot = this.ui_star.getChildByName(`star${i}`); const icon = slot?.getChildByName('star'); if (icon?.isValid) Tween.stopAllByTarget(icon); const sprite = icon?.getComponent(Sprite); if (sprite) Tween.stopAllByTarget(sprite); } } showReward(): void { const levelId = gg.game.CurentBattle.LevelConfigId; const rewardStr = gg.data.project.getLevelRewardStr(levelId, false); if (!rewardStr) { console.log('[BattleFail] Level 无 loserreward', levelId); } const pairs = gg.data.project.parseItemPipeNumList(rewardStr); const mt = 1; let addRatio = 1; addRatio += gg.data.getFightRewardRatio(); const itemsArray: ItemData[] = []; for (let i = 0; i < pairs.length; i++) { const goodId = pairs[i].id; const itemNum = pairs[i].num; if (goodId === 104 || goodId === 105 || goodId === 106 || goodId === 107) { continue; } if (gg.equip.getItemEquipId(goodId) > 0) { continue; } let num = Math.floor(itemNum * addRatio); if (goodId != 4) { num = num * mt; } if (num <= 0) continue; itemsArray.push(new ItemData(goodId, num)); } this._appendLootRewards(itemsArray); this._pendingCoin = this._extractCoinAmount(itemsArray); this._dataList = itemsArray; this._spawnWeaponLoot(); } /** ui_content:本局掉落宝石(克隆 ui_gemTemp) */ private _spawnWeaponLoot(): void { this._recycleWeaponLoot(); const root = this.ui_content; if (!root?.isValid) return; const drops = gg.game.CurentBattle?.weaponDrop?.allDrops ?? []; const aggregated = WeaponDropService.aggregateByWeaponId(drops); for (let i = 0; i < aggregated.length; i++) { this._addWeaponLootNode(root, aggregated[i].record); } root.getComponent(Layout)?.updateLayout(); } /** * 在 ui_content 追加一颗宝石。模板只有 icon,不显示数量。 * @param root ui_content * @param record 掉落记录 */ private _addWeaponLootNode(root: Node, record: WeaponDropRecord): void { const node = WeaponDropView.takeSettlePrefab(this.ui_gemTemp); if (!node) return; root.addChild(node); node.setScale(Vec3.ZERO); node.angle = 0; WeaponDropView.applySettleVisual(node, record); const view = node.getComponent(WeaponDropView) ?? node.addComponent(WeaponDropView); view.playDisplay(); this._lootWeaponNodes.push(node); } /** 回收 ui_content 宝石节点 */ private _recycleWeaponLoot(): void { for (let i = 0; i < this._lootWeaponNodes.length; i++) { WeaponDropView.recycleSettleNode(this._lootWeaponNodes[i]); } this._lootWeaponNodes.length = 0; } /** * 失败时把双方掉落碎片并入奖励列表(按道具 id 堆叠)。 * @param items 已解析的关卡失败奖励 */ private _appendLootRewards(items: ItemData[]): void { const drops = gg.game.CurentBattle?.weaponDrop?.collectRewardItems() ?? []; for (let i = 0; i < drops.length; i++) { const itemId = drops[i].itemId; const num = drops[i].num; let found = false; for (let j = 0; j < items.length; j++) { if (items[j].id !== itemId) continue; items[j].num += num; found = true; break; } if (!found) items.push(new ItemData(itemId, num)); } } /** lb_money:当前拥有金币 */ private _refreshMoneyDisplay(): void { if (!this.lb_money?.isValid) return; this.lb_money.string = `${gg.data.getMoney()}`; } /** ui_coin/num:本局待领金币(含倍率) */ private _refreshCoinRewardDisplay(): void { const numLb = this._coinNumLabel(); if (!numLb?.isValid) return; const gain = Math.floor(this._pendingCoin * this.addNum); numLb.string = gain > 0 ? `${gain}` : '0'; if (this.ui_coin?.isValid) this.ui_coin.active = gain > 0; } /** ui_coin 数量 Label */ private _coinNumLabel(): Label | null { return this.ui_coin?.getChildByName('num')?.getComponent(Label) ?? null; } /** ui_coin 金币 icon */ private _coinIconNode(): Node | null { return this.ui_coin?.getChildByName('icon') ?? null; } /** ui_money 金币 icon */ private _moneyCoinNode(): Node | null { return this.ui_money?.getChildByName('金币') ?? null; } /** 从奖励列表汇总金币并移除(仅 ui_coin 展示) */ private _extractCoinAmount(items: ItemData[]): number { let total = 0; for (let i = items.length - 1; i >= 0; i--) { if (items[i].id !== ItemNumType.money) continue; total += Math.floor(Number(items[i].num) || 0); items.splice(i, 1); } return total; } click_btnGet(): void { if (this._claiming) return; const curWaveNum = gg.game.CurentBattle.CurentWaveNum; gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-失败领取奖励`); this._claimWithCoinFly(); } /** 金币影子飞行 → 入账 → 刷新 lb_money → 回大厅 */ private _claimWithCoinFly(): void { if (this._claiming) return; this._claiming = true; this.btnGet.active = false; this.btnADGet.active = false; const coinGain = Math.floor(this._pendingCoin * this.addNum); const icon = this._coinIconNode(); const target = this._moneyCoinNode(); const fly = this._coinFly; if (!fly || coinGain <= 0 || !icon?.isValid || !target?.isValid) { this._applyRewards(); this._pendingCoin = 0; this._refreshMoneyDisplay(); this._refreshCoinRewardDisplay(); this._finishClaimNavigate(); return; } fly.play(icon, target, coinGain, () => { if (!this.isValid) return; this._applyRewards(); this._pendingCoin = 0; this._refreshMoneyDisplay(); this._refreshCoinRewardDisplay(); this._finishClaimNavigate(); }); } /** 发放除金币外的奖励并入账金币 */ private _applyRewards(): void { for (let i = 0; i < this._dataList.length; i++) { const value = this._dataList[i]; gg.data.addItemNum(Number(value.id), Number(value.num) * this.addNum); } const coinGain = Math.floor(this._pendingCoin * this.addNum); if (coinGain > 0) gg.data.addItemNum(ItemNumType.money, coinGain); gg.game.IsPlayedGame = true; DailyShopDataManager.refreshPoolsOnSettle(); DailyShopDataManager.markSettleBuyPop(); } private _finishClaimNavigate(): void { if (!this.isValid) return; this._claiming = false; gg.game.returnedFromBattleFail = true; gg.game.returnedFromBattleFailChapterId = gg.game.CurentBattle.LevelConfigId; gg.game.enterMain(UIID.Home); } click_btnADGet(): void { if (this._claiming) return; gg.sdk.showVideoAd((res) => { if (res) { const curWaveNum = gg.game.CurentBattle.CurentWaveNum; gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-AD-失败双倍领取奖励`); this.addNum = 2; this._refreshCoinRewardDisplay(); gg.game.CurentBattle.IsWatchAd = true; this._claimWithCoinFly(); } else { gg.ui.showToast('观看视频失败'); } }); } }