import SevenDayGift from "./SevenDayGift"; import SevenDayGiftApi, { SevenDayActivityInfo, SevenDayApiTrace, SevenDayRewardItem } from "./SevenDayGiftApi"; import SevenDayGiftReward from "./SevenDayGiftReward"; import SevenDayGiftDebugPanel from "./debug/SevenDayGiftDebugPanel"; import SevenDayGiftApiFeedbackPanel from "./debug/SevenDayGiftApiFeedbackPanel"; import SevenDayHomeEntryLayout from "./SevenDayHomeEntryLayout"; const { ccclass, property } = cc._decorator; const AUTO_OPEN_KEY = "seven_day_gift_auto_open_v1"; const DOOR_PREFAB_RESOURCE = "seven_day_gift/sevenDayGiftDoor"; @ccclass export default class SevenDayGiftHost extends cc.Component { @property(cc.Prefab) giftPrefab: cc.Prefab = null; @property(cc.Prefab) doorPrefab: cc.Prefab = null; private giftNode: cc.Node = null; private giftView: SevenDayGift = null; private doorNode: cc.Node = null; private redDot: cc.Node = null; private info: SevenDayActivityInfo = null; private querying = false; private retryCount = 0; private debugPanel: SevenDayGiftDebugPanel = null; private apiFeedbackPanel: SevenDayGiftApiFeedbackPanel = null; private homeEntryLayout: SevenDayHomeEntryLayout = null; private loadingSequence = 0; private loadingTimer: any = null; private loadingVisible = false; private debugMode = "尚未执行"; private debugMessage = "等待活动初始化"; onLoad() { if (!this.giftPrefab) { cc.error("[SevenDayGiftHost] sevenDayGift prefab is not assigned"); return; } if (this.node.parent) this.node.setContentSize(this.node.parent.getContentSize()); this.giftNode = cc.instantiate(this.giftPrefab); this.giftNode.active = false; this.giftNode.setPosition(0, 0); this.node.addChild(this.giftNode); this.giftView = this.giftNode.getComponent(SevenDayGift); this.setupDoor(); this.setupHomeEntryLayout(); this.setupDebugPanel(); cc.game.on(cc.game.EVENT_SHOW, this.onGameShow, this); this.queryActivity(true); } onDestroy() { cc.game.off(cc.game.EVENT_SHOW, this.onGameShow, this); this.unschedule(this.refreshHomeEntryLayout); this.closeHomeLoading(this.loadingSequence); if (this.doorNode) this.doorNode.off(cc.Node.EventType.TOUCH_END, this.onDoorClicked, this); if (this.debugPanel) this.debugPanel.destroy(); SevenDayGiftApi.setDebugTraceListener(null); if (this.apiFeedbackPanel) this.apiFeedbackPanel.destroy(); } private setupDoor() { this.doorNode = cc.find("Canvas/Load/Top/sevenDayDoor"); if (!this.doorNode) { cc.error("[SevenDayGiftHost] Canvas/Load/Top/sevenDayDoor is missing"); return; } let button = this.doorNode.getComponent(cc.Button); if (!button) button = this.doorNode.addComponent(cc.Button); button.transition = cc.Button.Transition.SCALE; button.duration = 0.1; button.zoomScale = 1.1; this.doorNode.active = false; this.doorNode.on(cc.Node.EventType.TOUCH_END, this.onDoorClicked, this); if (this.doorPrefab) { this.mountDoorContent(this.doorPrefab); return; } // 兼容 Creator 编辑器新增序列化字段后,旧场景缓存暂时没有带上引用的情况。 cc.loader.loadRes(DOOR_PREFAB_RESOURCE, cc.Prefab, (error: Error, prefab: cc.Prefab) => { if (!cc.isValid(this.node)) return; if (error || !prefab) { cc.error("[SevenDayGiftHost] sevenDayGiftDoor prefab load failed", error); return; } this.doorPrefab = prefab; this.mountDoorContent(prefab); }); } private mountDoorContent(prefab: cc.Prefab) { if (!this.doorNode || !cc.isValid(this.doorNode)) return; let content = this.doorNode.getChildByName("sevenDayGiftDoorContent"); if (!content) { content = cc.instantiate(prefab); content.setPosition(0, 0); this.doorNode.addChild(content); } this.redDot = content.getChildByName("redDot"); this.refreshActivityState(); } private queryActivity(allowAutoOpen: boolean, callback?: (success: boolean) => void, forceServer = false) { if (CC_PREVIEW && !forceServer) { this.showPreviewActivity(allowAutoOpen); if (callback) callback(true); return; } if (this.querying) { this.setDebugMessage("查询仍在进行中,请勿重复点击"); if (callback) callback(false); return; } if (!SevenDayGiftApi.getUid()) { this.setDebugMessage("等待 uid,第 " + (this.retryCount + 1) + " 次检查"); if (this.retryCount < 10) { this.retryCount++; this.scheduleOnce(() => this.queryActivity(allowAutoOpen, callback, forceServer), 1); } else { this.setDebugMessage("查询失败:10 秒内没有取得 uid"); if (callback) callback(false); } return; } this.querying = true; this.debugMode = "真实测试服"; this.setDebugMessage("正在请求 signInActivityInfo..."); SevenDayGiftApi.getInfo(response => { this.querying = false; this.setDebugMessage(this.formatDebugResponse(response)); if (!response || response.code !== 1 || !response.data) { if (callback) callback(false); return; } this.retryCount = 0; this.info = response.data; this.refreshActivityState(); if (this.giftView) this.giftView.setData(this.info, this.claimReward.bind(this)); if (allowAutoOpen && this.shouldAutoOpen()) { this.markAutoOpened(); this.giftView.show(); } if (callback) callback(true); }); } private refreshActivityState() { const visible = this.isActivityVisible(); if (this.doorNode) this.doorNode.active = visible; if (this.redDot) this.redDot.active = visible && !!this.info.canClaim; if (!visible && this.giftNode) this.giftNode.active = false; this.refreshHomeEntryLayout(); } private setupHomeEntryLayout() { const top = cc.find("Canvas/Load/Top"); if (!top) return; this.homeEntryLayout = new SevenDayHomeEntryLayout(top); this.refreshHomeEntryLayout(); this.schedule(this.refreshHomeEntryLayout, 0.25); } private refreshHomeEntryLayout() { if (this.homeEntryLayout) this.homeEntryLayout.refresh(); } private isActivityVisible(): boolean { if (!this.info || !this.info.activityId || !this.info.triggerReached) return false; if (this.info.status === "expired" || this.info.status === "completed") return false; const serverNow = this.toTime(this.info.serverNow); const endAt = this.toTime(this.info.endAt); return !serverNow || !endAt || serverNow < endAt; } private shouldAutoOpen(): boolean { if (!this.isActivityVisible() || !this.info.canClaim) return false; return cc.fx.StorageMessage.getStorage(this.getAutoOpenStorageKey()) !== this.getServerDayKey(); } private markAutoOpened() { cc.fx.StorageMessage.setStorage(this.getAutoOpenStorageKey(), this.getServerDayKey()); } private getAutoOpenStorageKey(): string { return AUTO_OPEN_KEY + ":" + SevenDayGiftApi.getUid() + ":" + this.info.activityId; } private getServerDayKey(): string { const time = this.toTime(this.info.serverNow) || Date.now(); return Math.floor((time + 8 * 60 * 60 * 1000) / (24 * 60 * 60 * 1000)).toString(); } private onDoorClicked(event: cc.Event) { event.stopPropagation(); const loadingToken = this.openHomeLoading(12000); this.queryActivity(false, success => { this.closeHomeLoading(loadingToken); if (success && this.isActivityVisible()) this.giftView.show(); else if (this.info && this.isActivityVisible()) this.giftView.show(); }); } private claimReward(callback: (success: boolean, message?: string) => void) { const loadingToken = this.openHomeLoading(7000); this.debugMode = "真实测试服"; this.setDebugMessage("正在请求 signInClaim(会修改测试服数据)..."); SevenDayGiftApi.claim(response => { if (!response || response.code !== 1 || !response.data) { this.closeHomeLoading(loadingToken); this.setDebugMessage("领取接口失败:" + this.formatDebugResponse(response)); callback(false, response && response.msg ? response.msg : "领取失败,请稍后重试"); return; } this.setDebugMessage("领取接口成功,正在发放前端奖励:claimId=" + response.data.claimId + " rewardDay=" + response.data.rewardDay); SevenDayGiftReward.grant(response.data, (success: boolean, message?: string) => { if (!success) { this.closeHomeLoading(loadingToken); this.setDebugMessage("前端发奖失败:" + (message || "未知错误")); callback(false, message); return; } this.applyClaimState(response.data.rewardDay, response.data.claimedCount, response.data.completed); this.closeHomeLoading(loadingToken); this.showRewardWindow(response.data.rewards); this.setDebugMessage("前端发奖完成,正在重新查询活动状态"); this.queryActivity(false, () => callback(true, "领取成功")); }); }); } private showPreviewActivity(open: boolean) { if (!this.info) this.info = this.createPreviewInfo(); this.debugMode = "Cocos 预览模拟(不发奖)"; this.refreshActivityState(); if (this.giftView) this.giftView.setData(this.info, this.claimPreviewReward.bind(this)); if (open && this.giftView && this.shouldAutoOpen()) { this.markAutoOpened(); this.giftView.show(); } this.setDebugMessage("已加载编辑器预览数据"); } private claimPreviewReward(callback: (success: boolean, message?: string) => void) { const day = Number(this.info.nextRewardDay); let previewRewards: SevenDayRewardItem[] = []; this.info.rewards.forEach(reward => { if (Number(reward.day) === day) { reward.claimed = true; previewRewards = reward.items || []; } }); this.info.claimedCount++; this.info.todayClaimed = true; this.info.canClaim = false; if (this.info.claimedCount >= 7) this.info.status = "completed"; this.refreshActivityState(); this.giftView.setData(this.info, this.claimPreviewReward.bind(this)); this.showRewardWindow(previewRewards); this.setDebugMessage("本地模拟领取完成:第 " + day + " 天(没有发放真实资产)"); callback(true, "预览领取成功(不会发放真实资产)"); } private showRewardWindow(rewards: SevenDayRewardItem[]) { if (!Array.isArray(rewards) || rewards.length === 0) return; const displayRewards = rewards.map(item => { const rewardCount = item.count !== undefined ? item.count : item.amount; return { type: item.type === "magic_wand" ? "magic" : item.type, count: Math.max(0, Number(rewardCount) || 0), spriteFrame: item.type === "cat_skin" && this.giftView ? this.giftView.catArt : null, }; }).filter(item => item.count > 0); if (displayRewards.length === 0) return; const canvas = cc.find("Canvas"); const home: any = canvas && canvas.getComponent("JiaZai"); if (!home || typeof home.openRewardWindow !== "function") { cc.warn("[SevenDayGift] 公共奖励弹窗不可用", displayRewards); return; } home.openRewardWindow(displayRewards); } private createPreviewInfo(): SevenDayActivityInfo { return { serverNow: Date.now(), activityId: "editor-preview", status: "active", triggerLevel: 23, triggerReached: true, startAt: Date.now(), endAt: Date.now() + 14 * 24 * 60 * 60 * 1000, claimedCount: 1, todayClaimed: false, canClaim: true, nextRewardDay: 2, rewards: [ { day: 1, claimed: true, items: [{ type: "infinite_health", count: 900 }] }, { day: 2, claimed: false, items: [{ type: "hammer", count: 1 }] }, { day: 3, claimed: false, items: [{ type: "infinite_health", count: 1800 }] }, { day: 4, claimed: false, items: [{ type: "freeze", count: 1 }, { type: "magic_wand", count: 1 }] }, { day: 5, claimed: false, items: [{ type: "coin", count: 600 }] }, { day: 6, claimed: false, items: [{ type: "hammer", count: 1 }, { type: "freeze", count: 1 }, { type: "magic_wand", count: 1 }] }, { day: 7, claimed: false, items: [{ type: "infinite_health", count: 3600 }, { type: "cat_skin", count: 1, itemId: 12 }] }, ], }; } private applyClaimState(rewardDay: number, claimedCount: number, completed: boolean) { if (!this.info) return; this.info.claimedCount = Number(claimedCount) || this.info.claimedCount; this.info.todayClaimed = true; this.info.canClaim = false; if (completed) this.info.status = "completed"; if (Array.isArray(this.info.rewards)) { this.info.rewards.forEach(reward => { if (Number(reward.day) === Number(rewardDay)) reward.claimed = true; }); } this.refreshActivityState(); if (this.giftView) this.giftView.setData(this.info, this.claimReward.bind(this)); } private onGameShow() { this.queryActivity(true); } private setupDebugPanel() { const environment = cc.fx.GameTool.getWechatGameVersion(); if (environment === "正式版") return; if (!CC_PREVIEW && !CC_DEBUG && environment !== "开发版" && environment !== "体验版") return; this.debugPanel = new SevenDayGiftDebugPanel(this.node, { getStatusText: () => this.getDebugStatusText(), queryServer: () => this.debugQueryServer(), openGift: () => this.debugOpenGift(), clearAutoOpen: () => this.debugClearAutoOpen(), mockDay: (day: number) => this.debugMockDay(day), mockTodayClaimed: () => this.debugMockTodayClaimed(), mockCompleted: () => this.debugMockCompleted(), mockExpired: () => this.debugMockExpired(), addCoins: () => this.debugAddCoins(), addHammer: () => this.debugAddProp("hammerAmount", "锤子"), addFreeze: () => this.debugAddProp("freezeAmount", "冻结"), addMagicWand: () => this.debugAddProp("magicAmount", "魔法棒"), addInfiniteHealth: () => this.debugAddInfiniteHealth(), }); this.debugPanel.build(); this.apiFeedbackPanel = new SevenDayGiftApiFeedbackPanel(this.node, { queryAll: () => this.debugQueryAllApis(), queryLevel: () => this.debugQueryLevel(), queryActivity: () => this.debugQueryServer(), queryCoin: () => this.debugQueryCoin(), queryProps: () => this.debugQueryProps(), queryPower: () => this.debugQueryPower(), }); this.apiFeedbackPanel.build(); SevenDayGiftApi.setDebugTraceListener((trace: SevenDayApiTrace) => { if (this.apiFeedbackPanel) this.apiFeedbackPanel.appendTrace(trace); }); } private openHomeLoading(timeoutMs: number): number { const token = ++this.loadingSequence; if (this.loadingTimer !== null) clearTimeout(this.loadingTimer); const home = this.getHomeController(); if (home && typeof home.openLoad === "function") { this.loadingVisible = true; home.openLoad(); } this.loadingTimer = setTimeout(() => this.closeHomeLoading(token), timeoutMs); return token; } private closeHomeLoading(token: number) { if (token !== this.loadingSequence) return; if (this.loadingTimer !== null) { clearTimeout(this.loadingTimer); this.loadingTimer = null; } if (!this.loadingVisible) return; this.loadingVisible = false; const home = this.getHomeController(); if (home && typeof home.closeLoad === "function") home.closeLoad(); } private getHomeController(): any { const canvas = cc.find("Canvas"); return canvas && canvas.getComponent("JiaZai"); } private getDebugStatusText(): string { const environment = cc.fx.GameTool.getWechatGameVersion(); const level = Number(cc.fx.GameConfig.GM_INFO.level) || 0; const lines = [ "模式:" + this.debugMode, "环境:" + environment + " 服务器:" + SevenDayGiftApi.getServerUrl(), "UID:" + (SevenDayGiftApi.getUid() || "未取得"), "主线:已通过 " + level + " 关 / 当前第 " + (level + 1) + " 关", "资产:金币 " + (Number(cc.fx.GameConfig.GM_INFO.coin) || 0) + " 锤子 " + (Number(cc.fx.GameConfig.GM_INFO.hammerAmount) || 0) + " 冻结 " + (Number(cc.fx.GameConfig.GM_INFO.freezeAmount) || 0) + " 魔法棒 " + (Number(cc.fx.GameConfig.GM_INFO.magicAmount) || 0), ]; if (this.info) { lines.push("活动:" + (this.info.activityId || "无") + " status=" + this.info.status + " trigger=" + this.info.triggerReached); lines.push("进度:" + this.info.claimedCount + "/7" + " next=" + this.info.nextRewardDay + " canClaim=" + this.info.canClaim + " todayClaimed=" + this.info.todayClaimed); } else { lines.push("活动:尚未取得有效数据"); } lines.push("结果:" + this.debugMessage); return lines.join("\n"); } private setDebugMessage(message: string) { this.debugMessage = new Date().toLocaleTimeString() + " " + message; if (this.debugPanel) this.debugPanel.refresh(); } private formatDebugResponse(response: any): string { if (!response) return "接口没有返回数据"; const data = response.data || {}; return "code=" + response.code + " msg=" + (response.msg || "") + " activityId=" + (data.activityId || "") + " status=" + (data.status || "") + " triggerReached=" + data.triggerReached + " canClaim=" + data.canClaim; } private debugOpenGift() { if (!this.info || !this.giftView) { this.setDebugMessage("无法打开:当前没有活动数据,请先查询或选择模拟状态"); return; } this.giftView.show(); this.setDebugMessage("已执行打开活动弹窗"); } private debugQueryServer() { if (!this.canRunDebugServerQuery()) return; if (this.debugMode.indexOf("模拟") >= 0) { this.info = null; this.refreshActivityState(); } this.debugMode = "真实测试服"; this.queryActivity(false, undefined, true); } private debugQueryAllApis() { if (!this.canRunDebugServerQuery()) return; if (this.apiFeedbackPanel) { this.apiFeedbackPanel.appendSystemMessage("开始查询活动、等级、金币、道具和无限体力。"); } this.debugQueryServer(); this.debugQueryLevel(); this.debugQueryCoin(); this.debugQueryProps(); this.debugQueryPower(); } private debugQueryLevel() { if (!this.canRunDebugServerQuery()) return; SevenDayGiftApi.getDebugLevel(response => { const levelAmount = response && response.data ? response.data.levelAmount : undefined; if (this.apiFeedbackPanel) { this.apiFeedbackPanel.appendSystemMessage("服务器主线 levelAmount = " + (levelAmount === undefined ? "未返回" : levelAmount)); } }); } private debugQueryCoin() { if (!this.canRunDebugServerQuery()) return; SevenDayGiftApi.getDebugCoin(() => { }); } private debugQueryProps() { if (!this.canRunDebugServerQuery()) return; SevenDayGiftApi.getDebugProps(() => { }); } private debugQueryPower() { if (!this.canRunDebugServerQuery()) return; SevenDayGiftApi.getDebugPower(() => { }); } private canRunDebugServerQuery(): boolean { const environment = cc.fx.GameTool.getWechatGameVersion(); const allowed = environment === "开发版" || environment === "体验版"; if (!allowed) { const message = "真实接口查询仅允许在微信开发版或体验版执行"; this.setDebugMessage(message); if (this.apiFeedbackPanel) this.apiFeedbackPanel.appendSystemMessage(message); } return allowed; } private debugClearAutoOpen() { if (!this.info || !this.info.activityId) { this.setDebugMessage("无法清除:当前没有 activityId"); return; } cc.fx.StorageMessage.removeStorage(this.getAutoOpenStorageKey()); this.setDebugMessage("已清除今天的自动弹出记录,重新进入主页即可验证"); } private debugMockDay(day: number) { const info = this.createPreviewInfo(); info.activityId = "local-debug-day-" + day; info.claimedCount = day - 1; info.nextRewardDay = day; info.todayClaimed = false; info.canClaim = true; info.rewards.forEach(reward => reward.claimed = reward.day < day); this.applyDebugInfo(info, "本地模拟第 " + day + " 天,可点击奖励测试领取表现"); } private debugMockTodayClaimed() { const info = this.createPreviewInfo(); info.activityId = "local-debug-today-claimed"; info.claimedCount = 4; info.nextRewardDay = 5; info.todayClaimed = true; info.canClaim = false; info.rewards.forEach(reward => reward.claimed = reward.day <= 4); this.applyDebugInfo(info, "本地模拟今天已领取:入口保留且不应自动弹出"); } private debugMockCompleted() { const info = this.createPreviewInfo(); info.activityId = "local-debug-completed"; info.status = "completed"; info.claimedCount = 7; info.nextRewardDay = 7; info.todayClaimed = true; info.canClaim = false; info.rewards.forEach(reward => reward.claimed = true); this.applyDebugInfo(info, "本地模拟七天完成:主页入口应隐藏"); } private debugMockExpired() { const info = this.createPreviewInfo(); info.activityId = "local-debug-expired"; info.status = "expired"; info.serverNow = Date.now(); info.endAt = Date.now() - 1000; info.canClaim = false; this.applyDebugInfo(info, "本地模拟活动过期:主页入口应隐藏"); } private debugAddCoins() { const amount = 1000; const coin = (Number(cc.fx.GameConfig.GM_INFO.coin) || 0) + amount; cc.fx.GameConfig.GM_INFO.coin = coin; cc.fx.StorageMessage.setStorage("coin", { coin, timestamp: Date.now() }); this.refreshHomeCoin(); if (!this.shouldSyncDebugAssets()) { this.setDebugMessage("已在本地增加金币 " + amount + "(未请求服务器)"); return; } this.setDebugMessage("金币已增加,正在同步测试服 userCoin..."); SevenDayGiftApi.saveDebugCoin(coin, response => { this.setDebugMessage("userCoin 同步完成:code=" + (response && response.code) + " msg=" + (response && response.msg || "")); }); } private debugAddProp(field: "hammerAmount" | "freezeAmount" | "magicAmount", label: string) { const info: any = cc.fx.GameConfig.GM_INFO; info[field] = (Number(info[field]) || 0) + 5; const propData = { freeze: Number(info.freezeAmount) || 0, hammer: Number(info.hammerAmount) || 0, magic_wand: Number(info.magicAmount) || 0, }; cc.fx.StorageMessage.setStorage("prop", { freezeAmount: propData.freeze, hammerAmount: propData.hammer, magicAmount: propData.magic_wand, timestamp: Date.now(), }); if (!this.shouldSyncDebugAssets()) { this.setDebugMessage("已在本地增加" + label + " 5 个(未请求服务器)"); return; } this.setDebugMessage(label + "已增加,正在同步测试服 userProp..."); SevenDayGiftApi.saveDebugProps(propData, response => { this.setDebugMessage("userProp 同步完成:code=" + (response && response.code) + " msg=" + (response && response.msg || "")); }); } private debugAddInfiniteHealth() { const seconds = 15 * 60; const now = Math.floor(Date.now() / 1000); const oldEndAt = Number(cc.fx.StorageMessage.getStorage("userPowerTime")) || 0; const endAt = Math.max(now, oldEndAt) + seconds; cc.fx.GameConfig.GM_INFO.userPowerTime = endAt; cc.fx.StorageMessage.setStorage("userPowerTime", endAt); if (!this.shouldSyncDebugAssets()) { this.setDebugMessage("已在本地增加无限体力 15 分钟(未请求服务器)"); return; } this.setDebugMessage("无限体力已增加,正在同步测试服 userPower..."); SevenDayGiftApi.saveDebugPower(endAt, response => { this.setDebugMessage("userPower 同步完成:code=" + (response && response.code) + " msg=" + (response && response.msg || "")); }); } private shouldSyncDebugAssets(): boolean { const environment = cc.fx.GameTool.getWechatGameVersion(); return environment === "开发版" || environment === "体验版"; } private refreshHomeCoin() { const canvas = cc.find("Canvas"); const home: any = canvas && canvas.getComponent("JiaZai"); if (home && home.updateCoin) home.updateCoin(); } private applyDebugInfo(info: SevenDayActivityInfo, message: string) { this.info = info; this.debugMode = "本地模拟(不发奖)"; this.refreshActivityState(); if (this.giftView) { this.giftView.setData(this.info, this.claimPreviewReward.bind(this)); this.giftView.show(); } this.setDebugMessage(message); } private toTime(value: string | number): number { if (typeof value === "number") return value < 100000000000 ? value * 1000 : value; if (!value) return 0; const parsed = new Date(value).getTime(); return isNaN(parsed) ? 0 : parsed; } }