diff --git a/docs/api.md b/docs/api.md index 29ff8ac..6da0f55 100644 --- a/docs/api.md +++ b/docs/api.md @@ -105,6 +105,7 @@ SiweMessage的nonce说明(具体参考例子): "title": "任务名", "desc": "任务描述", "type": 1, //任务类型, 1: 一次性任务, 2: 日常任务 + "repeat": 1, // 任务可重复次数 "pretasks": ["task id 1"], //前置任务 "score": 0, // 完成任务可获得的积分 "category": "", // 任务分类 diff --git a/initdatas/activity_info.json b/initdatas/activity_info.json index 6bb2046..5dddd21 100644 --- a/initdatas/activity_info.json +++ b/initdatas/activity_info.json @@ -199,6 +199,7 @@ "task": "BurnNft", "type": 1, "show": true, + "repeat": 2, "title": "Hero NFT", "desc": "Click to burn and redeem Flame", "category": "CF Pal", @@ -212,6 +213,7 @@ "task": "BurnNft", "type": 1, "show": true, + "repeat": 2, "title": "Candy Badge", "desc": "Click to burn and redeem Flame", "category": "CF Pal", @@ -224,6 +226,7 @@ "task": "BurnNft", "type": 1, "show": true, + "repeat": 2, "title": "Explorer Badge", "desc": "Click to burn and redeem Flame", "category": "CF Pal", @@ -237,6 +240,7 @@ "task": "BurnNft", "type": 1, "show": true, + "repeat": 2, "title": "Gacha Badge", "desc": "Click to burn and redeem Flame", "category": "CF Pal", diff --git a/src/controllers/tasks.controller.ts b/src/controllers/tasks.controller.ts index c3a3a03..a411b07 100644 --- a/src/controllers/tasks.controller.ts +++ b/src/controllers/tasks.controller.ts @@ -96,12 +96,12 @@ export default class TasksController extends BaseController { return t.id === preTask }); if (!preTaskData || (preTaskData.status === TaskStatusEnum.NOT_START || preTaskData.status === TaskStatusEnum.RUNNING)) { - throw new ZError(14, 'task previous not end') + throw new ZError(14, 'task previous not finish') } } } let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task); - if (currentTask.status === TaskStatusEnum.SUCCESS || currentTask.status === TaskStatusEnum.CLAIMED) { + if (currentTask.status === TaskStatusEnum.PART_SUCCESS || currentTask.status === TaskStatusEnum.SUCCESS || currentTask.status === TaskStatusEnum.CLAIMED) { throw new ZError(15, 'task already end') } if (currentTask.status === TaskStatusEnum.NOT_START) { @@ -126,13 +126,13 @@ export default class TasksController extends BaseController { if (!currentTask) { throw new ZError(11, 'task not found') } - if (currentTask.status === TaskStatusEnum.SUCCESS || currentTask.status === TaskStatusEnum.CLAIMED) { + if (currentTask.status === TaskStatusEnum.CLAIMED) { return currentTask; } if (currentTask.status === TaskStatusEnum.NOT_START) { throw new ZError(11, 'task not begin'); } - if (currentTask.status === TaskStatusEnum.RUNNING) { + if (currentTask.status === TaskStatusEnum.RUNNING || currentTask.status === TaskStatusEnum.PART_SUCCESS) { let Task = require('../tasks/' + currentTask.task); let taskInstance = new Task.default({user, activity}); await taskInstance.execute({task: currentTask}); @@ -152,7 +152,7 @@ export default class TasksController extends BaseController { if (currentTask.status === TaskStatusEnum.CLAIMED) { throw new ZError(12, 'task already claimed') } - if (currentTask.status !== TaskStatusEnum.SUCCESS) { + if (currentTask.status !== TaskStatusEnum.SUCCESS && currentTask.status !== TaskStatusEnum.PART_SUCCESS) { throw new ZError(13, 'task not end') } diff --git a/src/models/ActivityInfo.ts b/src/models/ActivityInfo.ts index 413f907..0f68b76 100644 --- a/src/models/ActivityInfo.ts +++ b/src/models/ActivityInfo.ts @@ -4,7 +4,7 @@ import { dbconn } from 'decorators/dbconn' import findOrCreate from 'mongoose-findorcreate' import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses' import { BaseModule } from './Base' -import exp from 'constants' + export enum TaskTypeEnum { ONCE = 1, @@ -20,6 +20,8 @@ export class TaskCfg { title: string @prop({ enum: TaskTypeEnum, default: TaskTypeEnum.ONCE }) type: TaskTypeEnum + @prop({default: 1}) + repeat: number @prop() desc: string @prop({ type: mongoose.Schema.Types.Mixed }) @@ -76,6 +78,7 @@ class ActivityInfoClass extends BaseModule { title: task.title, desc: task.desc, type: task.type, + repeat: task.repeat, pretasks: task.pretasks, score: task.score, category: task.category, diff --git a/src/models/ActivityUser.ts b/src/models/ActivityUser.ts index 94019e6..07cb5b6 100644 --- a/src/models/ActivityUser.ts +++ b/src/models/ActivityUser.ts @@ -13,6 +13,7 @@ export enum TaskStatusEnum { RUNNING = 1, SUCCESS = 2, CLAIMED = 3, + PART_SUCCESS = 4, } @modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }}) diff --git a/src/models/NFTBrunRecord.ts b/src/models/NFTBrunRecord.ts index 06026fd..c86b5ee 100644 --- a/src/models/NFTBrunRecord.ts +++ b/src/models/NFTBrunRecord.ts @@ -10,16 +10,12 @@ import { BaseModule } from './Base' class NftBurnRecordClass extends BaseModule { @prop({ required: true}) public user: string - @prop() public chain: number - @prop({ required: true}) public address: string - @prop() - public tokenId: number - + public tokenId: string @prop() public activity: string @prop() diff --git a/src/tasks/BurnNft.ts b/src/tasks/BurnNft.ts index 56047ef..3cf10e8 100644 --- a/src/tasks/BurnNft.ts +++ b/src/tasks/BurnNft.ts @@ -21,31 +21,50 @@ export default class BurnNft extends ITask { const localNft = await NftBurnRecord.find({ user: this.params.user.id, chain, - address + address, }) const localNftSet = new Set(); + let finishAmount = 0; + let tmpNftSet = new Set(); for (let nft of localNft) { localNftSet.add(nft.tokenId) - } - let finishNft - for (let nft of nftList) { - if (!localNftSet.has(nft.tokenId)) { - finishNft = nft.tokenId - break; + if (nft.activity === this.params.activity.id && nft.task === task.id) { + finishAmount += 1 + tmpNftSet.add(nft.tokenId) } } - const record = new NftBurnRecord({ - user: this.params.user.id, - chain, - address, - tokenId: finishNft, - activity: this.params.activity.id, - task: task.id - }) - await record.save() - task.status = TaskStatusEnum.SUCCESS - task.timeFinish = Date.now() - task.data = {tokenId: finishNft} + let finishNfts = new Set(); + for (let nft of nftList) { + if (!localNftSet.has(nft.tokenId)) { + finishNfts.add(nft.tokenId) + finishAmount += 1 + if (finishAmount >= cfg.repeat) { + break; + } + } + } + for (let finishNft of finishNfts) { + const record = new NftBurnRecord({ + user: this.params.user.id, + chain, + address, + tokenId: finishNft, + activity: this.params.activity.id, + task: task.id + }) + await record.save() + } + let dataToSave = task.data || {} + dataToSave.tokenId = [...tmpNftSet, ...finishNfts] + dataToSave.finishAmount = finishAmount + task.data = dataToSave + task.markModified('data') + if (finishAmount >= cfg.repeat) { + task.status = TaskStatusEnum.SUCCESS + task.timeFinish = Date.now() + } else if (finishAmount > 0) { + task.status = TaskStatusEnum.PART_SUCCESS + } try { await this.params.user.save() } catch(err) { @@ -60,5 +79,4 @@ export default class BurnNft extends ITask { } return true } - } \ No newline at end of file diff --git a/src/tasks/base/ITask.ts b/src/tasks/base/ITask.ts index c657556..1a9d9f9 100644 --- a/src/tasks/base/ITask.ts +++ b/src/tasks/base/ITask.ts @@ -14,30 +14,40 @@ export abstract class ITask { } abstract execute(data: any): Promise - public async claimReward(task: TaskStatus) { + public async claimReward(task: any) { const user = this.params.user const [taskId, dateTag] = task.id.split(':'); const cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === taskId) if (!cfg.score) { return; } + let claimAmount = task.data.claimAmount || 0 let score = cfg.score if (user.boost > 1 && Date.now() < user.boostExpire) { score = Math.floor(score * user.boost) } - await updateRankScore({ - user: user.id, - score: score, - activity: user.activity, - scoreType: cfg.task, - scoreParams: { - date: dateTag, - taskId: task.id, - boost: user.boost - } - }) - task.status = TaskStatusEnum.CLAIMED - task.timeClaim = Date.now() + let finishAmount = cfg.repeat > 1 ? task.data.finishAmount || 0 : 1 + for (let i = claimAmount; i < Math.min(cfg.repeat, finishAmount); i++) { + await updateRankScore({ + user: user.id, + score: score, + activity: user.activity, + scoreType: cfg.task, + scoreParams: { + date: dateTag, + taskId: task.id, + boost: user.boost + } + }) + claimAmount += 1 + } + task.data.claimAmount = claimAmount + task.markModified('data') + if ((cfg.repeat > 1 && claimAmount >= cfg.repeat) + || (cfg.repeat === 1 && claimAmount >= 1)) { + task.status = TaskStatusEnum.CLAIMED + task.timeClaim = Date.now() + } await user.save() } } \ No newline at end of file