diff --git a/configs/chest_bonus_item.json b/configs/chest_bonus_item.json index e4af209..8a78547 100644 --- a/configs/chest_bonus_item.json +++ b/configs/chest_bonus_item.json @@ -2,11 +2,11 @@ { "type": 1, "name": "White List", - "probability": 100 + "probability": 2 }, { "type": 2, "name": "NFT", - "probability": 100 + "probability": 1 } ] \ No newline at end of file diff --git a/configs/draw_cfg.json b/configs/draw_cfg.json new file mode 100644 index 0000000..012e4b3 --- /dev/null +++ b/configs/draw_cfg.json @@ -0,0 +1,65 @@ +[ + { + "position": 1, + "type": 1, + "name": "WhiteList", + "amount": 1, + "probability": 100 + }, + { + "position": 2, + "type": 0, + "amount": 60, + "probability": 1482 + }, + { + "position": 3, + "type": 0, + "amount": 30, + "probability": 2004 + }, + { + "position": 4, + "type": 0, + "amount": 240, + "probability": 157 + }, + { + "position": 5, + "type": 0, + "amount": 30, + "probability": 2004 + }, + { + "position": 6, + "type": 2, + "name": "NFT", + "amount": 1, + "probability": 100 + }, + { + "position": 7, + "type": 0, + "amount": 120, + "probability": 434 + }, + { + "position": 8, + "type": 0, + "amount": 30, + "probability": 2004 + }, + { + "position": 9, + "type": 0, + "amount": 120, + "probability": 434 + }, + + { + "position": 10, + "type": 0, + "amount": 50, + "probability": 1482 + } +] \ No newline at end of file diff --git a/configs/ingame_tasks.json b/configs/ingame_tasks.json new file mode 100644 index 0000000..cb21ac4 --- /dev/null +++ b/configs/ingame_tasks.json @@ -0,0 +1,111 @@ +[ + { + "id": "g3885fwjnddej6jmcha", + "title": "Bind Game Account", + "task": "GoogleConnect", + "show": false, + "type": 1, + "category": 1, + "desc": "", + "ticket": 0, + "score": 0, + "checkChain": false, + "autoclaim": true + }, + { + "id": "g3zkyh1rk6qs5exdefe", + "title": "登录游戏", + "desc": "", + "task": "GameLogin", + "show": true, + "type": 2, + "category": 1, + "pretasks": ["g3885fwjnddej6jmcha"], + "ticket": 0, + "score": 10, + "checkChain": false, + "cfg": { "key": "loginVal", "amount": 1 }, + "autoclaim": true + }, + { + "id": "g3amegdkchm4pdjtjeb", + "title": "参与3场任意模式的比赛", + "desc": "", + "task": "GameBattle", + "show": true, + "type": 2, + "category": 1, + "pretasks": ["g3885fwjnddej6jmcha"], + "ticket": 0, + "tickettasks": ["g31x9wzja7eg18t3vtm"], + "score": 20, + "checkChain": false, + "cfg": { "key": "battleTimes", "amount": 3 }, + "autoclaim": true + }, + { + "id": "g31x9wzja7eg18t3vtm", + "title": "累计完成10次击杀", + "desc": "", + "task": "GameKill", + "show": true, + "type": 2, + "category": 1, + "pretasks": ["g3885fwjnddej6jmcha"], + "ticket": 1, + "tickettasks": ["g3amegdkchm4pdjtjeb"], + "score": 20, + "checkChain": false, + "cfg": { "key": "kills", "amount": 10 }, + "autoclaim": true + }, + { + "id": "g38gejrp3twh1gvtr2e", + "title": "在Bounty或Rank模式下获得3次胜利", + "desc": "", + "task": "GameWin", + "show": true, + "type": 2, + "category": 2, + "pretasks": ["g3885fwjnddej6jmcha"], + "ticket": 0, + "score": 25, + "checkChain": false, + "checkTime": true, + "cfg": { "key": "winTimes", "amount": 3 }, + "autoclaim": true + }, + { + "id": "g33xf6688x67a2mdrrk", + "title": "在Gold模式下获取500金币", + "desc": "", + "task": "GameCoin", + "show": true, + "category": 2, + "pretasks": ["g3885fwjnddej6jmcha"], + "type": 2, + "ticket": 0, + "score": 25, + "checkTime": true, + "checkChain": false, + "cfg": { "key": "getGoldVal","amount": 500 }, + "autoclaim": true + }, + + { + "id": "g3s6esb83da9wy9p25a", + "title": "Own CounterFile|Founder's Tag", + "desc": "", + "task": "NftHolderCheck", + "show": true, + "pretasks": ["g3885fwjnddej6jmcha"], + "type": 2, + "category": 3, + "ticket": 0, + "score": 100, + "checkChain": false, + "checkTime": true, + "cfg": { "address": "0xec23679653337d4c6390d0eeba682246a6067777", "chain": "1" }, + "autoclaim": true + } +] \ No newline at end of file diff --git a/configs/nft_202403.json b/configs/nft_202403.json new file mode 100644 index 0000000..c49f52b --- /dev/null +++ b/configs/nft_202403.json @@ -0,0 +1,503 @@ +[ + {"rarity":"Common","id":1}, + {"rarity":"Rare","id":2}, + {"rarity":"Common","id":3}, + {"rarity":"Common","id":4}, + {"rarity":"Common","id":5}, + {"rarity":"Common","id":6}, + {"rarity":"Common","id":7}, + {"rarity":"Common","id":8}, + {"rarity":"Common","id":9}, + {"rarity":"Common","id":10}, + {"rarity":"Common","id":11}, + {"rarity":"Common","id":12}, + {"rarity":"Common","id":13}, + {"rarity":"Common","id":14}, + {"rarity":"Common","id":15}, + {"rarity":"Common","id":16}, + {"rarity":"Common","id":17}, + {"rarity":"Common","id":18}, + {"rarity":"Common","id":19}, + {"rarity":"Common","id":20}, + {"rarity":"Common","id":21}, + {"rarity":"Common","id":22}, + {"rarity":"Common","id":23}, + {"rarity":"Common","id":24}, + {"rarity":"Common","id":25}, + {"rarity":"Common","id":26}, + {"rarity":"Common","id":27}, + {"rarity":"Common","id":28}, + {"rarity":"Common","id":29}, + {"rarity":"Legendary","id":30}, + {"rarity":"Common","id":31}, + {"rarity":"Rare","id":32}, + {"rarity":"Rare","id":33}, + {"rarity":"Common","id":34}, + {"rarity":"Common","id":35}, + {"rarity":"Rare","id":36}, + {"rarity":"Rare","id":37}, + {"rarity":"Common","id":38}, + {"rarity":"Common","id":39}, + {"rarity":"Common","id":40}, + {"rarity":"Common","id":41}, + {"rarity":"Common","id":42}, + {"rarity":"Common","id":43}, + {"rarity":"Common","id":44}, + {"rarity":"Common","id":45}, + {"rarity":"Common","id":46}, + {"rarity":"Common","id":47}, + {"rarity":"Common","id":48}, + {"rarity":"Common","id":49}, + {"rarity":"Common","id":50}, + {"rarity":"Common","id":51}, + {"rarity":"Common","id":52}, + {"rarity":"Common","id":53}, + {"rarity":"Common","id":54}, + {"rarity":"Common","id":55}, + {"rarity":"Common","id":56}, + {"rarity":"Common","id":57}, + {"rarity":"Common","id":58}, + {"rarity":"Common","id":59}, + {"rarity":"Common","id":60}, + {"rarity":"Common","id":61}, + {"rarity":"Common","id":62}, + {"rarity":"Common","id":63}, + {"rarity":"Common","id":64}, + {"rarity":"Common","id":65}, + {"rarity":"Common","id":66}, + {"rarity":"Common","id":67}, + {"rarity":"Common","id":68}, + {"rarity":"Common","id":69}, + {"rarity":"Common","id":70}, + {"rarity":"Common","id":71}, + {"rarity":"Rare","id":72}, + {"rarity":"Common","id":73}, + {"rarity":"Common","id":74}, + {"rarity":"Common","id":75}, + {"rarity":"Common","id":76}, + {"rarity":"Common","id":77}, + {"rarity":"Common","id":78}, + {"rarity":"Rare","id":79}, + {"rarity":"Common","id":80}, + {"rarity":"Common","id":81}, + {"rarity":"Common","id":82}, + {"rarity":"Common","id":83}, + {"rarity":"Common","id":84}, + {"rarity":"Rare","id":85}, + {"rarity":"Common","id":86}, + {"rarity":"Common","id":87}, + {"rarity":"Rare","id":88}, + {"rarity":"Common","id":89}, + {"rarity":"Common","id":90}, + {"rarity":"Common","id":91}, + {"rarity":"Common","id":92}, + {"rarity":"Rare","id":93}, + {"rarity":"Common","id":94}, + {"rarity":"Rare","id":95}, + {"rarity":"Common","id":96}, + {"rarity":"Common","id":97}, + {"rarity":"Common","id":98}, + {"rarity":"Common","id":99}, + {"rarity":"Common","id":100}, + {"rarity":"Common","id":101}, + {"rarity":"Common","id":102}, + {"rarity":"Rare","id":103}, + {"rarity":"Common","id":104}, + {"rarity":"Legendary","id":105}, + {"rarity":"Common","id":106}, + {"rarity":"Common","id":107}, + {"rarity":"Common","id":108}, + {"rarity":"Common","id":109}, + {"rarity":"Common","id":110}, + {"rarity":"Common","id":111}, + {"rarity":"Common","id":112}, + {"rarity":"Common","id":113}, + {"rarity":"Common","id":114}, + {"rarity":"Common","id":115}, + {"rarity":"Rare","id":116}, + {"rarity":"Rare","id":117}, + {"rarity":"Common","id":118}, + {"rarity":"Common","id":119}, + {"rarity":"Common","id":120}, + {"rarity":"Common","id":121}, + {"rarity":"Rare","id":122}, + {"rarity":"Common","id":123}, + {"rarity":"Common","id":124}, + {"rarity":"Common","id":125}, + {"rarity":"Common","id":126}, + {"rarity":"Rare","id":127}, + {"rarity":"Common","id":128}, + {"rarity":"Rare","id":129}, + {"rarity":"Common","id":130}, + {"rarity":"Legendary","id":131}, + {"rarity":"Common","id":132}, + {"rarity":"Rare","id":133}, + {"rarity":"Common","id":134}, + {"rarity":"Common","id":135}, + {"rarity":"Common","id":136}, + {"rarity":"Common","id":137}, + {"rarity":"Common","id":138}, + {"rarity":"Common","id":139}, + {"rarity":"Rare","id":140}, + {"rarity":"Common","id":141}, + {"rarity":"Common","id":142}, + {"rarity":"Common","id":143}, + {"rarity":"Common","id":144}, + {"rarity":"Rare","id":145}, + {"rarity":"Common","id":146}, + {"rarity":"Common","id":147}, + {"rarity":"Common","id":148}, + {"rarity":"Common","id":149}, + {"rarity":"Common","id":150}, + {"rarity":"Common","id":151}, + {"rarity":"Common","id":152}, + {"rarity":"Common","id":153}, + {"rarity":"Legendary","id":154}, + {"rarity":"Common","id":155}, + {"rarity":"Common","id":156}, + {"rarity":"Common","id":157}, + {"rarity":"Common","id":158}, + {"rarity":"Common","id":159}, + {"rarity":"Rare","id":160}, + {"rarity":"Common","id":161}, + {"rarity":"Common","id":162}, + {"rarity":"Common","id":163}, + {"rarity":"Common","id":164}, + {"rarity":"Common","id":165}, + {"rarity":"Common","id":166}, + {"rarity":"Common","id":167}, + {"rarity":"Common","id":168}, + {"rarity":"Common","id":169}, + {"rarity":"Rare","id":170}, + {"rarity":"Common","id":171}, + {"rarity":"Common","id":172}, + {"rarity":"Common","id":173}, + {"rarity":"Common","id":174}, + {"rarity":"Rare","id":175}, + {"rarity":"Common","id":176}, + {"rarity":"Common","id":177}, + {"rarity":"Common","id":178}, + {"rarity":"Common","id":179}, + {"rarity":"Common","id":180}, + {"rarity":"Common","id":181}, + {"rarity":"Common","id":182}, + {"rarity":"Common","id":183}, + {"rarity":"Common","id":184}, + {"rarity":"Common","id":185}, + {"rarity":"Common","id":186}, + {"rarity":"Common","id":187}, + {"rarity":"Common","id":188}, + {"rarity":"Common","id":189}, + {"rarity":"Common","id":190}, + {"rarity":"Common","id":191}, + {"rarity":"Common","id":192}, + {"rarity":"Common","id":193}, + {"rarity":"Common","id":194}, + {"rarity":"Common","id":195}, + {"rarity":"Common","id":196}, + {"rarity":"Common","id":197}, + {"rarity":"Common","id":198}, + {"rarity":"Common","id":199}, + {"rarity":"Common","id":200}, + {"rarity":"Common","id":201}, + {"rarity":"Common","id":202}, + {"rarity":"Common","id":203}, + {"rarity":"Common","id":204}, + {"rarity":"Common","id":205}, + {"rarity":"Common","id":206}, + {"rarity":"Common","id":207}, + {"rarity":"Rare","id":208}, + {"rarity":"Common","id":209}, + {"rarity":"Common","id":210}, + {"rarity":"Common","id":211}, + {"rarity":"Common","id":212}, + {"rarity":"Common","id":213}, + {"rarity":"Common","id":214}, + {"rarity":"Common","id":215}, + {"rarity":"Common","id":216}, + {"rarity":"Common","id":217}, + {"rarity":"Common","id":218}, + {"rarity":"Common","id":219}, + {"rarity":"Common","id":220}, + {"rarity":"Common","id":221}, + {"rarity":"Common","id":222}, + {"rarity":"Common","id":223}, + {"rarity":"Common","id":224}, + {"rarity":"Common","id":225}, + {"rarity":"Common","id":226}, + {"rarity":"Common","id":227}, + {"rarity":"Common","id":228}, + {"rarity":"Common","id":229}, + {"rarity":"Common","id":230}, + {"rarity":"Common","id":231}, + {"rarity":"Rare","id":232}, + {"rarity":"Common","id":233}, + {"rarity":"Common","id":234}, + {"rarity":"Common","id":235}, + {"rarity":"Legendary","id":236}, + {"rarity":"Common","id":237}, + {"rarity":"Rare","id":238}, + {"rarity":"Common","id":239}, + {"rarity":"Common","id":240}, + {"rarity":"Common","id":241}, + {"rarity":"Common","id":242}, + {"rarity":"Common","id":243}, + {"rarity":"Common","id":244}, + {"rarity":"Common","id":245}, + {"rarity":"Common","id":246}, + {"rarity":"Common","id":247}, + {"rarity":"Common","id":248}, + {"rarity":"Common","id":249}, + {"rarity":"Common","id":250}, + {"rarity":"Common","id":251}, + {"rarity":"Rare","id":252}, + {"rarity":"Rare","id":253}, + {"rarity":"Common","id":254}, + {"rarity":"Common","id":255}, + {"rarity":"Common","id":256}, + {"rarity":"Common","id":257}, + {"rarity":"Rare","id":258}, + {"rarity":"Common","id":259}, + {"rarity":"Common","id":260}, + {"rarity":"Rare","id":261}, + {"rarity":"Common","id":262}, + {"rarity":"Rare","id":263}, + {"rarity":"Common","id":264}, + {"rarity":"Common","id":265}, + {"rarity":"Common","id":266}, + {"rarity":"Common","id":267}, + {"rarity":"Common","id":268}, + {"rarity":"Common","id":269}, + {"rarity":"Common","id":270}, + {"rarity":"Rare","id":271}, + {"rarity":"Common","id":272}, + {"rarity":"Common","id":273}, + {"rarity":"Common","id":274}, + {"rarity":"Common","id":275}, + {"rarity":"Common","id":276}, + {"rarity":"Common","id":277}, + {"rarity":"Common","id":278}, + {"rarity":"Common","id":279}, + {"rarity":"Common","id":280}, + {"rarity":"Common","id":281}, + {"rarity":"Common","id":282}, + {"rarity":"Common","id":283}, + {"rarity":"Common","id":284}, + {"rarity":"Common","id":285}, + {"rarity":"Common","id":286}, + {"rarity":"Common","id":287}, + {"rarity":"Common","id":288}, + {"rarity":"Common","id":289}, + {"rarity":"Common","id":290}, + {"rarity":"Common","id":291}, + {"rarity":"Common","id":292}, + {"rarity":"Common","id":293}, + {"rarity":"Common","id":294}, + {"rarity":"Common","id":295}, + {"rarity":"Common","id":296}, + {"rarity":"Common","id":297}, + {"rarity":"Common","id":298}, + {"rarity":"Common","id":299}, + {"rarity":"Common","id":300}, + {"rarity":"Common","id":301}, + {"rarity":"Common","id":302}, + {"rarity":"Common","id":303}, + {"rarity":"Rare","id":304}, + {"rarity":"Common","id":305}, + {"rarity":"Common","id":306}, + {"rarity":"Common","id":307}, + {"rarity":"Common","id":308}, + {"rarity":"Common","id":309}, + {"rarity":"Common","id":310}, + {"rarity":"Common","id":311}, + {"rarity":"Common","id":312}, + {"rarity":"Common","id":313}, + {"rarity":"Rare","id":314}, + {"rarity":"Common","id":315}, + {"rarity":"Common","id":316}, + {"rarity":"Common","id":317}, + {"rarity":"Common","id":318}, + {"rarity":"Common","id":319}, + {"rarity":"Common","id":320}, + {"rarity":"Common","id":321}, + {"rarity":"Common","id":322}, + {"rarity":"Rare","id":323}, + {"rarity":"Common","id":324}, + {"rarity":"Common","id":325}, + {"rarity":"Common","id":326}, + {"rarity":"Common","id":327}, + {"rarity":"Common","id":328}, + {"rarity":"Common","id":329}, + {"rarity":"Common","id":330}, + {"rarity":"Common","id":331}, + {"rarity":"Common","id":332}, + {"rarity":"Common","id":333}, + {"rarity":"Common","id":334}, + {"rarity":"Rare","id":335}, + {"rarity":"Common","id":336}, + {"rarity":"Common","id":337}, + {"rarity":"Common","id":338}, + {"rarity":"Common","id":339}, + {"rarity":"Common","id":340}, + {"rarity":"Common","id":341}, + {"rarity":"Common","id":342}, + {"rarity":"Common","id":343}, + {"rarity":"Common","id":344}, + {"rarity":"Common","id":345}, + {"rarity":"Common","id":346}, + {"rarity":"Common","id":347}, + {"rarity":"Common","id":348}, + {"rarity":"Common","id":349}, + {"rarity":"Common","id":350}, + {"rarity":"Common","id":351}, + {"rarity":"Common","id":352}, + {"rarity":"Common","id":353}, + {"rarity":"Common","id":354}, + {"rarity":"Common","id":355}, + {"rarity":"Common","id":356}, + {"rarity":"Common","id":357}, + {"rarity":"Rare","id":358}, + {"rarity":"Common","id":359}, + {"rarity":"Common","id":360}, + {"rarity":"Common","id":361}, + {"rarity":"Common","id":362}, + {"rarity":"Rare","id":363}, + {"rarity":"Common","id":364}, + {"rarity":"Common","id":365}, + {"rarity":"Common","id":366}, + {"rarity":"Common","id":367}, + {"rarity":"Common","id":368}, + {"rarity":"Rare","id":369}, + {"rarity":"Common","id":370}, + {"rarity":"Common","id":371}, + {"rarity":"Common","id":372}, + {"rarity":"Common","id":373}, + {"rarity":"Common","id":374}, + {"rarity":"Rare","id":375}, + {"rarity":"Common","id":376}, + {"rarity":"Common","id":377}, + {"rarity":"Common","id":378}, + {"rarity":"Common","id":379}, + {"rarity":"Common","id":380}, + {"rarity":"Common","id":381}, + {"rarity":"Common","id":382}, + {"rarity":"Common","id":383}, + {"rarity":"Common","id":384}, + {"rarity":"Common","id":385}, + {"rarity":"Common","id":386}, + {"rarity":"Common","id":387}, + {"rarity":"Common","id":388}, + {"rarity":"Common","id":389}, + {"rarity":"Common","id":390}, + {"rarity":"Common","id":391}, + {"rarity":"Common","id":392}, + {"rarity":"Common","id":393}, + {"rarity":"Rare","id":394}, + {"rarity":"Common","id":395}, + {"rarity":"Common","id":396}, + {"rarity":"Common","id":397}, + {"rarity":"Common","id":398}, + {"rarity":"Common","id":399}, + {"rarity":"Common","id":400}, + {"rarity":"Rare","id":401}, + {"rarity":"Common","id":402}, + {"rarity":"Common","id":403}, + {"rarity":"Common","id":404}, + {"rarity":"Rare","id":405}, + {"rarity":"Common","id":406}, + {"rarity":"Common","id":407}, + {"rarity":"Common","id":408}, + {"rarity":"Common","id":409}, + {"rarity":"Common","id":410}, + {"rarity":"Common","id":411}, + {"rarity":"Common","id":412}, + {"rarity":"Common","id":413}, + {"rarity":"Common","id":414}, + {"rarity":"Common","id":415}, + {"rarity":"Common","id":416}, + {"rarity":"Common","id":417}, + {"rarity":"Legendary","id":418}, + {"rarity":"Common","id":419}, + {"rarity":"Common","id":420}, + {"rarity":"Common","id":421}, + {"rarity":"Common","id":422}, + {"rarity":"Rare","id":423}, + {"rarity":"Common","id":424}, + {"rarity":"Rare","id":425}, + {"rarity":"Common","id":426}, + {"rarity":"Common","id":427}, + {"rarity":"Common","id":428}, + {"rarity":"Rare","id":429}, + {"rarity":"Common","id":430}, + {"rarity":"Rare","id":431}, + {"rarity":"Common","id":432}, + {"rarity":"Rare","id":433}, + {"rarity":"Rare","id":434}, + {"rarity":"Common","id":435}, + {"rarity":"Common","id":436}, + {"rarity":"Common","id":437}, + {"rarity":"Common","id":438}, + {"rarity":"Common","id":439}, + {"rarity":"Common","id":440}, + {"rarity":"Common","id":441}, + {"rarity":"Rare","id":442}, + {"rarity":"Common","id":443}, + {"rarity":"Common","id":444}, + {"rarity":"Common","id":445}, + {"rarity":"Common","id":446}, + {"rarity":"Common","id":447}, + {"rarity":"Common","id":448}, + {"rarity":"Common","id":449}, + {"rarity":"Common","id":450}, + {"rarity":"Common","id":451}, + {"rarity":"Common","id":452}, + {"rarity":"Common","id":453}, + {"rarity":"Common","id":454}, + {"rarity":"Common","id":455}, + {"rarity":"Common","id":456}, + {"rarity":"Common","id":457}, + {"rarity":"Common","id":458}, + {"rarity":"Common","id":459}, + {"rarity":"Common","id":460}, + {"rarity":"Common","id":461}, + {"rarity":"Common","id":462}, + {"rarity":"Common","id":463}, + {"rarity":"Common","id":464}, + {"rarity":"Common","id":465}, + {"rarity":"Common","id":466}, + {"rarity":"Common","id":467}, + {"rarity":"Common","id":468}, + {"rarity":"Common","id":469}, + {"rarity":"Common","id":470}, + {"rarity":"Common","id":471}, + {"rarity":"Common","id":472}, + {"rarity":"Common","id":473}, + {"rarity":"Common","id":474}, + {"rarity":"Common","id":475}, + {"rarity":"Common","id":476}, + {"rarity":"Common","id":477}, + {"rarity":"Common","id":478}, + {"rarity":"Common","id":479}, + {"rarity":"Common","id":480}, + {"rarity":"Common","id":481}, + {"rarity":"Common","id":482}, + {"rarity":"Common","id":483}, + {"rarity":"Common","id":484}, + {"rarity":"Common","id":485}, + {"rarity":"Common","id":486}, + {"rarity":"Common","id":487}, + {"rarity":"Common","id":488}, + {"rarity":"Common","id":489}, + {"rarity":"Common","id":490}, + {"rarity":"Common","id":491}, + {"rarity":"Legendary","id":492}, + {"rarity":"Common","id":493}, + {"rarity":"Common","id":494}, + {"rarity":"Legendary","id":495}, + {"rarity":"Legendary","id":496}, + {"rarity":"Common","id":497}, + {"rarity":"Common","id":498}, + {"rarity":"Common","id":499}, + {"rarity":"Legendary","id":500} + +] \ No newline at end of file diff --git a/configs/nft_rarity.json b/configs/nft_rarity.json new file mode 100644 index 0000000..fa33202 --- /dev/null +++ b/configs/nft_rarity.json @@ -0,0 +1,14 @@ +[ + { + "rarity": "Common", + "ticket": 1 + }, + { + "rarity": "Rare", + "ticket": 2 + }, + { + "rarity": "Legendary", + "ticket": 5 + } +] diff --git a/docs/uaw.md b/docs/uaw.md index 27f4899..48b55f2 100644 --- a/docs/uaw.md +++ b/docs/uaw.md @@ -64,6 +64,13 @@ 1. 增加验证google的access token(32) 2. 增加两种任务类型: GoogleConnect, GameAchievement +#### 20240508 +1. 用户状态接口(10) 增加字段, gameScore, gameTicket, googleId, rankGame +1. 增加接口: 游戏任务列表(33), 领取游戏任务奖励(34), 大转盘抽奖(35), 大转盘抽奖记录(36), 游戏积分详情列表(37), 游戏积分排行榜(38) +1. 社交任务活动信息(3), 增加字段drawTime, 表示转盘开始时间 +1. 增加接口: 转盘配置(39), 用户NFT列表(40) + + ### 1. 钱包预登录 #### Request @@ -164,6 +171,7 @@ SiweMessage的nonce说明(具体参考例子): "autoclaim": false // 任务完成后是否自动获取奖励 } ], + "drawTime": 1702628292366, // 抽奖活动开始时间 "startTime": 1702628292366, // 活动开始时间 "endTime": 1705220292366 // 活动结束时间 } @@ -186,7 +194,7 @@ SiweMessage的nonce说明(具体参考例子): ```js [ { - "status": 2, // 任务状态, 0: 未开始, 1: 进行中, 2: 成功, 9: 失败 + "status": 2, // 任务状态, 0: 未开始, 1: 进行中, 2: 成功, 3: 已领取, 9: 失败 "id": "TwitterConnect", // 任务id "timeStart": 1703150269527, // 任务开始时间 "data": { // 当前任务带的额外信息, 比如twitter的id和昵称等 @@ -357,10 +365,14 @@ body: "twitterName": "", "twitterAvatar": "", // twitter头像 "discordId": "", + "googleId": "", "discordName": "", "scoreToday": 100, // 今日获得积分 "scoreTotal": 200, // 总积分 + "gameScore": 100, // 游戏内积分 + "gameTicket": 1, // 游戏内可抽奖次数 "rankTotal": "-", + "rankGame": "", // 游戏内积分排行榜 "invite": "邀请人address", "inviteCount": 0, // 我邀请的用户总数 "inviteScore": 0, // 我邀请用户总数获得的分数 @@ -777,7 +789,7 @@ body: ``` -### 26.\ 宝箱助力状态查询 +### 26. 宝箱助力状态查询 #### Request @@ -991,4 +1003,209 @@ body: ```js { } +``` + +### 33. 游戏任务列表 + +#### Request + +- URL:`/api/ingame/tasks` +- 方法:`GET` + +#### Response + +```js + [ + { + "id": "任务id", + "task": "任务类型", + "title": "任务名", + "desc": "任务描述", + "show": 1, // 是否显示, 0: 不显示, 1: 显示 + "type": 1, //任务类型, 1: 一次性任务, 2: 日常任务 + "pretasks": ["task id 1"], //前置任务 + "score": 0, // 完成任务可获得的积分 + "ticket": 0, // 完成任务可获得抽奖次数 + "cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数 + "end": false, // 是否已经结束 + "status": 1,// 任务状态, -1:不可用 0: 未开始, 1: 进行中, 2: 成功, 3: 已领取, 9: 失败 + "autoclaim": false // 任务完成后是否自动获取奖励 + } + ] +``` + +### 34.\* 领取游戏任务奖励 + +#### Request + +- URL:`/api/ingame/claim` +- 方法:POST +- 头部: + - Authorization: Bearer JWT_token + + +body: + +```js +{ + "task": "g31x9wzja7eg18t3vtm" // 任务id +} + +``` + +#### Response + +```json +{ + "status": 1, // 任务状态, 0: 未开始, 1: 进行中, 2: 成功, 3: 已领取 9: 失败 + "score": 1, + "ticket": 3, // 获得的ticket, 可能没这个字段 + } +``` + +### 35.\* 大转盘抽奖 + +#### Request + +- URL:`/api/ingame/draw` +- 方法:POST +- 头部: + - Authorization: Bearer JWT_token + + +body: + +```js +{ + "step": 1 +} +``` + +#### Response + +```json +{ + score: 100, // 获得的积分 + items: [ + { + id: "001", // 物品id + type: 1, // 1白单, 2: nft + name: "", // 物品名 + desc: "", // 描述 + amount: 1 // 数量 + } + ] +} +``` + +### 36.\* 大转盘抽奖记录 + +#### Request + +- URL:`/api/ingame/draw_history` +- 方法:GET +- 头部: + - Authorization: Bearer JWT_token + + +#### Response + +```json +[{ + time: 12123, // 抽奖时间 + score: 100, // 获得的积分 + items: [ + { + id: "001", // 物品id + type: 1, // 1白单, 2: nft + name: "", // 物品名 + desc: "", // 描述 + amount: 1 // 数量 + } + ], + position: [1, 2] //每次转动的位置 +}] +``` + +### 37.\* 游戏积分详情列表 + +#### Request + +- URL:`/api/ingame/score_list` +- 方法:`GET` +- 头部: + - Authorization: Bearer JWT_token + +#### Response + +```js +[{ + score: 100, // 获得的积分 + type: '', // 获取原因 + time: 111 // 开启时间 +}] +``` + +### 38. 游戏 积分排行榜 + +#### Request + +- URL:`/api/ingame/leaderboard/:page` +- 方法:`GET` +- 参数: + - `page` (返回数据的分页序号, 0 开始) + +> 默认返回100条记录, 如果要返回不同数量, query param传 limit + +#### Response + +```json +[ + { + "rank": 1, // 排名, 从1开始 + "level": 1, // 段位 + "nickname": "昵称", + "score": 1 //获得的积分 + } +] +``` + +### 39. 转盘配置 + +#### Request + +- URL:`/api/ingame/draw_cfg` +- 方法:`GET` + + +#### Response + +```json +[ + { + "position": 1, // 位置, 从1开始 + "type": 0, // 类型, 0: 积分, 1: 白单, 2: nft + "amount": 1, // 数量 + "name": "", // 没有的话, 就是score + } +] +``` + +### 40. \* 用户NFT列表 + +#### Request + +- URL:`/api/ingame/nft_list` +- 方法:`GET` + + +#### Response + +```json +[ + { + "tokenId": "1", // tokenID + "rarity": "", // Common, Rare, Legendary + } +] ``` \ No newline at end of file diff --git a/initdatas/activity_info.json b/initdatas/activity_info.json index 722c459..f24b337 100644 --- a/initdatas/activity_info.json +++ b/initdatas/activity_info.json @@ -293,37 +293,71 @@ "params": { "time": 6, "failRate": 0 } }, { - "id": "e2yhq2lj30vwcpedv8p", - "task": "GoogleConnect", - "title": "Connect your game account", + "id": "e2djzxndhjyuc7xadqj", + "task": "TwitterRetweet", + "title": "RT - Canoe Gameplay", "type": 1, - "desc": "Please connect your game account", - "score": 100, + "desc": "Retweet specific tweets", "category": "Social Tasks", + "score": 50, "autoclaim": false, - "cfg": { "icon": "google" }, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": { "icon": "twitter", "content": "1788592368855478534"}, "start": "2024-01-01 00:00", "end": "2025-01-01 00:00", "checkChain": false, - "params": {} + "params": { "time": 6, "failRate": 0 } }, { - "id": "e2fclylj30vwcpe1szl", - "task": "GameAchievement", - "title": "Receive Game Achievement", + "id": "e2gefd7432pcxrrhjhz", + "task": "TwitterLike", + "title": "Like - Canoe Gameplay", "type": 1, - "desc": "Receive Game Achievement", + "desc": "Like specific tweets", "category": "Social Tasks", - "score": 100, + "score": 50, "autoclaim": false, - "pretasks": ["e2yhq2lj30vwcpedv8p"], - "cfg": { }, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": { "icon": "twitter", "content": "1788592368855478534" }, + "start": "2024-01-01 00:00", + "end": "2025-01-01 00:00", + "checkChain": false, + "params": { "time": 6, "failRate": 0 } + }, + { + "id": "e286nywaz7cmdf7keq5", + "task": "TwitterRetweet", + "title": "RT - Play-to-Airdrop Coming Soon", + "type": 1, + "desc": "Retweet specific tweets", + "category": "Social Tasks", + "score": 50, + "autoclaim": false, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": { "icon": "twitter", "content": "1788919520104681866"}, + "start": "2024-01-01 00:00", + "end": "2025-01-01 00:00", + "checkChain": false, + "params": { "time": 6, "failRate": 0 } + }, + { + "id": "e2cy5ha5nmre9w3pq2k", + "task": "TwitterLike", + "title": "Like - Play-to-Airdrop Coming Soon", + "type": 1, + "desc": "Like specific tweets", + "category": "Social Tasks", + "score": 50, + "autoclaim": false, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": { "icon": "twitter", "content": "1788919520104681866" }, "start": "2024-01-01 00:00", "end": "2025-01-01 00:00", "checkChain": false, "params": { "time": 6, "failRate": 0 } } ], + "drawTime": 1715245160457, "startTime": 1713355200000, "endTime": 1715903999999 } diff --git a/initdatas/activity_info.json_bak b/initdatas/activity_info.json_bak index d4da622..73eb2c9 100644 --- a/initdatas/activity_info.json_bak +++ b/initdatas/activity_info.json_bak @@ -266,7 +266,37 @@ "cfg": {"address": "0x7b6399DFbed8Bc46F6A498C6B1040E80c2B5C4bc"}, "score": 100, "params": {"address": "0x7b6399DFbed8Bc46F6A498C6B1040E80c2B5C4bc"} - }, { + },{ + "id": "e2yhq2lj30vwcpedv8p", + "task": "GoogleConnect", + "title": "Connect your game account", + "type": 1, + "desc": "Please connect your game account", + "score": 100, + "category": "Social Tasks", + "autoclaim": false, + "cfg": { "icon": "google" }, + "start": "2024-01-01 00:00", + "end": "2025-01-01 00:00", + "checkChain": false, + "params": {} + }, + { + "id": "e2fclylj30vwcpe1szl", + "task": "GameAchievement", + "title": "Receive Game Achievement", + "type": 1, + "desc": "Receive Game Achievement", + "category": "Social Tasks", + "score": 100, + "autoclaim": false, + "pretasks": ["e2yhq2lj30vwcpedv8p"], + "cfg": { }, + "start": "2024-01-01 00:00", + "end": "2025-01-01 00:00", + "checkChain": false, + "params": { "time": 6, "failRate": 0 } + }, { "id": "e2f7t4lj33vwcpe0ldr", "task": "BurnNft", "type": 1, diff --git a/package.json b/package.json index 11ed879..f48925a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "repairredis": "ts-node -r tsconfig-paths/register src/repairredis.ts", "checkredis": "ts-node -r tsconfig-paths/register src/checkredis.ts", "additem": "ts-node -r tsconfig-paths/register src/addboxdata.ts", + "taskid": "ts-node -r tsconfig-paths/register src/generateTaskId.ts", + "token": "ts-node -r tsconfig-paths/register src/generateToken.ts", "test:watch": "jest --watch", "test": "jest" }, diff --git a/src/api.server.ts b/src/api.server.ts index c7b3f66..22ce8f4 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -9,6 +9,7 @@ import logger from 'logger/logger' import NonceRecordSchedule from 'schedule/noncerecord.schedule' import { RouterMap, ZRedisClient } from 'zutils' import { SyncLocker } from 'common/SyncLocker' +import CacheSchedule from 'schedule/cache.schedule' const zReqParserPlugin = require('plugins/zReqParser') @@ -116,7 +117,7 @@ export class ApiServer { logger.log('REDIS Connected') } private initSchedules() { - // new CacheSchedule().scheduleAll() + new CacheSchedule().scheduleAll() new NonceRecordSchedule().scheduleAll() } diff --git a/src/common/Constants.ts b/src/common/Constants.ts index f85a980..b848138 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -68,3 +68,8 @@ export const BASE52_ALPHABET = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV // 检查role时默认的工会id export const DEFAULT_GUILD = '930002266868555827' + +// 游戏内积分排行榜key +export const INGAME_SCORE_KEY = 'ingame:score' + +export const ACTIVITY_NAME = 'uaw_activity' diff --git a/src/common/ReadOnlyCache.ts b/src/common/ReadOnlyCache.ts new file mode 100644 index 0000000..8220fbf --- /dev/null +++ b/src/common/ReadOnlyCache.ts @@ -0,0 +1,14 @@ +import { singleton } from 'zutils' + +@singleton +export class ReadOnlyCache { + map: Map = new Map() + + public getData(key: string) { + return this.map.get(key) + } + + public setData(key: string, value: any) { + this.map.set(key, value) + } +} diff --git a/src/controllers/activity.controller.ts b/src/controllers/activity.controller.ts index 250096d..5b7ca1e 100644 --- a/src/controllers/activity.controller.ts +++ b/src/controllers/activity.controller.ts @@ -9,6 +9,7 @@ import { formatNumShow, isValidShareCode } from 'common/Utils' import { checkReCaptcha } from 'services/google.svr' import { RANK_SCORE_SCALE, SCORE_INVITE_REBATE } from 'common/Constants' import logger from 'logger/logger' +import { ReadOnlyCache } from 'common/ReadOnlyCache' const MAX_LIMIT = 100 export default class ActivityController extends BaseController { @@ -19,7 +20,13 @@ export default class ActivityController extends BaseController { if (!id) { throw new ZError(11, 'invalid activity id') } - let activity = await ActivityInfo.findById(id) + let activity = new ReadOnlyCache().getData(id) + if (!activity) { + activity = await ActivityInfo.findById(id) + if (activity) { + new ReadOnlyCache().setData(id, activity) + } + } if (!activity) { throw new ZError(12, 'activity not found') } @@ -93,7 +100,7 @@ export default class ActivityController extends BaseController { const totalKey = rankKey(user.activity) const keyInvite = `${user.activity}:invite` let users = await ActivityUser.find({ inviteUser: user.id }) - let records = await ScoreRecord.find({ type: SCORE_INVITE_REBATE, user: user.id, activity: user.activity }) + let records = await ScoreRecord.find({ user: user.id, activity: user.activity, type: SCORE_INVITE_REBATE }) let scoreMap = new Map() for (let r of records) { if (!scoreMap.has(r.data.fromUser)) { @@ -126,7 +133,7 @@ export default class ActivityController extends BaseController { async scoreList(req) { let user = req.user const records = await ScoreRecord.find({ user: user.id }).sort({ - _id: -1, + createdAt: -1, }) return records.map(record => { return { diff --git a/src/controllers/ingame.controller.ts b/src/controllers/ingame.controller.ts new file mode 100644 index 0000000..74e5428 --- /dev/null +++ b/src/controllers/ingame.controller.ts @@ -0,0 +1,292 @@ +import { ACTIVITY_NAME, INGAME_SCORE_KEY, RANK_SCORE_SCALE } from 'common/Constants' +import { SyncLocker } from 'common/SyncLocker' +import { formatNumShow } from 'common/Utils' +import logger from 'logger/logger' +import { join } from 'path' +import { ActivityInfo } from 'models/ActivityInfo' +import { ActivityUser } from 'models/ActivityUser' +import { DrawRecord } from 'models/DrawRecord' +import { InGameScoreRecord } from 'models/InGameScoreRecord' +import { InGameTaskRecord } from 'models/InGameTaskRecord' +import { rankLevel } from 'services/rank.svr' +import { formatDate } from 'utils/utcdate.util' +import { BaseController, ROLE_ANON, ZError, ZRedisClient, role, router } from 'zutils' +import { formatAddress } from 'zutils/utils/chain.util' +import { InGameStats } from 'models/InGameStats' +import { drawCfgs, drawOnce, queryInGameInfo } from 'services/game.svr' +import { updateRankScore } from 'services/ingame.rank.svr' +import { NftHolder } from 'models/chain/NftHolder' +import { tokenRarity } from 'taskingame/NftHolderCheck' +const fs = require('fs') + +const sourceList = require('../../configs/ingame_tasks.json') +const taskMap = new Map() +for (let o of sourceList) { + taskMap.set(o.id, o) +} +const taskListStr = JSON.stringify(sourceList) + +const initTasks = () => { + const tasks = join(__dirname, '../taskingame') + const list = new Map() + fs.readdirSync(tasks) + .filter((file: string) => ~file.search(/^[^.].*\.(ts|js)$/)) + .forEach((file: any) => { + const Task = require('../taskingame/' + file) + list.set(Task.default.name, Task) + }) + return list +} +const allTasks = initTasks() +/** + * 游戏内任务相关接口 + */ +const MAX_LIMIT = 100 +class InGameController extends BaseController { + /** + * 任务列表 + */ + @role(ROLE_ANON) + @router('get /api/ingame/tasks') + async taskList(req) { + const user = req.user + const now = Date.now() + let activity = req.activity + let list = JSON.parse(taskListStr) + let gameData = { + loginVal: '0', + battleTimes: 0, + winTimes: 0, + kills: 0, + getGoldVal: '0', + } + for (let o of list) { + if (o.checkTime) { + o.status = now < activity.drawTime ? -1 : 0 + } else { + o.status = 0 + } + } + if (user) { + if (user.googleId) { + let res = await queryInGameInfo(user.googleId, '0') + Object.assign(gameData, res) + } + const dateTag = formatDate(new Date()) + let records = await InGameTaskRecord.find({ user: user.id, dateTag }) + let recordSet = new Set() + for (let record of records) { + recordSet.add(record.task) + } + for (let sub of list) { + const Task = allTasks.get(sub.task) + const taskInstance = new Task.default({ user, activity: req.activity }) + if (sub.status >= 0 && (await taskInstance.check(sub, gameData))) { + sub.status = 2 + if (sub.task === 'GoogleConnect') { + sub.status = 3 + } + } + sub.status = recordSet.has(sub.id) ? 3 : sub.status === 0 ? 1 : sub.status + } + } + return list + } + /** + * 领取任务奖励 + */ + @router('post /api/ingame/claim') + async claimInGameReward(req) { + await new SyncLocker().checkLock(req) + logger.db('claim_ingame', req) + const { task } = req.params + const user = req.user + if (!user.googleId) { + throw new ZError(10, 'need connect game account first') + } + if (!taskMap.has(task)) { + throw new ZError(11, 'task not found') + } + let cfg = taskMap.get(task) + if (!allTasks.has(cfg.task)) { + throw new ZError(12, 'task not found') + } + const dateTag = formatDate(new Date()) + let record = await InGameTaskRecord.findOne({ user: user.id, task, dateTag }) + if (record) { + throw new ZError(12, 'already claimed today') + } + const Task = allTasks.get(cfg.task) + const taskInstance = new Task.default({ user, activity: req.activity }) + const { score, ticket } = await taskInstance.claimReward(cfg) + let recordNew = new InGameTaskRecord({ + user: user.id, + task, + dateTag, + score, + ticket, + }) + await recordNew.save() + return { score, ticket, status: 3 } + } + /** + * 游戏内抽奖 + */ + @router('post /api/ingame/draw') + async draw(req) { + await new SyncLocker().checkLock(req) + logger.db('draw_ingame', req) + const user = req.user + const { step } = req.params + if (!user.googleId) { + throw new ZError(10, 'need connect game account first') + } + const ingameStat = await InGameStats.insertOrUpdate({ user: user.id }, {}) + if (ingameStat.ticket < step) { + throw new ZError(11, 'no ticket') + } + // TODO:: 抽奖逻辑 + let saveRes = await InGameStats.findOneAndUpdate( + { user: user.id, ticket: { $gte: step } }, + { $inc: { ticket: -step } }, + { + new: false, + upsert: false, + includeResultMetadata: true, + }, + ) + // @ts-ignore + if (!saveRes.ok || saveRes.lastErrorObject.n < 1) { + throw new ZError(11, 'no ticket.') + } + let score = 0 + let items: any[] = [] + let position: number[] = [] + const dateTag = formatDate(new Date()) + for (let i = 0; i < step; i++) { + let reward = await drawOnce(user.inWhiteList) + let record = new DrawRecord({ + user: user.id, + score: 0, + }) + if (reward.type === 0) { + score += reward.amount + record.score = reward.amount + await updateRankScore({ + user: user.id, + score: reward.amount, + scoreType: 'draw_ingame', + scoreParams: { + date: dateTag, + position: reward.position, + }, + }) + } else { + items.push(reward) + record.items = [reward] + if (reward.type === 1) { + user.inWhiteList = true + await user.save() + } + } + await record.save() + position.push(reward.position) + } + return { score, items, position } + } + + /** + * 游戏内抽奖记录 + */ + @router('get /api/ingame/draw_history') + async history(req) { + const user = req.user + let records = await DrawRecord.find({ user: user.id }).sort({ _id: -1 }) + return records.map(record => { + return { + score: formatNumShow(record.score), + // @ts-ignore + time: record.createdAt.getTime(), + items: record.items, + } + }) + } + + /** + * 游戏内积分列表 + */ + @router('get /api/ingame/score_list') + async scoreList(req) { + const user = req.user + let records = await InGameScoreRecord.find({ user: user.id }).sort({ _id: -1 }) + return records.map(record => { + return { + score: formatNumShow(record.score), + // @ts-ignore + time: record.createdAt.getTime(), + type: record.type, + } + }) + } + /** + * 游戏内积分排行榜 + */ + @role(ROLE_ANON) + @router('get /api/ingame/leaderboard/:page') + async leaderboard(req) { + let { page, limit } = req.params + page = parseInt(page || '0') + limit = parseInt(limit || MAX_LIMIT) + page = page < 0 ? 0 : page + const start = page * limit + const end = start + limit - 1 + const records = await new ZRedisClient().zrevrange(INGAME_SCORE_KEY, start, end) + let results: any = [] + for (let i = 0; i < records.length; i += 2) { + const id = records[i] + let score = formatNumShow(parseInt(records[i + 1]) / RANK_SCORE_SCALE) + const user = await ActivityUser.findById(id) + const rank = start + i / 2 + 1 + results.push({ + rank, + level: rankLevel(rank), + nickname: user?.twitterName || user?.discordName || user?.address ? formatAddress(user.address) : 'unknown', + avatar: user?.twitterAvatar || '', + score, + }) + } + return results + } + + @role(ROLE_ANON) + @router('get /api/ingame/draw_cfg') + async drawCfg(req) { + let cfgs = drawCfgs + let results = [] + for (let cfg of cfgs) { + results.push({ + position: cfg.position, + type: cfg.type, + amount: cfg.amount, + name: cfg.name, + }) + } + return results + } + + @router('get /api/ingame/nft_list') + async nftList(req) { + let user = req.user + let records = await NftHolder.find({ + chain: '1', + address: '0xec23679653337d4c6390d0eeba682246a6067777', + user: user.address.toLowerCase(), + }) + return records.map(record => { + return { + tokenId: record.tokenId, + rarity: tokenRarity(record.tokenId), + } + }) + } +} diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index d47a9d9..3a94b7a 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -13,16 +13,17 @@ import { aesDecrypt } from 'zutils/utils/security.util' import { base58ToHex } from 'zutils/utils/string.util' import { ActivityGame } from 'models/ActivityGame' import { + INGAME_SCORE_KEY, MAX_ENHANCE_COUNT_ADV, MAX_ENHANCE_COUNT_BASE, RANK_SCORE_SCALE, - SCORE_INVITE_USER, SCORE_SOCIAL_TASK, } from 'common/Constants' import { formatNumShow, isObjectIdString } from 'common/Utils' import { ChestEnhanceRecord } from 'models/ChestEnhanceRecord' import logger from 'logger/logger' import { verifyToken } from 'services/google.svr' +import { InGameStats } from 'models/InGameStats' const LOGIN_TIP = 'This signature is just to verify your identity' @@ -120,6 +121,7 @@ class SignController extends BaseController { const totalKey = rankKey(user.activity) const totalScore = await new ZRedisClient().zscore(totalKey, user.id) const totalRank = await new ZRedisClient().zrevrank(totalKey, user.id) + const rankGame = await new ZRedisClient().zrevrank(INGAME_SCORE_KEY, user.id) let invite = '' if (user.inviteUser) { const inviteUser = await ActivityUser.findById(user.inviteUser) @@ -139,6 +141,7 @@ class SignController extends BaseController { if (enhanceCount < 0) { enhanceCount = 0 } + const ingameStat = await InGameStats.insertOrUpdate({ user: user.id }, {}) let result = { address: user.address.toLowerCase(), boost: user.boost || 1, @@ -151,6 +154,7 @@ class SignController extends BaseController { scoreToday: formatNumShow(todayScore ? parseInt(todayScore + '') / RANK_SCORE_SCALE : 0), scoreTotal: formatNumShow(totalScore ? parseInt(totalScore + '') / RANK_SCORE_SCALE : 0), rankTotal: totalRank != undefined ? totalRank : '-', + rankGame: rankGame !== undefined ? rankGame : '-', invite, inviteCount: records.length, inviteScore, @@ -159,6 +163,10 @@ class SignController extends BaseController { mapopen: gameRecord.status, enhanceCount, inWhiteList: user.inWhiteList ? 1 : 0, + googleId: user.googleId, + googleMail: user.googleEmail, + gameScore: ingameStat.score, + gameTicket: ingameStat.ticket, } return result } diff --git a/src/generateTaskId.ts b/src/generateTaskId.ts new file mode 100644 index 0000000..bd856bf --- /dev/null +++ b/src/generateTaskId.ts @@ -0,0 +1,19 @@ +import mongoose from 'mongoose' +import * as dotenv from 'dotenv' + +const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' +dotenv.config({ path: envFile }) +console.log(process.env.DB_MAIN) +import { customAlphabet } from 'nanoid' +const BASE52_ALPHABET = '123456789abcdefghjkmnpqrstuvwxyz' +;(async () => { + try { + for (let i = 0; i < 20; i++) { + let code = 'g3' + customAlphabet(BASE52_ALPHABET, 17)() + console.log(code) + } + } catch (e) { + console.log(e) + } + process.exit(0) +})() diff --git a/src/generateToken.ts b/src/generateToken.ts new file mode 100644 index 0000000..32c09d1 --- /dev/null +++ b/src/generateToken.ts @@ -0,0 +1,30 @@ +import mongoose from 'mongoose' +import * as dotenv from 'dotenv' + +const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' +dotenv.config({ path: envFile }) +console.log(process.env.DB_MAIN) +import { createSigner } from 'fast-jwt' +import { ActivityUser } from 'models/ActivityUser' +;(async () => { + try { + const signSync = createSigner({ + key: process.env.API_TOKEN_SECRET, + }) + const address = '0x50A8e60041A206AcaA5F844a1104896224be6F39' + const user = await ActivityUser.findOne({ address }) + if (!user) { + console.log('User not found') + process.exit(0) + } + const token = signSync({ + id: user.id, + address, + activity: 'uaw_activity', + }) + console.log(token) + } catch (e) { + console.log(e) + } + process.exit(0) +})() diff --git a/src/models/ActivityInfo.ts b/src/models/ActivityInfo.ts index ae20336..54097ee 100644 --- a/src/models/ActivityInfo.ts +++ b/src/models/ActivityInfo.ts @@ -89,6 +89,9 @@ export class ActivityInfoClass extends BaseModule { @prop({ required: false }) public pause: boolean + @prop() + public drawTime: number + @prop() public startTime: number diff --git a/src/models/DrawRecord.ts b/src/models/DrawRecord.ts new file mode 100644 index 0000000..61fdf26 --- /dev/null +++ b/src/models/DrawRecord.ts @@ -0,0 +1,47 @@ +import { dbconn } from 'decorators/dbconn' +import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose' +import { BaseModule } from './Base' + +@modelOptions({ schemaOptions: { _id: false } }) +export class Item { + @prop() + id: string + @prop() + type: number + @prop() + name: string + @prop({ default: 1 }) + amount: number +} +/** + * 用户大转盘抽奖记录 + */ +@dbconn() +@index({ user: 1 }, { unique: false }) +@index({ user: 1, dateTag: 1 }, { unique: false }) +@modelOptions({ + schemaOptions: { collection: 'draw_record', timestamps: true }, +}) +class DrawRecordClass extends BaseModule { + @prop() + public user: string + @prop() + public dateTag: string + @prop() + public position: number + // 获得的游戏内积分数量 + @prop({ default: 0 }) + public score: number + + @prop({ type: () => [Item], default: [] }) + public items: Item[] + + public toJson() { + return { + day: this.dateTag, + score: this.score, + items: this.items, + } + } +} +export const DrawRecord = getModelForClass(DrawRecordClass, { existingConnection: DrawRecordClass['db'] }) diff --git a/src/models/InGameScoreRecord.ts b/src/models/InGameScoreRecord.ts new file mode 100644 index 0000000..523c3b1 --- /dev/null +++ b/src/models/InGameScoreRecord.ts @@ -0,0 +1,38 @@ +import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose' +import { dbconn } from 'decorators/dbconn' +import { BaseModule } from './Base' + +@dbconn() +@index({ user: 1 }, { unique: false }) +@index({ user: 1, type: 1 }, { unique: false }) +@index({ user: 1, score: 1 }, { unique: false }) +@modelOptions({ + schemaOptions: { collection: 'ingame_score_record', timestamps: true }, +}) +class InGameScoreRecordClass extends BaseModule { + @prop({ required: true }) + public user: string + + @prop() + public score: number + + @prop() + public type: string + + @prop({ type: mongoose.Schema.Types.Mixed }) + public data: any + + public toJson() { + return { + user: this.user, + score: this.score, + type: this.type, + //@ts-ignore + time: this.createdAt.getTime(), + } + } +} + +export const InGameScoreRecord = getModelForClass(InGameScoreRecordClass, { + existingConnection: InGameScoreRecordClass['db'], +}) diff --git a/src/models/InGameStats.ts b/src/models/InGameStats.ts new file mode 100644 index 0000000..9b2f48f --- /dev/null +++ b/src/models/InGameStats.ts @@ -0,0 +1,24 @@ +import { dbconn } from 'decorators/dbconn' +import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose' +import { Severity } from '@typegoose/typegoose/lib/internal/constants' +import { BaseModule } from './Base' + +/** + * 用户游戏内数据 + */ +@dbconn() +@index({ user: 1 }, { unique: true }) +@modelOptions({ + schemaOptions: { collection: 'ingame_stats', timestamps: true }, +}) +export class InGameStatsClass extends BaseModule { + @prop() + public user: string + // 游戏内抽奖次数 + @prop({ default: 0 }) + public ticket: number + // 游戏内积分 + @prop({ default: 0 }) + public score: number +} +export const InGameStats = getModelForClass(InGameStatsClass, { existingConnection: InGameStatsClass['db'] }) diff --git a/src/models/InGameTaskRecord.ts b/src/models/InGameTaskRecord.ts new file mode 100644 index 0000000..caf1270 --- /dev/null +++ b/src/models/InGameTaskRecord.ts @@ -0,0 +1,34 @@ +import { Severity, getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose' +import { dbconn } from 'decorators/dbconn' +import { BaseModule } from './Base' + +/** + * ingame task record + */ + +@dbconn() +@index({ user: 1, dateTag: 1 }, { unique: false }) +@index({ user: 1, task: 1 }, { unique: false }) +@index({ user: 1, task: 1, dateTag: 1 }, { unique: true }) +@modelOptions({ + schemaOptions: { collection: 'ingame_task_record', timestamps: true }, + options: { allowMixed: Severity.ALLOW }, +}) +class InGameTaskRecordClass extends BaseModule { + @prop({ required: true }) + public user: string + @prop({ required: true }) + public task: string + @prop() + public dateTag: string + // 获得的积分数量 + @prop({ default: 0 }) + public score: number + // 获得的抽奖次数 + @prop({ default: 0 }) + public ticket: number +} + +export const InGameTaskRecord = getModelForClass(InGameTaskRecordClass, { + existingConnection: InGameTaskRecordClass['db'], +}) diff --git a/src/models/NftClaimRecord.ts b/src/models/NftClaimRecord.ts new file mode 100644 index 0000000..3f320af --- /dev/null +++ b/src/models/NftClaimRecord.ts @@ -0,0 +1,24 @@ +import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose' +import { dbconn } from 'decorators/dbconn' +import { BaseModule } from './Base' + +@dbconn() +@index({ address: 1, tokenId: 1, dateTag: 1 }, { unique: true }) +@index({ address: 1, dateTag: 1 }, { unique: false }) +@modelOptions({ + schemaOptions: { collection: 'nft_claim_record', timestamps: true }, +}) +class NftClaimRecordClass extends BaseModule { + @prop() + public address: string + + @prop() + public tokenId: string + + @prop() + public dateTag: string +} + +export const NftClaimRecord = getModelForClass(NftClaimRecordClass, { + existingConnection: NftClaimRecordClass['db'], +}) diff --git a/src/models/chain/NftHolder.ts b/src/models/chain/NftHolder.ts index dcc2529..04c715b 100644 --- a/src/models/chain/NftHolder.ts +++ b/src/models/chain/NftHolder.ts @@ -3,7 +3,7 @@ import { dbconn } from 'decorators/dbconn' import { BaseModule } from '../Base' import { ZERO_ADDRESS } from 'common/Constants' -@dbconn('chain') +@dbconn('chain2') @index({ chain: 1, address: 1, tokenId: 1 }, { unique: true }) @index({ chain: 1, address: 1, user: 1 }, { unique: false }) @modelOptions({ diff --git a/src/plugins/apiauth.ts b/src/plugins/apiauth.ts index 2292864..8f04018 100644 --- a/src/plugins/apiauth.ts +++ b/src/plugins/apiauth.ts @@ -4,6 +4,8 @@ import { ActivityInfo, ActivityInfoClass } from 'models/ActivityInfo' import { ActivityUser, ActivityUserClass } from 'models/ActivityUser' import { ROLE_ANON } from 'zutils' import { DocumentType } from '@typegoose/typegoose' +import { ReadOnlyCache } from 'common/ReadOnlyCache' +import { ACTIVITY_NAME } from 'common/Constants' declare module 'fastify' { interface FastifyRequest { @@ -44,12 +46,16 @@ const apiAuthPlugin: FastifyPluginAsync = async function (fastif return reply.send({ errcode: 10, errmsg: 'need login' }) } request.user = account - if (account.activity) { - let activity = await ActivityInfo.findById(account.activity) + let activity = new ReadOnlyCache().getData(ACTIVITY_NAME) + if (!activity) { + activity = await ActivityInfo.findById(ACTIVITY_NAME) if (activity) { - request.activity = activity + new ReadOnlyCache().setData(ACTIVITY_NAME, activity) } } + if (activity) { + request.activity = activity + } } catch (err) { return reply.send({ errcode: 401, errmsg: 'need auth' }) } diff --git a/src/schedule/cache.schedule.ts b/src/schedule/cache.schedule.ts index f6e0910..7b2ea73 100644 --- a/src/schedule/cache.schedule.ts +++ b/src/schedule/cache.schedule.ts @@ -1,5 +1,7 @@ -import { LotteryCache } from 'common/LotteryCache' +import { ACTIVITY_NAME } from 'common/Constants' +import { ReadOnlyCache } from 'common/ReadOnlyCache' import logger from 'logger/logger' +import { ActivityInfo } from 'models/ActivityInfo' import * as schedule from 'node-schedule' import { singleton } from 'zutils' @@ -10,14 +12,16 @@ import { singleton } from 'zutils' export default class CacheSchedule { async updateCache() { try { - new LotteryCache().flush() + const activity = await ActivityInfo.findById(ACTIVITY_NAME) + new ReadOnlyCache().setData(ACTIVITY_NAME, activity) } catch (err) { logger.warn(err) } } scheduleAll() { - schedule.scheduleJob('*/10 * * * * *', async () => { + schedule.scheduleJob('*/1 * * * *', async () => { await this.updateCache() }) + this.updateCache() } } diff --git a/src/services/game.svr.ts b/src/services/game.svr.ts index 48af435..16ace6e 100644 --- a/src/services/game.svr.ts +++ b/src/services/game.svr.ts @@ -2,6 +2,16 @@ import { STEP_CHEST_RATE, STEP_SCORE_MAX, STEP_SCORE_MIN } from 'common/Constant import { ActivityChest, ActivityChestClass, ChestStatusEnum } from 'models/ActivityChest' import { ZError, ZRedisClient } from 'zutils' import { DocumentType } from '@typegoose/typegoose' +import { timeoutFetch } from 'zutils/utils/net.util' + +export const drawCfgs = require('../../configs/draw_cfg.json') +// 处理draw_cfg.json中的probability, 计算出每个奖励的概率区间 +let probability = 0 +for (let i = 0; i < drawCfgs.length; i++) { + let cfg = drawCfgs[i] + probability += cfg.probability + cfg.probability = probability +} const chestCfg = require('../../configs/chest.json') const chestBonusItems = require('../../configs/chest_bonus_item.json') @@ -121,3 +131,56 @@ export const generateChestBonus = async (chest: DocumentType }) return rewards } + +export const queryInGameInfo = async (openId: string, channel: string) => { + let url = `${process.env.GAME_SERVER_URL}&channel=${channel}&openId=${openId}` + const key = `gameinfo:${openId}:${channel}` + const cache = await new ZRedisClient().get(key) + if (cache) { + try { + let result = JSON.parse(cache) + return result + } catch (e) { + console.error(e) + } + } + let res = await timeoutFetch(url, {}, 5000) + let data = await res.json() + if (data.errcode) { + throw new ZError(data.errcode, data.errmsg) + } + let result = data.info + new ZRedisClient().pub.set(key, JSON.stringify(result), 'EX', 60, () => { + console.log('cache set success: ', key) + }) + return result +} + +/** + * 根据drawCfgs中设定的probability, 抽取指定的奖励 + * 如果抽到的type为1或2, 则从redis中spop一个值, 如果该值为空, 则再次调用本方法, 直到抽到奖励位置 + * */ +export const drawOnce = async (hasWhite: boolean) => { + let random = Math.random() * probability + let reward + for (let i = 0; i < drawCfgs.length; i++) { + let cfg = drawCfgs[i] + if (random < cfg.probability) { + // deep copy + reward = JSON.parse(JSON.stringify(cfg)) + break + } + } + if (!reward) { + return drawOnce(hasWhite) + } + if (reward.type === 1 || reward.type === 2) { + const redisKey = `draw_${reward.type}` + const itemId = await new ZRedisClient().spop(redisKey) + if (!itemId) { + return drawOnce(hasWhite) + } + reward.id = itemId + } + return reward +} diff --git a/src/services/ingame.rank.svr.ts b/src/services/ingame.rank.svr.ts new file mode 100644 index 0000000..4a65aa4 --- /dev/null +++ b/src/services/ingame.rank.svr.ts @@ -0,0 +1,71 @@ +import { INGAME_SCORE_KEY, RANK_SCORE_SCALE } from 'common/Constants' +import { InGameScoreRecord } from 'models/InGameScoreRecord' +import { InGameStats } from 'models/InGameStats' +import { ZRedisClient } from 'zutils' +const rankLevels = require('../../configs/uaw_rank_level.json') +/** + * 更新排行榜 + * @param param0 + * user: 用户id + * score: 分数 + * scoreType: 分数类型 + * scoreParams: 额外的参数 + */ +export const updateRankScore = async ({ + user, + score, + scoreType, + scoreParams, +}: { + user: string + score: number + scoreType: string + scoreParams: any + rebateLevel?: number +}) => { + let record = new InGameScoreRecord({ + user: user, + score, + type: scoreType, + data: scoreParams, + }) + await record.save() + await InGameStats.updateOne({ user }, { $inc: { score } }) + const key = INGAME_SCORE_KEY + await updateRank(key, score, user) + // 给邀请人返利 + // if (rebateLevel > 0) { + // let userMod = await ActivityUser.findById(user) + // if (userMod?.inviteUser) { + // const score1 = score * INVITE_REBATE + // let scoreParams1 = Object.assign({ fromUser: user }, scoreParams) + // const keyInvite = `${activity}:invite` + // await updateRankInvite(keyInvite, score1, `${userMod.inviteUser}_${user}`) + // await updateRankScore({ + // user: userMod.inviteUser, + // score: score1, + // activity, + // scoreType: SCORE_INVITE_REBATE, + // scoreParams: scoreParams1, + // rebateLevel: rebateLevel - 1, + // }) + // } + // } +} + +/** + * 更新排行榜 + * @param key + * @param score + * @param member + */ +export const updateRank = async (key: string, score: number, member: string) => { + score = parseInt(score * RANK_SCORE_SCALE + '') + await new ZRedisClient().zincrby(key, score, member) +} + +export const rankLevel = (rank: number | string) => { + rank = parseInt(rank + '') + const data = rankLevels.find(o => rank >= o.rankMin && rank <= o.rankMax) + return data ? data.level : '-' +} diff --git a/src/taskingame/GameBattle.ts b/src/taskingame/GameBattle.ts new file mode 100644 index 0000000..efc547f --- /dev/null +++ b/src/taskingame/GameBattle.ts @@ -0,0 +1,10 @@ +import { ITask } from './base/ITask' + +export default class GameBattle extends ITask { + static desc = 'game battle' + static key = 'battleTimes' + static show: boolean = true + async execute(data: any) { + return true + } +} diff --git a/src/taskingame/GameCoin.ts b/src/taskingame/GameCoin.ts new file mode 100644 index 0000000..bd6eff9 --- /dev/null +++ b/src/taskingame/GameCoin.ts @@ -0,0 +1,13 @@ +import { ITask } from './base/ITask' +import { TaskStatusEnum } from 'models/ActivityUser' +import { ZError } from 'zutils' +import { TaskCfg } from 'models/ActivityInfo' + +export default class GameCoin extends ITask { + static desc = 'game coin' + static key = 'getGoldVal' + static show: boolean = true + async execute(data: any) { + return true + } +} diff --git a/src/taskingame/GameKill.ts b/src/taskingame/GameKill.ts new file mode 100644 index 0000000..c434767 --- /dev/null +++ b/src/taskingame/GameKill.ts @@ -0,0 +1,13 @@ +import { ITask } from './base/ITask' +import { TaskStatusEnum } from 'models/ActivityUser' +import { ZError } from 'zutils' +import { TaskCfg } from 'models/ActivityInfo' + +export default class GameKill extends ITask { + static desc = 'game kill' + static key = 'kills' + static show: boolean = true + async execute(data: any) { + return true + } +} diff --git a/src/taskingame/GameLogin.ts b/src/taskingame/GameLogin.ts new file mode 100644 index 0000000..336f09a --- /dev/null +++ b/src/taskingame/GameLogin.ts @@ -0,0 +1,10 @@ +import { ITask } from './base/ITask' + +export default class GameLogin extends ITask { + static desc = 'game login' + static key = 'loginVal' + static show: boolean = true + async execute(data: any) { + return true + } +} diff --git a/src/taskingame/GameWin.ts b/src/taskingame/GameWin.ts new file mode 100644 index 0000000..697702c --- /dev/null +++ b/src/taskingame/GameWin.ts @@ -0,0 +1,10 @@ +import { ITask } from './base/ITask' + +export default class GameWin extends ITask { + static desc = 'game win' + static key = 'winTimes' + static show: boolean = true + async execute(data: any) { + return true + } +} diff --git a/src/taskingame/GoogleConnect.ts b/src/taskingame/GoogleConnect.ts new file mode 100644 index 0000000..38f8043 --- /dev/null +++ b/src/taskingame/GoogleConnect.ts @@ -0,0 +1,21 @@ +import { ZError } from 'zutils' +import { ITask } from './base/ITask' + +export default class GoogleConnect extends ITask { + static desc = 'connect google account' + static show: boolean = true + async execute(data: any) { + return true + } + + public async claimReward(task: any) { + if (!this.user.googleId) { + throw new ZError(100, 'google account not binded') + } + return { score: 0, ticket: 0 } + } + + public async check(cfg: any, gameData: any) { + return !!this.user.googleId + } +} diff --git a/src/taskingame/NftHolderCheck.ts b/src/taskingame/NftHolderCheck.ts new file mode 100644 index 0000000..c2ba125 --- /dev/null +++ b/src/taskingame/NftHolderCheck.ts @@ -0,0 +1,86 @@ +import { NftHolder } from 'models/chain/NftHolder' +import { ITask } from './base/ITask' +import { formatDate } from 'utils/utcdate.util' +import { NftClaimRecord } from 'models/NftClaimRecord' +import { ZError } from 'zutils' + +const sourceList = require('../../configs/nft_202403.json') +const commonSet = new Set() +const legendarySet = new Set() +const rareSet = new Set() +for (let o of sourceList) { + if (o.rarity === 'Common') { + commonSet.add(o.id) + } else if (o.rarity === 'Legendary') { + legendarySet.add(o.id) + } else if (o.rarity === 'Rare') { + rareSet.add(o.id) + } +} +export const tokenRarity = (tokenId: string | number) => { + if (commonSet.has(parseInt(tokenId + ''))) { + return 'Common' + } else if (legendarySet.has(parseInt(tokenId + ''))) { + return 'Legendary' + } else if (rareSet.has(parseInt(tokenId + ''))) { + return 'Rare' + } +} +const rarityList = require('../../configs/nft_rarity.json') +export const rarityMap = new Map() +for (let o of rarityList) { + rarityMap.set(o.rarity, o.ticket) +} + +export default class NftHolderCheck extends ITask { + static desc = 'nft holder' + static show: boolean = true + async execute(data: any) { + return true + } + public async claimReward(cfg: any) { + const { chain, address } = cfg.cfg + let score = 0 + let ticket = 0 + const dateTag = formatDate(new Date()) + let historys = await NftClaimRecord.find({ address, dateTag }) + let historySet = new Set() + for (let history of historys) { + historySet.add(history.tokenId) + } + let records = await NftHolder.find({ chain, address, user: this.user.address.toLowerCase() }) + // remove record which tokenId in historySet first + records = records.filter(o => !historySet.has(o.tokenId)) + if (records.length === 0) { + throw new ZError(20, 'not NFT holder') + } + for (let record of records) { + if (commonSet.has(parseInt(record.tokenId))) { + ticket += rarityMap.get('Common') + } else if (legendarySet.has(parseInt(record.tokenId))) { + ticket += rarityMap.get('Legendary') + } else if (rareSet.has(parseInt(record.tokenId))) { + ticket += rarityMap.get('Rare') + } + let claimRecord = new NftClaimRecord({ + address, + tokenId: record.tokenId, + dateTag, + }) + await claimRecord.save() + } + return { score, ticket } + } + + public async check(cfg: any, gameData: any) { + const { chain, address } = cfg.cfg + const dateTag = formatDate(new Date()) + let historys = await NftClaimRecord.find({ address, dateTag }) + let historySet = new Set() + for (let history of historys) { + historySet.add(history.tokenId) + } + let records = await NftHolder.find({ chain, address, user: this.user.address.toLowerCase() }) + return records.length > 0 + } +} diff --git a/src/taskingame/base/ITask.ts b/src/taskingame/base/ITask.ts new file mode 100644 index 0000000..db211ee --- /dev/null +++ b/src/taskingame/base/ITask.ts @@ -0,0 +1,90 @@ +import { ActivityInfoClass, TaskCfg } from 'models/ActivityInfo' +import { ActivityItem } from 'models/ActivityItem' +import { ActivityUserClass } from 'models/ActivityUser' +import { DocumentType } from '@typegoose/typegoose' +import { queryInGameInfo } from 'services/game.svr' +import { ZError } from 'zutils' +import { updateRankScore } from 'services/ingame.rank.svr' +import { formatDate } from 'utils/utcdate.util' +import { InGameTaskRecord } from 'models/InGameTaskRecord' +import { InGameStats } from 'models/InGameStats' + +export abstract class ITask { + static desc: string + static key: string + static show: boolean = true + static auto: boolean = false + + user: DocumentType + activity: DocumentType + + constructor({ + user, + activity, + }: { + user: DocumentType + activity: DocumentType + }) { + // do nothing + this.user = user + this.activity = activity + } + abstract execute(data: any): Promise + + public async claimReward(cfg: any) { + const user = this.user + let gameData = await queryInGameInfo(user.googleId, '0') + if (!(await this.check(cfg, gameData))) { + throw new ZError(50, 'not match condition') + } + const dateTag = formatDate(new Date()) + let score = cfg.score + if (score > 0) { + await updateRankScore({ + user: user.id, + score: score, + scoreType: 'ingame_task', + scoreParams: { + date: dateTag, + taskId: cfg.id, + boost: user.boost, + }, + }) + } + let ticket = 0 + if (cfg.ticket > 0 && Date.now() >= this.activity.drawTime) { + let canGet = false + if (cfg.tickettasks) { + let records = await InGameTaskRecord.find({ user: user.id, dateTag, task: { $in: cfg.tickettasks } }) + if (records.length === cfg.tickettasks.length) { + canGet = true + } + } else { + canGet = true + } + if (canGet) { + ticket = cfg.ticket + await InGameStats.updateOne({ user: user.id }, { $inc: { ticket } }) + } + } + return { score, ticket } + } + + public async check(cfg: any, gameData: any) { + const subCfg = cfg.cfg + const val = parseInt(gameData[subCfg.key] || '0') + return val >= subCfg.amount + } + + public async claimItem(cfg: TaskCfg) { + if (cfg.params.items) { + for (let key in cfg.params.items) { + let amount = cfg.params.items[key] + await ActivityItem.insertOrUpdate( + { user: this.user.id, activity: this.user.activity, item: key }, + { $inc: { amount }, last: Date.now() }, + ) + } + } + } +}