import Utils from './module/Pay/Utils'; import { MiniGameSdk } from './Sdk/MiniGameSdk'; import HomePopupQueue from '../home_popup_queue/HomePopupQueue'; const { ccclass } = cc._decorator; const BUNDLE = 'coin_madness'; /** Resident bridge only: never import the bundle's UI class or preload its assets. */ @ccclass export default class CoinMadnessHost extends cc.Component { private static occupied = false; private bundle: cc.AssetManager.Bundle = null; private panel: cc.Node = null; private entry: cc.Node = null; private loading = false; private releasing = false; private requestId = 0; private data: any = null; private finishQueue: () => void = null; private alive = true; private refreshing = false; private busy = false; private revision = 0; private shown = () => this.refresh(); private static receiptKey(): string { return 'coinMadnessReceipt_' + cc.fx.GameConfig.GM_INFO.uid; } static restorePending(user: any): void { let pending = user && user.coinMadnessPending; if (typeof pending === 'string') { try { pending = JSON.parse(pending); } catch { return; } } if (!pending || !Number.isSafeInteger(pending.balance)) return; // Login already includes the server grant. Override only coins, not other progression. cc.fx.GameConfig.GM_INFO.coin = pending.balance; cc.fx.StorageMessage.setStorage('coin', { coin: pending.balance, timestamp: Date.now(), coinMadnessReceipt: pending.id }); cc.fx.StorageMessage.setStorage(this.receiptKey(), pending.id); } start() { cc.game.on(cc.game.EVENT_SHOW, this.shown, this); this.refresh(); } private request(action: string, body: any, done: (result: any) => void) { Utils.POST('coinMadness', { uid: cc.fx.GameConfig.GM_INFO.uid, action, ...body }, done); } private refresh() { if (!this.alive || this.refreshing || this.busy || !cc.fx.GameConfig.GM_INFO.uid || cc.fx.GameConfig.GM_INFO.canIos === false) return; this.refreshing = true; const revision = this.revision; this.request('read', {}, result => { this.refreshing = false; if (!this.alive || revision !== this.revision || result?.code !== 1) return; this.data = result.data; this.updateEntry(); this.unschedule(this.refresh); if (this.data.state && !this.data.state.settled) { const remaining = (this.data.state.config.endsAt - this.data.serverNow) / 1000; if (remaining > 0) this.scheduleOnce(this.refresh, Math.min(remaining + 0.1, 60)); } if (this.panel) this.panel.getComponent('CoinMadnessPanel').render(this.data); if (this.data.pending || this.data.needsSettlement) this.queueSettlement(); }); } private updateEntry() { const visible = this.data?.available && (!this.data.state?.settled || this.data.pending); if (!visible) { if (this.entry) this.entry.destroy(); this.entry = null; return; } if (!this.entry) { this.entry = new cc.Node('CoinMadnessEntry'); (this.node.getChildByName('Load') || this.node).addChild(this.entry); this.entry.setContentSize(146, 112); const widget = this.entry.addComponent(cc.Widget); widget.isAlignLeft = true; widget.left = 24; widget.isAlignBottom = true; widget.bottom = 425; const g = this.entry.addComponent(cc.Graphics); g.fillColor = new cc.Color(231, 121, 24); g.roundRect(-73, -56, 146, 112, 22); g.fill(); const title = new cc.Node('Title'); this.entry.addChild(title); const label = title.addComponent(cc.Label); label.string = '金币狂热'; label.fontSize = 27; label.useSystemFont = true; label.lineHeight = 31; title.color = cc.Color.WHITE; this.entry.on(cc.Node.EventType.TOUCH_END, this.open, this); } const state = this.data.state; const target = state.config.stages[state.stage]; const ready = state.paidAt && target && state.progress >= target.wins; this.entry.getChildByName('Title').getComponent(cc.Label).string = '金币狂热\n' + (ready ? '可领取' : state.paidAt ? '闯关领奖' : '限时活动'); this.entry.active = true; } open() { if (this.loading || this.releasing || this.panel || this.busy || !this.data) return; HomePopupQueue.get(this.node).enqueue('coinMadness', 55, done => { if (!this.alive) { done(); return; } this.finishQueue = done; this.load(); }); } private load() { if (CoinMadnessHost.occupied) { this.close(); this.toast('活动资源释放中,请稍后重试'); return; } CoinMadnessHost.occupied = true; this.loading = true; const requestId = ++this.requestId; cc.assetManager.loadBundle(BUNDLE, (error, bundle) => { if (error) { CoinMadnessHost.occupied = false; this.loading = false; this.close(); this.toast('活动加载失败,请重试'); return; } if (!this.alive || requestId !== this.requestId) { this.release(bundle); return; } this.bundle = bundle; bundle.load('CoinMadness', cc.Prefab, (loadError, prefab: cc.Prefab) => { this.loading = false; if (!this.alive || requestId !== this.requestId) { this.release(bundle); return; } if (loadError) { this.close(); this.toast('活动资源加载失败,请重试'); return; } this.panel = cc.instantiate(prefab); this.node.addChild(this.panel); this.panel.zIndex = cc.macro.MAX_ZINDEX - 1; this.panel.getComponent('CoinMadnessPanel').initialize(this.data, { close: () => this.close(), refresh: () => this.refresh(), request: (action, body, done) => this.action(action, body, done), order: (endpoint, body, done) => Utils.POST(endpoint, { uid: cc.fx.GameConfig.GM_INFO.uid, coinMadnessLevel: cc.fx.GameConfig.GM_INFO.level, ...body }, done), }); this.refresh(); }); }); } private action(action: string, body: any, done: (result: any) => void) { if (this.busy) return; this.busy = true; this.revision++; const uid = cc.fx.GameConfig.GM_INFO.uid; this.request(action, { ...body, coinAmount: Math.floor(cc.fx.GameConfig.GM_INFO.coin) }, result => { if (uid !== cc.fx.GameConfig.GM_INFO.uid) { this.busy = false; return; } if (result?.code !== 1) { this.busy = false; if (this.alive) done(result); return; } this.acceptReceipt(result.data, acknowledged => { this.busy = false; if (!this.alive) return; this.data = acknowledged.data || result.data; this.updateEntry(); done(acknowledged); }); }); } private acceptReceipt(data: any, done: (result: any) => void) { const receipt = data.pending; if (!receipt) { done({ code: 1, data }); return; } const info = cc.fx.GameConfig.GM_INFO; const coinCache = cc.fx.StorageMessage.getStorage('coin'); if (cc.fx.StorageMessage.getStorage(CoinMadnessHost.receiptKey()) !== receipt.id && coinCache?.coinMadnessReceipt !== receipt.id) { info.coin += receipt.coins; cc.fx.StorageMessage.setStorage('coin', { coin: info.coin, timestamp: Date.now(), coinMadnessReceipt: receipt.id }); cc.fx.StorageMessage.setStorage(CoinMadnessHost.receiptKey(), receipt.id); } const home: any = cc.isValid(this.node) && this.node.getComponent('JiaZai'); if (home) home.updateCoin(); this.request('ack', { receiptId: receipt.id, coinAmount: Math.floor(info.coin) }, result => { if (result?.code === 1 && this.alive) this.toast((receipt.expired ? '到期补发 ' : '获得 ') + receipt.coins + ' 金币'); done(result); }); } private queueSettlement() { if (this.panel) return; HomePopupQueue.get(this.node).enqueue('coinMadnessSettlement', 54, done => { if (!this.alive || this.busy) { done(); return; } const mask = new cc.Node('CoinMadnessSettlement'); this.node.addChild(mask); mask.setContentSize(this.node.getContentSize()); mask.zIndex = cc.macro.MAX_ZINDEX - 1; mask.addComponent(cc.BlockInputEvents); this.action(this.data.pending ? 'read' : 'settle', { periodId: this.data.state?.config.periodId }, result => { mask.destroy(); done(); if (result?.code !== 1) { this.toast('活动奖励待同步,请稍后打开活动重试'); return; } this.refresh(); }); }); } close() { if (this.busy && this.alive) return; ++this.requestId; const wasLoading = this.loading; this.loading = false; if (this.panel) { this.panel.destroy(); this.panel = null; } const bundle = this.bundle; this.bundle = null; if (bundle && !wasLoading) this.release(bundle); const done = this.finishQueue; this.finishQueue = null; if (done) done(); } private release(bundle: cc.AssetManager.Bundle) { this.releasing = true; cc.director.once(cc.Director.EVENT_AFTER_DRAW, () => { bundle.releaseAll(); if (cc.assetManager.getBundle(BUNDLE) === bundle) cc.assetManager.removeBundle(bundle); this.releasing = false; CoinMadnessHost.occupied = false; }); } private toast(message: string) { if (this.alive) MiniGameSdk.API.showToast(message); } onDestroy() { this.alive = false; this.unscheduleAllCallbacks(); cc.game.off(cc.game.EVENT_SHOW, this.shown, this); this.close(); this.data = null; if (this.entry) this.entry.destroy(); this.entry = null; } }