MatchMaster/assets/jungle_treasure/script/jungle_Manager.ts
COMPUTER\EDY da57305717 更新
2026-08-07 16:06:02 +08:00

1162 lines
38 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import JungleAnimator from "./JungleAnimator";
import JungleProgress from "./JungleProgress";
import JungleRewardService from "./JungleRewardService";
import {
JungleActivityConfig,
JungleRenderState,
JungleTierConfig,
JungleTierState,
JungleViewAssets,
} from "./JungleTypes";
import JungleView from "./JungleView";
import Utils from "../../Script/module/Pay/Utils";
import { MiniGameSdk } from "../../Script/Sdk/MiniGameSdk";
const { ccclass, property } = cc._decorator;
/**
* Jungle Treasure 流程控制器。
* 只管理活动状态和模块协作UI、动画、配置、存档与发奖均由独立模块负责。
*/
@ccclass
export default class JungleManager extends cc.Component {
@property(cc.SpriteFrame)
cardFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
trackFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
titleFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
buttonFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
freeFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
claimFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
yuanFrame: cc.SpriteFrame = null;
@property(cc.BitmapFont)
priceFont: cc.BitmapFont = null;
@property(cc.BitmapFont)
countFont: cc.BitmapFont = null;
@property(cc.SpriteFrame)
lockFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
unlockFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
timerFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
closeFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
coinFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
freezeFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
hammerFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
magicWandFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
infiniteHealthFrame: cc.SpriteFrame = null;
@property({ tooltip: "服务器支付尚未接入时,点击付费档位自动模拟支付成功" })
useMockPayment: boolean = true;
@property({ tooltip: "只预览领取和轨道动画,不发道具、不支付、不保存进度" })
previewAnimationOnly: boolean = true;
private activityConfig: JungleActivityConfig = null;
private currentTierIndex: number = 0;
private cards: cc.Node[] = [];
private slotPositions: cc.Vec2[] = [];
private isMoving: boolean = false;
private isPurchasing: boolean = false;
private isClaiming: boolean = false;
private isUnlockAnimating: boolean = false;
private isAttentionAnimating: boolean = false;
private hasExpired: boolean = false;
private claimTimeout: any = null;
private paymentTimeout: any = null;
private onShowListener: () => void = null;
private pendingPaymentTierId: number = 0;
private pendingPaymentProductId: string = "";
private pendingPaymentPrice: number = 0;
private checkingIosOrder: boolean = false;
private entrySource: string = "jump_window";
private unlockPollDelays: number[] = [3000, 3000, 3000, 3000, 6000, 9000, 9000];
private unlockPollMaxAttempts: number = 8;
private unlockPollAttempt: number = 0;
private unlockPollTierId: number = 0;
private unlockPollSession: number = 0;
private unlockPollTimer: any = null;
private unlockRequestTimeout: any = null;
private unlockRequestTimeoutMs: number = 5500;
private isUnlockPolling: boolean = false;
private directPayPollDelays: number[] = [3000, 3000, 3000, 3000, 6000, 9000, 9000];
private directPayPollMaxAttempts: number = 8;
private directPayPollAttempt: number = 0;
private directPayPollSession: number = 0;
private directPayPollTimer: any = null;
private directPayRequestTimeout: any = null;
private directPayRequestTimeoutMs: number = 5500;
private directPayOrderNo: string = "";
private directPayTier: JungleTierConfig = null;
private isDirectPayPolling: boolean = false;
private paymentRetryStage: string = "";
private view: JungleView = null;
private animator: JungleAnimator = null;
private progress: JungleProgress = null;
private rewardService: JungleRewardService = null;
protected onLoad(): void {
this.progress = new JungleProgress();
this.rewardService = new JungleRewardService();
this.activityConfig = null;
this.currentTierIndex = 0;
this.view = new JungleView(
this.node,
this.getViewAssets(),
this,
this.closeActivity,
this.onCardClicked,
);
const refs = this.view.build();
this.cards = refs.cards;
this.slotPositions = refs.slotPositions;
this.loadClaimFrame();
this.animator = new JungleAnimator(
this,
refs.contentNode,
this.cardFrame,
this.unlockFrame,
);
this.cards.forEach((card) => card.active = false);
this.schedule(this.updateTimer, 1);
this.schedule(this.playFirstCardAttention, 2);
this.registerWechatOnShow();
}
/**
* 领取图是后加入的分包资源,运行时再按路径加载一次。
* 这样即使旧 prefab 缓存没有反序列化 claimFrame也不会出现付费按钮空白。
*/
private loadClaimFrame(): void {
let bundle = cc.assetManager.getBundle("jungle_treasure");
if (!bundle) {
cc.warn("[JungleTreasure] 领取图片所在分包未加载");
return;
}
bundle.load("img/lingqu", cc.SpriteFrame, (error: Error, frame: cc.SpriteFrame) => {
if (error || !frame || !cc.isValid(this.node) || !this.view) {
if (error) {
cc.warn("[JungleTreasure] 领取图片加载失败", error);
}
return;
}
this.claimFrame = frame;
this.view.setClaimFrame(frame);
this.renderAllCards();
});
}
protected onDestroy(): void {
this.unschedule(this.updateTimer);
this.unschedule(this.playFirstCardAttention);
this.clearClaimTimeout();
this.clearPaymentTimeout();
this.stopDirectPayResultPolling(true);
this.stopUnlockStatePolling(true);
this.unregisterWechatOnShow();
}
/** 使用后端配置和 0/1/2 状态数组初始化;状态 2 的档位不进入展示队列。 */
public setServerData(
config: JungleActivityConfig,
tierStates: JungleTierState[],
source?: string,
): boolean {
if (!config || !Array.isArray(config.tiers) || !Array.isArray(tierStates)
|| tierStates.length < config.tiers.length) {
cc.warn("[JungleTreasure] server config or tierStates is invalid");
return false;
}
if (source !== undefined) {
this.entrySource = source === "front_page" || source === "shop_page"
? source
: "jump_window";
}
const visibleTiers: JungleTierConfig[] = [];
for (let index = 0; index < config.tiers.length; index++) {
const sourceTier = config.tiers[index];
const rawState = Number(tierStates[index]);
if (rawState !== 0 && rawState !== 1 && rawState !== 2) {
cc.warn("[JungleTreasure] invalid tier state:", index + 1, tierStates[index]);
return false;
}
if (rawState === 2) {
continue;
}
visibleTiers.push(Object.assign({}, sourceTier, {
state: rawState as JungleTierState,
}));
}
this.activityConfig = Object.assign({}, config, {
tiers: visibleTiers,
});
this.currentTierIndex = 0;
this.hasExpired = false;
this.renderAllCards();
this.updateTimer();
return true;
}
/** 正式支付层完成后回调。支付失败或取消不会移动轨道。 */
public completePurchase(success: boolean): void {
if (!this.isPurchasing) {
return;
}
this.isPurchasing = false;
if (success) {
const tier = this.activityConfig && this.activityConfig.tiers[this.currentTierIndex];
if (tier) {
tier.state = 1;
}
this.renderAllCards();
if (tier) {
this.playTierUnlockAnimation(tier.id);
}
} else {
this.renderAllCards();
}
}
public openActivity(): void {
this.node.active = true;
this.registerWechatOnShow();
this.renderAllCards();
this.updateTimer();
}
public closeActivity(): void {
if (this.isMoving || this.isPurchasing || this.isClaiming || this.isUnlockAnimating) {
return;
}
this.stopFirstCardAttention();
this.unregisterWechatOnShow();
this.node.active = false;
this.node.emit("jungle-close");
}
/** 仅用于当前模拟配置的联调。 */
public resetMockProgress(): void {
if (!this.activityConfig) {
return;
}
this.progress.reset(this.activityConfig);
this.currentTierIndex = 0;
this.renderAllCards();
}
public getCurrentTierIndex(): number {
return this.currentTierIndex;
}
private onCardClicked(buttonComponent: cc.Button): void {
if (this.isMoving || this.isPurchasing || this.isClaiming
|| this.isUnlockAnimating || !this.activityConfig) {
return;
}
const button = buttonComponent && buttonComponent.node;
if (!button) {
return;
}
const card = button.parent;
if (card !== this.cards[0]) {
this.animator.playLockedFeedback(card);
return;
}
this.stopFirstCardAttention();
const tier = this.activityConfig.tiers[this.currentTierIndex];
if (!tier) {
return;
}
const isPurchaseBlocked = tier.price > 0
&& tier.state === 0
&& !!this.activityConfig.endAt
&& Date.now() >= this.activityConfig.endAt;
if (isPurchaseBlocked) {
return;
}
if (this.previewAnimationOnly) {
this.completeCurrentTier();
} else if (tier.state === 0) {
this.requestPurchase(tier);
} else {
this.requestClaim(tier);
}
}
private trackJungleAction(eventName: string, tier: JungleTierConfig): void {
let currentBlock = Number(tier && tier.id);
if (!isFinite(currentBlock) || currentBlock < 1 || currentBlock > 51) {
cc.warn("[JungleTreasure] 埋点格子编号无效:", currentBlock);
return;
}
cc.fx.GameTool.shushu_Track(eventName, {
current_block: currentBlock,
source: this.entrySource,
});
}
private requestPurchase(tier: JungleTierConfig): void {
this.trackJungleAction("pay_jungle", tier);
if (!tier.productId) {
this.showToast("礼包商品配置错误");
return;
}
this.isPurchasing = true;
this.pendingPaymentTierId = tier.id;
this.pendingPaymentProductId = tier.productId;
this.pendingPaymentPrice = Math.round(Number(tier.price) * 100);
this.renderAllCards();
this.openLoad();
if (this.useMockPayment) {
this.scheduleOnce(() => {
this.closeLoad();
this.completePurchase(true);
}, 0.45);
return;
}
const systemType = this.getSystemType();
if (systemType === "ios" && !cc.fx.GameConfig.GM_INFO.iosCanPay) {
this.startOldIosPayment(tier);
return;
}
this.startDirectPayment(tier, systemType);
}
/** 状态 1 的礼包必须先由后端改成 2成功后才发奖励并播放轨道动画。 */
private requestClaim(tier: JungleTierConfig): void {
this.trackJungleAction("collect_jungle", tier);
this.isClaiming = true;
this.renderAllCards();
this.openLoad();
let completed = false;
this.clearClaimTimeout();
this.claimTimeout = setTimeout(() => {
if (completed) {
return;
}
completed = true;
this.claimTimeout = null;
this.finishClaimFailure("领取请求超时,请稍后重试");
}, 8000);
Utils.claimJungleTreasure(tier.id, (response) => {
if (completed || !cc.isValid(this.node)) {
return;
}
completed = true;
this.clearClaimTimeout();
const data = response && response.code === 1 ? response.data : null;
const states = data && data.tierStates;
const claimedTierId = Number(data && data.claimedTierId);
if (!Array.isArray(states)
|| claimedTierId !== tier.id
|| Number(states[tier.id - 1]) !== 2) {
this.finishClaimFailure((response && (response.msg || response.message)) || "礼包领取失败");
return;
}
this.closeLoad();
this.isClaiming = false;
tier.state = 2;
this.publishServerState(data);
// 后端已确认领取后,才执行客户端道具发放与礼包移动动画。
this.completeCurrentTier();
});
}
private finishClaimFailure(message: string): void {
this.closeLoad();
this.isClaiming = false;
this.renderAllCards();
this.showToast(message);
}
private startDirectPayment(tier: JungleTierConfig, systemType: string): void {
let callbackReceived = false;
this.clearPaymentTimeout();
this.paymentTimeout = setTimeout(() => {
if (callbackReceived) {
return;
}
callbackReceived = true;
this.paymentTimeout = null;
this.finishPaymentFailure("支付请求超时,请稍后重试");
// 包含玩家在微信支付面板中的操作时间,不能按普通接口的 8 秒计算。
}, 5 * 60 * 1000);
Utils.buyProp(
tier.productId,
1,
this.pendingPaymentPrice,
systemType,
tier.productId,
(result) => {
if (!cc.isValid(this.node)) {
return;
}
callbackReceived = true;
this.clearPaymentTimeout();
if (!result) {
this.finishPaymentFailure("支付拉起失败");
return;
}
if (result.err) {
if (Number(result.errCode) === 16) {
this.startOldIosPayment(tier);
return;
}
this.finishPaymentFailure(Number(result.errCode) === -2 ? "已取消支付" : "支付拉起失败");
return;
}
this.pollDirectPaymentResult(tier);
},
);
}
private pollDirectPaymentResult(tier: JungleTierConfig): void {
this.directPayOrderNo = String(Utils.outTradeNo || "");
this.directPayTier = tier;
if (!this.directPayOrderNo) {
this.finishPaymentFailure("支付订单号获取失败");
return;
}
this.startDirectPayResultPolling(false);
}
private startDirectPayResultPolling(isManualRetry: boolean): void {
this.stopDirectPayResultPolling(false);
if (!this.directPayOrderNo || !this.directPayTier) {
this.finishPaymentFailure("支付订单信息已失效");
return;
}
this.directPayPollAttempt = 0;
this.isDirectPayPolling = true;
this.isPurchasing = true;
this.paymentRetryStage = "";
this.hideJungleRetryConfirm();
this.requestDirectPayResult(this.directPayPollSession);
}
private requestDirectPayResult(session: number): void {
if (!this.isDirectPayPolling || session !== this.directPayPollSession || !cc.isValid(this.node)) {
return;
}
this.directPayPollAttempt++;
const currentAttempt = this.directPayPollAttempt;
const orderNo = this.directPayOrderNo;
let requestFinished = false;
this.clearDirectPayRequestTimeout();
this.directPayRequestTimeout = setTimeout(() => {
if (requestFinished || session !== this.directPayPollSession) {
return;
}
requestFinished = true;
this.directPayRequestTimeout = null;
this.scheduleNextDirectPayPoll(session);
}, this.directPayRequestTimeoutMs);
try {
Utils.POST("wx/getPayInfo", {
uid: cc.fx.GameConfig.GM_INFO.uid || Utils.uid,
outTradeNo: orderNo,
}, (response) => {
if (requestFinished || session !== this.directPayPollSession || !cc.isValid(this.node)) {
return;
}
requestFinished = true;
this.clearDirectPayRequestTimeout();
const payState = Number(response && response.data && response.data.pay_state);
if (response && response.code === 1 && payState === 2) {
this.completeDirectPayResultPolling();
return;
}
if (response && response.code === 1 && payState === 1) {
this.stopDirectPayResultPolling(true);
this.finishPaymentFailure("已取消支付");
return;
}
this.scheduleNextDirectPayPoll(session);
});
} catch (error) {
if (requestFinished || session !== this.directPayPollSession) {
return;
}
requestFinished = true;
this.clearDirectPayRequestTimeout();
this.scheduleNextDirectPayPoll(session);
}
}
private scheduleNextDirectPayPoll(session: number): void {
if (!this.isDirectPayPolling || session !== this.directPayPollSession) {
return;
}
if (this.directPayPollAttempt >= this.directPayPollMaxAttempts) {
this.handleDirectPayPollExhausted();
return;
}
const delayIndex = Math.min(this.directPayPollAttempt - 1, this.directPayPollDelays.length - 1);
const delay = this.directPayPollDelays[delayIndex];
this.clearDirectPayPollTimer();
this.directPayPollTimer = setTimeout(() => {
this.directPayPollTimer = null;
this.requestDirectPayResult(session);
}, delay);
}
private completeDirectPayResultPolling(): void {
const tier = this.directPayTier;
const orderNo = this.directPayOrderNo;
const attempts = this.directPayPollAttempt;
this.stopDirectPayResultPolling(true);
this.paymentRetryStage = "";
if (!tier) {
this.finishPaymentFailure("支付成功,但礼包信息已失效");
return;
}
const name = "购买丛林宝藏礼包:" + tier.productId;
MiniGameSdk.API.yinli_Pay(this.pendingPaymentPrice, orderNo, name);
this.refreshAfterPayment(tier.id);
}
private handleDirectPayPollExhausted(): void {
const orderNo = this.directPayOrderNo;
const attempts = this.directPayPollAttempt;
this.stopDirectPayResultPolling(false);
this.paymentRetryStage = "pay_result";
this.isPurchasing = false;
this.closeLoad();
this.renderAllCards();
this.showJungleRetryConfirm();
}
private stopDirectPayResultPolling(clearContext: boolean): void {
this.directPayPollSession++;
this.isDirectPayPolling = false;
this.clearDirectPayPollTimer();
this.clearDirectPayRequestTimeout();
this.directPayPollAttempt = 0;
if (clearContext) {
this.directPayOrderNo = "";
this.directPayTier = null;
}
}
private clearDirectPayPollTimer(): void {
if (this.directPayPollTimer) {
clearTimeout(this.directPayPollTimer);
this.directPayPollTimer = null;
}
}
private clearDirectPayRequestTimeout(): void {
if (this.directPayRequestTimeout) {
clearTimeout(this.directPayRequestTimeout);
this.directPayRequestTimeout = null;
}
}
/** iOS 旧支付:跳转客服,回到小游戏时由 wx.onShow 查询订单结果。 */
private startOldIosPayment(tier: JungleTierConfig): void {
this.clearPaymentTimeout();
const payInfo = {
price: this.pendingPaymentPrice,
payment_name: tier.productId,
payment_count: 1,
};
Utils.GoKEFu(payInfo, (result) => {
if (!cc.isValid(this.node)) {
return;
}
if (result !== "success") {
this.finishPaymentFailure("客服支付入口打开失败");
}
});
}
private registerWechatOnShow(): void {
if (this.onShowListener) {
return;
}
// @ts-ignore
if (typeof wx === "undefined" || !wx.onShow) {
return;
}
this.onShowListener = () => this.handleWechatOnShow();
// @ts-ignore
wx.onShow(this.onShowListener);
}
private unregisterWechatOnShow(): void {
// @ts-ignore
if (!this.onShowListener || typeof wx === "undefined" || !wx.offShow) {
return;
}
// @ts-ignore
wx.offShow(this.onShowListener);
this.onShowListener = null;
}
private handleWechatOnShow(): void {
const gameInfo: any = cc.fx.GameConfig.GM_INFO;
const orderNo = gameInfo.iosJungleOrder;
if (!orderNo || this.checkingIosOrder) {
return;
}
this.checkingIosOrder = true;
this.isPurchasing = true;
this.openLoad();
Utils.getIosPayInfo(orderNo, (response) => {
if (!cc.isValid(this.node)) {
return;
}
this.checkingIosOrder = false;
const data = response && response.data;
const productId = (data && data.payment_name) || this.pendingPaymentProductId;
const price = Number(data && data.goodsPrice) || this.pendingPaymentPrice;
if (response && response.code === 1) {
gameInfo.iosJungleOrder = "";
const name = "购买丛林宝藏礼包:" + productId;
MiniGameSdk.API.yinli_Pay(price, orderNo, name);
this.refreshAfterPayment(this.resolvePendingTierId(productId));
return;
}
if (response && response.code === 2) {
this.closeLoad();
this.isPurchasing = false;
this.renderAllCards();
this.showToast("支付结果确认超时,稍后返回活动会继续查询");
return;
}
gameInfo.iosJungleOrder = "";
this.finishPaymentFailure("支付未成功");
});
}
private refreshAfterPayment(tierId: number): void {
this.startUnlockStatePolling(tierId, false);
}
/** ConfirmBox 重试按钮事件:按失败阶段继续查询支付结果或礼包解锁状态。 */
public retryJungleUnlock(): void {
if (this.paymentRetryStage === "pay_result") {
if (!this.directPayOrderNo || !this.directPayTier) {
this.hideJungleRetryConfirm();
this.showToast("支付订单信息已失效");
return;
}
if (this.isDirectPayPolling) {
return;
}
this.hideJungleRetryConfirm();
this.isPurchasing = true;
this.renderAllCards();
this.openLoad();
this.startDirectPayResultPolling(true);
return;
}
if (this.unlockPollTierId <= 0) {
this.hideJungleRetryConfirm();
this.showToast("没有需要重试的礼包");
return;
}
if (this.isUnlockPolling) {
return;
}
this.hideJungleRetryConfirm();
this.isPurchasing = true;
this.renderAllCards();
this.openLoad();
this.startUnlockStatePolling(this.unlockPollTierId, true);
}
private startUnlockStatePolling(tierId: number, isManualRetry: boolean): void {
this.clearPaymentTimeout();
this.stopUnlockStatePolling(false);
if (tierId <= 0) {
this.finishPaymentFailure("支付成功,但礼包档位信息无效");
return;
}
this.unlockPollTierId = tierId;
this.unlockPollAttempt = 0;
this.isUnlockPolling = true;
this.isPurchasing = true;
this.paymentRetryStage = "";
this.hideJungleRetryConfirm();
const totalAttempts = this.unlockPollMaxAttempts;
this.requestUnlockState(this.unlockPollSession);
}
private requestUnlockState(session: number): void {
if (!this.isUnlockPolling || session !== this.unlockPollSession || !cc.isValid(this.node)) {
return;
}
this.unlockPollAttempt++;
const currentAttempt = this.unlockPollAttempt;
const totalAttempts = this.unlockPollMaxAttempts;
const tierId = this.unlockPollTierId;
let requestFinished = false;
this.clearUnlockRequestTimeout();
this.unlockRequestTimeout = setTimeout(() => {
if (requestFinished || session !== this.unlockPollSession) {
return;
}
requestFinished = true;
this.unlockRequestTimeout = null;
this.scheduleNextUnlockPoll(session);
}, this.unlockRequestTimeoutMs);
try {
Utils.getJungleTreasure((response) => {
if (requestFinished || session !== this.unlockPollSession || !cc.isValid(this.node)) {
return;
}
requestFinished = true;
this.clearUnlockRequestTimeout();
const data = response && response.code === 1 ? response.data : null;
const states = data && data.tierStates;
const refreshedState = Array.isArray(states) && tierId > 0
? Number(states[tierId - 1])
: -1;
if (data && data.config && Array.isArray(states)
&& (refreshedState === 1 || refreshedState === 2)) {
this.completeUnlockStatePolling(tierId, refreshedState, data, states);
return;
}
this.scheduleNextUnlockPoll(session);
});
} catch (error) {
if (requestFinished || session !== this.unlockPollSession) {
return;
}
requestFinished = true;
this.clearUnlockRequestTimeout();
this.scheduleNextUnlockPoll(session);
}
}
private scheduleNextUnlockPoll(session: number): void {
if (!this.isUnlockPolling || session !== this.unlockPollSession) {
return;
}
const totalAttempts = this.unlockPollMaxAttempts;
if (this.unlockPollAttempt >= totalAttempts) {
this.handleUnlockPollExhausted();
return;
}
const delayIndex = Math.min(this.unlockPollAttempt - 1, this.unlockPollDelays.length - 1);
const delay = this.unlockPollDelays[delayIndex];
this.clearUnlockPollTimer();
this.unlockPollTimer = setTimeout(() => {
this.unlockPollTimer = null;
this.requestUnlockState(session);
}, delay);
}
private completeUnlockStatePolling(
tierId: number,
refreshedState: number,
data: any,
states: JungleTierState[],
): void {
this.stopUnlockStatePolling(true);
this.paymentRetryStage = "";
this.isPurchasing = false;
this.publishServerState(data);
const refreshed = this.setServerData(data.config, states);
if (refreshed && refreshedState === 1) {
this.playTierUnlockAnimation(tierId);
}
if (!refreshed) {
this.closeLoad();
this.renderAllCards();
this.showToast("支付成功,礼包状态刷新失败,请重新进入活动");
return;
}
this.hideJungleRetryConfirm();
// setServerData 已将付费档位从“几元”重新渲染为“领取”,完成后再关闭 Loading。
this.closeLoad();
this.showToast("支付成功,礼包已解锁");
}
private handleUnlockPollExhausted(): void {
const tierId = this.unlockPollTierId;
const attempts = this.unlockPollAttempt;
this.stopUnlockStatePolling(false);
this.paymentRetryStage = "unlock_state";
this.isPurchasing = false;
this.closeLoad();
this.renderAllCards();
this.showJungleRetryConfirm();
}
private showJungleRetryConfirm(): void {
const confirmBox = this.node.getChildByName("ConfirmBox");
if (!confirmBox) {
this.showToast("网络异常,请检查网络后重新进入活动");
return;
}
confirmBox.zIndex = 100;
if (confirmBox.parent) {
confirmBox.setSiblingIndex(confirmBox.parent.childrenCount - 1);
}
confirmBox.active = true;
}
private hideJungleRetryConfirm(): void {
const confirmBox = this.node.getChildByName("ConfirmBox");
if (confirmBox) {
confirmBox.active = false;
}
}
private stopUnlockStatePolling(clearTierId: boolean): void {
this.unlockPollSession++;
this.isUnlockPolling = false;
this.clearUnlockPollTimer();
this.clearUnlockRequestTimeout();
this.unlockPollAttempt = 0;
if (clearTierId) {
this.unlockPollTierId = 0;
}
}
private clearUnlockPollTimer(): void {
if (this.unlockPollTimer) {
clearTimeout(this.unlockPollTimer);
this.unlockPollTimer = null;
}
}
private clearUnlockRequestTimeout(): void {
if (this.unlockRequestTimeout) {
clearTimeout(this.unlockRequestTimeout);
this.unlockRequestTimeout = null;
}
}
private finishPaymentFailure(message: string): void {
this.stopDirectPayResultPolling(true);
this.stopUnlockStatePolling(true);
this.paymentRetryStage = "";
this.hideJungleRetryConfirm();
this.clearPaymentTimeout();
this.closeLoad();
this.isPurchasing = false;
this.checkingIosOrder = false;
this.renderAllCards();
this.showToast(message);
}
private resolvePendingTierId(productId: string): number {
if (this.pendingPaymentTierId > 0) {
return this.pendingPaymentTierId;
}
const match = String(productId || "").match(/^jungle_treasure_(\d+)$/);
if (!match) {
return 0;
}
const paidIndex = Number(match[1]);
return paidIndex === 1 ? 4 : 4 + (paidIndex - 1) * 6;
}
private publishServerState(data: any): void {
this.node.emit("jungle-state-changed", data);
}
private getSystemType(): string {
try {
// @ts-ignore
const info = typeof wx !== "undefined" && wx.getSystemInfoSync
// @ts-ignore
? wx.getSystemInfoSync()
: null;
return info && info.platform === "ios" ? "ios" : "Android";
} catch (error) {
cc.warn("[JungleTreasure] 获取系统类型失败", error);
return "Android";
}
}
private getSceneController(): any {
const canvas = cc.find("Canvas");
return canvas && (canvas.getComponent("JiaZai") || canvas.getComponent("SceneManager"));
}
private openLoad(): void {
const controller = this.getSceneController();
if (controller && typeof controller.openLoad === "function") {
controller.openLoad();
}
}
private closeLoad(): void {
const controller = this.getSceneController();
if (controller && typeof controller.closeLoad === "function") {
controller.closeLoad();
}
}
private showToast(message: string): void {
console.log("显示提示:", message);
if (MiniGameSdk && MiniGameSdk.API && MiniGameSdk.API.showToast) {
MiniGameSdk.API.showToast(message);
}
}
private clearClaimTimeout(): void {
if (this.claimTimeout) {
clearTimeout(this.claimTimeout);
this.claimTimeout = null;
}
}
private clearPaymentTimeout(): void {
if (this.paymentTimeout) {
clearTimeout(this.paymentTimeout);
this.paymentTimeout = null;
}
}
private completeCurrentTier(): void {
console.log("点击礼包档位按钮完成当前档位");
const tier = this.activityConfig.tiers[this.currentTierIndex];
if (!tier || this.isMoving) {
return;
}
this.isMoving = true;
if (!this.previewAnimationOnly) {
this.rewardService.grant(this.node, tier.rewards);
}
const leavingCard = this.cards[0];
this.animator.playClaimEffect(
leavingCard,
() => this.animator.playCardExit(leavingCard, () => this.shiftCards()),
);
}
private shiftCards(): void {
const oldCards = this.cards.slice();
this.currentTierIndex++;
if (!this.previewAnimationOnly) {
this.progress.save(this.activityConfig, this.currentTierIndex);
}
const recycled = oldCards[0];
recycled.stopAllActions();
recycled.active = false;
recycled.scale = 1;
recycled.opacity = 0;
recycled.setAnchorPoint(0.5, 0.5);
this.animator.moveCardsWithOverlap(oldCards, this.slotPositions, 1, () => {
this.cards.splice(
0,
this.cards.length,
oldCards[1],
oldCards[2],
oldCards[3],
oldCards[4],
oldCards[5],
recycled,
);
this.showNewSixthCard(recycled);
});
}
private showNewSixthCard(card: cc.Node): void {
const sixthTierIndex = this.currentTierIndex + 5;
const targetCenter = this.slotPositions[5];
this.view.renderCard(card, this.activityConfig, this.getRenderState(), sixthTierIndex, 5);
if (sixthTierIndex >= this.activityConfig.tiers.length) {
card.setAnchorPoint(0.5, 0.5);
card.setPosition(targetCenter);
this.finishShift();
return;
}
this.animator.showNewCard(card, targetCenter, () => this.finishShift());
}
private finishShift(): void {
this.isMoving = false;
this.renderAllCards();
if (this.currentTierIndex >= this.activityConfig.tiers.length) {
this.node.emit("jungle-complete");
return;
}
let currentTier = this.activityConfig.tiers[this.currentTierIndex];
if (currentTier && currentTier.state !== 0) {
this.playTierUnlockAnimation(currentTier.id);
}
}
private renderAllCards(): void {
if (this.view) {
this.view.renderAllCards(this.activityConfig, this.getRenderState());
}
}
/** 无论当前礼包是否解锁,每两秒让最前方礼包整体 duang 一次。 */
private playFirstCardAttention(): void {
if (!this.node.activeInHierarchy || !this.activityConfig || !this.animator
|| this.isAttentionAnimating || this.isMoving || this.isPurchasing
|| this.isClaiming || this.isUnlockAnimating) {
return;
}
let firstCard = this.cards[0];
if (!firstCard || !firstCard.activeInHierarchy) {
return;
}
this.isAttentionAnimating = true;
this.animator.playCardAttention(firstCard, () => {
if (cc.isValid(this.node)) {
this.isAttentionAnimating = false;
}
});
}
private stopFirstCardAttention(): void {
if (!this.isAttentionAnimating || !this.animator) {
return;
}
this.animator.stopCardAttention(this.cards[0]);
this.isAttentionAnimating = false;
}
private playTierUnlockAnimation(tierId: number): void {
let targetCard: cc.Node = null;
for (let index = 0; index < this.cards.length; index++) {
let card = this.cards[index];
if (card && card.active && card.name === "reward_card_" + tierId) {
targetCard = card;
break;
}
}
if (!targetCard || !this.animator) {
return;
}
this.isUnlockAnimating = true;
this.animator.playUnlockEffect(targetCard, () => {
if (!cc.isValid(this.node)) {
return;
}
this.isUnlockAnimating = false;
this.renderAllCards();
});
}
private updateTimer(): void {
if (!this.view || !this.activityConfig) {
return;
}
if (this.view.updateTimer(this.activityConfig) === 0 && !this.hasExpired) {
this.hasExpired = true;
this.renderAllCards();
this.node.emit("jungle-expired");
}
}
private getRenderState(): JungleRenderState {
return {
currentTierIndex: this.currentTierIndex,
isMoving: this.isMoving,
isPurchasing: this.isPurchasing || this.isClaiming || this.isUnlockAnimating,
};
}
private getViewAssets(): JungleViewAssets {
return {
cardFrame: this.cardFrame,
trackFrame: this.trackFrame,
titleFrame: this.titleFrame,
buttonFrame: this.buttonFrame,
freeFrame: this.freeFrame,
claimFrame: this.claimFrame,
yuanFrame: this.yuanFrame,
priceFont: this.priceFont,
countFont: this.countFont,
lockFrame: this.lockFrame,
timerFrame: this.timerFrame,
closeFrame: this.closeFrame,
coinFrame: this.coinFrame,
freezeFrame: this.freezeFrame,
hammerFrame: this.hammerFrame,
magicWandFrame: this.magicWandFrame,
infiniteHealthFrame: this.infiniteHealthFrame,
};
}
}