1172 lines
56 KiB
TypeScript
1172 lines
56 KiB
TypeScript
// 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[] = [];
|
||
|
||
@property([cc.SpriteFrame])
|
||
rewardDigits: cc.SpriteFrame[] = [];
|
||
|
||
@property(cc.SpriteFrame)
|
||
rewardMultiply: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
rewardMinutes: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
rewardHours: cc.SpriteFrame = null;
|
||
|
||
@property([cc.SpriteFrame])
|
||
weeklyDigits: cc.SpriteFrame[] = [];
|
||
|
||
@property([cc.SpriteFrame])
|
||
taskDigits: cc.SpriteFrame[] = [];
|
||
|
||
@property(cc.SpriteFrame)
|
||
taskSlash: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
countPlus: cc.SpriteFrame = null;
|
||
|
||
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 rewardEffects: { layer: cc.Node, complete: () => void }[] = [];
|
||
private pendingClaims = new Set<string>();
|
||
private animatingClaims = new Set<string>();
|
||
private displaySession = 0;
|
||
private dailyTaskOpeningPlayed = false;
|
||
private dailyTaskProgressTween: cc.Tween<{ ratio: number }> = null;
|
||
private dailyTaskStateAnimations: {
|
||
node: cc.Node, task: any, index: number, scaleX: number, scaleY: number,
|
||
widget: cc.Widget, widgetEnabled: boolean, opening: boolean, complete?: () => void
|
||
}[] = [];
|
||
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;
|
||
if (this.animatingClaims.size) { this.refreshFromCache(); 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.hasOpeningStateAnimation()) {
|
||
this.cacheRefreshPending = true;
|
||
return;
|
||
}
|
||
this.cacheRefreshPending = false;
|
||
const data = cc.fx.GameTool.getCachedDailyQuests() || cc.fx.GameConfig.GM_INFO.tasks;
|
||
if (!DailyTaskProgressTracker.isValidTaskData(data) || !cc.isValid(this.node) || !this.node.activeInHierarchy) return;
|
||
data.dailyTasks.forEach(task => {
|
||
if (this.animatingClaims.has(this.claimKey(task.taskId, "daily"))) return;
|
||
const row = this.content.getChildByName(task.taskId);
|
||
if (!row) return;
|
||
if (this.pendingClaims.has(this.claimKey(task.taskId, "daily"))) this.updateDailyClaimButton(row, task, task.state);
|
||
else this.renderDailyTask(row, task, this.content.children.indexOf(row));
|
||
});
|
||
this.renderWeeklyRewards(data);
|
||
const finish = this.node.getChildByName("finishi");
|
||
if (finish) finish.active = !this.animatingClaims.size && data.dailyTasks.every(task => task.state === 2);
|
||
}
|
||
|
||
private hasOpeningStateAnimation(): boolean {
|
||
return this.dailyTaskStateAnimations.some(animation => animation.opening);
|
||
}
|
||
|
||
private claimKey(id: string, scope: "daily" | "weekly"): string {
|
||
return `${scope}:${scope === "daily" ? this.dayEndsAt : this.weekEndsAt}:${id}`;
|
||
}
|
||
|
||
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.hasOpeningStateAnimation()) {
|
||
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 ? `本周剩余: ${this.formatClock(weekSeconds)}` : "");
|
||
// 倒计时归零后保持显示,重新打开时由统一缓存入口检查过期并刷新。
|
||
}
|
||
|
||
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);
|
||
if (totalSeconds >= 86400) return `${Math.floor(hours / 24)}天${hours % 24}时`;
|
||
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.getChildByName("background").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);
|
||
this.updateDailyClaimButton(child, task, state);
|
||
}
|
||
if (completeNode) completeNode.active = claimed;
|
||
}
|
||
|
||
private updateDailyClaimButton(row: cc.Node, task: any, state: number): void {
|
||
const node = row.getChildByName("btnGet");
|
||
if (!node) return;
|
||
const pending = this.pendingClaims.has(this.claimKey(task.taskId, "daily"));
|
||
node.opacity = pending ? 130 : 255;
|
||
const button = node.getComponent(cc.Button);
|
||
if (button) button.interactable = state === 1 && !pending && !this.animatingClaims.has(this.claimKey(task.taskId, "daily"))
|
||
&& !this.dailyTaskProgressTween && !this.hasOpeningStateAnimation();
|
||
}
|
||
|
||
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.setRewardCount(node.getChildByName("processLabel"), `${Math.floor(value / unit)}/${Math.floor(target / unit)}`, this.taskDigits, 28);
|
||
}
|
||
|
||
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;
|
||
}
|
||
const animations: typeof this.dailyTaskStateAnimations = [];
|
||
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;
|
||
}
|
||
animations.push({ node, task, index, scaleX: node.scaleX, scaleY: node.scaleY, widget, widgetEnabled, opening: !claimedTaskId });
|
||
});
|
||
let remaining = animations.length;
|
||
if (!remaining) {
|
||
if (onComplete) onComplete();
|
||
return;
|
||
}
|
||
animations[0].complete = onComplete;
|
||
this.dailyTaskStateAnimations.push(...animations);
|
||
// 同一帧启动所有任务行,不按任务顺序逐个播放。
|
||
animations.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(animations);
|
||
})
|
||
.start();
|
||
});
|
||
}
|
||
|
||
private clearDailyTaskStateAnimations(batch = this.dailyTaskStateAnimations.slice()): void {
|
||
const animations = batch.filter(animation => this.dailyTaskStateAnimations.indexOf(animation) >= 0);
|
||
this.dailyTaskStateAnimations = this.dailyTaskStateAnimations.filter(animation => animations.indexOf(animation) < 0);
|
||
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;
|
||
});
|
||
animations.forEach(animation => { if (animation.complete) animation.complete(); });
|
||
}
|
||
|
||
private renderTaskRewards(node: cc.Node, task: any): void {
|
||
// 仅每日任务使用动态克隆;周奖励 preview 不调用此方法。
|
||
const rewards = (task.rewards || []).concat([{ type: "stars", count: task.stars }]);
|
||
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;
|
||
if (arrangeDaily) {
|
||
// 以当前金币素材的裁剪高度为基准,保持金币和罐头的现有显示大小。
|
||
this.fitRewardIconHeight(icon, 95);
|
||
}
|
||
const fallback = slot.getChildByName("rewardName");
|
||
if (fallback) {
|
||
fallback.active = false;
|
||
this.setNodeText(fallback, "");
|
||
}
|
||
const timed = reward.type.indexOf("infinite_") === 0;
|
||
this.setRewardCount(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 fitRewardIconHeight(node: cc.Node, height: number): void {
|
||
const sprite = node.getComponent(cc.Sprite);
|
||
node.scale = 1;
|
||
sprite.sizeMode = cc.Sprite.SizeMode.TRIMMED;
|
||
sprite.trim = true;
|
||
if (!sprite.spriteFrame) return; // 未配置素材的奖励保留空节点占位。
|
||
const rect = sprite.spriteFrame.getRect();
|
||
if (rect.height <= 0) return;
|
||
node.setContentSize(rect.width, rect.height);
|
||
const scaleFactor = sprite.spriteFrame === this.rewardIcons[0] || sprite.spriteFrame === this.rewardIcons[4] ? 0.75 : 1;
|
||
node.scale = height / rect.height * scaleFactor;
|
||
}
|
||
|
||
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 setRewardCount(node: cc.Node, text: string, digits: cc.SpriteFrame[] = this.rewardDigits, maxHeight: number = 40): void {
|
||
if (!node) return;
|
||
const label = node.getComponent(cc.Label);
|
||
const outline = node.getComponent(cc.LabelOutline);
|
||
const tokens: string[] = text.match(/分钟|小时|./g) || [];
|
||
const frames = tokens.map(token => /^\d$/.test(token) ? digits[Number(token)]
|
||
: token === "×" || token === "x" ? this.rewardMultiply
|
||
: token === "分钟" ? this.rewardMinutes : token === "小时" ? this.rewardHours
|
||
: token === "+" ? this.countPlus : token === "/" ? this.taskSlash : null);
|
||
let content = node.getChildByName("RewardCountGlyphs");
|
||
// Keep the existing text as a fallback if an editor copy has missing frame bindings.
|
||
const ready = tokens.every((token, index) => !/^(?:[\d×x+\/]|分钟|小时)$/.test(token) || !!frames[index]);
|
||
if (label) { label.string = text; label.enabled = !ready; }
|
||
if (content) content.active = ready;
|
||
if (!ready) return;
|
||
if (!content) {
|
||
// Hide the prefab preview digits before rendering the current value.
|
||
node.children.forEach(child => child.active = false);
|
||
content = new cc.Node("RewardCountGlyphs"); node.addChild(content);
|
||
}
|
||
const height = Math.min(maxHeight, label ? label.fontSize : maxHeight, node.height);
|
||
const scales = tokens.map(token => token === "×" || token === "x" ? 0.6 : 1);
|
||
const widths = tokens.map((token, index) => {
|
||
const frame = frames[index];
|
||
if (frame) { const size = frame.getRect(); return height * size.width / size.height; }
|
||
// Punctuation retains the authored label style.
|
||
return Array.from(token).reduce((width, char) => width + height * (char === "." ? 0.3 : char === "+" ? 0.65 : 1), 0);
|
||
});
|
||
const total = widths.reduce((sum, width, index) => sum + width * scales[index], 0);
|
||
content.scale = Math.min(1, node.width / Math.max(1, total), node.height / height);
|
||
content.setPosition((0.5 - node.anchorX) * node.width, (0.5 - node.anchorY) * node.height);
|
||
let x = -total / 2;
|
||
tokens.forEach((token, index) => {
|
||
let glyph = content.children[index];
|
||
if (!glyph) { glyph = new cc.Node("glyph" + index); content.addChild(glyph); }
|
||
glyph.active = true;
|
||
glyph.scale = scales[index];
|
||
let sprite = glyph.getComponent(cc.Sprite), textLabel = glyph.getComponent(cc.Label);
|
||
if (frames[index]) {
|
||
if (!sprite) sprite = glyph.addComponent(cc.Sprite);
|
||
sprite.enabled = true; sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM; sprite.trim = true;
|
||
sprite.spriteFrame = frames[index];
|
||
if (textLabel) textLabel.enabled = false;
|
||
} else {
|
||
if (sprite) sprite.enabled = false;
|
||
if (!textLabel) textLabel = glyph.addComponent(cc.Label);
|
||
textLabel.enabled = true; textLabel.string = token;
|
||
textLabel.fontSize = height; textLabel.lineHeight = height;
|
||
textLabel.fontFamily = label ? label.fontFamily : "Arial";
|
||
textLabel.enableBold = !!label && label.enableBold;
|
||
textLabel.overflow = cc.Label.Overflow.SHRINK;
|
||
textLabel.enableWrapText = false;
|
||
textLabel.horizontalAlign = cc.Label.HorizontalAlign.CENTER;
|
||
textLabel.verticalAlign = cc.Label.VerticalAlign.CENTER;
|
||
if (outline) {
|
||
const stroke = glyph.getComponent(cc.LabelOutline) || glyph.addComponent(cc.LabelOutline);
|
||
stroke.color = outline.color; stroke.width = outline.width;
|
||
}
|
||
}
|
||
glyph.setContentSize(widths[index], height);
|
||
glyph.setPosition(x + widths[index] * scales[index] / 2, 0);
|
||
x += widths[index] * scales[index];
|
||
});
|
||
content.children.forEach((glyph, index) => { if (index >= tokens.length) glyph.active = false; });
|
||
}
|
||
|
||
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.setRewardCount(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");
|
||
const pending = this.pendingClaims.has(this.claimKey(tier.name, "weekly"));
|
||
const button = tier.getComponent(cc.Button);
|
||
if (button) button.interactable = !pending;
|
||
tier.opacity = pending ? 130 : 255;
|
||
this.updateWeeklyChestSwing(chest, !!reward && reward.state === 1);
|
||
if (!reward) return;
|
||
this.setRewardCount(tier.getChildByName("target"), `${reward.target}`, this.weeklyDigits, 34);
|
||
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 || this.hasOpeningStateAnimation()) 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.setContentSize(136, 140);
|
||
this.fitRewardIconHeight(icon, 95);
|
||
const count = slot.getChildByName("count");
|
||
count.scale = 1;
|
||
count.setPosition(0, -51);
|
||
count.setContentSize(148, 48);
|
||
});
|
||
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.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 chest = tier.getChildByName("chest");
|
||
const chestTop = weekly.convertToNodeSpaceAR(chest.convertToWorldSpaceAR(cc.v2(
|
||
(0.5 - chest.anchorX) * chest.width, (1 - chest.anchorY) * chest.height
|
||
)));
|
||
const arrow = preview.getChildByName("arrow");
|
||
// 箭头底端与各自宝箱顶部保持 24 像素间距,兼容手动调整的位置和多行奖励。
|
||
preview.y = chestTop.y + 24 - arrow.y + arrow.height * arrow.anchorY * arrow.scaleY;
|
||
const limit = Math.max(0, weekly.width / 2 - preview.width / 2 - 10);
|
||
preview.x = Math.max(-limit, Math.min(limit, chestTop.x));
|
||
arrow.x = chestTop.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);
|
||
const data = {
|
||
change_reason: "quests",
|
||
id: 1001 + "",
|
||
num: shuju.coin,
|
||
compensate: false
|
||
};
|
||
cc.fx.GameTool.shushu_Track("resource_get", data);
|
||
}
|
||
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 = (rewards || []).concat([{ type: "stars", count: task.stars }]);
|
||
// 在任务行缩小前记录每个图标的位置,后续效果不跟随任务行的状态切换。
|
||
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 {
|
||
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.rewardEffects.push({ layer, complete: 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.setContentSize(136, 140);
|
||
this.fitRewardIconHeight(icon, 95);
|
||
});
|
||
const countNode = node.getChildByName("count");
|
||
countNode.scale = 1;
|
||
countNode.setPosition(0, origin ? -63 : -51);
|
||
countNode.setContentSize(168, 48);
|
||
const count = reward.type.indexOf("infinite_") === 0
|
||
? `${Math.round(Number(reward.count) / 60 * 10) / 10}分钟` : `${reward.count}`;
|
||
this.setRewardCount(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(layer);
|
||
})
|
||
.start();
|
||
});
|
||
}
|
||
|
||
private clearRewardAnimation(layer?: cc.Node): void {
|
||
const effects = this.rewardEffects.filter(effect => !layer || effect.layer === layer);
|
||
this.rewardEffects = this.rewardEffects.filter(effect => effects.indexOf(effect) < 0);
|
||
effects.forEach(effect => {
|
||
if (cc.isValid(effect.layer)) {
|
||
effect.layer.children.forEach(node => cc.Tween.stopAllByTarget(node));
|
||
effect.layer.destroy();
|
||
}
|
||
effect.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(event?: cc.Event) {
|
||
if (event) cc.fx.AudioManager._instance.playEffect("anniu_little", null);
|
||
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") {
|
||
cc.fx.AudioManager._instance.playEffect("anniu_little", null);
|
||
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.touchStart || this.hasOpeningStateAnimation() || !this.canUseDisplayedTasks()) 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 (event) cc.fx.AudioManager._instance.playEffect("anniu_little", null);
|
||
this.claimReward(data, "daily", task);
|
||
}
|
||
|
||
getWeeklyReward(rewardId: string) {
|
||
if (!this.touchStart || this.hasOpeningStateAnimation() || !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.claimReward(rewardId, "weekly", reward);
|
||
}
|
||
|
||
private claimReward(id: string, scope: "daily" | "weekly", task: any): void {
|
||
const key = this.claimKey(id, scope);
|
||
if (this.pendingClaims.has(key) || this.animatingClaims.has(key)) return;
|
||
this.pendingClaims.add(key);
|
||
this.refreshFromCache();
|
||
const session = this.displaySession;
|
||
const visible = () => session === this.displaySession && cc.isValid(this.node)
|
||
&& this.node.activeInHierarchy && !this.hasExpiredDisplayedTasks();
|
||
let responded = false;
|
||
// 排队或网络较慢时只提示,不解除同一奖励的请求锁,避免重复提交。
|
||
const onTimeout = () => {
|
||
if (!responded && visible()) MiniGameSdk.API.showToast("奖励仍在领取中,请稍候");
|
||
};
|
||
this.scheduleOnce(onTimeout, 8);
|
||
cc.fx.GameTool.claimDailyQuestReward(id, res => {
|
||
if (responded) return;
|
||
responded = true;
|
||
this.unschedule(onTimeout);
|
||
this.pendingClaims.delete(key);
|
||
if (res.code !== 1) {
|
||
if (visible()) MiniGameSdk.API.showToast(res.msg || "网络异常,领取奖励失败");
|
||
this.refreshFromCache();
|
||
return;
|
||
}
|
||
// 入账与动画分离;占用对应任务行,缓存通知只刷新其他行。
|
||
this.animatingClaims.add(key);
|
||
this.reward = true;
|
||
this.openReward(id, res.data.rewards, false);
|
||
let finished = false;
|
||
const finish = () => {
|
||
if (finished) return;
|
||
finished = true;
|
||
this.animatingClaims.delete(key);
|
||
this.reward = this.animatingClaims.size > 0;
|
||
this.refreshFromCache();
|
||
const canvas = cc.find("Canvas");
|
||
const home = canvas && canvas.getComponent("JiaZai");
|
||
if (home) home.updateCoin();
|
||
};
|
||
// 关闭后返回的结果仍入账,但不能在重新打开的面板上重播旧动画。
|
||
if (!visible()) { finish(); return; }
|
||
this.refreshFromCache();
|
||
if (scope === "weekly") {
|
||
this.playWeeklyRewardAnimation(id, res.data.rewards, finish);
|
||
return;
|
||
}
|
||
const row = this.content.getChildByName(id);
|
||
const claimNode = row && row.getChildByName("btnGet");
|
||
const button = claimNode && claimNode.getComponent(cc.Button);
|
||
if (button) button.interactable = false;
|
||
if (claimNode) claimNode.opacity = 255;
|
||
const rewardList = row && row.getChildByName("rewards");
|
||
if (rewardList) rewardList.active = false;
|
||
// 每一条任务独立保持“奖励动画 -> 已领取状态切换”的顺序。
|
||
this.playDailyRewardAnimation(row, task, res.data.rewards, () => {
|
||
if (!visible()) { finish(); return; }
|
||
const claimed = res.data.task.dailyTasks.find(item => item.taskId === id);
|
||
this.playDailyTaskStateAnimations(claimed ? [claimed] : [], id, finish);
|
||
if (cc.isValid(rewardList)) rewardList.active = false;
|
||
});
|
||
}, scope);
|
||
}
|
||
|
||
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);
|
||
}
|
||
if (propData.hammer > 0) {
|
||
const data2 = {
|
||
change_reason: "quests",
|
||
id: 2002,
|
||
num: propData.hammer,
|
||
compensate: false
|
||
}
|
||
cc.fx.GameTool.shushu_Track("resource_get", data2);
|
||
}
|
||
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.displaySession++;
|
||
this.clearDailyTaskProgressAnimation();
|
||
this.clearDailyTaskStateAnimations();
|
||
this.dailyTaskOpeningPlayed = false;
|
||
this.clearRewardAnimation();
|
||
this.stopWeeklyChestAnimations();
|
||
}
|
||
|
||
onDestroy() {
|
||
this.displaySession++;
|
||
this.clearDailyTaskProgressAnimation();
|
||
this.clearDailyTaskStateAnimations();
|
||
this.clearRewardAnimation();
|
||
this.stopWeeklyChestAnimations();
|
||
this.node.off(cc.Node.EventType.TOUCH_START, this.onWeeklyPreviewOutsideTouch, this, true);
|
||
this.unschedule(this.updateCountdowns);
|
||
}
|
||
|
||
}
|