Files
MatchMaster/assets/action_bundle/script/DailyQuests.ts
T

1047 lines
48 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.
// Learn TypeScript:
// - https://docs.cocos.com/creator/2.4/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/2.4/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/2.4/manual/en/scripting/life-cycle-callbacks.html
import JiaZai from "../../Script/JiaZai";
import LoadingCatAnimation from "../../Script/LoadingCatAnimation";
import { MiniGameSdk } from "../../Script/Sdk/MiniGameSdk";
import DailyTaskProgressTracker from "../../Script/module/Task/DailyTaskProgressTracker";
// import JiaZai from "./JiaZai";
// import Utils from "./module/Pay/Utils";
// import { MiniGameSdk } from "./Sdk/MiniGameSdk";
const { ccclass, property } = cc._decorator;
@ccclass
export default class DailyQuests extends cc.Component {
@property(cc.Node)
content: cc.Node = null;
@property(cc.SpriteFrame)
taskNormal: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
taskClaimable: cc.SpriteFrame = null;
// coin, freeze, hammer, magic_wand, infinite_health, hourglass, rocket,
// infinite_hourglass, infinite_rocket;缺图时保留空图标占位。
@property([cc.SpriteFrame])
rewardIcons: cc.SpriteFrame[] = [];
public juwai = false;
btn_Touch: boolean = false;
private iosPrice: number = 0;
private iosProductId: string = "";
private iosCount: number = 1;
public home: number = 0;
public touchStart: boolean = false;
public reward: boolean = false;
sceneValue: any;
private dayEndsAt: number = 0;
private weekEndsAt: number = 0;
private cacheRefreshPending = false;
private selectedWeeklyRewardId: string = "";
private rewardEffectLayer: cc.Node = null;
private rewardEffectComplete: (() => void) = null;
private dailyTaskOpeningPlayed = false;
private dailyTaskProgressTween: cc.Tween<{ ratio: number }> = null;
private dailyTaskStateComplete: (() => void) = null;
private dailyTaskStateAnimations: {
node: cc.Node, task: any, index: number, scaleX: number, scaleY: number,
widget: cc.Widget, widgetEnabled: boolean
}[] = [];
private weeklyChestAnimations: { node: cc.Node, tween: cc.Tween<cc.Node>, angle: number }[] = [];
onLoad() {
this.btn_Touch = true;
// 检测微信小游戏切到后台
this.home = 0;
this.node.on(cc.Node.EventType.TOUCH_START, this.onWeeklyPreviewOutsideTouch, this, true);
}
start() {
// this.init();
}
init(data) {
// 当前这次打开跨期后保留原界面,关闭再打开才接收新周期数据。
if (this.dailyTaskOpeningPlayed && this.hasExpiredDisplayedTasks()) return;
this.cacheRefreshPending = false;
this.clearDailyTaskProgressAnimation();
this.clearDailyTaskStateAnimations();
data = cc.fx.GameTool.getCachedDailyQuests() || data;
this.sceneValue = cc.fx.GameConfig.GM_INFO.sceneValue;
this.touchStart = true;
this.reward = false;
const finishNode = this.node.getChildByName("finishi");
if (finishNode) finishNode.active = false;
if (!DailyTaskProgressTracker.isValidTaskData(data)) return;
const animateOpening = !this.dailyTaskOpeningPlayed;
this.dailyTaskOpeningPlayed = true;
this.initCountdowns(data);
this.removeGeneratedWeeklyNodes();
while (this.content.children.length < data.dailyTasks.length) {
const template = this.content.children[this.content.children.length - 1];
if (!template) break;
this.content.addChild(cc.instantiate(template));
}
data.dailyTasks.forEach((task, index) => this.renderDailyTask(this.content.children[index], task, index));
for (let i = this.content.children.length - 1; i >= data.dailyTasks.length; i--) {
if (!this.content.children[i].name.startsWith("weekly_")) this.content.children[i].active = false;
}
this.renderWeeklyRewards(data);
if (finishNode) finishNode.active = data.dailyTasks.every(task => task.state === 2);
const layout = this.content.getComponent(cc.Layout);
if (layout) layout.updateLayout();
if (animateOpening) this.playDailyTaskProgressAnimation(data.dailyTasks, this.cacheOpenedTaskProgress(data));
}
refreshFromCache() {
if (this.hasExpiredDisplayedTasks()) return;
if (!this.touchStart || this.dailyTaskProgressTween || this.dailyTaskStateAnimations.length || this.rewardEffectLayer) {
this.cacheRefreshPending = true;
return;
}
this.cacheRefreshPending = false;
const data = cc.fx.GameTool.getCachedDailyQuests();
if (data) this.init(data);
}
private initCountdowns(data: any): void {
this.unschedule(this.updateCountdowns);
this.dayEndsAt = Number(data.dayEndsAt) || 0;
this.weekEndsAt = Number(data.weekEndsAt) || 0;
// 校时在收到接口响应时完成,重复打开缓存不会把倒计时拨回去。
this.updateCountdowns();
if (this.dayEndsAt > 0 || this.weekEndsAt > 0) this.schedule(this.updateCountdowns, 1);
}
private updateCountdowns(): void {
if (this.cacheRefreshPending && this.touchStart
&& !this.dailyTaskProgressTween && !this.dailyTaskStateAnimations.length && !this.rewardEffectLayer) {
this.refreshFromCache();
}
const bg = this.node.getChildByName("bg");
if (!bg) return;
const dayNode = bg.getChildByName("dayCountDown");
const weekNode = bg.getChildByName("weekCountDown");
const correctedNow = cc.fx.GameTool.getDailyTaskServerTime();
const daySeconds = this.getRemainingSeconds(this.dayEndsAt, correctedNow);
const weekSeconds = this.getRemainingSeconds(this.weekEndsAt, correctedNow);
if (dayNode) this.setNodeText(dayNode, this.dayEndsAt ? `刷新倒计时: ${this.formatClock(daySeconds)}` : "");
if (weekNode) this.setNodeText(weekNode, this.weekEndsAt ? `本周剩余: ${Math.floor(weekSeconds / 86400)}天 ${this.formatClock(weekSeconds % 86400)}` : "");
// 倒计时归零后保持显示,重新打开时由统一缓存入口检查过期并刷新。
}
private hasExpiredDisplayedTasks(): boolean {
const now = cc.fx.GameTool.getDailyTaskServerTime();
return this.dayEndsAt > 0 && now >= this.dayEndsAt || this.weekEndsAt > 0 && now >= this.weekEndsAt;
}
private canUseDisplayedTasks(): boolean {
if (!this.hasExpiredDisplayedTasks()) return true;
MiniGameSdk.API.showToast("任务已过期,请关闭后重新打开");
return false;
}
private getRemainingSeconds(endsAt: number, now: number): number {
return endsAt > 0 ? Math.max(0, Math.ceil((endsAt - now) / 1000)) : 0;
}
private formatClock(totalSeconds: number): string {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor(totalSeconds % 3600 / 60);
const seconds = totalSeconds % 60;
const pad = (value: number) => value < 10 ? `0${value}` : `${value}`;
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
private renderDailyTask(child: cc.Node, task: any, index: number, state = task.state): void {
if (!child) return;
child.active = true;
child.name = task.taskId;
child.opacity = 255;
child.setSiblingIndex(index);
child.setPosition(0, this.content.height / 2 - child.height / 2 - index * (child.height + 14));
const claimable = state === 1;
const claimed = state === 2;
child.getComponent(cc.Sprite).spriteFrame = claimable ? this.taskClaimable : this.taskNormal;
const descNode = child.getChildByName("desc");
this.setNodeText(descNode, this.getTaskTitle(task));
descNode.active = !claimable;
descNode.y = claimed ? 0 : 32;
const processNode = child.getChildByName("process");
this.renderDailyTaskProgress(child, task, Number(task.value) || 0);
const processLabel = child.getChildByName("processLabel");
processLabel.active = state === 0;
const rewardsNode = child.getChildByName("rewards");
rewardsNode.active = !claimed;
this.renderTaskRewards(rewardsNode, task);
const getNode = child.getChildByName("btnGet");
const completeNode = child.getChildByName("complete");
const lockNode = child.getChildByName("lock");
if (lockNode) lockNode.active = false;
if (processNode) processNode.active = state === 0;
if (getNode) {
getNode.active = claimable;
this.bindButtonData(getNode, task.taskId);
const button = getNode.getComponent(cc.Button);
if (button) button.interactable = claimable && !this.dailyTaskProgressTween && this.dailyTaskStateAnimations.length === 0;
}
if (completeNode) completeNode.active = claimed;
}
private renderDailyTaskProgress(node: cc.Node, task: any, value: number): void {
const target = Math.max(0, Number(task.target) || 0);
value = Math.max(0, Math.min(value, target));
node.getChildByName("process").getComponent(cc.ProgressBar).progress = target > 0 ? value / target : 0;
// 在线任务仍按秒累计和填充进度条,只将显示单位转换为整分钟。
const unit = task.metric === "online_seconds" ? 60 : 1;
this.setNodeText(node.getChildByName("processLabel"), `${Math.floor(value / unit)}/${Math.floor(target / unit)}`);
}
private cacheOpenedTaskProgress(data: any): number[] {
const current = data.dailyTasks.map(task => Math.max(0, Math.min(Number(task.value) || 0, Number(task.target) || 0)));
const day = data.dayKey || data.dayEndsAt;
if (!day) return current;
// taskId 可能复用;将目标和随机参数一起匹配,避免调试重置后沿用另一任务的进度。
const keys = data.dailyTasks.map(task => JSON.stringify([task.taskId, task.metric, task.target, task.params || {}]));
try {
const uid = cc.fx.StorageMessage.getStorage("uid") || cc.fx.GameConfig.GM_INFO.uid;
if (!uid) return current;
const storageKey = `dailyTaskOpenedProgress:${uid}`;
const previous = cc.fx.StorageMessage.getStorage(storageKey);
const values = previous && previous.day === day && previous.values || {};
const starts = keys.map((key, index) => typeof values[key] === "number" && Number.isFinite(values[key])
? Math.max(0, Math.min(values[key], current[index])) : 0);
const snapshot = {};
keys.forEach((key, index) => snapshot[key] = current[index]);
// 保存打开时的最终进度;后续刷新、领取以及关闭不覆盖这份快照。
cc.fx.StorageMessage.setStorage(storageKey, { day, values: snapshot });
return starts;
} catch (error) {
// 存储不可用时正常展示任务,不影响领取。
return current;
}
}
private playDailyTaskProgressAnimation(tasks: any[], starts: number[]): void {
const rows = tasks.map((task, index) => ({ task, index, node: this.content.getChildByName(task.taskId),
start: starts[index], end: Math.max(0, Math.min(Number(task.value) || 0, Number(task.target) || 0)) }))
.filter(row => cc.isValid(row.node) && row.task.state !== 2);
if (!this.node.activeInHierarchy || !rows.some(row => row.end > row.start)) {
this.playDailyTaskStateAnimations(tasks);
return;
}
this.touchStart = false;
rows.forEach(({ node, task, index, start }) => {
this.renderDailyTask(node, task, index, 0);
this.renderDailyTaskProgress(node, task, start);
});
// 共用一个补间,让所有行同时开始和结束,且不改动 GM_INFO.tasks。
this.dailyTaskProgressTween = cc.tween({ ratio: 0 })
.to(0.6, { ratio: 1 }, { easing: "quadOut", onUpdate: (value: { ratio: number }) => {
rows.forEach(row => this.renderDailyTaskProgress(row.node, row.task, row.start + (row.end - row.start) * value.ratio));
} })
.call(() => {
this.dailyTaskProgressTween = null;
rows.forEach(row => this.renderDailyTaskProgress(row.node, row.task, row.end));
this.touchStart = true;
this.playDailyTaskStateAnimations(tasks);
}).start();
}
private clearDailyTaskProgressAnimation(): void {
if (!this.dailyTaskProgressTween) return;
this.dailyTaskProgressTween.stop();
this.dailyTaskProgressTween = null;
this.touchStart = true;
}
private playDailyTaskStateAnimations(tasks: any[], claimedTaskId = "", onComplete?: () => void): void {
if (!cc.isValid(this.node) || !this.node.activeInHierarchy) {
if (onComplete) onComplete();
return;
}
this.dailyTaskStateComplete = onComplete || null;
tasks.forEach(task => {
if (claimedTaskId ? task.taskId !== claimedTaskId || task.state !== 2 : task.state !== 1) return;
const node = this.content.getChildByName(task.taskId);
if (!cc.isValid(node)) return;
const index = this.content.children.indexOf(node);
const widget = node.getComponent(cc.Widget);
const widgetEnabled = !!widget && widget.enabled;
// 先按原尺寸完成布局,动画期间避免 Widget 拉伸抵消缩放。
if (widgetEnabled) {
widget.updateAlignment();
widget.enabled = false;
}
this.dailyTaskStateAnimations.push({ node, task, index, scaleX: node.scaleX, scaleY: node.scaleY, widget, widgetEnabled });
});
let remaining = this.dailyTaskStateAnimations.length;
if (!remaining) {
this.clearDailyTaskStateAnimations();
return;
}
// 同一帧启动所有任务行,不按任务顺序逐个播放。
this.dailyTaskStateAnimations.forEach(({ node, task, index, scaleX, scaleY }) => {
this.renderDailyTask(node, task, index, claimedTaskId ? 1 : 0);
cc.tween(node)
.to(0.14, { scaleX: 0, scaleY: 0 }, { easing: "quadIn" })
.call(() => this.renderDailyTask(node, task, index))
.to(0.22, { scaleX: scaleX * 1.1, scaleY: scaleY * 1.1 }, { easing: "quadOut" })
.to(0.12, { scaleX, scaleY }, { easing: "sineInOut" })
.call(() => {
if (--remaining === 0) this.clearDailyTaskStateAnimations();
})
.start();
});
}
private clearDailyTaskStateAnimations(): void {
const animations = this.dailyTaskStateAnimations;
const complete = this.dailyTaskStateComplete;
this.dailyTaskStateAnimations = [];
this.dailyTaskStateComplete = null;
animations.forEach(({ node, task, index, scaleX, scaleY, widget, widgetEnabled }) => {
cc.Tween.stopAllByTarget(node);
if (!cc.isValid(node)) return;
node.scaleX = scaleX;
node.scaleY = scaleY;
this.renderDailyTask(node, task, index);
if (cc.isValid(widget)) widget.enabled = widgetEnabled;
});
if (complete) complete();
}
private renderTaskRewards(node: cc.Node, task: any): void {
// 仅每日任务使用动态克隆;周奖励 preview 不调用此方法。
const rewards = [{ type: "stars", count: task.stars }].concat(task.rewards || []);
if (!cc.isValid(node)) return;
const template = node.getChildByName("rewardItem");
if (!cc.isValid(template)) {
cc.error("DailyQuests: rewards 下缺少 rewardItem 奖励模板,请检查 dailyQuests 预制体。");
return;
}
while (node.children.length < rewards.length) {
const slot = cc.instantiate(template);
if (!cc.isValid(slot)) return;
node.addChild(slot);
}
this.renderRewardItems(node, rewards);
}
private renderRewardItems(node: cc.Node, rewards: any[], arrangeDaily = true): void {
if (!cc.isValid(node)) return;
const types = ["coin", "freeze", "hammer", "magic_wand", "infinite_health",
"hourglass", "rocket", "infinite_hourglass", "infinite_rocket"];
const spacing = Math.min(146, 300 / rewards.length);
node.children.forEach((slot, index) => {
slot.active = index < rewards.length;
if (!slot.active) return;
if (arrangeDaily) {
slot.x = (index - (rewards.length - 1) / 2) * spacing;
slot.scale = Math.min(1, spacing / 125);
}
const reward = rewards[index];
slot.getChildByName("stars").active = reward.type === "stars";
const icon = slot.getChildByName("icon");
const frame = this.rewardIcons[types.indexOf(reward.type === "magic" ? "magic_wand" : reward.type)];
icon.active = reward.type !== "stars";
icon.getComponent(cc.Sprite).spriteFrame = frame || null;
const fallback = slot.getChildByName("rewardName");
if (fallback) {
fallback.active = false;
this.setNodeText(fallback, "");
}
const timed = reward.type.indexOf("infinite_") === 0;
this.setNodeText(slot.getChildByName("count"), timed
? `${Math.round(reward.count / 60 * 10) / 10}分钟`
: `${reward.type === "stars" || reward.type === "coin" ? "" : "×"}${reward.count}`);
slot.getChildByName("plus").active = arrangeDaily && index < rewards.length - 1;
});
const layout = node.getComponent(cc.Layout);
if (layout) layout.updateLayout();
}
private setNodeText(node: cc.Node, text: string): void {
if (!node) return;
const label = node.getComponent(cc.Label);
if (label) label.string = text;
const richText = node.getComponent(cc.RichText);
if (richText) richText.string = text;
}
private bindButtonData(node: cc.Node, taskId: string): void {
const button = node.getComponent(cc.Button);
if (!button) return;
button.clickEvents.forEach(event => event.customEventData = taskId);
}
private getTaskTitle(task: any): string {
const colorNames = { 1: "蓝色", 2: "紫色", 3: "浅绿色", 4: "米黄色", 5: "青色", 6: "橙色", 7: "深绿色", 8: "黄色", 9: "粉色", 10: "红色" };
const propNames = { freeze: "冻结", hammer: "锤子", magic_wand: "魔法棒", hourglass: "沙漏", rocket: "火箭" };
const params = task.params || {};
switch (task.metric) {
case "clear_blocks": return `清除${task.target}个方块`;
case "clear_size": return params.cellCount != null ? `清除${task.target}个占${params.cellCount}格的方块`
: `清除${task.target}个${params.width || 1}x${params.height || 1}方块`;
case "clear_color": return `清除${task.target}个${colorNames[params.colorId] || "指定颜色"}方块`;
case "pass_levels": return `通过${task.target}关`;
case "no_prop_pass": return `不使用道具通过${task.target}关`;
case "win_streak": return `连续通过${task.target}关`;
case "first_try_pass": return `首次尝试通过${task.target}关`;
case "collect_coin": return `收集${task.target}金币`;
case "spend_coin": return `消耗${task.target}金币`;
case "use_prop": return `使用${task.target}个道具`;
case "use_specific_prop": return `使用${task.target}次${propNames[params.propType] || "指定道具"}`;
case "online_seconds": return `累计在线${Math.floor(task.target / 60)}分钟`;
case "login": return `登录游戏${task.target}次`;
case "share": return `分享${task.target}次游戏`;
default: return task.taskId;
}
}
private removeGeneratedWeeklyNodes(): void {
this.content.children.filter(node => node.name.startsWith("weekly_")).forEach(node => {
node.removeFromParent();
node.destroy();
});
}
private renderWeeklyRewards(data: any): void {
const weekly = this.node.getChildByName("bg").getChildByName("weeklyRewards");
if (!weekly) {
this.stopWeeklyChestAnimations();
return;
}
const rewards = Array.isArray(data.weeklyRewards) ? data.weeklyRewards : [];
weekly.active = rewards.length > 0;
if (!weekly.active) {
this.stopWeeklyChestAnimations();
return;
}
const stars = Math.max(0, Number(data.weeklyStars) || 0);
const maximum = Math.max(1, ...rewards.map(reward => Number(reward.target) || 0));
const progress = weekly.getChildByName("progress").getComponent(cc.ProgressBar);
progress.progress = Math.min(1, stars / maximum);
this.setNodeText(weekly.getChildByName("current").getChildByName("count"), `${stars}`);
const tiers = weekly.getChildByName("tiers");
tiers.children.forEach(tier => {
const reward = rewards.find(item => item.rewardId === tier.name);
tier.active = !!reward;
const chest = tier.getChildByName("chest");
this.updateWeeklyChestSwing(chest, !!reward && reward.state === 1);
if (!reward) return;
this.setNodeText(tier.getChildByName("target"), `${reward.target}`);
tier.getChildByName("claimable").active = reward.state === 1;
tier.getChildByName("claimed").active = reward.state === 2;
chest.opacity = reward.state === 2 ? 150 : 255;
this.bindButtonData(tier, reward.rewardId);
});
this.updateWeeklyPreview(data);
}
private updateWeeklyChestSwing(node: cc.Node, claimable: boolean): void {
if (!claimable || !cc.isValid(node) || !node.activeInHierarchy) {
this.stopWeeklyChestAnimations(node);
return;
}
if (this.weeklyChestAnimations.some(animation => animation.node === node)) return;
const angle = node.angle;
// chest 的锚点已在预制体中设为顶部中间,只旋转图标,数字和点击区域保持不动。
const tween = cc.tween(node).repeatForever(
cc.tween()
.to(0.12, { angle: angle + 6 }, { easing: "sineInOut" })
.to(0.24, { angle: angle - 6 }, { easing: "sineInOut" })
.to(0.12, { angle }, { easing: "sineInOut" })
.delay(2)
);
this.weeklyChestAnimations.push({ node, tween, angle });
tween.start();
}
private stopWeeklyChestAnimations(node?: cc.Node): void {
this.weeklyChestAnimations = this.weeklyChestAnimations.filter(animation => {
if (node && animation.node !== node) return true;
animation.tween.stop();
if (cc.isValid(animation.node)) animation.node.angle = animation.angle;
return false;
});
}
selectWeeklyReward(event: cc.Event, rewardId: string): void {
if (!this.touchStart) return;
if (!this.canUseDisplayedTasks()) return;
const data = cc.fx.GameConfig.GM_INFO.tasks;
const reward = data && data.weeklyRewards && data.weeklyRewards.find(item => item.rewardId === rewardId);
if (!reward) return;
if (reward.state === 1) {
this.closeWeeklyPreview();
this.getWeeklyReward(rewardId);
return;
}
this.selectedWeeklyRewardId = rewardId;
this.updateWeeklyPreview(data);
}
closeWeeklyPreview(): void {
this.selectedWeeklyRewardId = "";
this.updateWeeklyPreview(cc.fx.GameConfig.GM_INFO.tasks);
}
private onWeeklyPreviewOutsideTouch(event: cc.Event.EventTouch): void {
const bg = this.node.getChildByName("bg");
const weekly = bg && bg.getChildByName("weeklyRewards");
const preview = weekly && weekly.getChildByName("preview");
if (!preview || !preview.active) return;
const point = event.getLocation();
const camera = cc.Camera.findCamera(preview);
if (camera) camera.getScreenToWorldPoint(point, point);
const arrow = preview.getChildByName("arrow");
if (preview.getBoundingBoxToWorld().contains(point)
|| (arrow && arrow.getBoundingBoxToWorld().contains(point))) return;
this.closeWeeklyPreview();
event.stopPropagation();
}
private layoutWeeklyPreview(preview: cc.Node, rewardList: cc.Node): void {
// 只调整编辑器已有节点,兼容旧预制体中残留的两层缩放。
preview.scale = 1;
rewardList.scale = 1;
rewardList.y = 0;
rewardList.children.forEach(slot => {
slot.scale = 1;
slot.y = 0;
slot.setContentSize(148, 164);
const icon = slot.getChildByName("icon");
icon.scale = 1;
icon.setPosition(0, 12);
icon.getComponent(cc.Sprite).sizeMode = cc.Sprite.SizeMode.CUSTOM;
icon.setContentSize(136, 140);
const count = slot.getChildByName("count");
count.scale = 1;
count.setPosition(0, -51);
count.setContentSize(148, 48);
const label = count.getComponent(cc.Label);
label.fontSize = 40;
label.lineHeight = 44;
label.overflow = cc.Label.Overflow.SHRINK;
});
const layout = rewardList.getComponent(cc.Layout);
if (layout) layout.enabled = false;
const visible = rewardList.children.filter(slot => slot.active);
const columns = Math.min(3, visible.length);
const rows = Math.max(1, Math.ceil(visible.length / 3));
rewardList.setContentSize(columns * 148 + Math.max(0, columns - 1) * 16, rows * 180 - 16);
visible.forEach((slot, index) => {
const row = Math.floor(index / 3);
const rowCount = Math.min(3, visible.length - row * 3);
slot.setPosition((index % 3 - (rowCount - 1) / 2) * 164, ((rows - 1) / 2 - row) * 180);
});
const background = preview.getComponent(cc.Sprite);
background.type = cc.Sprite.Type.SLICED;
background.sizeMode = cc.Sprite.SizeMode.CUSTOM;
if (background.spriteFrame) {
background.spriteFrame.insetLeft = background.spriteFrame.insetRight = 28;
background.spriteFrame.insetTop = background.spriteFrame.insetBottom = 28;
}
preview.setContentSize(rewardList.width + 32, rows * 180);
// 增高时向上扩展,箭头保持指向原宝箱的位置。
preview.y = 124 + (preview.height - 180) / 2;
preview.getChildByName("arrow").y = -preview.height / 2 - 9;
}
private updateWeeklyPreview(data: any): void {
const weekly = this.node.getChildByName("bg").getChildByName("weeklyRewards");
if (!weekly) return;
const preview = weekly.getChildByName("preview");
const reward = data && Array.isArray(data.weeklyRewards)
&& data.weeklyRewards.find(item => item.rewardId === this.selectedWeeklyRewardId);
preview.active = !!reward;
if (!reward) return;
const tier = weekly.getChildByName("tiers").getChildByName(reward.rewardId);
if (!tier) {
preview.active = false;
return;
}
const rewardList = preview.getChildByName("rewards");
// preview 的节点已显式保存在预制体中,运行时只更新图片、数量和状态。
// 不再执行原动态创建方式:
// rewardList.addChild(cc.instantiate(template));
this.renderRewardItems(rewardList, reward.rewards || [], false);
this.layoutWeeklyPreview(preview, rewardList);
const limit = Math.max(0, weekly.width / 2 - preview.width / 2 - 10);
preview.x = Math.max(-limit, Math.min(limit, tier.x));
preview.getChildByName("arrow").x = tier.x - preview.x;
}
//入场动画
setAction() {
this.node.getChildByName("gold_1").getComponent(sp.Skeleton).setAnimation(0, "animation", false);
setTimeout(() => {
this.node.getChildByName("qipao3").active = true;
this.node.getChildByName("qipao3").getComponent(sp.Skeleton).setAnimation(0, "animation", false);
cc.tween(this.node.getChildByName("propBg1"))
.delay(0.1)
.to(0.2, { opacity: 255 })
.start();
}, 400);
setTimeout(() => {
this.node.getChildByName("qipao2").active = true;
this.node.getChildByName("qipao2").getComponent(sp.Skeleton).setAnimation(0, "animation", false);
cc.tween(this.node.getChildByName("propBg2"))
.delay(0.1)
.to(0.2, { opacity: 255 })
.start();
}, 600);
setTimeout(() => {
this.node.getChildByName("gold_2").active = true;
this.node.getChildByName("qipao1").active = true;
this.node.getChildByName("qipao1").getComponent(sp.Skeleton).setAnimation(0, "animation", false);
cc.tween(this.node.getChildByName("propBg3"))
.delay(0.1)
.to(0.2, { opacity: 255 })
.start();
}, 800);
setTimeout(() => {
// this.node.getChildByName("gold_2").active = true;
this.node.getChildByName("baoxiang").active = true;
}, 1000);
}
//領取獎勵
openReward(name, res, showWindow = true) {
console.log("获取奖励名称", name);
const data = Array.isArray(res) ? res : [];
const tasks = cc.fx.GameConfig.GM_INFO.tasks;
const task = tasks && tasks.dailyTasks && tasks.dailyTasks.find(item => item.taskId === name);
DailyTaskProgressTracker.trackTask(task || { taskId: name }, "claim");
console.log("获得任务奖励", data);
let shuju = {
"coin": 0,
"freeze": 0,
"hammer": 0,
"magic": 0,
"magic_wand": 0,
"infinite_health": 0,
"hourglass": 0,
"rocket": 0,
"infinite_hourglass": 0,
"infinite_rocket": 0,
}
for (let i = 0; i < data.length; i++) {
let type = data[i].type;
let count = data[i].count;
shuju[type] += count;
}
let propData = {
"freeze": shuju.freeze || 0,
"hammer": shuju.hammer || 0,
"magic_wand": shuju.magic_wand || shuju.magic || 0,
"infinite_health": shuju.infinite_health || 0,
"price": 0
}
this.getShopProp(propData);
this.grantFutureProps(shuju);
if (shuju.coin > 0) {
cc.fx.GameTool.changeCoin(shuju.coin);
}
if (shuju.infinite_health > 0) {
cc.fx.GameTool.setUserPowerTime(shuju.infinite_health, "DailyQuest");
}
// if (name == "share") {
// // console.log("_______获得无限体力时间:", shuju.infinite_health, name);
// cc.fx.GameTool.setUserPowerTime(900, "DailyQuest");
// }
const canvasTemp = cc.find("Canvas"); // 假设 Canvas 节点
if (canvasTemp) {
const JiaZai = canvasTemp.getComponent("JiaZai");
if (JiaZai) {
if (showWindow) JiaZai.openRewardWindow(data);
if (showWindow && shuju.coin > 0) {
JiaZai.updateCoin();
}
if (shuju.infinite_health > 0) {
JiaZai.updatePower();
}
}
}
// console.log("领取结果:", res.code, "关闭加载1");
if (cc.isValid(this.node)) this.closeLoad();
}
private playDailyRewardAnimation(row: cc.Node, task: any, rewards: any[], onComplete: () => void): void {
const rewardList = cc.isValid(row) && row.getChildByName("rewards");
const items = [{ type: "stars", count: task.stars }].concat(rewards || []);
// 在任务行缩小前记录每个图标的位置,后续效果不跟随任务行的状态切换。
const origins = items.map((reward, index) => {
const slot = rewardList && rewardList.children[index];
const icon = slot && slot.getChildByName(reward.type === "stars" ? "stars" : "icon");
return cc.isValid(icon) ? icon.convertToWorldSpaceAR(cc.v2()) : null;
});
this.playRewardAnimation(items, onComplete, origins);
}
private playWeeklyRewardAnimation(rewardId: string, rewards: any[], onComplete: () => void): void {
const chest = cc.isValid(this.node) && cc.find(`bg/weeklyRewards/tiers/${rewardId}/chest`, this.node);
// 宝箱以顶部为锚点,奖励从图标的可见中心出现。
const origin = cc.isValid(chest) ? chest.convertToWorldSpaceAR(cc.v2(
(0.5 - chest.anchorX) * chest.width, (0.5 - chest.anchorY) * chest.height
)) : null;
this.playRewardAnimation(rewards, onComplete, (rewards || []).map(() => origin));
}
private playRewardAnimation(rewards: any[], onComplete: () => void, origins: cc.Vec2[] = []): void {
this.clearRewardAnimation();
if (!cc.isValid(this.node) || !this.node.activeInHierarchy) {
onComplete();
return;
}
const items = (rewards || []).map((reward, index) => ({ reward, origin: origins[index] }))
.filter(item => Number(item.reward.count) > 0);
const previewRewards = cc.find("bg/weeklyRewards/preview/rewards", this.node);
const template = previewRewards && previewRewards.children[0];
if (!items.length || !cc.isValid(template)) {
onComplete();
return;
}
// 复用编辑器中的奖励模板,在独立效果层展示;不向 preview 动态添加节点。
const layer = new cc.Node("taskRewardEffects");
this.node.addChild(layer);
layer.zIndex = cc.macro.MAX_ZINDEX;
const bg = this.node.getChildByName("bg");
layer.setPosition(bg ? bg.position : cc.v3());
this.rewardEffectLayer = layer;
this.rewardEffectComplete = onComplete;
items.forEach(() => layer.addChild(cc.instantiate(template)));
this.renderRewardItems(layer, items.map(item => item.reward), false);
let remaining = items.length;
layer.children.forEach((node, index) => {
const { reward, origin } = items[index];
node.setPosition(origin ? layer.convertToNodeSpaceAR(origin) : cc.v2());
node.setContentSize(168, 190);
node.scale = 0;
node.opacity = 0;
["icon", "stars"].forEach(name => {
const icon = node.getChildByName(name);
icon.scale = 1;
icon.setPosition(0, origin ? 0 : 12);
icon.getComponent(cc.Sprite).sizeMode = cc.Sprite.SizeMode.CUSTOM;
icon.setContentSize(136, 140);
});
const countNode = node.getChildByName("count");
countNode.scale = 1;
countNode.setPosition(0, origin ? -63 : -51);
countNode.setContentSize(168, 48);
const label = countNode.getComponent(cc.Label);
if (label) {
label.fontSize = 40;
label.lineHeight = 44;
label.overflow = cc.Label.Overflow.SHRINK;
}
const count = reward.type.indexOf("infinite_") === 0
? `${Math.round(Number(reward.count) / 60 * 10) / 10}分钟` : `${reward.count}`;
this.setNodeText(countNode, `+${count}`);
cc.tween(node)
.delay(index * 0.5)
.set({ opacity: 255 })
.to(0.18, { scale: 1.1 }, { easing: "quadOut" })
.to(0.12, { scale: 1 }, { easing: "sineInOut" })
// 每隔 0.5 秒启动下一项;保留展示时间,让相邻奖励同时可见。
.delay(0.2)
.to(0.5, { y: node.y + 140, opacity: 0 }, { easing: "sineIn" })
.call(() => {
node.destroy();
if (--remaining === 0) this.clearRewardAnimation();
})
.start();
});
}
private clearRewardAnimation(): void {
const layer = this.rewardEffectLayer;
this.rewardEffectLayer = null;
if (cc.isValid(layer)) {
layer.children.forEach(node => cc.Tween.stopAllByTarget(node));
layer.destroy();
}
const complete = this.rewardEffectComplete;
this.rewardEffectComplete = null;
if (complete) complete();
}
private grantFutureProps(rewards: any): void {
const now = Math.floor(Date.now() / 1000);
cc.fx.GameConfig.GM_INFO.hourglassAmount = (Number(cc.fx.GameConfig.GM_INFO.hourglassAmount) || 0) + (rewards.hourglass || 0);
cc.fx.GameConfig.GM_INFO.rocketAmount = (Number(cc.fx.GameConfig.GM_INFO.rocketAmount) || 0) + (rewards.rocket || 0);
if (rewards.infinite_hourglass > 0) {
cc.fx.GameConfig.GM_INFO.infiniteHourglassTime = Math.max(now, cc.fx.GameConfig.GM_INFO.infiniteHourglassTime || 0) + rewards.infinite_hourglass;
}
if (rewards.infinite_rocket > 0) {
cc.fx.GameConfig.GM_INFO.infiniteRocketTime = Math.max(now, cc.fx.GameConfig.GM_INFO.infiniteRocketTime || 0) + rewards.infinite_rocket;
}
cc.fx.StorageMessage.setStorage("futureProp", {
hourglassAmount: cc.fx.GameConfig.GM_INFO.hourglassAmount,
rocketAmount: cc.fx.GameConfig.GM_INFO.rocketAmount,
infiniteHourglassTime: cc.fx.GameConfig.GM_INFO.infiniteHourglassTime,
infiniteRocketTime: cc.fx.GameConfig.GM_INFO.infiniteRocketTime,
timestamp: Date.now(),
});
cc.fx.GameTool.setUserProp(0, 0, () => { });
}
setReward(id) {
}
closeDailyQuests() {
if (this.sceneValue)
cc.fx.GameConfig.GM_INFO.sceneValue = this.sceneValue;
if (this.node)
this.node.active = false;
const jiazaiNode = cc.find("Canvas"); // 假设 JiaZai 挂在 Canvas 节点
const jiazaiComp = jiazaiNode.getComponent(JiaZai);
if (jiazaiComp) {
jiazaiComp.shareIng = false;
jiazaiComp.refreshTaskRedDot();
}
}
openLoad() {
console.log("每日任务打开加载2");
LoadingCatAnimation.play(this.node.getChildByName("Loading"));
}
openConfirmBox() {
console.log("每日任务打开确认框,关闭加载");
this.closeLoad();
this.node.getChildByName("ConfirmBox").active = true;
}
closeConfirmBox() {
this.node.getChildByName("ConfirmBox").active = false;
}
closeLoad() {
LoadingCatAnimation.stop(this.node.getChildByName("Loading"));
}
//点击去完成
clickBtn(event, data) {
this.closeDailyQuests();
const taskData: any = cc.fx.GameConfig.GM_INFO.tasks;
const task = taskData && taskData.dailyTasks && taskData.dailyTasks.find(item => item.taskId === data);
if (!task) return;
if (["use_prop", "use_specific_prop", "pass_levels", "no_prop_pass", "win_streak", "first_try_pass", "clear_blocks", "clear_size", "clear_color"].indexOf(task.metric) >= 0) {
const jiazaiNode = cc.find("Canvas"); // 假设 JiaZai 挂在 Canvas 节点
const jiazaiComp = jiazaiNode.getComponent(JiaZai);
if (jiazaiComp) {
jiazaiComp.startGame();
}
}
else if (task.metric == "share") {
let iphoneArr = [
"https://mmocgame.qpic.cn/wechatgame/Lf3SBqy9XpNkakoIZygRzXqww3HTibq6VyibazqmicwibjCS3YpgqbZtkdyABm4Y1wAr/0",
"https://mmocgame.qpic.cn/wechatgame/TWKuFxnCn7ksT3KXfhCC4yOfZeD4b0hrptDSJ2DFmwz02Yc8SppcwyPAOoS1MsMr/0",
"https://mmocgame.qpic.cn/wechatgame/dibaH2x79o1wSwBDymhyzXwfcyicaDb6R5icrFIO7251T4NgxIzXRXErHvAvn50vXFA/0",
"https://mmocgame.qpic.cn/wechatgame/Pgxad80d8ws3o69OicV3DTuTkcP81upQeJ0JBNS1xib3pzYLTF1ZqGY3niciaI7ICKlL/0",
"https://mmocgame.qpic.cn/wechatgame/SfB1vrRBIHKn9ffKFt5sib62yPLE31m2rCvk6DKlEicJNVZSoryEObD6ItwsQn4xibR/0",
"https://mmocgame.qpic.cn/wechatgame/OiaWk33I6QjgWiatrb5YVUq2p0QRmQgO6rLUWxEQDZ4ib9Ny4Pr8iaHnHI6WdxibY2nPL/0",
"https://mmocgame.qpic.cn/wechatgame/CG5xBibollws251aYD4msEPWCiafrcn4Fgtic4T2wME6sWmdfAUtfibgsWoxm59VadDD/0"
]
let randomIphone = iphoneArr[Math.floor(Math.random() * iphoneArr.length)];
let img = randomIphone;
const shareParams = {
title: '如果你突然打了个喷嚏,那一定是我在等你帮忙过关!', // 分享标题
imageUrl: img // 分享图片链接
};
// 调用微信分享 API
if (cc.sys.platform === cc.sys.WECHAT_GAME) {
//@ts-ignore
wx.shareAppMessage(shareParams);
if (cc.fx.GameTool && cc.fx.GameTool.recordDailyTaskShare) cc.fx.GameTool.recordDailyTaskShare();
}
}
}
getReward(event, data, share) {
if (!this.canUseDisplayedTasks()) return;
if (this.dailyTaskStateAnimations.length > 0) return;
const tasks = cc.fx.GameConfig.GM_INFO.tasks;
const task = tasks && tasks.dailyTasks && tasks.dailyTasks.find(item => item.taskId === data);
if (!task || task.state !== 1) return;
if (this.touchStart == false) {
return;
}
console.log("领取奖励", data);
this.touchStart = false;
this.reward = true;
console.log("领取奖励", data, "打开加载1");
this.openLoad();
let responded = false;
const onTimeout = () => {
this.touchStart = true;
this.reward = false;
this.closeLoad();
MiniGameSdk.API.showToast("网络异常,领取奖励失败");
};
this.scheduleOnce(onTimeout, 8);
cc.fx.GameTool.claimDailyQuestReward(data, (res) => {
if (responded) return;
responded = true;
this.unschedule(onTimeout);
if (res.code == 1) {
this.touchStart = false;
const dataNode = cc.isValid(this.content) && this.content.getChildByName(data);
// 入账不依赖动画完成,关闭面板也不会丢失或重复发放奖励。
this.openReward(data, res.data.rewards, false);
const onAnimationComplete = () => {
this.touchStart = true;
this.reward = false;
if (cc.isValid(this.node) && this.node.activeInHierarchy) this.init(res.data.task);
const canvas = cc.find("Canvas");
const home = canvas && canvas.getComponent("JiaZai");
if (home) home.updateCoin();
};
const claimNode = dataNode && dataNode.getChildByName("btnGet");
const claimButton = claimNode && claimNode.getComponent(cc.Button);
if (claimButton) claimButton.interactable = false;
const rewardList = dataNode && dataNode.getChildByName("rewards");
if (rewardList) rewardList.active = false;
// 所有奖励图标播放结束后再切换任务状态,两段结束后恢复领取。
this.playDailyRewardAnimation(dataNode, task, res.data.rewards, () => {
this.playDailyTaskStateAnimations(res.data.task.dailyTasks, data, onAnimationComplete);
if (cc.isValid(rewardList)) rewardList.active = false;
});
}
else {
this.touchStart = true;
this.reward = false;
MiniGameSdk.API.showToast(res.msg || "网络异常,领取奖励失败");
if (cc.isValid(this.node)) this.closeLoad();
}
});
}
getWeeklyReward(rewardId: string) {
if (!this.touchStart) return;
if (!this.canUseDisplayedTasks()) return;
const data = cc.fx.GameConfig.GM_INFO.tasks;
const reward = data && data.weeklyRewards && data.weeklyRewards.find(item => item.rewardId === rewardId);
if (!reward || reward.state !== 1) return;
this.touchStart = false;
this.updateWeeklyPreview(data);
this.openLoad();
let responded = false;
const onTimeout = () => {
this.touchStart = true;
this.closeLoad();
this.updateWeeklyPreview(cc.fx.GameConfig.GM_INFO.tasks);
MiniGameSdk.API.showToast("网络异常,请稍后重试");
};
this.scheduleOnce(onTimeout, 8);
cc.fx.GameTool.claimDailyQuestReward(rewardId, (res) => {
if (responded) return;
responded = true;
this.unschedule(onTimeout);
if (res.code !== 1) {
this.touchStart = true;
if (cc.isValid(this.node)) {
this.closeLoad();
this.updateWeeklyPreview(cc.fx.GameConfig.GM_INFO.tasks);
}
MiniGameSdk.API.showToast(res.msg || "周奖励领取失败");
return;
}
this.touchStart = false;
this.reward = true;
this.openReward(rewardId, res.data.rewards, false);
if (cc.isValid(this.node) && this.node.activeInHierarchy && !this.hasExpiredDisplayedTasks()) this.renderWeeklyRewards(res.data.task);
this.playWeeklyRewardAnimation(rewardId, res.data.rewards, () => {
this.touchStart = true;
this.reward = false;
if (cc.isValid(this.node) && this.node.activeInHierarchy) this.init(res.data.task);
const canvas = cc.find("Canvas");
const home = canvas && canvas.getComponent("JiaZai");
if (home) home.updateCoin();
});
}, "weekly");
}
getShopProp(propData) {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
const num = 0;
const timestamp = Date.now();
console.log("_________道具增加的数量为:", propData);
cc.fx.GameConfig.GM_INFO.freezeAmount += propData.freeze;
cc.fx.GameConfig.GM_INFO.hammerAmount += propData.hammer;
cc.fx.GameConfig.GM_INFO.magicAmount += propData.magic_wand;
let propInfo = cc.fx.StorageMessage.getStorage("prop");
propInfo.hammerAmount = cc.fx.GameConfig.GM_INFO.hammerAmount;
propInfo.magicAmount = cc.fx.GameConfig.GM_INFO.magicAmount;
propInfo.freezeAmount = cc.fx.GameConfig.GM_INFO.freezeAmount;
propInfo.timestamp = timestamp;
cc.fx.StorageMessage.setStorage("prop", propInfo);
cc.fx.GameTool.setUserProp(0, num, (data) => {
})
if (propData.freeze > 0) {
const data1 = {
change_reason: "quests",
id: 2001,
num: propData.freeze,
compensate: false
}
cc.fx.GameTool.shushu_Track("resource_get", data1);
}
else if (propData.hammer > 0) {
const data2 = {
change_reason: "quests",
id: 2002,
num: propData.hammer,
compensate: false
}
cc.fx.GameTool.shushu_Track("resource_get", data2);
}
else if (propData.magic_wand > 0) {
const data3 = {
change_reason: "quests",
id: 2003,
num: propData.magic_wand,
compensate: false
}
cc.fx.GameTool.shushu_Track("resource_get", data3);
}
}
}
onDisable() {
this.clearDailyTaskProgressAnimation();
this.clearDailyTaskStateAnimations();
this.dailyTaskOpeningPlayed = false;
this.clearRewardAnimation();
this.stopWeeklyChestAnimations();
}
onDestroy() {
this.clearDailyTaskProgressAnimation();
this.clearDailyTaskStateAnimations();
this.clearRewardAnimation();
this.stopWeeklyChestAnimations();
this.node.off(cc.Node.EventType.TOUCH_START, this.onWeeklyPreviewOutsideTouch, this, true);
this.unschedule(this.updateCountdowns);
}
}