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/BattleSuccess/UIBattleSuccess.ts

653 lines
24 KiB

3 weeks ago
//*********************
// create by 流云
// time: Thu Sep 25 2025
// desc:
//*********************
import { _decorator, Label, Layout, Node, tween, Tween, UITransform, v3, Vec3 } from 'cc';
import { Auto_BattleSuccess } from './Auto_BattleSuccess';
import {
BattleSuccessNodes,
playBattleSuccessOpen,
prepareBattleSuccessOpen,
stopBattleSuccessOpen,
stopHighlightButtonAnim,
} from './BattleSuccessOpenAnim';
import { BattleSuccessCoinFlyPresenter } from './BattleSuccessCoinFlyPresenter';
import { TaskType, ItemNumType } from '../../game/GameData';
import { ItemData } from '../../game/ConfigProjectData';
import MTools from 'db://assets/mx/tools/MTools';
import { GAudioPath } from '../../game/ConfigRes';
import { VivoAd } from '../../sdk/functions/VivoAd';
import { IPopAnimConfig } from 'db://assets/mx/module/ui/popAnim';
import { recordKeepHeroClearFromBattle } from '../../battle/bag/KeepHeroClearRecord';
import { DailyShopDataManager } from '../../manager/DailyShopDataManager';
import { MatchWinStats } from '../../manager/MatchWinStats';
import { RankDataManager } from '../../manager/RankDataManager';
import { WeaponDropView } from '../BattleGame/BattleWeaponDropPresenter';
import { WeaponDropService, type WeaponDropRecord } from '../../battle/combat/WeaponDropService';
const { ccclass } = _decorator;
/**游戏结果奖励类型 */
export enum GameResultAwardType {
Coin = 1,//金币
Diamond = 2,//钻石
}
@ccclass('UIBattleSuccess')
export class UIBattleSuccess extends Auto_BattleSuccess {
addNum = 1
private _victoryNodes: BattleSuccessNodes = null;
/** ui_animStar 初始本地坐标 */
private _animStarHome: Vec3 | null = null;
/** ui_rankName 初始本地坐标 */
private _rankNameHome: Vec3 | null = null;
/** 升星动画起点(已点亮数) */
private _starAnimFrom = 0;
/** 升星动画终点(已点亮数) */
private _starAnimTo = 0;
/** 结算后段位 id */
private _rankAfterId = 0;
/** 结算后星条点亮数 */
private _afterStarCount = 0;
/** 飞星节点 */
private _uiAnimStar: Node | null = null;
/** 飞段位节点 */
private _uiRankName: Node | null = null;
/** 本局待领金币(不含倍率) */
private _pendingCoin = 0;
/** 结算奖励列表(领取时入账) */
private _dataList: ItemData[] = [];
/** ui_content 武器展示节点 */
private readonly _lootWeaponNodes: Node[] = [];
/** 结算武器模板 ui_center/ui_weaponTemp */
private _weaponTemp: Node | null = null;
/** 金币飞行 */
private _coinFly: BattleSuccessCoinFlyPresenter | null = null;
/** 领取流程中 */
private _claiming = false;
onLoad(): void {
super.onLoad();
this._bindVictoryNodes();
this._bindRankAnimNodes();
this._bindWeaponTemp();
this._coinFly = new BattleSuccessCoinFlyPresenter();
this._coinFly.bind(this.ui_center);
}
/** 绑定结算武器模板 */
private _bindWeaponTemp(): void {
const temp = this.ui_center?.getChildByName('ui_weaponTemp') ?? null;
if (!temp?.isValid) {
console.log('[UIBattleSuccess] ui_weaponTemp 缺失');
this._weaponTemp = null;
return;
}
this._weaponTemp = temp;
}
/** preparePopOpenPose 可能在 onLoad 之前调用,需惰性绑定节点 */
private _bindVictoryNodes(): void {
if (this._victoryNodes || !this.ui_center) return;
this._victoryNodes = {
bg: this._child('背景'),
title: this._child('挑战成功标题'),
rewardLabel: this._child('获得奖励'),
tipBg: this._child('你已赢得该对局'),
spine: this._child('结算页'),
scroll: this.ui_content,
btnLayout: this._child('btnLayout'),
};
}
/** 绑定升星 / 升段飞入节点 */
private _bindRankAnimNodes(): void {
if (!this.ui_center?.isValid) return;
if (!this._uiAnimStar) {
this._uiAnimStar = this.ui_center.getChildByName('ui_animStar');
if (!this._uiAnimStar) console.log('[UIBattleSuccess] 缺节点 ui_animStar');
}
if (!this._uiRankName) {
this._uiRankName = this.ui_center.getChildByName('ui_rankName');
if (!this._uiRankName) console.log('[UIBattleSuccess] 缺节点 ui_rankName');
}
}
/**
* @param name ui_center 下子节点名
*/
private _child(name: string): Node | null {
const n = this.ui_center.getChildByName(name);
if (!n) console.log('[UIBattleSuccess] 缺节点', name);
return n;
}
onEnable(): void {
super.onEnable();
gg.sdk.pfTriggerGC();
this._coinFly?.bind(this.ui_center);
}
onDisable(): void {
if (this._victoryNodes) {
stopBattleSuccessOpen(this._victoryNodes);
}
this._stopStarUpgradeAnim();
this._stopRankNameFly();
this.unschedule(this._onStarAnimDelay);
this.unschedule(this._onRankFlyDelay);
this._recycleWeaponLoot();
this._coinFly?.unbind();
VivoAd.hideNative();
super.onDisable();
}
preparePopOpenPose(): void {
const cfg = this.uiInfo?.uiconf?.popAnim as IPopAnimConfig | undefined;
if (!cfg) return;
// UIManager 会在 onLoad 前调用;此处仅遮罩,节点隐藏与序列在 onInit 执行
gg.ui.popAnim.prepareOpen(this.node, cfg.mask ?? 'dim', 'none');
}
playPopFromConfig(): void {
const cfg = this.uiInfo?.uiconf?.popAnim as IPopAnimConfig | undefined;
if (!cfg) return;
if (cfg.mask !== 'none') {
this.popMask(cfg.mask ?? 'dim');
}
}
onInit(): void {
this.addNum = 1;
this.btnADGet.active = false;
this.btnShareGet.active = false;
const levelId = gg.sdk.get30rLevelId();
gg.sdk.report30r('LEVEL_PASS', {
level_id: levelId,
stage_type: gg.sdk.get30rStageType(),
ad_cnt: gg.sdk.get30rAdCount(),
action_param: levelId,
});
gg.audio.playEffect({ name: '游戏胜利', path: GAudioPath.SfxUi });
MatchWinStats.record(true, gg.game.CurentBattle?.opponentRankId ?? 0);
gg.sdk.reportDY('inLevel', `${gg.game.getReportDYRankLabel()}-胜利`);
this._bindRankAnimNodes();
this._resetAnimStarHome();
this._resetRankNameFly();
this._refreshRankUi();
// 通关时记录场上完整英雄(刀/斧/悟空等;不同局内等级算不同单位)
recordKeepHeroClearFromBattle();
gg.data.saveToServer();
gg.data.addDayTaskProgressData(TaskType.successPass, 1)
this.showReward()
this._refreshMoneyDisplay();
this._refreshCoinRewardDisplay();
this.btnADGet.active = true
this.btnShareGet.active = false
if (!gg.game.CurentBattle.IsWatchAd && gg.game.CurentSelectLevelId >= 3) {
gg.sdk.showInterstitialAd(() => {
}, () => {
}, true)
}
this.offlineRewardTimeCount()
this._bindVictoryNodes();
if (this._victoryNodes) {
prepareBattleSuccessOpen(this._victoryNodes);
}
this._playVictoryCelebration();
}
private _playVictoryCelebration(): void {
if (!this._victoryNodes) return;
const buttons = [this.btnGet];
if (this.btnADGet.active) buttons.push(this.btnADGet);
playBattleSuccessOpen({
nodes: this._victoryNodes,
rewardItems: this._lootWeaponNodes.slice(),
buttons,
highlightBtn: this.btnADGet.active ? this.btnADGet : null,
onComplete: () => {
this.notifyOpenAnimEnd();
},
});
}
offlineRewardTimeCount() {
}
/** 结算段位与星条:先展示升前,再播飞星 / 飞段位 */
private _refreshRankUi(): void {
void RankDataManager.ensureTable().then(() => {
if (!this.isValid) return;
const starsAfter = RankDataManager.getPlayerRankStars();
const starsBefore = Math.max(1, starsAfter - 1);
const rankAfter = RankDataManager.resolveTierByStars(starsAfter);
const rankBefore = RankDataManager.resolveTierByStars(starsBefore);
this._rankAfterId = rankAfter?.id ?? 0;
this._afterStarCount = RankDataManager.getStarModCount(starsAfter);
const beforeStarCount = RankDataManager.getStarModCount(starsBefore);
const majorUp = RankDataManager.isMajorTierPromotion(rankBefore, rankAfter);
const starUp = !majorUp && this._afterStarCount > beforeStarCount;
RankDataManager.applyRankLevelIconToNode(
this.ui_rankLevel,
majorUp ? (rankBefore?.id ?? this._rankAfterId) : this._rankAfterId,
);
this._refreshSubRankStars((majorUp || starUp) ? beforeStarCount : this._afterStarCount);
this.unschedule(this._onStarAnimDelay);
this.unschedule(this._onRankFlyDelay);
if (majorUp) {
this.scheduleOnce(this._onRankFlyDelay, 0.45);
return;
}
if (!starUp) return;
this._starAnimFrom = beforeStarCount;
this._starAnimTo = this._afterStarCount;
this.scheduleOnce(this._onStarAnimDelay, 0.45);
});
}
private _onRankFlyDelay = (): void => {
this._playRankNameFly();
};
private _onStarAnimDelay = (): void => {
this._playStarUpgradeAnim(this._starAnimFrom, this._starAnimTo);
};
/** ui_star:star1~star5,内层 star 控制点亮 */
private _refreshSubRankStars(litCount: number): void {
if (!this.ui_star?.isValid) {
console.log('[UIBattleSuccess] 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;
Tween.stopAllByTarget(slot);
slot.setScale(1, 1, 1);
const icon = slot.getChildByName('star');
if (!icon?.isValid) continue;
Tween.stopAllByTarget(icon);
icon.setScale(1, 1, 1);
icon.active = i <= count;
}
}
private _resetAnimStarHome(): void {
this._stopStarUpgradeAnim();
const anim = this._uiAnimStar;
if (!anim?.isValid) return;
if (!this._animStarHome) this._animStarHome = anim.position.clone();
anim.setPosition(this._animStarHome);
anim.setScale(Vec3.ONE);
anim.active = false;
}
private _stopStarUpgradeAnim(): void {
this.unschedule(this._onStarAnimDelay);
const anim = this._uiAnimStar;
if (anim?.isValid) {
Tween.stopAllByTarget(anim);
anim.active = false;
}
if (!this.ui_star?.isValid) return;
for (let i = 1; i <= 5; i++) {
const slot = this.ui_star.getChildByName(`star${i}`);
if (slot?.isValid) {
Tween.stopAllByTarget(slot);
slot.setScale(1, 1, 1);
}
const icon = slot?.getChildByName('star');
if (icon?.isValid) {
Tween.stopAllByTarget(icon);
icon.setScale(1, 1, 1);
}
}
}
/** ui_animStar 逐颗飞向 star1~star5 并点亮 */
private _playStarUpgradeAnim(fromCount: number, toCount: number): void {
if (!this.isValid) return;
const anim = this._uiAnimStar;
const starRoot = this.ui_star;
if (!anim?.isValid || !starRoot?.isValid) {
this._refreshSubRankStars(toCount);
return;
}
const home = this._animStarHome ?? anim.position.clone();
this._animStarHome = home;
const parentUt = anim.parent?.getComponent(UITransform);
if (!parentUt) {
this._refreshSubRankStars(toCount);
return;
}
const flyOne = (litCount: number): void => {
if (!this.isValid || !anim.isValid) return;
if (litCount >= toCount) {
anim.active = false;
return;
}
const slotIndex = litCount + 1;
const target = starRoot.getChildByName(`star${slotIndex}`);
if (!target?.isValid) {
flyOne(litCount + 1);
return;
}
const dest = parentUt.convertToNodeSpaceAR(target.worldPosition);
Tween.stopAllByTarget(anim);
anim.setPosition(home);
anim.setScale(Vec3.ONE);
anim.active = true;
anim.setSiblingIndex(Math.max(0, (anim.parent?.children.length ?? 1) - 1));
tween(anim)
.delay(0.12)
.to(0.4, { position: dest }, { easing: 'sineIn' })
.call(() => {
if (!this.isValid) return;
anim.active = false;
this._popStarToSolid(slotIndex);
})
.delay(0.28)
.call(() => flyOne(litCount + 1))
.start();
};
flyOne(fromCount);
}
/** 点亮实星并放大回弹 */
private _popStarToSolid(slotIndex: number): void {
const slot = this.ui_star?.getChildByName(`star${slotIndex}`);
if (!slot?.isValid) return;
const icon = slot.getChildByName('star');
if (icon?.isValid) {
Tween.stopAllByTarget(icon);
icon.setScale(1, 1, 1);
icon.active = true;
}
Tween.stopAllByTarget(slot);
slot.setScale(1, 1, 1);
tween(slot)
.to(0.18, { scale: v3(1.4, 1.4, 1) }, { easing: 'backOut' })
.to(0.14, { scale: Vec3.ONE }, { easing: 'sineIn' })
.start();
}
private _resetRankNameFly(): void {
this._stopRankNameFly();
const n = this._uiRankName;
if (!n?.isValid) return;
if (!this._rankNameHome) this._rankNameHome = n.position.clone();
n.setPosition(this._rankNameHome);
n.setScale(Vec3.ONE);
n.active = false;
}
private _stopRankNameFly(): void {
const n = this._uiRankName;
if (n?.isValid) {
Tween.stopAllByTarget(n);
n.active = false;
}
}
/** ui_rankName 赋新段位图,飞到 ui_rankLevel 后刷新段位 */
private _playRankNameFly(): void {
const fly = this._uiRankName;
const dest = this.ui_rankLevel;
const rankId = this._rankAfterId;
if (!fly?.isValid || !dest?.isValid || rankId <= 0) {
RankDataManager.applyRankLevelIconToNode(dest, rankId);
this._refreshSubRankStars(this._afterStarCount);
return;
}
const home = this._rankNameHome ?? fly.position.clone();
this._rankNameHome = home;
const parentUt = fly.parent?.getComponent(UITransform);
if (!parentUt) {
RankDataManager.applyRankLevelIconToNode(dest, rankId);
this._refreshSubRankStars(this._afterStarCount);
return;
}
RankDataManager.applyRankLevelIconToNode(fly, rankId);
Tween.stopAllByTarget(fly);
fly.setPosition(home);
fly.setScale(Vec3.ONE);
fly.active = true;
fly.setSiblingIndex(Math.max(0, (fly.parent?.children.length ?? 1) - 1));
const to = parentUt.convertToNodeSpaceAR(dest.worldPosition);
tween(fly)
.delay(0.08)
.to(0.45, { position: to }, { easing: 'sineIn' })
.call(() => {
if (!this.isValid) return;
fly.active = false;
fly.setPosition(home);
RankDataManager.applyRankLevelIconToNode(dest, rankId);
this._refreshSubRankStars(this._afterStarCount);
})
.start();
}
showReward(): void {
this._recycleWeaponLoot();
if (this.ui_content?.isValid) this.ui_content.removeAllChildren();
const levelId = gg.game.CurentBattle.LevelConfigId;
const rewardStr = gg.data.project.getLevelRewardStr(levelId, true);
const pairs = gg.data.project.parseItemPipeNumList(rewardStr);
const itemsArray: ItemData[] = [];
let addRatio = 1;
addRatio += gg.data.getFightRewardRatio();
for (let i = 0; i < pairs.length; i++) {
const goodId = pairs[i].id;
const itemNum = Math.floor(pairs[i].num * addRatio);
if (goodId == 104 || goodId == 105 || goodId == 106 || goodId == 107) {
const boxRewardArray = gg.data.getFragmentNumArray(goodId + ',' + itemNum);
const statsArray = MTools.mergeToStatsArray(boxRewardArray);
for (let j = 0; j < statsArray.length; j++) {
const array = statsArray[j].split('|');
itemsArray.push(new ItemData(Number(array[0]), Number(array[1])));
}
} else {
itemsArray.push(new ItemData(goodId, itemNum));
}
}
this._appendLootRewards(itemsArray);
this._pendingCoin = this._extractCoinAmount(itemsArray);
this._dataList = itemsArray;
this._spawnWeaponLoot();
}
/** 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;
}
/** ui_content:本局掉落武器(武器预制体) */
private _spawnWeaponLoot(): void {
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++) {
const entry = aggregated[i];
this._addWeaponLootNode(root, entry.record, entry.count);
}
root.getComponent(Layout)?.updateLayout();
}
/** 在 ui_content 追加单个武器展示(同 weaponId 合并数量) */
private _addWeaponLootNode(root: Node, record: WeaponDropRecord, count: number): void {
const node = WeaponDropView.takeSettlePrefab(this._weaponTemp);
if (!node) return;
root.addChild(node);
node.setScale(Vec3.ZERO);
node.angle = 0;
WeaponDropView.applySettleVisual(node, record);
WeaponDropView.applyFragmentCount(node, count);
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));
}
}
click_btnGet(): void {
if (this._claiming) return;
let 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;
this.btnShareGet.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.enterMain();
}
click_btnADGet(): void {
if (this._claiming) return;
gg.sdk.showVideoAd((res) => {
if (res) {
let curWaveNum = gg.game.CurentBattle.CurentWaveNum
gg.sdk.reportDY("inLevel", `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-AD-广告成功双倍领取`)
this.addNum = 2
stopHighlightButtonAnim(this.btnADGet);
this._refreshCoinRewardDisplay();
gg.game.CurentBattle.IsWatchAd = true
this._claimWithCoinFly();
} else {
gg.ui.showToast('观看视频失败');
}
})
}
click_btnShareGet() {
gg.ui.showToast('该功能未开放');
}
}