import {ApiCall} from 'tsrpc'; import {Wjjl} from '../module/collection_wjjl'; import {formatNpcData} from '../shared/fightControl/fightFun'; import {fightResult} from '../shared/fightControl/fightType'; import {hongdianVal} from '../shared/protocols/hongdian/PtlGet'; import {jjcChange} from '../shared/protocols/jjc/PtlOpen'; import {rankInfo} from '../shared/protocols/type'; import {PublicShared} from '../shared/public/public'; import {Queue} from '../shared/public/queue'; import {FightFun} from './fight'; import {UserFun} from './user'; import {sortEd} from '../module/redis' const jjcQueue = new Queue(); /**++++++++++++++++++特别注意:redis内的排名数据"rank:jjc:sort"数组值均为唯一,不要手动修改为重复值,否则排名计算逻辑会不准确+++++++++++++++++++++++++ */ /**++++++++++++++++++mongodb rank表的update与insert对性能有影响时可删除,保留只为尽量保持原有逻辑的数据逻辑不改变*/ export class JJCFun { /**当玩家挑战或者被挑战时不可被其他玩家挑战 */ static lockPlayer: k_v = {}; static async getMyData(uid: string) { let data = await G.mongodb.collection('jjc').findOne( {uid: uid} ); if (!data) { data = (await G.mongodb.collection('jjc').findOneAndUpdate( {uid: uid}, { $set: {resetTime: G.time, receivedArr: [], buyFightNum: 0, useFightNum: 0, recordWin: 0} }, {upsert: true, returnDocument: 'after'} )).value } return data } // 设置挑战状态 static async setLockPlayer(uid, boolean) { return boolean ? await G.redis.rawSet(`rank:jjc:lockPlayer:${uid}`, "1", {EX: 10}) : await G.redis.rawDel(`rank:jjc:lockPlayer:${uid}`); } // 获取挑战状态 static async getLockPlayer(uid) { return await G.redis.rawGet(`rank:jjc:lockPlayer:${uid}`); } /**排名缓存 */ // static rankList: rankInfo[]; static uTimeOffset: number = 60; /** * 获取指定条数的rankList * @param min * @param max * @param isUpdate 是否更新数据 */ static async getRankList(min: number = 0, max: number = 50, isUpdate: boolean = true) { // 获取指定排名用户uid let sortInfo = await this.getRankListUid(min, max) if (!sortInfo.length) return [] // 获取用户详细数据 let rankList = [] let updateArr = [] for (let i = 0; i < sortInfo.length; i++) { let uid = sortInfo[i] let rankInfo = await G.redis.hGet('rank:jjc:data', uid) if (!rankInfo?.player) continue rankList.push(rankInfo) // 比对utime,判断是否更新数据 if (!rankInfo.player.isNpc) { if (!rankInfo.utime || (rankInfo.utime && rankInfo.utime < (G.time - this.uTimeOffset))) { updateArr.push(uid) } } //if (!rankInfo.player.isNpc && rankInfo.utime && rankInfo.utime < (G.time - this.uTimeOffset)) updateArr.push(uid) } // 更新数据 if (isUpdate && updateArr.length > 0) { let playerArrInfo = await G.mongodb.collection("user").find({uid: {$in: updateArr}}).toArray() for (let i = 0; i < playerArrInfo.length; i++) { let playerInfo = playerArrInfo[i] let index = rankList.findIndex(x => x.player.uid == playerInfo.uid) rankList[index].player = playerInfo rankList[index].utime = G.time this.updatePlayerData(playerInfo.uid, rankList[index]); } } return rankList } /** * 每10分钟刷新前一千名玩家的用户数据 * @param min * @param max * @param uTimeOffset * @param isUpdate 是否更新数据 */ // static async checkUpdatePlayer(min: number = 0, max: number = 1000, uTimeOffset: number = 600, isUpdate: boolean = true) { // // 获取指定排名用户uid // let sortInfo = await this.getRankListUid(min, max) // if (!sortInfo.length) return [] // let updateArr = [] // let users = await G.redis.hGetAll('rank:jjc:data') // for (let i = 0; i < sortInfo.length; i++) { // let uid = sortInfo[i] // let rankInfo = users[uid] // if (!rankInfo?.player) continue // // 比对utime,判断是否更新数据 // if (!rankInfo.player.isNpc) { // if (!rankInfo.utime || (rankInfo.utime && rankInfo.utime < (G.time - uTimeOffset))) { // updateArr.push(rankInfo) // } // } // } // // 更新数据 // if (isUpdate && updateArr.length > 0) { // let playerArrInfo = await G.mongodb.collection("user").find({uid: {$in: updateArr.map(i => i.uid)}}).toArray() // for (let i = 0; i < playerArrInfo.length; i++) { // let playerInfo = playerArrInfo[i] // let index = updateArr.findIndex(x => x.player.uid == playerInfo.uid) // updateArr[index].player = playerInfo // updateArr[index].utime = G.time // this.updatePlayerData(playerInfo.uid, updateArr[index]); // } // } // } /** * 获取指定范围排名的用户,仅返回uid[] * @param min * @param max * @return uid[] */ static async getRankListUid(min: number = 0, max: number = 50) { let sortInfo = await G.redis.zRange('rank:jjc:sort', min, max - 1) return sortInfo } /** * 获取玩家排名 * @param uid * @return number */ static async getRankSortByUid(uid: string): Promise { let rank = await G.redis.zRank('rank:jjc:sort', uid) // 原逻辑索引未+1,此处返回索引。 return rank === 0 || rank > 0 ? rank : -1 } /** * 获取指定玩家数据 * @param uid * @return */ static async getPlayerData(uid: string): Promise { let rankInfo = await G.redis.hGet('rank:jjc:data', uid) // 原逻辑索引未+1,此处返回索引。 if (!rankInfo || (!rankInfo.player.isNpc && rankInfo.utime && rankInfo.utime < (G.time - this.uTimeOffset))) { // 新玩家数据,更新 if (!rankInfo) { this.addNewPlayerSort(uid) rankInfo = {} } rankInfo.player = await G.mongodb.collection("user").findOne({uid: uid}) rankInfo.utime = G.time this.updatePlayerData(uid, rankInfo) } return rankInfo } /** * 获取指定排名玩家数据 * @param rank * @return */ static async getRankPlayerData(rank: number): Promise { // 函数内max存在-1操作,查自己需保持min=max let rankList = await this.getRankList(rank, rank + 1) let rankInfo = rankList[0] return rankInfo || {} } // 写入新玩家排名 static async addNewPlayerSort(uid: string) { let rankLen = await G.redis.zCard('rank:jjc:sort'); // 获取排名的总条数,确认新玩家的排名值(ps:总条数既新玩家排名,不需+1) this.updatePlayerRank({value: uid, score: rankLen}) } /**更新玩家排名数据 */ static updatePlayerRank(sortEd: sortEd | sortEd[]) { G.redis.zAdd('rank:jjc:sort', sortEd) return } /**更新玩家数据 */ static updatePlayerData(uid: string, rankInfo: rankInfo) { G.redis.hSet('rank:jjc:data', uid, rankInfo) return } /**新玩家第一次进入或者排名变更 */ static async addRankInfo(rankInfo: rankInfo, changeInfo?: rankInfo) { // jjcQueue.enqueue(async () => { if (!changeInfo) { // 空数据预防 if (!rankInfo) return // redis写入新玩家数据 this.updatePlayerData(rankInfo.player.uid, rankInfo) // redis写入新玩家排名 this.addNewPlayerSort(rankInfo.player.uid) let rankLen = await G.redis.zCard('rank:jjc:sort'); // 获取排名的总条数,确认新玩家的排名值(ps:总条数既新玩家排名,不需+1) // this.updatePlayerRank({value: rankInfo.player.uid, score: rankLen}) Wjjl.setVal(rankInfo.player.uid, 'jjc_rank', rankLen); // G.mongodb.collection('rank').updateOne({type: 'jjc'}, {$push: {rankList: rankInfo}}); } else { // 获取自己与对手的排名索引 const myIndex = await G.redis.zScore('rank:jjc:sort', rankInfo.player.uid) const heIndex = await G.redis.zScore('rank:jjc:sort', changeInfo.player.uid) if (myIndex <= heIndex) return; Wjjl.setVal(rankInfo.player.uid, 'jjc_rank', heIndex + 1); UserFun.activeHeadFrame(rankInfo.player.uid, 'jjc_rank', heIndex + 1); // 交换更新排名索引值,无需更新数据 this.updatePlayerRank([ {value: rankInfo.player.uid, score: heIndex}, {value: changeInfo.player.uid, score: myIndex} ]) let change = G.mongodb.createTreeObj({ key: `rankList.${heIndex}`, val: rankInfo }, {key: `rankList.${myIndex}`, val: changeInfo}); G.mongodb.collection('rank').updateOne({type: 'jjc'}, {$set: change}); } // }); return rankInfo; } /**刷新对手 */ static async randomEnemy(uid: string) { const enemy: number[] = []; const curIndex = await this.getRankSortByUid(uid); if (curIndex == 0) enemy.push(1, 2, [3, 4].random()); else if (curIndex == 1) enemy.push(0, [2, 3].random(), 4); else if (curIndex == 2) enemy.push(0, 1, [3, 4].random()); else if (curIndex == 3) enemy.push(0, [1, 2].random(), 4); else if (curIndex == 4) enemy.push([0, 1].random(), 2, 3); else if (curIndex == 5) enemy.push(1, [2, 3].random(), 4); else if (curIndex == 6) enemy.push(2, [3, 4].random(), 5); else if (curIndex == 7) enemy.push(3, [4, 5].random(), 6); else if (curIndex == 8) enemy.push(4, [5, 6].random(), 7); else if (curIndex == 9) enemy.push(5, [6, 7].random(), 8); else { enemy.push(...[ [Math.floor(curIndex * .7), Math.floor(curIndex * .8) - 1], [Math.floor(curIndex * .8), Math.floor(curIndex * .9) - 1], [Math.floor(curIndex * .9), curIndex - 1], ].map(arr => PublicShared.randomNum(arr[0], arr[1]))); } let enemyRes = [] for (let e of enemy) { if (e >= 0) { let rankInfo = await this.getRankPlayerData(e) let rank = await this.getRankSortByUid(rankInfo.player.uid) enemyRes.push({...rankInfo, rank: rank}) } } return { rank: curIndex, enemy: enemyRes }; } /**存录像 */ static async saveFightLog(uid: string, fightLog: fightResult) { if (uid.indexOf('npc_') != -1) return; FightFun.saveLog(uid, 'jjc', fightLog); } /**服务器启动后初始化缓存数据 */ static async init() { console.log('加载竞技场缓存...'); // redis数据存在,不初始化, mongodb在此的数据可以剔除。 // 或建立与redis"rank:jjc:sort"相对应的表,但走新表逻辑要考虑初始化的影响,暂缓。 // 重新初始化一定要删除redis内数据 if ((await this.getRankList()).length > 0) return let rankInfo = await G.mongodb.collection('rank').findOne({type: 'jjc'}); if (!rankInfo) { rankInfo = { _id: null, type: 'jjc', rankList: Object.values(G.gc.jjc_npc).map(npc => { let {id, npcId, ...attr} = npc; return { ...formatNpcData(npcId, {...attr, uid: id}) }; }) }; G.mongodb.collection('rank').insertOne({type: 'jjc', rankList: rankInfo.rankList}); } // 剔除空数据 rankInfo.rankList = rankInfo.rankList.filter(x => x && x.player) // 获取全部用户uid let uidArr = rankInfo.rankList.map(x => x.player.uid) // 数组去重 uidArr = [...new Set(uidArr)] // 整理写入redis的排名数据 let rank: sortEd[] = uidArr.map((uid, index) => ({value: uid, score: index})) this.updatePlayerRank(rank) // 记录ranklist详细数据到redis rankInfo.rankList && rankInfo.rankList.forEach(item => { this.updatePlayerData(item.player.uid, item) }) console.log('加载竞技场缓存 succ'); } /**获取玩家jjc数据 */ static async getData(call: ApiCall) { let jjsPlayer = await this.getMyData(call.uid); if (jjsPlayer) { let rank = await this.getRankSortByUid(call.uid); let data = JSON.parse(JSON.stringify(jjsPlayer)) data.rank = rank; } // 排名数据即时取 return jjsPlayer; } /**修改玩家jjc数据 */ static async changeData(call: ApiCall, change: jjcChange) { //console.log(change); // Object.entries(change).forEach(val => G.redis.set('jjc', call.uid, val[0] as any, val[1])); await G.mongodb.collection('jjc').updateOne({uid: call.uid}, {$set: change}); } /**红点 */ static async getHongDian(call: ApiCall) { let _res: hongdianVal = { show: false }; // 挑战次数不足 let _mydata = await this.getData(call) || { _id: null, uid: call.uid, ...{ resetTime: G.time, receivedArr: [], buyFightNum: 0, useFightNum: 0 } }; if (_mydata.useFightNum < _mydata.buyFightNum + G.gc.jjc_com.fightNum) { _res.show = true; } if (!_res.show) { let _con = JSON.parse(JSON.stringify(G.gc.jjc_tz)); for (let index = 0; index < Object.keys(_con).length; index++) { const element = Object.keys(_con)[index]; let _tmp = _con[element]; if (_mydata.receivedArr.find(r => r == _tmp.id)) continue; if (_mydata.useFightNum < _tmp.num) continue; _res.show = true; break; } } return _res; } }