Files
ZYZ/game-server/app/services/rankService.ts
2021-11-02 20:02:44 +08:00

1086 lines
43 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { KeyName, KeyNameParam, RankParam, GuildRankParam, RoleRankInfo, GuildLeader, LineupParam, myIdInter, GeneralRankParam, ValueConfig, GuildRankInfo } from "../domain/rank";
import { REDIS_RANK_TO_INFO, ROLE_SELECT, GUILD_SELECT, REDIS_KEY, REDIS_RANK_TO_EXTRA, HERO_SELECT, COMPOSE_FIELD_TYPE, KEY_TO_COMPOSE_FIELD, RANK_TYPE_TO_KEY, COUNTER } from "../consts";
import { redisClient, delKeys } from "./redisService";
import { RoleType, RoleModel } from "../db/Role";
import { GuildType, GuildModel } from "../db/Guild";
import { HeroModel, HeroType, HeroUpdate } from "../db/Hero";
import { PVPConfigModel } from "../db/SystemConfig";
import { PvpDefenseModel } from "../db/PvpDefense";
import { gameData } from "../pubUtils/data";
import { nowSeconds } from "../pubUtils/timeUtil";
import { getWorldChannelSid } from "./chatChannelService";
import { pinus } from "pinus";
import { RankFirstModel, RankFirstType, RankFirstUpdateParam } from "../db/RankFirst";
import { getRandSingleEelm } from "../pubUtils/util";
import { RANK_FIRST_REWARD_STATUS } from '../consts';
import { CounterModel } from "../db/Counter";
/**
* @description 排行榜相关操作
* @export
* @class Rank
*/
export class Rank {
isInit: boolean = false; // 初始排行榜
key: REDIS_KEY; // 排行榜原始key
keyName: KeyName; // 拼接之后的key
infoKey: REDIS_KEY; // 玩家数据key
extraKeys: string[];
isUnion: boolean; // 是否使用多个zset联合计算
limit: number = 200; // 排行榜长度
unionRankLife: number = 10;
valueConfig: ValueConfig[] = []; // 值的组合方案
constructor(key: REDIS_KEY, keyParam: KeyNameParam, isUnion = false, limit = 200) {
this.key = key;
this.keyName = new KeyName(key, keyParam);
this.infoKey = REDIS_RANK_TO_INFO.get(key);
this.extraKeys = REDIS_RANK_TO_EXTRA.get(key) || [];
this.isUnion = isUnion;
this.limit = limit;
this.setValueConfig(key);
}
public setIsInit(init: boolean) {
this.isInit = init;
}
private setValueConfig(key: REDIS_KEY) {
switch (key) {
case REDIS_KEY.TOP_LINEUP_RANK:
case REDIS_KEY.TOP_HERO_RANK:
case REDIS_KEY.SUM_CE_RANK:
case REDIS_KEY.HERO_RANK:
this.valueConfig = [
new ValueConfig(true, 'score', 0, false, false)
];
break;
case REDIS_KEY.RACE_ACTIVITY:
this.valueConfig = [
new ValueConfig(true, 'score', 0, false, false),
new ValueConfig(false, 'time', 10, true, true)
];
break;
case REDIS_KEY.GUILD_LV_RANK:
this.valueConfig = [
new ValueConfig(true, 'lv', 0, false, false),
new ValueConfig(false, 'active', 5, false, false)
];
break;
default:
this.valueConfig = [
new ValueConfig(true, 'score', 0, false, false),
new ValueConfig(false, 'time', 10, true, true)
];
break;
}
for(let i = 0; i < this.valueConfig.length; i++) {
let weight = 0;
for(let j = i + 1; j < this.valueConfig.length; j++) {
weight = this.valueConfig[j].len;
}
this.valueConfig[i].setWeight(weight);
}
}
/**
* 返回字段修改
* @param obj
*/
private async generFields(obj: GuildRankInfo | RoleRankInfo) {
return obj;
}
/**
* 从外部设置返回字段
* @param cb
*/
public setGenerFieldsFun(cb: (obj: GuildRankInfo | RoleRankInfo) => any) {
this.generFields = cb;
}
/**
* 设置合成的排行榜的生命时长 单位s
* @param unionRankLife
*/
public setUnionRankLife(unionRankLife: number) {
this.unionRankLife = unionRankLife;
}
/**
* 该排行是否为空
* @param void
*/
public async existsRank() {
const result = await redisClient().existsAsync(this.keyName.getName());
return result;
}
/**
* 根据role表设置更新排行榜&玩家信息
* @param roleId 玩家id
* @param score 得分
* @param timestamp 时间点
* @param role 玩家数据库数据
* @param isInc 得分是累加上的还是直接设置的
*/
public async setRankWithRoleInfo(roleId: string, score: number, timestamp: number, role?: RoleType, isInc = false) {
// 如果没有信息,更新玩家信息
for (let infoKey of [this.infoKey, ...this.extraKeys]) {
const hasCurUser = await redisClient().hexistsAsync(infoKey, roleId);
if (!hasCurUser) {
await this.generParamAndSet(infoKey, { roleId }, { role });
}
}
let newScore = await this.setRank({ roleId }, [score, timestamp], isInc);
return newScore;
}
/**
* 根据军团表设置更新排行榜&军团信息
* @param guildCode 军团code
* @param score 得分
* @param timestamp 时间点
* @param guild 军团数据库
* @param isInc 得分是累加上的还是直接设置的
*/
public async setRankWithGuildInfo(guildCode: string, score: number, timestamp: number, guild?: GuildType, isInc = false) {
return await this.setRankWithGuildInfoArrParam(guildCode, [score, timestamp], guild, isInc);
}
public async setRankWithGuildInfo2(guildCode: string, lv: number, active: number, timestamp: number, guild?: GuildType, isInc = false) {
return await this.setRankWithGuildInfoArrParam(guildCode, [lv, active, timestamp], guild, isInc);
}
public async setRankWithGuildInfoArrParam(guildCode: string, scores: number[], guild?: GuildType, isInc = false) {
for (let infoKey of [this.infoKey, ...this.extraKeys]) {
const hasCurUser = await redisClient().hexistsAsync(infoKey, guildCode);
if (!hasCurUser) {
await this.generParamAndSet(infoKey, { guildCode }, { guild });
}
}
let newScore = await this.setRank({ guildCode }, scores, isInc);
return newScore;
}
/**
* 根据武将表设置更新排行榜&玩家信息
* @param roleId 玩家id
* @param hid 武将id
* @param score 得分
* @param timestamp 时间点
* @param hero 武将数据库
* @param isInc 得分是累加上的还是直接设置的
*/
public async setRankWithHeroInfo(roleId: string, hid: number, score: number, timestamp: number, hero?: HeroType, isInc = true) {
// 如果没有信息,更新玩家信息
for (let infoKey of [this.infoKey, ...this.extraKeys]) {
await this.generParamAndSet(infoKey, { roleId, hid }, { hero });
}
let newScore = await this.setRank({ roleId, hid }, [score, timestamp], isInc);
return newScore;
}
/**
* 将玩家信息按顺序组成 如roleId:hid
* @param {{ string }} key redis key
* @param {{ roleId?: string; guildCode?: string; hid?: number }} param 玩家信息
*/
private composeFields(key: REDIS_KEY, param: myIdInter) {
let type = KEY_TO_COMPOSE_FIELD.get(key);
if (type == COMPOSE_FIELD_TYPE.ROLE) {
return param.roleId;
} else if (type == COMPOSE_FIELD_TYPE.GUILD) {
return param.guildCode;
} else if (type == COMPOSE_FIELD_TYPE.ROLE_HERO) {
return `${param.roleId}:${param.hid}`;
} else {
return '';
}
}
/**
* 将合成了的玩家顺序解开
* @param key redis key
* @param field
* @returns {{ roleId?: string; guildCode?: string; hid?: number }}
*/
private decodeFields(key: REDIS_KEY, field: string) {
let type = KEY_TO_COMPOSE_FIELD.get(key);
let arr = field.split(':');
if (type == COMPOSE_FIELD_TYPE.ROLE) {
return { roleId: arr[0] };
} else if (type == COMPOSE_FIELD_TYPE.GUILD) {
return { guildCode: arr[0] };
} else if (type == COMPOSE_FIELD_TYPE.ROLE_HERO) {
return { roleId: arr[0], hid: parseInt(arr[1]) };
} else {
return {};
}
}
/**
* 将玩家数据设置进redis
* @param infoKey 玩家信息的redis key
* @param fields 玩家id
* @param db 数据库内的数据
*/
public async generParamAndSet(infoKey: string, fields: myIdInter, db: { role?: RoleType, guild?: GuildType, hero?: HeroType }) {
let { roleId, guildCode, hid } = fields;
let { role, guild, hero } = db;
if (infoKey == REDIS_KEY.USER_INFO) {
if (!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
}
let param = new RankParam(role, true);
await this.setUserInfo(infoKey, { roleId }, param);
} else if (infoKey == REDIS_KEY.GUILD_INFO) {
if (!guild) {
guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK)
}
let param = new GuildRankParam(guild, true);
await this.setUserInfo(infoKey, { guildCode }, param);
} else if (infoKey == REDIS_KEY.TOP_LINEUP_INFO) {
if (!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
}
let { topLineup = [] } = role;
let heroes = await HeroModel.findByRole(roleId, [], HERO_SELECT.RANK_LINEUP);
let arr = new Array<LineupParam>();
for (let { hid } of topLineup) {
let curHero = heroes.find(cur => cur.hid == hid);
if (curHero) {
let param = new LineupParam(curHero);
arr.push(param);
}
}
await this.setUserInfo(infoKey, { roleId }, arr);
} else if (infoKey == REDIS_KEY.HERO_INFO) {
if (!hero) {
hero = await HeroModel.findByHidAndRole(hid, roleId, HERO_SELECT.RANK_LINEUP, true);
}
let arr = new Array<LineupParam>();
if (hero) {
let param = new LineupParam(hero);
arr.push(param);
await this.setUserInfo(infoKey, { roleId, hid }, arr);
}
} else if (infoKey == REDIS_KEY.DUNGEON_LINEUP) {
if (!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
}
let { dungeonWarId, dungeonHeroes } = role;
if (dungeonWarId > 0) {
let cur = dungeonHeroes.find(cur => cur.battleId == dungeonWarId);
let lineup = cur ? cur.heroes : [];
let heroes = await HeroModel.findByRole(roleId, [], HERO_SELECT.RANK_LINEUP);
let arr = new Array<LineupParam>();
for (let seqId of lineup) {
let curHero = heroes.find(cur => cur.seqId == seqId);
if (curHero) {
let param = new LineupParam(curHero);
arr.push(param);
}
}
await this.setUserInfo(infoKey, { roleId }, arr);
}
} else if (infoKey == REDIS_KEY.SHOW_LINEUP) {
if (!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
}
let { showLineup, topLineup = [] } = role;
let heroes = await HeroModel.findByRole(roleId, [], HERO_SELECT.RANK_LINEUP);
let arr = new Array<LineupParam>();
if (!showLineup) showLineup = topLineup.map(cur => cur.hid);
for (let hid of showLineup) {
let curHero = heroes.find(cur => cur.hid == hid);
if (curHero) {
let param = new LineupParam(curHero);
arr.push(param);
}
}
await this.setUserInfo(infoKey, { roleId }, arr);
}
}
/**
* 设置信息
* @param infoKey
* @param myId
* @param param
*/
private async setUserInfo(infoKey: REDIS_KEY, myId: myIdInter, param: RankParam | GuildRankParam | LineupParam[]) {
let field = this.composeFields(infoKey, myId);
let value = JSON.stringify(param);
return await redisClient().hsetAsync(infoKey, field, value);
}
/**
* 更新排行榜数据
* @param myId
* @param score
* @param timestamp
* @param isInc
*/
public async setRank(myId: myIdInter, scores: number[], isInc = false) {
let oldTop = await this.getRankByRank(0, 0);
let oldScore = oldTop.length > 0 && oldTop[0] ? oldTop[0].num : 0;
// 更新分数
let newScore = 0;
if (this.isUnion) {
newScore = await this.updateRankScoreAtom(myId, scores, isInc);
} else {
newScore = await this.updateRankScoreEncode(myId, scores, isInc)
}
this.checkMyRankAndPush(myId, oldScore, newScore);
return newScore;
}
/**
* @description 使用将score和time合成一个字段更新分数
* @param myId
* @param score
* @param timestamp
*/
private async updateRankScoreEncode(myId: myIdInter, scores: number[] = [], isInc = false) {
let newScore = 0;
let key = this.keyName.getName();
let newScores: number[] = [];
for(let index = 0; index < this.valueConfig.length; index ++) {
let config = this.valueConfig[index];
let score = scores[index];
if(config.isMain) {
if (isInc) {
let oldScore = await this.getMyScore(myId);
newScore = oldScore + score;
} else {
newScore = score;
}
newScores.push(newScore);
} else {
newScores.push(score);
}
}
let scoreStr = this.encodeScore(newScores);
await redisClient().zaddAsync(key, scoreStr, this.composeFields(this.key, myId));
return newScore
}
/**
* @description 使用分两个zset更新redis中某field的值
* @param myId
* @param score
* @param timestamp
* @param isInc
* @class Rank
*/
private async updateRankScoreAtom(myId: myIdInter, scores: number[], isInc = false) {
let newScore = 0;
for(let index = 0; index < this.valueConfig.length; index ++) {
let config = this.valueConfig[index];
let score = scores[index];
if(config.isMain) {
let key = this.keyName.getName();
// 分数zset
if (isInc) {
newScore = await redisClient().zincrbyAsync(key, score, this.composeFields(this.key, myId));
} else {
newScore = await redisClient().zaddAsync(key, score, this.composeFields(this.key, myId));
}
} else {
let key = this.keyName.getNameWithPlus(config.name);
if(config.isTimestamp) score = this.handleTimestamp(score, config.len);
if(config.reverse) {
let pow = Math.pow(10, config.len);
score = pow - 1 - score;
}
await redisClient().zaddAsync(key, score, this.composeFields(this.key, myId));
}
}
return parseInt(newScore.toString());
}
/**
* @description 设置到期时间
* @param time 到期时间10位时间戳
* @class Rank
*/
public async setExpire(time: number) {
let key = this.keyName.getName();
let timeKey = this.keyName.getTimeName();
await redisClient().expireatAsync(key, time);
await redisClient().expireatAsync(timeKey, time);
}
public async generMyRankWithRole(roleId: string, score: number = 0, time: number = 0, role?: RoleType) {
// 如果没有信息,更新玩家信息
let param: RoleRankInfo;
let hasCurUser = await redisClient().hexistsAsync(this.infoKey, roleId);
if (!hasCurUser) {
if (!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
}
param = new RoleRankInfo(role, true);
param.setInfo(0, { roleId }, score, time);
} else {
const info = await redisClient().hgetAsync(this.infoKey, roleId);
const userInfo = JSON.parse(info);
param = new RoleRankInfo(userInfo, false);
param.setInfo(0, { roleId }, score, time);
}
for (let extraKey of this.extraKeys) {
await this.setExInfoToParam(extraKey, param, { roleId }, { role });
}
return await this.generFields(param);
}
public async generMyRankWithGuild(guildCode: string, score: number, time: number, guild?: GuildType) {
// 如果没有信息,更新玩家信息
let param: GuildRankInfo;
let hasCurUser = await redisClient().hexistsAsync(this.infoKey, guildCode);
if (!hasCurUser) {
if (!guild) {
guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK);
}
param = new GuildRankInfo(guild, true);
param.setInfo(0, { guildCode }, score, time);
} else {
const info = await redisClient().hgetAsync(this.infoKey, guildCode);
const guildInfo = JSON.parse(info);
param = new GuildRankInfo(guildInfo, false);
param.setInfo(0, { guildCode }, score, time);
}
for (let extraKey of this.extraKeys) {
await this.setExInfoToParam(extraKey, param, { guildCode }, { guild });
}
return await this.generFields(param);
}
public async generMyRankWithGuild2(guildCode: string, score: number, time: number, active: number, guild?: GuildType) {
// 如果没有信息,更新玩家信息
let param: GuildRankInfo;
let hasCurUser = await redisClient().hexistsAsync(this.infoKey, guildCode);
if (!hasCurUser) {
if (!guild) {
guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK);
}
param = new GuildRankInfo(guild, true);
param.setInfo2(0, { guildCode }, score, time, active);
} else {
const info = await redisClient().hgetAsync(this.infoKey, guildCode);
const guildInfo = JSON.parse(info);
param = new GuildRankInfo(guildInfo, false);
param.setInfo2(0, { guildCode }, score, time, active);
}
for (let extraKey of this.extraKeys) {
await this.setExInfoToParam(extraKey, param, { guildCode }, { guild });
}
return await this.generFields(param);
}
public async generMyRankWithHero(roleId: string, hid: number, score: number, time: number, hero?: HeroType, role?: RoleType) {
// 如果没有信息,更新玩家信息
let param: RoleRankInfo;
let hasCurUser = await redisClient().hexistsAsync(this.infoKey, roleId);
if (!hasCurUser) {
if (!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
}
param = new RoleRankInfo(role, true);
param.setInfo(0, { roleId, hid }, score, time);
} else {
const info = await redisClient().hgetAsync(this.infoKey, roleId);
const userInfo = JSON.parse(info);
param = new RoleRankInfo(userInfo, false);
param.setInfo(0, { roleId, hid }, score, time);
}
for (let extraKey of this.extraKeys) {
await this.setExInfoToParam(extraKey, param, { roleId, hid }, { hero, role });
}
return await this.generFields(param);
}
private async setExInfoToParam(extraKey: string, param: RoleRankInfo | GuildRankInfo, myId: myIdInter, db: { role?: RoleType, guild?: GuildType, hero?: HeroType }) {
let { role, guild, hero } = db;
if (extraKey == REDIS_KEY.TOP_LINEUP_INFO) {
param = <RoleRankInfo>param;
let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId));
if (!hasCurUser) {
if (!role) {
role = await RoleModel.findByRoleId(myId.roleId, ROLE_SELECT.RANK, true);
}
let { topLineup } = role;
let heroes = await HeroModel.findByRole(myId.roleId, [], HERO_SELECT.HERO_DETAIL);
let arr = new Array<LineupParam>();
for (let { hid } of topLineup) {
let curHero = heroes.find(cur => cur.hid == hid);
if (curHero) {
let param = new LineupParam(curHero);
arr.push(param);
}
}
param.setTopLine(arr);
} else {
const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId));
const infoObj = JSON.parse(info);
param.setTopLine(infoObj);
}
} else if (extraKey == REDIS_KEY.HERO_INFO) {
param = <RoleRankInfo>param;
let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId));
if (!hasCurUser) {
if (!hero) {
hero = await HeroModel.findByHidAndRole(myId.hid, myId.roleId, HERO_SELECT.RANK_LINEUP);
}
let arr = new Array<LineupParam>();
let lineParam = new LineupParam(hero);
arr.push(lineParam);
if (this.key == REDIS_KEY.HERO_RANK) {
param.setSingleHero(arr);
} else {
param.setTopLine(arr);
}
} else {
const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId));
const infoObj = JSON.parse(info);
if (this.key == REDIS_KEY.HERO_RANK) {
param.setSingleHero(infoObj);
} else {
param.setTopLine(infoObj);
}
}
} else if (extraKey == REDIS_KEY.DUNGEON_LINEUP) {
param = <RoleRankInfo>param;
let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId));
if (!hasCurUser) {
if (!role) {
role = await RoleModel.findByRoleId(myId.roleId, ROLE_SELECT.RANK, true);
}
let { dungeonWarId, dungeonHeroes } = role;
let cur = dungeonHeroes.find(cur => cur.battleId == dungeonWarId);
let lineup = cur ? cur.heroes : [];
let heroes = await HeroModel.findByRole(myId.roleId, [], HERO_SELECT.RANK_LINEUP);
let arr = new Array<LineupParam>();
for (let seqId of lineup) {
let curHero = heroes.find(cur => cur.seqId == seqId);
if (curHero) {
let param = new LineupParam(curHero);
arr.push(param);
}
}
param.setTopLine(arr);
} else {
const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId));
const infoObj = JSON.parse(info);
param.setTopLine(infoObj);
}
} else if (extraKey == REDIS_KEY.SHOW_LINEUP) {
param = <RoleRankInfo>param;
let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId));
if (!hasCurUser) {
if (!role) {
role = await RoleModel.findByRoleId(myId.roleId, ROLE_SELECT.RANK, true);
}
let { showLineup, topLineup = [] } = role;
let heroes = await HeroModel.findByRole(myId.roleId, [], HERO_SELECT.HERO_DETAIL);
let arr = new Array<LineupParam>();
if (!showLineup) showLineup = topLineup.map(cur => cur.hid);
for (let hid of showLineup) {
let curHero = heroes.find(cur => cur.hid == hid);
if (curHero) {
let param = new LineupParam(curHero);
arr.push(param);
}
}
param.setTopLine(arr);
} else {
const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId));
const infoObj = JSON.parse(info);
param.setTopLine(infoObj);
}
}
}
public async getRankListWithMyRank(myId: myIdInter) {
let ranks = await this.getRankByRange();
let newRanks = [], newMyRank: RoleRankInfo | GuildRankInfo;
let myRank = ranks.find(cur => {
return cur.isMyInfo(myId);
});
if (this.generFields) {
for (let rank of ranks) {
let n = await this.generFields(rank);
newRanks.push(n);
}
if (myRank) {
newMyRank = await this.generFields(myRank);
}
}
return { myRank: newMyRank, ranks: newRanks }
}
public async getRankByRange(from: number | string = '+inf', to: number | string = '-inf') {
let ranks = new Array<RoleRankInfo | GuildRankInfo>();
let key = this.keyName.getName();
if (this.isUnion) {
key = await this.generateUnionRank();
}
const rankFromDb = await redisClient().zrevrangebyscoreAsync(key, from, to, "WITHSCORES");
let num = 0;
for (let ii = 0; ii < rankFromDb.length; ii += 2) {
if (num >= this.limit) break;
const field = rankFromDb[ii];
let myId = this.decodeFields(this.key, field);
const scores = this.decodeScore(rankFromDb[ii + 1]);
const info = await redisClient().hgetAsync(this.infoKey, this.composeFields(this.infoKey, myId));
const userInfo = JSON.parse(info);
let param: RoleRankInfo | GuildRankInfo;
if (this.infoKey == REDIS_KEY.USER_INFO) {
if (nowSeconds() - userInfo.updatedAt > 2 * 30 * 24 * 60 * 60) {
continue;
}
param = new RoleRankInfo(userInfo, false);
param.setInfo(Math.floor(ii / 2) + 1, myId, scores[0], scores[1]);
} else if (this.infoKey == REDIS_KEY.GUILD_INFO) {
if(this.key == REDIS_KEY.GUILD_LV_RANK) {
param = new GuildRankInfo(userInfo, false);
param.setInfo2(Math.floor(ii / 2) + 1, myId, scores[0], scores[1], scores[2]);
} else {
param = new GuildRankInfo(userInfo, false);
param.setInfo(Math.floor(ii / 2) + 1, myId, scores[0], scores[1]);
}
}
for (let extraKey of this.extraKeys) {
await this.setExInfoToParam(extraKey, param, myId, {});
}
ranks.push(param);
num++;
}
return ranks
}
public async getRankByRank(start: number, end: number) {
let ranks = new Array<RoleRankInfo | GuildRankInfo>();
let key = this.keyName.getName();
if (this.isUnion) {
key = await this.generateUnionRank();
}
const rankFromDb = await redisClient().zrevrangeAsync(key, start, end, "WITHSCORES");
// console.log(key, start, end, rankFromDb)
let num = 0;
for (let ii = 0; ii < rankFromDb.length; ii += 2) {
if (num >= this.limit) break;
const field = rankFromDb[ii];
let myId = this.decodeFields(this.key, field);
const scores = this.decodeScore(rankFromDb[ii + 1]);
const info = await redisClient().hgetAsync(this.infoKey, this.composeFields(this.infoKey, myId));
const userInfo = JSON.parse(info);
let param: RoleRankInfo | GuildRankInfo;
if (this.infoKey == REDIS_KEY.USER_INFO) {
if (nowSeconds() - userInfo.updatedAt > 2 * 30 * 24 * 60 * 60) {
continue;
}
param = new RoleRankInfo(userInfo, false);
param.setInfo(Math.floor(ii / 2) + 1, myId, scores[0], scores[1]);
} else if (this.infoKey == REDIS_KEY.GUILD_INFO) {
if(this.key == REDIS_KEY.GUILD_LV_RANK) {
param = new GuildRankInfo(userInfo, false);
param.setInfo2(Math.floor(ii / 2) + 1, myId, scores[0], scores[1], scores[2]);
} else {
param = new GuildRankInfo(userInfo, false);
param.setInfo(Math.floor(ii / 2) + 1, myId, scores[0], scores[1]);
}
}
for (let extraKey of this.extraKeys) {
await this.setExInfoToParam(extraKey, param, myId, {});
}
ranks.push(param);
num++;
}
return ranks
}
// 获取我的排名
public async getMyRank(myId: myIdInter) {
let key = this.keyName.getName();
if (this.isUnion) {
key = await this.generateUnionRank();
}
// console.log('**getMyRank',key, this.composeFields(this.key, myId), this.key, myId)
let myRank = await redisClient().zrevrankAsync(key, this.composeFields(this.key, myId));
if (!myRank && myRank != 0) {
return 0;
} else {
return myRank + 1;
}
}
// 获取排名第几名的信息
public async getUserByRank(rank: number) {
let key = this.keyName.getName();
let myRank = await redisClient().zrevrangeAsync(key, rank - 1, rank - 1);
return myRank;
}
// 获取排名第几名的信息
public async getMyScore(myId: myIdInter) {
let key = this.keyName.getName();
let score = await redisClient().zscoreAsync(key, this.composeFields(this.key, myId));
if (!score) score = 0;
if (!this.isUnion) {
let result = this.decodeScore(score.toString());
score = result[0];
}
return parseInt(score.toString());
}
// 从排行榜中移除
public async removeFromRank(myId: myIdInter) {
let key = this.keyName.getName();
await redisClient().zremAsync(key, this.composeFields(this.key, myId));
return true;
}
private async generateUnionRank() {
let unionKey = this.keyName.getUnionName(); // 联合的key
let keys = [], weights = [];
this.valueConfig.forEach(({ isMain, name, weight }) => {
if(isMain) {
keys.push(this.keyName.getName());
} else {
keys.push(this.keyName.getNameWithPlus(name));
}
weights.push(Math.pow(10, weight))
})
await redisClient().zunionstoreAsync(unionKey, 2, ...keys, 'WEIGHTS', ...weights);
await redisClient().expireAsync(unionKey, this.unionRankLife); // 10秒更新一次
return unionKey;
}
// 有序排行综合时间和得分排序
private encodeScore(scores: number[]) {
let encodeResult = 0;
this.valueConfig.forEach(({ reverse, isTimestamp, len, weight }, index) => {
let score = scores[index];
if(isTimestamp) score = this.handleTimestamp(score, len);
if(!reverse) {
encodeResult += score * Math.pow(10, weight);
} else {
let pow = Math.pow(10, len);
encodeResult += pow - 1 - score;
}
})
return encodeResult
}
private decodeScore(num: string) {
let _num = parseInt(num);
let scores: number[] = [];
this.valueConfig.forEach(({ reverse, isTimestamp, len, weight }) => {
let pow = Math.pow(10, weight);
let score = Math.floor(_num / pow);
_num -= pow * score;
if(isTimestamp) score = this.handleTimestamp(score, len);
if(reverse) {
let pow = Math.pow(10, len);
score = pow - 1 - score;
}
scores.push(score);
})
return scores;
}
private handleTimestamp(timestamp: number = 0, len: number) {
let l = timestamp.toString().length;
if (l > len) {
timestamp = Math.floor(timestamp / Math.pow(10, l - len));
}
return timestamp
}
private async checkMyRankAndPush(myId: myIdInter, oldScore: number, newScore: number) {
let ts = this;
if (ts.isInit) return;
let myRank = await this.getMyRank(myId);
// console.log('*******', ts.key, myRank, newScore, oldScore);
if (myRank == 1 && newScore > oldScore) {
let serverId = this.keyName.serverId;
let sid = await getWorldChannelSid(serverId);
let dicRank = gameData.rank.find(({ id }) => {
let redisKey = RANK_TYPE_TO_KEY.get(id);
return redisKey == ts.key
});
// console.log('*******', dicRank);
if(dicRank) {
let r = new Rank(ts.key, { serverId }, false, 1);
let ranks = await r.getRankByRange();
let rankFirstRecs: (RankFirstType & { status: number })[] = [];
if(ranks.length > 0 && ranks[0]) {
let userInfo: RankParam, guildInfo: GuildRankParam;
if(ranks[0] instanceof RoleRankInfo) {
userInfo = ranks[0];
}
if(ranks[0] instanceof GuildRankInfo) {
guildInfo = ranks[0];
}
for(let [id, { rankId, condition }] of gameData.generalRankReward) {
// console.log(rankId, dicRank.id, oldScore, condition, newScore);
if(dicRank.id == rankId && oldScore < condition && newScore >= condition) {
let rankFirstRec = await RankFirstModel.createRankFirst(serverId, id, rankId, { userInfo, guildInfo });
let roleServers = pinus.app.getServersByType('role');
for(let server of roleServers) {
pinus.app.rpc.role.roleRemote.setRankFirst.toServer(server.id, rankFirstRec);
}
rankFirstRecs.push({ ... rankFirstRec, status: 1});
}
}
}
if(rankFirstRecs.length > 0) {
pinus.app.rpc.chat.chatRemote.sendRankTopUpdated.toServer(sid, serverId, rankFirstRecs);
}
}
}
}
}
/**
* 从数据库内获取排行榜存入redis
* @param type 排行榜类型
* @param serverId 分服
*/
export async function setRankRedisFromDb(type: string, args?: { serverId?: number }) {
if (type == REDIS_KEY.TOWER_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('tower', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
// console.log(roleId);
if(role.towerLv > 1) {
await r.setRankWithRoleInfo(role.roleId, role.towerLv, role.towerUpTime ? role.towerUpTime.getTime() : 0, role);
}
}
} else if (type == REDIS_KEY.GUILD_ACTIVE_RANK) {
let serverId = args.serverId;
let ranks = await GuildModel.getRank(type, serverId);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let guild of ranks) {
await r.setRankWithGuildInfo(guild.code, guild.activeWeekly, guild.activeUpdateTime, guild);
}
} else if (type == REDIS_KEY.GUILD_LV_RANK) {
let serverId = args.serverId;
let ranks = await GuildModel.getRank(type, serverId);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let guild of ranks) {
await r.setRankWithGuildInfoArrParam(guild.code, [guild.lv, guild.activeWeekly, guild.activeUpdateTime], guild);
}
} else if (type == REDIS_KEY.PVP_RANK) {
let seasonNum = await CounterModel.getCounter(COUNTER.PVP_SEASON_NUM);
if(seasonNum > 0) {
let keyName = new KeyName(type, { seasonNum })
await delKeys(keyName.getName());
let ranks = await PvpDefenseModel.getRank(seasonNum);//获得全服前1000名的排名加入到redis中
let r = new Rank(type, { seasonNum });
r.setIsInit(true);
for (let { roleId, role: _role, score, updatedAt } of ranks) {
let role = <RoleType>_role;
if (!role) {
continue;
}
await r.setRankWithRoleInfo(roleId, score, updatedAt.getTime(), role);
}
}
} else if (type == REDIS_KEY.TOP_LINEUP_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('topLineup', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
// console.log(roleId);
await r.setRankWithRoleInfo(role.roleId, role.topLineupCe, role.updatedAt.getTime(), role);
}
} else if (type == REDIS_KEY.TOP_HERO_RANK) {
let serverId = args.serverId;
let ranks = await HeroModel.getAllRank(serverId, HERO_SELECT.RANK_LINEUP);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let hero of ranks) {
await r.setRankWithHeroInfo(hero.roleId, hero.hid, hero.ce, hero.updatedAt.getTime(), hero);
}
} else if (type == REDIS_KEY.HERO_NUM_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('heroNum', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
await r.setRankWithRoleInfo(role.roleId, role.heroNum, role.heroNumUpdatedAt, role);
}
} else if (type == REDIS_KEY.USER_LV) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('lv', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
await r.setRankWithRoleInfo(role.roleId, role.lv, role.updatedAt.getTime(), role);
}
} else if (type == REDIS_KEY.SUM_CE_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('ce', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
await r.setRankWithRoleInfo(role.roleId, role.ce, role.updatedAt.getTime(), role);
}
} else if (type == REDIS_KEY.DUNGEON_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('dungeon', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
await r.setRankWithRoleInfo(role.roleId, role.dungeonWarId, role.dungeonUpdatedAt, role);
}
} else if (type == REDIS_KEY.MAIN_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('main', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
await r.setRankWithRoleInfo(role.roleId, role.mainWarId, role.mainUpdatedAt, role);
}
} else if (type == REDIS_KEY.MAIN_ELITE_RANK) {
let serverId = args.serverId;
let ranks = await RoleModel.getRank('mainElite', serverId, ROLE_SELECT.RANK);
let r = new Rank(type, { serverId });
r.setIsInit(true);
for (let role of ranks) {
await r.setRankWithRoleInfo(role.roleId, role.mainEliteWarId, role.mainEliteUpdatedAt, role);
}
} else if (type == REDIS_KEY.HERO_RANK) {
let serverId = args.serverId;
for (let hid of gameData.dicMyHeroes) {
let ranks = await HeroModel.getRank(hid, serverId, HERO_SELECT.RANK_LINEUP);
let r = new Rank(type, { serverId, hid });
r.setIsInit(true);
for (let hero of ranks) {
await r.setRankWithHeroInfo(hero.roleId, hid, hero.ce, hero.updatedAt.getTime(), hero);
}
}
}
}
/**
* 获取排行榜总览
* @param role 玩家数据
* @param serverId 所在服务器
*/
export async function getGeneralRank(role: RoleType & { rankReceived: number[] }, serverId: number) {
let { rankReceived = [] } = role;
let result: (GeneralRankParam & {general: number})[] = [];
for (let { id, general } of gameData.rank) {
let redisKey = RANK_TYPE_TO_KEY.get(id);
if (redisKey) {
let received = rankReceived.filter(rewardId => {
let dic = gameData.generalRankReward.get(rewardId);
return dic && dic.rankId == id;
});
let r = new Rank(redisKey, { serverId }, false, 1);
let ranks = await r.getRankByRange();
// if (ranks.length > 0) {
let param = new GeneralRankParam(id, ranks[0] || new RoleRankInfo({}, false), general, received);
result.push({...param, general});
}
}
return result;
}
/**
* 获取排行榜总览
* @param role 玩家数据
* @param serverId 所在服务器
*/
export async function getRankFirstReward(role: RoleType & { rankReceived: number[] }, serverId: number) {
let { rankReceived = [] } = role;
let result: (RankFirstUpdateParam & {status: number})[] = [];
for (let [id, { rankId: type } ] of gameData.generalRankReward) {
let rankFirst: RankFirstType;
if(pinus.app.getServerType() == 'role') {
rankFirst = pinus.app.get('rankFirstRewards').get(serverId)?.get(id);
} else {
let roleServers = pinus.app.getServersByType('role');
let server = getRandSingleEelm(roleServers);
rankFirst = await pinus.app.rpc.role.roleRemote.getRankFirstById.toServer(server.id, serverId, id);
}
if(rankFirst) {
let status = rankReceived.includes(id)?RANK_FIRST_REWARD_STATUS.RECEIVED: RANK_FIRST_REWARD_STATUS.CAN_RECEIVE;
result.push({...rankFirst, status});
} else {
result.push({ id, type, status: RANK_FIRST_REWARD_STATUS.WAITING });
}
}
return result;
}