feat: 每日任务改版

This commit is contained in:
guanchao
2026-09-09 09:53:59 +08:00
parent 122b5041ce
commit f455953ecc
91 changed files with 21804 additions and 8937 deletions
+9 -1
View File
@@ -264,6 +264,10 @@ export class GameConfig {
freezeAmount: number; //冻结道具次数
hammerAmount: number; //锤子道具次数
magicAmount: number; //魔法棒道具次数
hourglassAmount: number; //预留:沙漏道具次数
rocketAmount: number; //预留:火箭道具次数
infiniteHourglassTime: number; //预留:无限沙漏到期时间(秒)
infiniteRocketTime: number; //预留:无限火箭到期时间(秒)
musicOpen: boolean; //音乐
effectOpen: boolean; //音效
vibrateOpen: boolean; //震动
@@ -301,7 +305,7 @@ export class GameConfig {
getItemType: number; popPassCheck: boolean; // 弹窗通行证
passCheckActivate: boolean;
winStreak: number; //连胜
tasks: { levelPass: { value: number; target: number; state: number; }; share: { value: number; target: number; state: number; }; useEnergy: { value: number; target: number; state: number; }; useProp: { value: number; target: number; state: number; }; }; addLevel: number; //每日过关数
tasks: any; addLevel: number; //每日/周任务数据
address: string; //用户城市信息
firstTime: number; //用户注册时间
longitude: string; //用户经度
@@ -475,6 +479,10 @@ export class GameConfig {
freezeAmount: 0, //冻结道具次数
hammerAmount: 0, //锤子道具次数
magicAmount: 0, //魔法棒道具次数
hourglassAmount: 0,
rocketAmount: 0,
infiniteHourglassTime: 0,
infiniteRocketTime: 0,
musicOpen: true, //音乐
effectOpen: true, //音效
vibrateOpen: true, //震动
+24 -24
View File
@@ -9,8 +9,8 @@ export default class Utils {
static outTradeNo: string = "";
// static httpip: string = `https://laf.nika4games.com/`; //五彩消消乐
static httpip: string = `https://q6rvwvtnga.sealoshzh.site/`; //消消王者
// static testHttpip: string = `https://sor779u2w8.sealoshzh.site/`; //测试服务器
static testHttpip: string = `https://q6rvwvtnga.sealoshzh.site/`; //消消王者测试服暂替
static testHttpip: string = `https://sor779u2w8.sealoshzh.site/`; //测试服务器
// static testHttpip: string = `https://q6rvwvtnga.sealoshzh.site/`; //消消王者测试服暂替
//#region 安卓支付
/**获取用户信息*/
static getUserInfo(callBack, from?: string) {
@@ -332,8 +332,18 @@ export default class Utils {
* @memberof HttpUtil
*/
public static POST(url, param: object | any, callback) {
const onResponse = callback;
let responded = false;
callback = response => {
if (responded) return;
responded = true;
onResponse(response);
};
var xhr = cc.loader.getXMLHttpRequest();
xhr.timeout = 5000;//超时时间
xhr.onerror = () => callback({ code: 0, msg: "网络请求失败,请重试" });
xhr.ontimeout = () => callback({ code: 0, msg: "网络请求超时,请重试" });
xhr.onabort = () => callback({ code: 0, msg: "网络请求已取消" });
let dataStr = '';
let isDebug = false;
param.token = cc.fx.GameConfig.GM_INFO.token;
@@ -377,10 +387,10 @@ export default class Utils {
try {
response = JSON.parse(response);
} catch {
response = { data: "数据有误", code: 0 };
response = { data: null, msg: "数据有误", code: 0 };
}
if (response.code == 0) {
if (response.msg.toString() == "token校验失败") {
if (response && response.code == 0) {
if (response.msg === "token校验失败") {
if (MiniGameSdk) {
MiniGameSdk.API.showToast("游戏已更新,请关闭游戏重新进入");
}
@@ -392,6 +402,8 @@ export default class Utils {
} else {
callback({ data: "网络请求失败,请检查网络连接", code: 0 });
}
} else if (xhr.readyState === 4) {
callback({ code: 0, msg: "服务器未返回数据,请重试" });
}
}
@@ -1206,7 +1218,7 @@ export default class Utils {
}
//设置每日任务信息
static setDailyQuestInfo(callBack) {
static setDailyQuestInfo(callBack, task = cc.fx.GameConfig.GM_INFO.tasks) {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
let uid = cc.fx.StorageMessage.getStorage("uid");
@@ -1214,24 +1226,14 @@ export default class Utils {
if (uid != undefined && uid != "" && uid != null) {
cc.fx.GameConfig.GM_INFO.uid = uid;
}
let tasks = {
levelPass: { value: cc.fx.GameConfig.GM_INFO.tasks.levelPass.value },
share: { value: cc.fx.GameConfig.GM_INFO.tasks.share.value },
useEnergy: { value: cc.fx.GameConfig.GM_INFO.tasks.useEnergy.value },
useProp: { value: cc.fx.GameConfig.GM_INFO.tasks.useProp.value }
}
let setData = {
uid: cc.fx.GameConfig.GM_INFO.uid,
action: 'save',
task: JSON.stringify(tasks),
task: JSON.stringify(task),
}
Utils.POST("dayTaskInfo", setData, res => {
if (res.code === 1) {
cc.fx.GameConfig.GM_INFO.tasks.levelPass = res.data.task["levelPass"];
cc.fx.GameConfig.GM_INFO.tasks.share = res.data.task["share"];
cc.fx.GameConfig.GM_INFO.tasks.useEnergy = res.data.task["useEnergy"];
cc.fx.GameConfig.GM_INFO.tasks.useProp = res.data.task["useProp"];
// 由 DailyTaskSync 合并回包,保留请求期间新产生的进度。
// //console.log("服务器:领取获得monthGetReward成功'✅ ", res);
if (callBack) callBack(res);
} else {
@@ -1243,7 +1245,7 @@ export default class Utils {
}
//获取每日任务奖励
static getDailyQuestReward(data, callBack) {
static getDailyQuestReward(data, callBack, scope: "daily" | "weekly" = "daily") {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
let uid = cc.fx.StorageMessage.getStorage("uid");
@@ -1253,14 +1255,12 @@ export default class Utils {
}
let setData = {
uid: cc.fx.GameConfig.GM_INFO.uid,
taskType: JSON.stringify(data)
scope: scope,
...(scope === "weekly" ? { rewardId: data } : { taskId: data }),
}
Utils.POST("getTaskReward", setData, res => {
if (res.code === 1) {
cc.fx.GameConfig.GM_INFO.tasks.levelPass = res.data.task["levelPass"];
cc.fx.GameConfig.GM_INFO.tasks.share = res.data.task["share"];
cc.fx.GameConfig.GM_INFO.tasks.useEnergy = res.data.task["useEnergy"];
cc.fx.GameConfig.GM_INFO.tasks.useProp = res.data.task["useProp"];
// 领取状态和并发进度由 DailyTaskSync 统一更新。
// //console.log("服务器:领取获得monthGetReward成功'✅ ", res);
if (callBack) callBack(res);
} else {
+13
View File
@@ -0,0 +1,13 @@
{
"ver": "1.1.3",
"uuid": "0e31acce-6470-4ae3-8776-15abf57d9477",
"importer": "folder",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
@@ -0,0 +1,313 @@
export type DailyTaskEvent = {
metric: string;
amount?: number;
width?: number;
height?: number;
colorId?: number;
propType?: string;
};
/** 新版日任务统一进度入口。任务仍按现有架构由客户端累计并批量上传。 */
export default class DailyTaskProgressTracker {
private static saveTimer: any = null;
private static onlineStartedAt = 0;
private static onlineTimer: any = null;
private static pendingOnlineSeconds = 0;
private static unsavedOnlineRecovery: { dayKey: string, values: { [taskId: string]: number } } = null;
private static firstAttemptValid = true;
private static usedPropInLevel = false;
private static pendingClearEvents: { [key: string]: DailyTaskEvent } = {};
private static debugPaused = false;
private static waitingForPeriod = false;
private static pendingPeriodEvents: DailyTaskEvent[] = [];
static setDebugPaused(paused: boolean): void {
this.debugPaused = paused;
if (this.onlineStartedAt) this.onlineStartedAt = Date.now();
if (paused) this.pendingClearEvents = {};
}
static changePeriod(discardPending = false): void {
this.unsavedOnlineRecovery = null;
if (discardPending) {
this.pendingOnlineSeconds = 0;
this.pendingPeriodEvents = [];
this.waitingForPeriod = false;
}
// 等待新周期时已清理过旧消除事件,保留之后新产生的事件。
if (!this.waitingForPeriod) this.pendingClearEvents = {};
}
static confirmSaved(data: any): void {
const recovery = this.unsavedOnlineRecovery;
if (!recovery || data.dayKey !== recovery.dayKey) return;
if (Object.keys(recovery.values).every(id => {
const task = data.dailyTasks.find(task => task.taskId === id);
return task && (task.state === 2 || Number(task.value) >= recovery.values[id]);
})) this.unsavedOnlineRecovery = null;
}
static beginLevel(): void {
this.firstAttemptValid = true;
this.usedPropInLevel = false;
this.pendingClearEvents = {};
}
static invalidateAttempt(): void {
this.firstAttemptValid = false;
}
static recordPropUsed(propType: string): void {
this.usedPropInLevel = true;
this.record({ metric: "use_prop" });
this.record({ metric: "use_specific_prop", propType });
}
static recordShare(): void {
this.record({ metric: "share" });
this.flushNow();
}
static recordLevelWon(): void {
this.commitClearProgress();
this.record({ metric: "pass_levels" });
this.record({ metric: "win_streak" });
if (this.firstAttemptValid) this.record({ metric: "first_try_pass" });
if (!this.usedPropInLevel) this.record({ metric: "no_prop_pass" });
}
/** 任务结构版本的统一入口;升级协议时集中调整此处的兼容规则。 */
static isValidTaskData(data: any): boolean {
return !!data && data.version === 2 && Array.isArray(data.dailyTasks);
}
static setTaskData(data: any): void {
if (!this.isValidTaskData(data)) return;
let changed = false;
const recovery = this.unsavedOnlineRecovery;
if (recovery && recovery.dayKey === data.dayKey) {
// 补计保存成功前,后续任务读取的旧响应不能撤销本次补计。
data.dailyTasks.forEach((task: any) => {
const value = Math.min(Number(task.target) || 0, recovery.values[task.taskId] || 0);
if (task.metric !== "online_seconds" || task.state === 2 || value <= (Number(task.value) || 0)) return;
task.value = value;
if (value >= task.target) task.state = 1;
changed = true;
});
} else if (recovery) {
this.unsavedOnlineRecovery = null;
}
cc.fx.GameConfig.GM_INFO.tasks = data;
if (this.isExpired()) return;
if (this.waitingForPeriod) {
this.waitingForPeriod = false;
const events = this.pendingPeriodEvents;
this.pendingPeriodEvents = [];
events.forEach(event => { if (this.applyEvent(data, event)) changed = true; });
}
// 只补计加载前已经结算的整秒;先清空,重复接收同一份任务数据也不会重复补计。
const seconds = this.pendingOnlineSeconds;
this.pendingOnlineSeconds = 0;
if (seconds > 0 && this.applyEvent(data, { metric: "online_seconds", amount: seconds })) {
const values = {};
data.dailyTasks.forEach((task: any) => {
if (task.metric === "online_seconds" && task.state !== 2) values[task.taskId] = task.value;
});
this.unsavedOnlineRecovery = { dayKey: data.dayKey, values };
changed = true;
}
if (changed) this.scheduleSave();
}
static getTaskData(): any {
const tasks: any = cc.fx.GameConfig.GM_INFO.tasks;
return this.isValidTaskData(tasks) ? tasks : null;
}
static record(event: DailyTaskEvent): void {
if (this.debugPaused) return;
if (cc.fx.GameConfig.GM_INFO.otherLevel != 0 && this.isLevelMetric(event.metric)) return;
if (this.isExpired()) {
this.waitForPeriod();
if (this.isClearMetric(event.metric)) this.queueClearProgress(event);
else this.pendingPeriodEvents.push({ ...event });
cc.fx.GameTool.getDailyQuestsInfo(() => {});
return;
}
const data = this.getTaskData();
if (!data) {
if (event.metric === "online_seconds") {
this.pendingOnlineSeconds += Math.max(0, Number(event.amount == null ? 1 : event.amount) || 0);
}
return;
}
if (this.isClearMetric(event.metric)) {
if (data.dailyTasks.some((task: any) => task.state !== 2 && this.matches(task, event))) {
this.queueClearProgress(event);
}
return;
}
if (this.applyEvent(data, event)) this.scheduleSave();
}
private static applyEvent(data: any, event: DailyTaskEvent): boolean {
let changed = false;
data.dailyTasks.forEach((task: any) => {
if (task.state === 2 || !this.matches(task, event)) return;
const amount = Math.max(0, Number(event.amount == null ? 1 : event.amount) || 0);
const next = Math.min(Number(task.target) || 0, (Number(task.value) || 0) + amount);
if (next === task.value) return;
task.value = next;
if (task.value >= task.target) {
task.state = 1;
if (cc.fx.GameTool && cc.fx.GameTool.shushu_Track) {
cc.fx.GameTool.shushu_Track("daily_task", { id: task.taskId, status: "complete" });
}
}
changed = true;
});
return changed;
}
static resetWinStreak(): void {
if (this.debugPaused) return;
this.pendingClearEvents = {};
const data = this.getTaskData();
if (!data) return;
let changed = false;
data.dailyTasks.forEach((task: any) => {
if (task.metric === "win_streak" && task.state !== 2 && task.value !== 0) {
task.value = 0;
task.state = 0;
if (cc.fx.GameTool.markDailyTaskReset) cc.fx.GameTool.markDailyTaskReset(task.taskId);
changed = true;
}
});
if (changed) this.scheduleSave();
}
private static queueClearProgress(event: DailyTaskEvent): void {
const key = `${event.metric}:${event.width || ""}:${event.height || ""}:${event.colorId || ""}`;
const amount = Math.max(0, Number(event.amount == null ? 1 : event.amount) || 0);
const pending = this.pendingClearEvents[key];
if (pending) pending.amount = (Number(pending.amount) || 0) + amount;
else this.pendingClearEvents[key] = { ...event, amount };
}
private static commitClearProgress(): void {
if (this.debugPaused) return;
if (this.isExpired()) {
this.waitForPeriod();
this.pendingPeriodEvents.push(...Object.keys(this.pendingClearEvents).map(key => this.pendingClearEvents[key]));
this.pendingClearEvents = {};
cc.fx.GameTool.getDailyQuestsInfo(() => {});
return;
}
const data = this.getTaskData();
const events = Object.keys(this.pendingClearEvents).map(key => this.pendingClearEvents[key]);
this.pendingClearEvents = {};
if (!data) return;
let changed = false;
events.forEach(event => {
if (this.applyEvent(data, event)) changed = true;
});
if (changed) this.scheduleSave();
}
static startOnline(): void {
if (this.onlineStartedAt) return;
this.onlineStartedAt = Date.now();
if (!this.onlineTimer) {
this.onlineTimer = setInterval(() => this.flushOnline(), 30000);
}
}
static stopOnline(): void {
this.flushOnline();
if (this.onlineTimer) clearInterval(this.onlineTimer);
this.onlineTimer = null;
this.onlineStartedAt = 0;
}
static flushNow(save = true): void {
if (this.debugPaused) return;
this.flushOnline();
if (this.saveTimer) clearTimeout(this.saveTimer);
this.saveTimer = null;
if (save) this.save();
}
private static flushOnline(): void {
if (!this.onlineStartedAt) return;
const now = Date.now();
let seconds = Math.floor((now - this.onlineStartedAt) / 1000);
if (seconds <= 0) return;
this.onlineStartedAt += seconds * 1000;
if (this.isExpired()) {
// 旧日期已经结束的时间不能补进新日期;只缓冲零点之后的部分。
const data = this.getTaskData();
const serverNow = cc.fx.GameTool.getDailyTaskServerTime();
const elapsedAfterReset = Math.max(0, Math.floor((serverNow - Number(data.dayEndsAt)) / 1000));
seconds = Math.min(seconds, elapsedAfterReset);
}
if (seconds <= 0) return;
this.record({ metric: "online_seconds", amount: seconds });
}
private static matches(task: any, event: DailyTaskEvent): boolean {
if (task.metric !== event.metric) return false;
const params = task.params || {};
if (event.metric === "clear_size") {
const same = Number(event.width) === Number(params.width) && Number(event.height) === Number(params.height);
const rotated = Number(event.width) === Number(params.height) && Number(event.height) === Number(params.width);
return same || rotated;
}
if (event.metric === "clear_color") return Number(event.colorId) === Number(params.colorId);
if (event.metric === "use_specific_prop") return event.propType === params.propType;
return true;
}
private static isLevelMetric(metric: string): boolean {
return ["clear_blocks", "clear_size", "clear_color", "pass_levels", "no_prop_pass", "first_try_pass", "win_streak"].indexOf(metric) >= 0;
}
private static isClearMetric(metric: string): boolean {
return ["clear_blocks", "clear_size", "clear_color"].indexOf(metric) >= 0;
}
private static scheduleSave(): void {
if (cc.fx.GameTool && cc.fx.GameTool.markDailyTasksDirty) cc.fx.GameTool.markDailyTasksDirty();
if (this.saveTimer) return;
this.saveTimer = setTimeout(() => {
this.saveTimer = null;
this.save();
}, 1000);
}
private static save(): void {
if (this.debugPaused) return;
if (!this.getTaskData() || !cc.fx.GameTool || !cc.fx.GameTool.setDailyQuestInfo) return;
const recovery = this.unsavedOnlineRecovery;
cc.fx.GameTool.setDailyQuestInfo((response: any) => {
if (response && response.code === 1) {
if (this.unsavedOnlineRecovery === recovery) {
this.unsavedOnlineRecovery = null;
} else if (this.unsavedOnlineRecovery) {
// 同步期间又出现补计时,保留这批尚未确认的时间。
const data = this.getTaskData();
if (data && data.dayKey === this.unsavedOnlineRecovery.dayKey) this.setTaskData(data);
}
}
});
}
private static isExpired(): boolean {
return !!(cc.fx.GameTool && cc.fx.GameTool.isDailyTaskCacheExpired && cc.fx.GameTool.isDailyTaskCacheExpired());
}
private static waitForPeriod(): void {
if (this.waitingForPeriod) return;
this.waitingForPeriod = true;
this.pendingClearEvents = {};
}
}
@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "2be10e31-1384-4cb8-95c7-3a596d2107e2",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
+242
View File
@@ -0,0 +1,242 @@
import Utils from "../Pay/Utils";
import DailyTaskProgressTracker from "./DailyTaskProgressTracker";
/** 任务内存缓存及串行同步。UI 读取缓存,进度保存与领奖共用同一条请求队列。 */
export default class DailyTaskSync {
private static loaded = false;
private static uid = "";
private static offset = 0;
private static revision = 0;
private static savedRevision = 0;
private static resets: { [taskId: string]: number } = {};
private static queue: Array<(done: () => void) => void> = [];
private static busy = false;
private static readers: Array<(response: any) => void> = null;
private static paused = false;
private static needsRefresh = false;
private static hiddenAt = 0;
static now(): number { return Date.now() + this.offset; }
static isExpired(): boolean {
const data = DailyTaskProgressTracker.getTaskData();
return !!data && [data.dayEndsAt, data.weekEndsAt]
.some(end => Number(end) > 0 && this.now() >= Number(end));
}
static cached(): any {
const data = DailyTaskProgressTracker.getTaskData();
return this.loaded && this.uid === this.currentUid() && !this.needsRefresh && !this.isExpired()
&& data && Number(data.dayEndsAt) > this.now() && Number(data.weekEndsAt) > this.now() ? data : null;
}
static markDirty(): void { this.revision++; this.notify(); }
static markReset(taskId: string): void { this.resets[taskId] = this.revision + 1; }
static read(callback: (response: any) => void, force = false): void {
const cache = this.cached();
if (!force && cache) { callback(this.success(cache)); return; }
if (this.readers) { this.readers.push(callback); return; }
this.readers = [callback];
this.enqueue(done => {
this.saveDirty(saved => {
if (saved.code !== 1) { this.finishRead(saved, done); return; }
const snapshot = this.snapshot();
const revision = this.revision;
const requestUid = this.currentUid();
Utils.getDailyQuestInfo(response => {
if (requestUid !== this.currentUid()) response = this.failure("登录用户已变更,请重新获取任务");
if (this.validResponse(response)) {
this.needsRefresh = false;
this.apply(response.data.task, snapshot, revision);
response.data.task = DailyTaskProgressTracker.getTaskData();
if (!this.cached()) {
this.needsRefresh = true;
response = this.failure("任务已过期或缺少刷新时间,请重试");
}
} else {
this.needsRefresh = true;
response = response && response.code !== 1 ? response : this.failure("任务数据无效,请重试");
}
this.finishRead(response, done);
});
});
});
}
static save(callback: (response: any) => void = () => {}): void {
this.enqueue(done => this.saveDirty(response => { done(); callback(response); }));
}
static claim(id: string, callback: (response: any) => void, scope: "daily" | "weekly" = "daily"): void {
if (this.paused) { callback(this.failure("任务测试已暂停,请先恢复任务同步")); return; }
// 结算在线进度;其保存请求和本次领奖将按顺序执行。
DailyTaskProgressTracker.flushNow(false);
this.enqueue(done => {
this.saveDirty(saved => {
if (saved.code !== 1) { done(); callback(saved); return; }
if (!this.cached()) {
done();
this.read(() => callback(this.failure("任务数据已刷新,请重新领取")), true);
return;
}
const snapshot = this.snapshot();
const revision = this.revision;
const taskData = DailyTaskProgressTracker.getTaskData();
const item = scope === "weekly" ? taskData.weeklyRewards.find(item => item.rewardId === id)
: taskData.dailyTasks.find(item => item.taskId === id);
if (!item || item.state !== 1) { done(); callback(this.failure("任务状态已变化,请查看最新任务")); return; }
const requestUid = this.currentUid();
Utils.getDailyQuestReward(id, response => {
if (requestUid !== this.currentUid()) response = this.failure("登录用户已变更");
if (this.validResponse(response)) {
this.apply(response.data.task, snapshot, revision);
response.data.task = DailyTaskProgressTracker.getTaskData();
} else {
this.needsRefresh = true;
response = response && response.code !== 1 ? response : this.failure("领奖返回数据异常,请刷新任务后重试");
}
// 先让领奖界面消费结果,再执行后续排队的同步。
try { callback(response); } finally { done(); }
}, scope);
});
});
}
static background(): void { this.hiddenAt = Date.now(); }
static networkChanged(connected: boolean): void {
if (!connected) this.needsRefresh = true;
else this.foreground();
}
static foreground(): void {
if (!this.loaded || this.paused) return;
const longAbsence = this.hiddenAt > 0 && Date.now() - this.hiddenAt >= 5 * 60 * 1000;
this.hiddenAt = 0;
if (longAbsence || !this.cached()) this.read(() => {}, true);
}
/** 微信开发者工具控制台:pause -> Postman 修改 -> refresh -> resume。 */
static debug(action: "pause" | "refresh" | "resume" | "status", callback: (response: any) => void): void {
if (cc.fx.GameTool.getWechatGameVersion() !== "开发版") {
callback(this.failure("任务调试仅限开发版")); return;
}
if (action === "status") {
callback({ code: 1, data: { paused: this.paused, busy: this.busy, cached: !!this.cached(), expiresAt: this.expiration() } });
return;
}
if ((action === "refresh" || action === "resume") && this.paused
&& this.revision !== this.savedRevision && !this.isExpired()) {
callback(this.failure("暂停前的进度尚未保存成功,请重新执行 pause")); return;
}
if (action === "pause") {
// 先结算,再暂停新事件;等待已发送请求和现有进度保存完成后才允许改服务器。
DailyTaskProgressTracker.flushNow(false);
this.paused = true;
DailyTaskProgressTracker.setDebugPaused(true);
this.enqueue(done => this.saveDirty(response => { done(); callback(response); }, true));
} else if (action === "refresh") {
this.read(callback, true);
} else if (action === "resume") {
this.read(response => {
if (response.code === 1) {
this.paused = false;
DailyTaskProgressTracker.setDebugPaused(false);
}
callback(response);
}, true);
} else callback(this.failure("未知任务调试操作"));
}
private static saveDirty(callback: (response: any) => void, allowPaused = false): void {
const data = DailyTaskProgressTracker.getTaskData();
if (!data || this.revision === this.savedRevision || (this.paused && !allowPaused)
|| this.isExpired() || (this.loaded && this.uid !== this.currentUid())) {
callback(this.success(data)); return;
}
const snapshot = this.snapshot();
const revision = this.revision;
const requestUid = this.currentUid();
Utils.setDailyQuestInfo(response => {
if (requestUid !== this.currentUid()) response = this.failure("登录用户已变更");
if (!this.validResponse(response)) {
this.needsRefresh = true;
callback(response && response.code !== 1 ? response : this.failure("任务进度保存失败"));
return;
}
this.savedRevision = revision;
this.needsRefresh = false;
DailyTaskProgressTracker.confirmSaved(response.data.task);
this.apply(response.data.task, snapshot, revision);
// 保存期间新增的事件仍保留在缓存中,继续提交后才允许领奖/强制读取。
this.saveDirty(callback, allowPaused);
}, snapshot);
}
private static apply(data: any, snapshot: any, revision: number): void {
const current = DailyTaskProgressTracker.getTaskData();
const accountChanged = this.loaded && this.uid !== this.currentUid();
const sameDay = !accountChanged && snapshot && current && data.dayKey === snapshot.dayKey && data.dayKey === current.dayKey;
if (sameDay) {
data.dailyTasks.forEach(task => {
const before = snapshot.dailyTasks.find(item => item.taskId === task.taskId);
const local = current.dailyTasks.find(item => item.taskId === task.taskId);
if (!before || !local || task.state === 2) return;
if (local.state === 2 && before.state !== 2) { task.state = 2; task.value = local.value; return; }
const delta = Number(local.value) - Number(before.value);
const reset = (this.resets[task.taskId] || 0) > revision;
if (!delta && local.state === before.state && !reset) return;
// 连胜可被清零,不能简单地取进度最大值。
task.value = Math.max(0, Math.min(task.target, reset || delta < 0 ? local.value : Number(task.value) + delta));
task.state = task.value >= task.target ? 1 : 0;
});
} else if (accountChanged || current && data.dayKey !== current.dayKey) {
this.revision = this.savedRevision = 0;
this.resets = {};
DailyTaskProgressTracker.changePeriod(accountChanged);
}
this.uid = this.currentUid();
this.loaded = true;
if (Number(data.serverNow) > 0) this.offset = Number(data.serverNow) - Date.now();
DailyTaskProgressTracker.setTaskData(data);
this.notify();
}
private static finishRead(response: any, done: () => void): void {
const readers = this.readers;
this.readers = null;
try { readers.forEach(callback => callback(response)); } finally { done(); }
}
private static enqueue(operation: (done: () => void) => void): void {
this.queue.push(operation); this.pump();
}
private static pump(): void {
if (this.busy || !this.queue.length) return;
this.busy = true;
this.queue.shift()(() => { this.busy = false; this.pump(); });
}
private static snapshot(): any {
const data = DailyTaskProgressTracker.getTaskData();
return data ? JSON.parse(JSON.stringify(data)) : null;
}
private static currentUid(): string {
return cc.fx.StorageMessage.getStorage("uid") || cc.fx.GameConfig.GM_INFO.uid || "";
}
private static validResponse(response: any): boolean {
const data = response && response.code === 1 && response.data && response.data.task;
return DailyTaskProgressTracker.isValidTaskData(data) && Array.isArray(data.weeklyRewards);
}
private static success(task: any): any { return { code: 1, data: { task } }; }
private static failure(msg: string): any { return { code: 0, msg }; }
private static expiration(): number {
const data = DailyTaskProgressTracker.getTaskData();
return data ? Math.min(Number(data.dayEndsAt) || 0, Number(data.weekEndsAt) || 0) : 0;
}
private static notify(): void {
if (cc.director && cc.director.emit) cc.director.emit("daily-tasks-changed");
}
}
@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "e998846d-b313-41a7-81b4-2f6166fa724f",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
+34 -7
View File
@@ -5,6 +5,8 @@ import Freeze from "../../prop/Freeze";
import SceneManager from "../../SceneManager";
import { MiniGameSdk } from "../../Sdk/MiniGameSdk";
import Utils from "../Pay/Utils";
import DailyTaskProgressTracker from "../Task/DailyTaskProgressTracker";
import DailyTaskSync from "../Task/DailyTaskSync";
//@ts-ignore
//最大工具类 各种公共方法,以及处理上传,获取后端接口数据
var GameTool = {
@@ -485,6 +487,10 @@ var GameTool = {
return;
}
cc.fx.GameConfig.GM_INFO.coin += coin;
DailyTaskProgressTracker.record({
metric: coin >= 0 ? "collect_coin" : "spend_coin",
amount: Math.abs(coin),
});
if (coin > 0) {
// let data = "获得" + (coin) + "金币";
// MiniGameSdk.API.showToast(data);
@@ -826,6 +832,10 @@ var GameTool = {
freeze: cc.fx.GameConfig.GM_INFO.freezeAmount,
hammer: cc.fx.GameConfig.GM_INFO.hammerAmount,
magic_wand: cc.fx.GameConfig.GM_INFO.magicAmount,
hourglass: cc.fx.GameConfig.GM_INFO.hourglassAmount,
rocket: cc.fx.GameConfig.GM_INFO.rocketAmount,
infinite_hourglass: cc.fx.GameConfig.GM_INFO.infiniteHourglassTime,
infinite_rocket: cc.fx.GameConfig.GM_INFO.infiniteRocketTime,
}
}
else if (propid == 2001) {
@@ -1888,16 +1898,33 @@ var GameTool = {
},
getDailyQuestsInfo(callBack: Function) {
Utils.getDailyQuestInfo((data) => {
if (callBack) callBack(data);
})
getDailyQuestsInfo(callBack: Function, forceRefresh = false) {
DailyTaskSync.read(data => { if (callBack) callBack(data); }, forceRefresh);
},
setDailyQuestInfo(callBack: Function) {
Utils.setDailyQuestInfo((data) => {
if (callBack) callBack(data);
})
DailyTaskSync.save(data => { if (callBack) callBack(data); });
},
getCachedDailyQuests() { return DailyTaskSync.cached(); },
isDailyTaskCacheExpired() { return DailyTaskSync.isExpired(); },
getDailyTaskServerTime() { return DailyTaskSync.now(); },
markDailyTasksDirty() { DailyTaskSync.markDirty(); },
markDailyTaskReset(taskId: string) { DailyTaskSync.markReset(taskId); },
onDailyTasksBackground() { DailyTaskSync.background(); },
onDailyTasksForeground() { DailyTaskSync.foreground(); },
onDailyTaskNetworkChange(connected: boolean) { DailyTaskSync.networkChanged(connected); },
claimDailyQuestReward(id: string, callback: (data: any) => void, scope: "daily" | "weekly" = "daily") {
DailyTaskSync.claim(id, callback, scope);
},
debugDailyTasks(action: "pause" | "refresh" | "resume" | "status", callback = (data: any) => console.log("任务调试", data)) {
DailyTaskSync.debug(action, callback);
},
recordDailyTaskShare() {
DailyTaskProgressTracker.recordShare();
},
setWinStreak(type) {