feat: 每日任务更新任务配置
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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":
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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节。
|
||||
@@ -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++; };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user