import CloudRiseService from "./module/Config/CloudRiseService"; import { MiniGameSdk } from "./Sdk/MiniGameSdk"; import HomePopupQueue from "../home_popup_queue/HomePopupQueue"; const { ccclass } = cc._decorator; @ccclass export default class CloudRiseView extends cc.Component { private host: cc.Node; private play: () => void; private map: any = null; private entry: cc.Node; private overlay: cc.Node = null; private unsubscribe: () => void; private panel: any = null; private busy = false; private priorPause = false; private queuedResult = ""; private popupDone: () => void = null; private displayedCoin = -1; private nextSyncAt = 0; private timeState = ""; private queuedElimination = ""; private eliminationDone: () => void = null; /** All win continuations present pending results; only the activity overview starts another stage. */ static async continueFromNextLevel(host: cc.Node): Promise { const current = CloudRiseService.run(); if (!current || !["playing", "waiting"].includes(current.status) && !CloudRiseService.elimination() && !CloudRiseService.unseenResult()) return true; try { if (!await CloudRiseService.sync()) { MiniGameSdk.API.showToast(CloudRiseService.error || "活动进度待同步,请重试"); return false; } // Includes the NewMode confirmation path and delayed finish responses. // Keep the level transition waiting while the result and overview are displayed. await CloudRiseView.playElimination(host); if (CloudRiseService.elimination() || CloudRiseService.unseenResult()) { MiniGameSdk.API.showToast("活动结算尚未展示完成,请重试"); return false; } } catch (error) { cc.warn(error); MiniGameSdk.API.showToast("活动进度待同步,请重试"); return false; } return true; } /** Network delays never hold the main level settlement indefinitely. */ static async playElimination(host: cc.Node): Promise { const entry = host && host.getChildByName("CloudRiseEntry"); const view = entry && entry.getComponent(CloudRiseView); if (!view || !CloudRiseService.run() || !CloudRiseService.elimination() && !CloudRiseService.unseenResult() && CloudRiseService.run().status !== "playing") return; let timer: any; try { await Promise.race([CloudRiseService.sync(), new Promise(resolve => { timer = setTimeout(resolve, 2500); })]); while (cc.isValid(view.node, true) && (CloudRiseService.elimination() || CloudRiseService.unseenResult())) { const item = CloudRiseService.elimination(), resultKey = CloudRiseService.resultKey(); await view.showElimination(); if (item ? CloudRiseService.elimination() && CloudRiseService.elimination().key === item.key : CloudRiseService.unseenResult() && CloudRiseService.resultKey() === resultKey) break; } } catch (error) { cc.warn(error); } finally { clearTimeout(timer); } } static mount(host: cc.Node, play: () => void, map?: any): CloudRiseView { const existing = host.getChildByName("CloudRiseEntry"); if (existing) return existing.getComponent(CloudRiseView); const node = new cc.Node("CloudRiseEntry"); host.addChild(node); const view = node.addComponent(CloudRiseView); view.host = host; view.play = play; view.map = map || null; view.initialize(); return view; } private initialize(): void { this.node.zIndex = 0; const home: any = !this.map && this.host.getComponent("JiaZai"); const load = !this.map && this.host.getChildByName("Load"), top = load && load.getChildByName("Top"); this.entry = new cc.Node("cloudRise"); (top || this.node).addChild(this.entry); this.entry.active = false; if (home && home.startUI2) { const sprite = this.entry.addComponent(cc.Sprite); sprite.spriteFrame = home.startUI2.getSpriteFrame("cloudRise.png") || home.startUI2.getSpriteFrame("cloudRise"); sprite.sizeMode = cc.Sprite.SizeMode.RAW; const button = this.entry.addComponent(cc.Button); button.transition = cc.Button.Transition.SCALE; button.zoomScale = 1.1; } this.entry.on("click", () => { void this.open(); }); this.unsubscribe = CloudRiseService.subscribe(() => this.refresh()); this.schedule(this.tick, 1); cc.game.on(cc.game.EVENT_SHOW, this.onShow, this); this.onShow(); } private onShow = (): void => { this.nextSyncAt = 0; this.tick(); }; private tick = (): void => { this.refresh(); const state = CloudRiseService.activityOpen() + ":" + CloudRiseService.registrationOpen() + ":" + CloudRiseService.challengeExpired(); if (state !== this.timeState || Date.now() >= this.nextSyncAt) { this.timeState = state; this.nextSyncAt = Date.now() + 30000; void CloudRiseService.sync(); } }; private refresh(): void { if (!this.node || !cc.isValid(this.node)) return; this.entry.active = !this.map && CloudRiseService.visible(); this.layoutEntry(); if (!this.map && this.displayedCoin !== cc.fx.GameConfig.GM_INFO.coin) { this.displayedCoin = cc.fx.GameConfig.GM_INFO.coin; const home: any = this.host.getComponent("JiaZai"); if (home && home.updateCoin) home.updateCoin(); } const elimination = CloudRiseService.elimination(); if (!this.map && elimination && !this.overlay && elimination.key !== this.queuedElimination) { this.queuedElimination = elimination.key; HomePopupQueue.get(this.host).enqueue("cloudRiseElimination", 55, done => { void this.showElimination().then(done, done); }); } const key = CloudRiseService.resultKey(); if (!this.map && !elimination && !this.overlay && CloudRiseService.allowsAutomaticResults() && CloudRiseService.unseenResult() && key !== this.queuedResult) { this.queuedResult = key; HomePopupQueue.get(this.host).enqueue("cloudRiseResult", 60, done => { this.popupDone = done; void this.open("result").then(() => { if (!this.overlay) this.finishPopup(); }, () => this.finishPopup()); }); } } private async showElimination(): Promise { if (this.busy || this.overlay) return; const item = CloudRiseService.elimination(); if (!item && !CloudRiseService.unseenResult()) return; this.busy = true; let loadTimer: any; try { const resources = Promise.all([CloudRiseService.pagePrefab(), CloudRiseService.pageFragment("stage", item ? item.after.stage : CloudRiseService.stage().stage)]).then(([prefab]) => prefab); const prefab = await Promise.race([resources, new Promise((_, reject) => { loadTimer = setTimeout(() => reject(new Error("百人赛动画资源待加载")), 2500); })]); clearTimeout(loadTimer); if (!cc.isValid(this.node, true)) return; this.overlay = cc.instantiate(prefab); // Settings and other dynamic popups use MAX_ZINDEX - 2; settlement must cover them. this.host.addChild(this.overlay); this.overlay.zIndex = cc.macro.MAX_ZINDEX - 1; this.panel = this.overlay.getComponent("CloudRisePanel"); await this.panel.preparePage(item ? "elimination" : "result", item && item.after.stage); if (!cc.isValid(this.node, true)) return; this.panel.initialize(item ? "elimination" : "result", !!this.map, () => { this.panel.acknowledge(); if (this.eliminationDone) this.eliminationDone(); }, () => {}); await new Promise(resolve => { this.eliminationDone = resolve; // The cloud motion may already be acknowledged while its stage result is still unseen. if (!item) return; this.panel.playElimination(item).then(completed => { if (completed && cc.isValid(this.node, true)) CloudRiseService.acknowledgeElimination(item.key); const stage = CloudRiseService.stage(); if (completed && cc.isValid(this.node, true) && ["won", "lost"].includes(item.after.status) && stage && stage.stage === item.after.stage && stage.status === item.after.status && CloudRiseService.unseenResult()) { this.panel.openResult(); } else resolve(); }).catch(error => { cc.warn(error); resolve(); }); }); } catch (error) { cc.warn(error); } finally { clearTimeout(loadTimer); this.eliminationDone = null; if (this.overlay && cc.isValid(this.overlay)) this.overlay.destroy(); this.overlay = null; this.panel = null; this.busy = false; this.queuedElimination = ""; } } async open(mode?: string): Promise { if (this.busy || this.overlay) return; this.busy = true; try { await CloudRiseService.sync(); if (!cc.isValid(this.node, true)) return; const canOpen = () => CloudRiseService.visible() || mode === "result" && CloudRiseService.unseenResult(); if (!canOpen()) { MiniGameSdk.API.showToast("活动未开启"); return; } const locked = () => { const level = CloudRiseService.lockedUntilLevel(); if (level) MiniGameSdk.API.showToast(`月光宝盒活动通过第${level}关后解锁`); return !!level; }; if (locked()) return; const prefab = await CloudRiseService.pagePrefab(); const pageMode = mode || (CloudRiseService.unseenResult() ? "result" : CloudRiseService.run() && CloudRiseService.run().status === "playing" ? "progress" : "overview"); if (pageMode !== "overview") await CloudRiseService.pageFragment("stage", CloudRiseService.stage().stage); if (!cc.isValid(this.node, true) || !canOpen()) return; if (locked()) return; this.overlay = cc.instantiate(prefab); this.host.addChild(this.overlay); this.overlay.zIndex = cc.macro.MAX_ZINDEX - 1; this.panel = this.overlay.getComponent("CloudRisePanel"); await this.panel.preparePage(pageMode); if (!cc.isValid(this.node, true)) return; if (this.map) { this.priorPause = this.map.pause; this.map.pause = true; } this.panel.initialize(pageMode, !!this.map, () => this.close(), () => { this.close(); if (!this.map) this.play(); }); } catch (error) { if (this.overlay && cc.isValid(this.overlay)) this.overlay.destroy(); this.overlay = null; this.panel = null; MiniGameSdk.API.showToast("活动资源加载失败,请重试"); cc.warn(error); } finally { this.busy = false; } } private close(): void { if (this.busy) return; if (this.panel) this.panel.acknowledge(); if (this.overlay) this.overlay.destroy(); this.overlay = null; this.panel = null; this.finishPopup(); if (this.map && cc.isValid(this.map.node) && !this.map.gameOver && !this.map.gameWin) this.map.pause = this.priorPause; } private layoutEntry(): void { if (this.map || !this.entry.active || !this.entry.parent) return; const top = this.entry.parent, first = top.getChildByName("shop"); if (!first) return; const visible = ["shop", "yicon", "redeemBtn", "passBtn", "jungle", "xinshou"] .map(name => top.getChildByName(name)).filter(node => node && node.active); this.entry.setPosition(first.x, visible.length ? Math.min(...visible.map(node => node.y)) - 220 : first.y); } onDestroy(): void { cc.game.off(cc.game.EVENT_SHOW, this.onShow, this); this.unschedule(this.tick); if (this.unsubscribe) this.unsubscribe(); if (this.entry && cc.isValid(this.entry)) this.entry.destroy(); if (this.overlay && cc.isValid(this.overlay)) this.overlay.destroy(); if (this.eliminationDone) this.eliminationDone(); this.finishPopup(); } private finishPopup(): void { const done = this.popupDone; this.popupDone = null; if (done) done(); } }