feat: 每日任务改版
This commit is contained in:
@@ -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, //震动
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user