From af624bcb5fc0cdcb41656055f1a4eadc1631d733 Mon Sep 17 00:00:00 2001 From: guanchao <18612113337@163.com> Date: Mon, 21 Sep 2026 16:40:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=AF=8F=E6=97=A5=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BB=BB=E5=8A=A1=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/Script/Block.ts | 1 + .../module/Task/DailyTaskProgressTracker.ts | 17 +- assets/Script/module/Tool/GameTool.ts | 3 +- .../action_bundle/prefab/dailyQuests.prefab | 1292 +++++++++++++++++ assets/action_bundle/script/DailyQuests.ts | 29 +- tools/DAILY_WEEKLY_TASK_CONFIG.md | 119 ++ tools/test-daily-reward-animation.cjs | 11 + tools/test-free-prop-task-progress.cjs | 24 +- tools/test-task-tracking.cjs | 25 + tools/test-weekly-quests.cjs | 15 + 10 files changed, 1520 insertions(+), 16 deletions(-) create mode 100644 tools/DAILY_WEEKLY_TASK_CONFIG.md create mode 100644 tools/test-task-tracking.cjs diff --git a/assets/Script/Block.ts b/assets/Script/Block.ts index fcb688f2..6bc9467c 100644 --- a/assets/Script/Block.ts +++ b/assets/Script/Block.ts @@ -125,6 +125,7 @@ export default class Block extends cc.Component { DailyTaskProgressTracker.record({ metric: "clear_blocks" }); DailyTaskProgressTracker.record({ metric: "clear_size", + cellCount: this.allBlocks ? this.allBlocks.length : 0, width: Number(this.heng) || 1, height: Number(this.shu) || 1, }); diff --git a/assets/Script/module/Task/DailyTaskProgressTracker.ts b/assets/Script/module/Task/DailyTaskProgressTracker.ts index bddce8df..ac58f035 100644 --- a/assets/Script/module/Task/DailyTaskProgressTracker.ts +++ b/assets/Script/module/Task/DailyTaskProgressTracker.ts @@ -1,6 +1,7 @@ export type DailyTaskEvent = { metric: string; amount?: number; + cellCount?: number; width?: number; height?: number; colorId?: number; @@ -160,15 +161,22 @@ export default class DailyTaskProgressTracker { 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" }); - } + this.trackTask(task, "complete"); } changed = true; }); return changed; } + static trackTask(task: any, status: "complete" | "claim"): void { + if (cc.fx.GameTool && cc.fx.GameTool.shushu_Track) { + cc.fx.GameTool.shushu_Track("daily_task", { + id: task.trackingId || task.taskId, status, + ...(task.metric === "clear_color" ? { colorId: task.params.colorId } : {}), + }); + } + } + static resetWinStreak(): void { if (this.debugPaused) return; this.pendingClearEvents = {}; @@ -187,7 +195,7 @@ export default class DailyTaskProgressTracker { } private static queueClearProgress(event: DailyTaskEvent): void { - const key = `${event.metric}:${event.width || ""}:${event.height || ""}:${event.colorId || ""}`; + const key = `${event.metric}:${event.cellCount || ""}:${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; @@ -258,6 +266,7 @@ export default class DailyTaskProgressTracker { if (task.metric !== event.metric) return false; const params = task.params || {}; if (event.metric === "clear_size") { + if (params.cellCount != null) return Number(event.cellCount) === Number(params.cellCount); 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; diff --git a/assets/Script/module/Tool/GameTool.ts b/assets/Script/module/Tool/GameTool.ts index cf0aab10..a1bc9d8c 100644 --- a/assets/Script/module/Tool/GameTool.ts +++ b/assets/Script/module/Tool/GameTool.ts @@ -1473,7 +1473,8 @@ var GameTool = { case "daily_task": eventData = { task_id: data.id, - task_status: data.status + task_status: data.status, + ...(data.colorId != null ? { color_id: data.colorId } : {}), } break; case "interface_exposure": diff --git a/assets/action_bundle/prefab/dailyQuests.prefab b/assets/action_bundle/prefab/dailyQuests.prefab index a45ba63e..96f706fd 100644 --- a/assets/action_bundle/prefab/dailyQuests.prefab +++ b/assets/action_bundle/prefab/dailyQuests.prefab @@ -6342,6 +6342,12 @@ }, { "__id__": 207 + }, + { + "__id__": 235 + }, + { + "__id__": 253 } ], "_active": true, @@ -8542,5 +8548,1291 @@ }, "fileId": "", "sync": false + }, + { + "__type__": "cc.Node", + "_name": "rewardItem4", + "_objFlags": 0, + "_parent": { + "__id__": 170 + }, + "_children": [ + { + "__id__": 236 + }, + { + "__id__": 239 + }, + { + "__id__": 242 + }, + { + "__id__": 245 + }, + { + "__id__": 249 + } + ], + "_active": false, + "_components": [], + "_prefab": { + "__id__": 252 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 148, + "height": 164 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + -164, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Node", + "_name": "stars", + "_objFlags": 0, + "_parent": { + "__id__": 235 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 237 + } + ], + "_prefab": { + "__id__": 238 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 86, + "height": 90 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + 8, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 236 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_spriteFrame": { + "__uuid__": "a6d59909-5b9a-421a-b80b-fe78646fbeb4" + }, + "_type": 0, + "_sizeMode": 0, + "_fillType": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "f26c692d-21bb-470d-aee1-a2b6c7a4e34c", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "icon", + "_objFlags": 0, + "_parent": { + "__id__": 235 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 240 + } + ], + "_prefab": { + "__id__": 241 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 136, + "height": 140 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + 12, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 239 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_spriteFrame": null, + "_type": 0, + "_sizeMode": 0, + "_fillType": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "ac533bec-ea3e-4483-a757-f70d58a9d03e", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "rewardName", + "_objFlags": 0, + "_parent": { + "__id__": 235 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 243 + } + ], + "_prefab": { + "__id__": 244 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 164, + "g": 110, + "b": 66, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 118, + "height": 78 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + 10, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 242 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_string": "奖励1", + "_N$string": "奖励1", + "_fontSize": 28, + "_lineHeight": 32, + "_enableWrapText": true, + "_N$file": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_batchAsBitmap": false, + "_styleFlags": 1, + "_underlineHeight": 0, + "_N$horizontalAlign": 1, + "_N$verticalAlign": 1, + "_N$fontFamily": "Arial", + "_N$overflow": 2, + "_N$cacheMode": 0, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "6eda8ead-3779-43fb-874e-a6fb5505683f", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "count", + "_objFlags": 0, + "_parent": { + "__id__": 235 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 246 + }, + { + "__id__": 247 + } + ], + "_prefab": { + "__id__": 248 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 148, + "height": 48 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + -51, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 245 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_string": "2", + "_N$string": "2", + "_fontSize": 40, + "_lineHeight": 44, + "_enableWrapText": true, + "_N$file": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_batchAsBitmap": false, + "_styleFlags": 1, + "_underlineHeight": 0, + "_N$horizontalAlign": 1, + "_N$verticalAlign": 1, + "_N$fontFamily": "Arial", + "_N$overflow": 2, + "_N$cacheMode": 0, + "_id": "" + }, + { + "__type__": "cc.LabelOutline", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 245 + }, + "_enabled": true, + "_color": { + "__type__": "cc.Color", + "r": 150, + "g": 83, + "b": 44, + "a": 255 + }, + "_width": 3, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "22f4767d-6dbf-47ce-b1d4-b2978a71fc39", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "plus", + "_objFlags": 0, + "_parent": { + "__id__": 235 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 250 + } + ], + "_prefab": { + "__id__": 251 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 24, + "height": 28 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 73, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 249 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_spriteFrame": { + "__uuid__": "8595bc55-9288-4ca6-b1c7-5c6df0855e61" + }, + "_type": 0, + "_sizeMode": 0, + "_fillType": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "ae1b5fe7-7e08-43ba-adfa-77836ccf7d02", + "sync": false + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "2566c476-9394-44a8-a09b-5735a6bae51e", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "rewardItem5", + "_objFlags": 0, + "_parent": { + "__id__": 170 + }, + "_children": [ + { + "__id__": 254 + }, + { + "__id__": 257 + }, + { + "__id__": 260 + }, + { + "__id__": 263 + }, + { + "__id__": 267 + } + ], + "_active": false, + "_components": [], + "_prefab": { + "__id__": 270 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 148, + "height": 164 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + -164, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Node", + "_name": "stars", + "_objFlags": 0, + "_parent": { + "__id__": 253 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 255 + } + ], + "_prefab": { + "__id__": 256 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 86, + "height": 90 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + 8, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 254 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_spriteFrame": { + "__uuid__": "a6d59909-5b9a-421a-b80b-fe78646fbeb4" + }, + "_type": 0, + "_sizeMode": 0, + "_fillType": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "3a70d434-491a-4bea-8fc1-29fa2d75a2d5", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "icon", + "_objFlags": 0, + "_parent": { + "__id__": 253 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 258 + } + ], + "_prefab": { + "__id__": 259 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 136, + "height": 140 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + 12, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 257 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_spriteFrame": null, + "_type": 0, + "_sizeMode": 0, + "_fillType": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "ee2a135b-d8a5-490f-a6f4-657fd3925ba6", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "rewardName", + "_objFlags": 0, + "_parent": { + "__id__": 253 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 261 + } + ], + "_prefab": { + "__id__": 262 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 164, + "g": 110, + "b": 66, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 118, + "height": 78 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + 10, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 260 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_string": "奖励1", + "_N$string": "奖励1", + "_fontSize": 28, + "_lineHeight": 32, + "_enableWrapText": true, + "_N$file": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_batchAsBitmap": false, + "_styleFlags": 1, + "_underlineHeight": 0, + "_N$horizontalAlign": 1, + "_N$verticalAlign": 1, + "_N$fontFamily": "Arial", + "_N$overflow": 2, + "_N$cacheMode": 0, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "780d206a-e75b-4c3c-b20c-a2c28a1180c9", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "count", + "_objFlags": 0, + "_parent": { + "__id__": 253 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 264 + }, + { + "__id__": 265 + } + ], + "_prefab": { + "__id__": 266 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 148, + "height": 48 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 0, + -51, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 263 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_string": "2", + "_N$string": "2", + "_fontSize": 40, + "_lineHeight": 44, + "_enableWrapText": true, + "_N$file": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_batchAsBitmap": false, + "_styleFlags": 1, + "_underlineHeight": 0, + "_N$horizontalAlign": 1, + "_N$verticalAlign": 1, + "_N$fontFamily": "Arial", + "_N$overflow": 2, + "_N$cacheMode": 0, + "_id": "" + }, + { + "__type__": "cc.LabelOutline", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 263 + }, + "_enabled": true, + "_color": { + "__type__": "cc.Color", + "r": 150, + "g": 83, + "b": 44, + "a": 255 + }, + "_width": 3, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "ef368cca-18aa-412f-8973-96fa65bb63f0", + "sync": false + }, + { + "__type__": "cc.Node", + "_name": "plus", + "_objFlags": 0, + "_parent": { + "__id__": 253 + }, + "_children": [], + "_active": false, + "_components": [ + { + "__id__": 268 + } + ], + "_prefab": { + "__id__": 269 + }, + "_opacity": 255, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 24, + "height": 28 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_trs": { + "__type__": "TypedArray", + "ctor": "Float64Array", + "array": [ + 73, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1 + ] + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "node": { + "__id__": 267 + }, + "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, + "_spriteFrame": { + "__uuid__": "8595bc55-9288-4ca6-b1c7-5c6df0855e61" + }, + "_type": 0, + "_sizeMode": 0, + "_fillType": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "6a14a24b-9de8-44ec-9efa-f78f3767257e", + "sync": false + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "81189af4-7832-44b7-87ba-c7b6709294d4", + "sync": false } ] diff --git a/assets/action_bundle/script/DailyQuests.ts b/assets/action_bundle/script/DailyQuests.ts index eab57ff1..ef3c3f8b 100644 --- a/assets/action_bundle/script/DailyQuests.ts +++ b/assets/action_bundle/script/DailyQuests.ts @@ -323,12 +323,13 @@ export default class DailyQuests extends cc.Component { } private getTaskTitle(task: any): string { - const colorNames = { 1: "黄色", 2: "深绿色", 3: "深蓝色", 4: "浅粉色", 5: "橙色" }; + 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 `清除${task.target}个${params.width || 1}x${params.height || 1}方块`; + 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}关`; @@ -472,10 +473,16 @@ export default class DailyQuests extends cc.Component { label.overflow = cc.Label.Overflow.SHRINK; }); const layout = rewardList.getComponent(cc.Layout); - if (layout) { - layout.spacingX = 16; - layout.updateLayout(); - } + 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; @@ -483,8 +490,10 @@ export default class DailyQuests extends cc.Component { background.spriteFrame.insetLeft = background.spriteFrame.insetRight = 28; background.spriteFrame.insetTop = background.spriteFrame.insetBottom = 28; } - preview.setContentSize(rewardList.width + 32, 180); - preview.getChildByName("arrow").y = -99; + preview.setContentSize(rewardList.width + 32, rows * 180); + // 增高时向上扩展,箭头保持指向原宝箱的位置。 + preview.y = 124 + (preview.height - 180) / 2; + preview.getChildByName("arrow").y = -preview.height / 2 - 9; } private updateWeeklyPreview(data: any): void { @@ -548,7 +557,9 @@ export default class DailyQuests extends cc.Component { openReward(name, res, showWindow = true) { console.log("获取奖励名称", name); const data = Array.isArray(res) ? res : []; - cc.fx.GameTool.shushu_Track("daily_task", { id: name, status: "claim" }); + 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, diff --git a/tools/DAILY_WEEKLY_TASK_CONFIG.md b/tools/DAILY_WEEKLY_TASK_CONFIG.md new file mode 100644 index 00000000..e4a6bf0e --- /dev/null +++ b/tools/DAILY_WEEKLY_TASK_CONFIG.md @@ -0,0 +1,119 @@ +# 每日任务、周奖励与埋点配置 + +确认日期:2026-09-21。适用:MatchMaster / server 的 feat-dailyQuest 分支。 + +## 1. 生成及生效规则 + +- 每天生成5个任务,分别来自2、3、4、5、6星池;全部直接开放,共20星。 +- 每个任务完成后领取对应星数,以及一项已确定的奖励。 +- 奖励表中的“或”表示互斥随机选项,候选选项等概率;服务端在生成任务列表时选定,写入 `dailyTasks[].rewards`,不是点击领取时抽取。 +- 颜色任务也在生成时确定 `params.colorId`;颜色和奖励使用不同的散列输入,以用户、日期、任务ID生成伪随机结果。当天读取、重登、保存及领取不会重新抽取。 +- 日任务北京时间每天00:00刷新,周奖励北京时间周一00:00刷新;每个星级尽量不重复前一天的任务。 +- 保留已有周期内的任务目标、奖励、进度和领取状态。新每日配置在下一次生成日任务时生效,新周奖励在下一次生成周奖励时生效,避免更新配置后改掉玩家已经看到的奖励。 +- 数据结构版本保持 `version: 2`。新增可选字段 `trackingId`;未刷新到新配置的历史任务继续使用原业务ID上报。 +- 新池移除登录、无道具通关、使用指定道具任务。局内使用任意道具的任务沿用第8关起进入候选池的限制,免费道具与库存道具都计数。 +- 好友、群聊不严格区分,统一使用 `share` 指标和“分享1次游戏”文案。 +- 弹窗打开时跨天仍保留旧界面;关闭后重新打开再展示新周期。 + +## 2. 埋点编号及目标 + +表中为“埋点编号:目标数”。“—”表示不存在该任务。在线目标单位为分钟,接口中为秒。共57个候选任务。 + +|任务|2星|3星|4星|5星|6星|奖励组| +|---|---|---|---|---|---|---| +|清除方块|2-1:10|3-1:25|4-1:50|5-1:75|6-1:100|A| +|清除指定颜色方块|2-2:1|3-2:3|4-2:6|5-2:10|6-2:15|A| +|清除占1格方块|2-3:3|3-3:5|4-3:8|5-3:10|6-3:15|A| +|清除占2格方块|2-4:5|3-4:10|4-4:15|5-4:20|6-4:30|A| +|清除占3格方块|2-5:2|3-5:3|4-5:4|5-5:6|6-5:9|A| +|通过关卡|2-6:1|3-6:3|4-6:4|5-6:6|6-6:8|A| +|收集金币|2-7:40|3-7:120|4-7:160|5-7:240|6-7:320|A| +|首次尝试即通关|—|3-11:2|4-11:3|5-11:4|6-11:6|B| +|连续通关|—|—|4-12:2|5-12:3|6-12:5|B| +|使用金币|—|—|4-13:500|5-13:600|6-13:1200|B| +|局内使用道具|2-16:1|3-16:2|4-16:3|5-16:4|6-16:5|C| +|累计在线(分钟)|2-29:5|3-29:10|4-29:20|5-29:30|6-29:45|D| +|分享游戏|2-22:1|3-22:1|—|—|—|E| + +业务ID采用 `daily_星级_编号`,例如 `daily_2_2`;埋点字段 `trackingId` 为 `2-2`。颜色变化不增加埋点编号。 + +## 3. 每日奖励 + +|奖励组|2星|3星|4星|5星|6星| +|---|---|---|---|---|---| +|A|100金币或沙漏×1|150金币或沙漏×2|无限体力15分钟|200金币或冻结×1或火箭×1|300金币或魔法棒×1| +|B|—|150金币或沙漏×2|冻结×1或火箭×1|魔法棒×1|锤子×1| +|C|150金币或沙漏×2|无限体力15分钟|300金币或火箭×1|无限体力30分钟|500金币或无限火箭30分钟| +|D|无限体力15分钟|100金币|无限体力15分钟|200金币|300金币| +|E|无限体力15分钟|无限体力15分钟|—|—|—| + +时长奖励 `count` 单位为秒:15分钟=900,30分钟=1800,45分钟=2700,60分钟=3600。领取时以服务端保存的奖励为准,客户端提交的奖励、颜色、编号不覆盖服务端配置。 + +## 4. 周奖励 + +|业务ID|累计星数|奖励(全部发放)| +|---|---:|---| +|weekly_35|35|金币×300+无限体力15分钟+冻结×1| +|weekly_70|70|金币×600+无限体力30分钟+魔法棒×1| +|weekly_105|105|金币×1000+无限体力45分钟+锤子×1| +|weekly_140|140|金币×2000+无限体力60分钟+冻结×1+魔法棒×1+锤子×1| + +前三档各3种奖励,140星档5种奖励。周奖励预览使用预制体中5个显式奖励节点;超过3种时按上排3个、下排居中排列,九宫格背景向上扩展,箭头仍指向宝箱。 + +## 5. 颜色块对应规则 + +核对依据:当前1940份 `assets/custom/Json/level*.json`、实际 `Block.color` 和 `assets/Block/{colorId}color0.png` 贴图。普通颜色池为1~10,共10种。下面的“名称”是任务列表文案,“图案”帮助区分实际贴图。 + +|colorId|任务文案颜色|贴图图案|图片文件| +|---:|---|---|---| +|1|蓝色|月牙|1color0.png| +|2|紫色|五角星|2color0.png| +|3|浅绿色|圆形|3color0.png| +|4|米黄色|圆角方形|4color0.png| +|5|青色|云朵|5color0.png| +|6|橙色|三角形|6color0.png| +|7|深绿色|菱形|7color0.png| +|8|黄色|六边形|8color0.png| +|9|粉色|爱心|9color0.png| +|10|红色|皇冠|10color0.png| + +特殊暗灰色 `colorId=11` 使用 `0color0.png`(叉号)显示,不进入随机池。0不是本次统计中关卡使用的普通颜色ID,12等特殊状态也不进入随机池。 + +`BlockColor` 历史枚举的中文名称与实际贴图存在偏差,任务按以上真实ID映射显示;不要按枚举名称重新编号。变色块按消除事件触发时的 `Block.color` 统计。 + +## 6. 方块大小统计 + +`params.cellCount` 表示实际占用格数。客户端使用 `Block.allBlocks.length`,不使用宽×高外接矩形面积,也不区分横竖方向、直条或L形。 + +|占用格数|当前对应形状编号 `block`| +|---:|---| +|1|0| +|2|1、2| +|3|3、4、19、20、21、22| + +例如3格L形虽然外接矩形为2×2,仍计入占3格任务。每个被消除方块计1次;沿用通关时提交消除进度、失败不累计该关消除进度的规则。历史 `width/height` 任务继续按旧规则处理,直到自然刷新。 + +## 7. 数数埋点字段 + +事件名保留 `daily_task`。 + +|字段|内容| +|---|---| +|task_id|`trackingId`,例如 `2-2`、`6-29`| +|task_status|完成为 `complete`;成功领取为 `claim`| +|color_id|仅颜色任务附加,数值1~10| + +例:清除1个红色方块完成时上报: + +```json +{"event":"daily_task","task_id":"2-2","task_status":"complete","color_id":10} +``` + +成功领取同一任务时 `task_status` 为 `claim`,编号、颜色保持一致。领取失败不上报领取成功。历史任务缺少 `trackingId` 时回退原业务ID;周奖励领取继续沿用 `weekly_35` 等原有ID。 + +## 8. 代码位置与验证 + +- 服务端:`laf-cloud/functions/dailyTaskConfig.ts`(57项任务、奖励候选、颜色池、周奖励);`dailyTaskService.ts`(选定并保存奖励/颜色)。 +- 客户端:`Block.ts`(占格数)、`DailyTaskProgressTracker.ts`(进度与埋点)、`GameTool.ts`(SDK字段)、`DailyQuests.ts`(颜色文案、领取埋点及奖励布局)、`dailyQuests.prefab`(5个奖励节点)。 +- 自动化验证包含:57项任务编号和目标、候选奖励和颜色覆盖、重复读取保持不变、服务端忽略客户端伪造奖励、重复领奖、L形占格数、埋点到SDK的完整链路、5种奖励节点和布局。 +- 本次没有推送或部署。前后端修改需配套发布;保留周期内任务的策略见第1节。 diff --git a/tools/test-daily-reward-animation.cjs b/tools/test-daily-reward-animation.cjs index 980c3232..6eb3b19d 100644 --- a/tools/test-daily-reward-animation.cjs +++ b/tools/test-daily-reward-animation.cjs @@ -212,6 +212,17 @@ test('expired displayed tasks reject daily and weekly actions even when the cach assert.equal(h.daily.dayEndsAt, updated.dayEndsAt); }); +test('daily claim analytics use tracking ID and selected color instead of the business ID', () => { + const h=setup(), events=[]; + Object.assign(h.task,{trackingId:'2-2',metric:'clear_color',params:{colorId:10}}); + Object.assign(h.result.dailyTasks[0],{trackingId:'2-2',metric:'clear_color',params:{colorId:10}}); + h.cc.fx.GameTool.shushu_Track=(name,data)=>events.push({name,...data}); + h.daily.getReward(null,h.task.taskId);h.requests[0][1](h.success);h.flushTweens(); + assert.equal(events[0].name,'daily_task');assert.equal(events[0].id,'2-2');assert.equal(events[0].colorId,10);assert.equal(events[0].status,'claim'); + assert.equal(h.daily.getTaskTitle(h.task),'清除1个红色方块'); + assert.equal(h.daily.getTaskTitle({metric:'clear_size',target:2,params:{cellCount:3}}),'清除2个占3格的方块'); +}); + test('daily claim failure grants nothing, starts no animation, and permits retry', () => { const h = setup(); let animations = 0; h.daily.playDailyRewardAnimation = () => { animations++; }; diff --git a/tools/test-free-prop-task-progress.cjs b/tools/test-free-prop-task-progress.cjs index b5e55e8f..b27edaab 100644 --- a/tools/test-free-prop-task-progress.cjs +++ b/tools/test-free-prop-task-progress.cjs @@ -26,7 +26,7 @@ const methods = ['useHammerSpecial', 'useHammer', 'cancleHammer', 'costHammer', const code = stripTypeScriptTypes([ trackerSource.replace(/^export type /gm, 'type ').replace('export default class', 'class'), `class MapConroler { ${methods.map(name => method(mapSource, name)).join('\n')} }`, - `class Block { ${method(blockSource, 'touchStart')} }`, + `class Block { ${['touchStart','recordDailyTaskClear','initBlocks'].map(name=>method(blockSource,name)).join('\n')} }`, 'globalThis.classes = { MapConroler, Block, DailyTaskProgressTracker };', ].join('\n')); const enumSource = blockSource.match(/export enum BlockType \{([\s\S]*?)^}/m)[1]; @@ -59,7 +59,7 @@ function setup({ otherLevel = 0, winOnEliminate = false } = {}) { ['no_prop', 'no_prop_pass'], ['pass', 'pass_levels'], ].map(([taskId, metric, propType]) => ({ taskId, metric, params: { propType }, value: 0, target: 9, state: 0 })) } }; const storage = { hammerAmount: 3, freezeAmount: 3, magicAmount: 3 }; - const cc = { isValid: Boolean, v3: vector, ParticleSystem: 'ParticleSystem', + const cc = { isValid: Boolean, v2: vector, v3: vector, ParticleSystem: 'ParticleSystem', Intersection: { pointInPolygon: point => point.inside !== false }, fx: { GameConfig: { GM_INFO: info }, AudioManager: { _instance: { playEffect() {} } }, StorageMessage: { getStorage: () => storage, setStorage() {} }, @@ -111,6 +111,26 @@ function setup({ otherLevel = 0, winOnEliminate = false } = {}) { return { map, block, secondBlock, tracker, info, value, touch, eliminations, propWrites, toasts, runShortTimers }; } +test('size tasks count occupied cells for straight and L shapes, independent of bounding boxes', () => { + for (const [shape,cells] of [[0,1],[1,2],[2,2],[3,3],[4,3],[19,3],[20,3],[21,3],[22,3],[5,4]]) { + const h=setup(); + h.info.tasks.dailyTasks=[1,2,3].map(cellCount=>({taskId:'size'+cellCount,metric:'clear_size',params:{cellCount},target:20,value:0,state:0})); + Object.assign(h.block,{block_Info:{block:shape},heng:2,shu:2,color:10}); + h.block.initBlocks();h.block.recordDailyTaskClear();h.block.recordDailyTaskClear(); + h.tracker.recordLevelWon(); + for(const task of h.info.tasks.dailyTasks)assert.equal(task.value,task.params.cellCount===cells?1:0,'shape '+shape); + } +}); + +test('same bounding boxes with different occupied cell counts never merge progress', () => { + const h=setup(); + h.info.tasks.dailyTasks=[3,4].map(cellCount=>({taskId:'size'+cellCount,metric:'clear_size',params:{cellCount},target:20,value:0,state:0})); + h.tracker.record({metric:'clear_size',width:2,height:2,cellCount:3}); + h.tracker.record({metric:'clear_size',width:2,height:2,cellCount:4}); + h.tracker.recordLevelWon(); + assert.deepEqual(h.info.tasks.dailyTasks.map(t=>t.value),[1,1]); +}); + for (const free of [true, false]) { test(`${free ? 'free streak' : 'inventory'} hammer counts once after a valid hit and survives level completion`, () => { const s = setup(); diff --git a/tools/test-task-tracking.cjs b/tools/test-task-tracking.cjs new file mode 100644 index 00000000..2f932ea0 --- /dev/null +++ b/tools/test-task-tracking.cjs @@ -0,0 +1,25 @@ +const test=require('node:test'); +const assert=require('node:assert/strict'); +const fs=require('node:fs'); +const path=require('node:path'); +const vm=require('node:vm'); +const {stripTypeScriptTypes}=require('node:module'); +const read=p=>fs.readFileSync(path.join(__dirname,'..',p),'utf8'); +const tracker=read('assets/Script/module/Task/DailyTaskProgressTracker.ts').replace('export type','type').replace('export default class','class'); +const tool=read('assets/Script/module/Tool/GameTool.ts'); +const start=tool.indexOf(' shushu_Track: function ('),end=tool.indexOf('\n },',start)+7; +const code=stripTypeScriptTypes(tracker+'\nconst GameTool={'+tool.slice(start,end)+'};globalThis.Tracker=DailyTaskProgressTracker;globalThis.GameTool=GameTool;'); + +test('complete and claim events reach the SDK with the configured task ID and color',()=>{ + const events=[],task={taskId:'daily_2_2',trackingId:'2-2',metric:'clear_color',params:{colorId:10},target:1,value:0,state:0}; + const context={console:{log(){}},setTimeout:()=>1,clearTimeout(){},cc:{fx:{GameConfig:{GM_INFO:{otherLevel:0,tasks:{version:2,dailyTasks:[task]}}}}}, + MiniGameSdk:{API:{shushu_Track:(name,data)=>events.push({name,...data})}}}; + vm.runInNewContext(code,context);context.cc.fx.GameTool=context.GameTool; + context.Tracker.record({metric:'clear_color',colorId:9});context.Tracker.recordLevelWon();assert.equal(events.length,0); + context.Tracker.record({metric:'clear_color',colorId:10});context.Tracker.recordLevelWon(); + context.Tracker.trackTask(task,'claim'); + assert.deepEqual(events,[{name:'daily_task',task_id:'2-2',task_status:'complete',color_id:10},{name:'daily_task',task_id:'2-2',task_status:'claim',color_id:10}]); + context.Tracker.record({metric:'clear_color',colorId:10});context.Tracker.recordLevelWon();assert.equal(events.length,2); + context.Tracker.trackTask({taskId:'daily_6_29',trackingId:'6-29'},'complete'); + assert.deepEqual(events[2],{name:'daily_task',task_id:'6-29',task_status:'complete'}); +}); diff --git a/tools/test-weekly-quests.cjs b/tools/test-weekly-quests.cjs index ce7cb3a0..ff8fa1ce 100644 --- a/tools/test-weekly-quests.cjs +++ b/tools/test-weekly-quests.cjs @@ -317,6 +317,21 @@ test('progress remains bounded and empty weekly data hides the section',()=>{ data.weeklyRewards=[];daily.renderWeeklyRewards(data);assert.equal(weekly.active,false); }); +test('five weekly rewards reuse editor slots in centered rows with a fixed arrow tip',()=>{ + const {daily,data,preview,cc}=setup(0); + const rewards=[{type:'coin',count:2000},{type:'infinite_health',count:3600},{type:'freeze',count:1},{type:'magic_wand',count:1},{type:'hammer',count:1}]; + data.weeklyRewards[3].rewards=rewards; + cc.instantiate=()=>{throw Error('preview must not create reward nodes');}; + daily.selectWeeklyReward(null,'weekly_140'); + const list=preview.getChildByName('rewards'),slots=list.children.filter(n=>n.active); + assert.equal(slots.length,5);assert.equal(preview.width,508);assert.equal(preview.height,360); + assert.deepEqual(slots.map(n=>[n.x,n.y]),[[-164,90],[0,90],[164,90],[-82,-90],[82,-90]]); + assert.equal(preview.y+preview.getChildByName('arrow').y,25); + assert.deepEqual(slots.map(n=>n.getChildByName('count').getComponent('cc.Label').string),['2000','60分钟','×1','×1','×1']); + daily.selectWeeklyReward(null,'weekly_35'); + assert.equal(preview.height,180);assert.equal(preview.y,124);assert.equal(list.children.filter(n=>n.active).length,2); +}); + test('preview uses full-scale large icons and count labels without action buttons',()=>{ const {daily,preview}=setup(0);daily.selectWeeklyReward(null,'weekly_35'); const rewards=preview.getChildByName('rewards');assert.equal(rewards.scaleX,1);