增加游戏内任务相关接口
This commit is contained in:
parent
e5d2e653e8
commit
2a145b7f37
@ -2,11 +2,11 @@
|
||||
{
|
||||
"type": 1,
|
||||
"name": "White List",
|
||||
"probability": 100
|
||||
"probability": 2
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"name": "NFT",
|
||||
"probability": 100
|
||||
"probability": 1
|
||||
}
|
||||
]
|
65
configs/draw_cfg.json
Normal file
65
configs/draw_cfg.json
Normal file
@ -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
|
||||
}
|
||||
]
|
111
configs/ingame_tasks.json
Normal file
111
configs/ingame_tasks.json
Normal file
@ -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
|
||||
}
|
||||
]
|
503
configs/nft_202403.json
Normal file
503
configs/nft_202403.json
Normal file
@ -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}
|
||||
|
||||
]
|
14
configs/nft_rarity.json
Normal file
14
configs/nft_rarity.json
Normal file
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"rarity": "Common",
|
||||
"ticket": 1
|
||||
},
|
||||
{
|
||||
"rarity": "Rare",
|
||||
"ticket": 2
|
||||
},
|
||||
{
|
||||
"rarity": "Legendary",
|
||||
"ticket": 5
|
||||
}
|
||||
]
|
221
docs/uaw.md
221
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
|
||||
}
|
||||
]
|
||||
```
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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'
|
||||
|
14
src/common/ReadOnlyCache.ts
Normal file
14
src/common/ReadOnlyCache.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { singleton } from 'zutils'
|
||||
|
||||
@singleton
|
||||
export class ReadOnlyCache {
|
||||
map: Map<string, any> = new Map()
|
||||
|
||||
public getData(key: string) {
|
||||
return this.map.get(key)
|
||||
}
|
||||
|
||||
public setData(key: string, value: any) {
|
||||
this.map.set(key, value)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
292
src/controllers/ingame.controller.ts
Normal file
292
src/controllers/ingame.controller.ts
Normal file
@ -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),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
19
src/generateTaskId.ts
Normal file
19
src/generateTaskId.ts
Normal file
@ -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)
|
||||
})()
|
30
src/generateToken.ts
Normal file
30
src/generateToken.ts
Normal file
@ -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)
|
||||
})()
|
@ -89,6 +89,9 @@ export class ActivityInfoClass extends BaseModule {
|
||||
@prop({ required: false })
|
||||
public pause: boolean
|
||||
|
||||
@prop()
|
||||
public drawTime: number
|
||||
|
||||
@prop()
|
||||
public startTime: number
|
||||
|
||||
|
47
src/models/DrawRecord.ts
Normal file
47
src/models/DrawRecord.ts
Normal file
@ -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'] })
|
38
src/models/InGameScoreRecord.ts
Normal file
38
src/models/InGameScoreRecord.ts
Normal file
@ -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'],
|
||||
})
|
24
src/models/InGameStats.ts
Normal file
24
src/models/InGameStats.ts
Normal file
@ -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'] })
|
34
src/models/InGameTaskRecord.ts
Normal file
34
src/models/InGameTaskRecord.ts
Normal file
@ -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'],
|
||||
})
|
24
src/models/NftClaimRecord.ts
Normal file
24
src/models/NftClaimRecord.ts
Normal file
@ -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'],
|
||||
})
|
@ -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({
|
||||
|
@ -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<ApiAuthOptions> = 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' })
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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<ActivityChestClass>
|
||||
})
|
||||
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
|
||||
}
|
||||
|
71
src/services/ingame.rank.svr.ts
Normal file
71
src/services/ingame.rank.svr.ts
Normal file
@ -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 : '-'
|
||||
}
|
10
src/taskingame/GameBattle.ts
Normal file
10
src/taskingame/GameBattle.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
13
src/taskingame/GameCoin.ts
Normal file
13
src/taskingame/GameCoin.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
13
src/taskingame/GameKill.ts
Normal file
13
src/taskingame/GameKill.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
10
src/taskingame/GameLogin.ts
Normal file
10
src/taskingame/GameLogin.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
10
src/taskingame/GameWin.ts
Normal file
10
src/taskingame/GameWin.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
21
src/taskingame/GoogleConnect.ts
Normal file
21
src/taskingame/GoogleConnect.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
86
src/taskingame/NftHolderCheck.ts
Normal file
86
src/taskingame/NftHolderCheck.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
90
src/taskingame/base/ITask.ts
Normal file
90
src/taskingame/base/ITask.ts
Normal file
@ -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<ActivityUserClass>
|
||||
activity: DocumentType<ActivityInfoClass>
|
||||
|
||||
constructor({
|
||||
user,
|
||||
activity,
|
||||
}: {
|
||||
user: DocumentType<ActivityUserClass>
|
||||
activity: DocumentType<ActivityInfoClass>
|
||||
}) {
|
||||
// do nothing
|
||||
this.user = user
|
||||
this.activity = activity
|
||||
}
|
||||
abstract execute(data: any): Promise<boolean>
|
||||
|
||||
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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user