// 征战中原相关 import moment = require("moment"); import { GVG_PERIOD, GVG_RETURN_ITEM_TYPE, REDIS_KEY, VESTIGE_OPP_STATUS, VESTIGE_STATUS } from "../../consts"; import { ArtifactModel } from "../../db/Artifact"; import { GVGConfigModel } from "../../db/GVGConfig"; import { GVGLeagueModel, GVGLeagueType } from "../../db/GVGLeague"; import { GVGUserDataModel } from "../../db/GVGUserData"; import { GVGVestigeModel } from "../../db/GVGVestige"; import { GVGVestigeLeagueRankModel, GVGVestigeLeagueRankType } from "../../db/GVGVestigeLeagueRank"; import { GVGVestigeLockModel } from "../../db/GVGVestigeLock"; import { GVGVestigeRankModel, GVGVestigeRankType, GVGVestigeRankUpdate } from "../../db/GVGVestigeRank"; import { GVGVestigeRecType } from "../../db/GVGVestigeRec"; import { GVGVestigeSumRankModel, GVGVestigeSumRankType } from "../../db/GVGVestigeSumRank"; import { HeroModel, HeroType } from "../../db/Hero"; import { RoleModel } from "../../db/Role"; import { OppDetailData, OppPlayerHeroInfo, OppPlayerInfo } from "../../domain/gvgField/gvgDb"; import { GVGVestigeOppPlayer } from "../../domain/gvgField/returnData"; import { KeyNameParam, LeagueRankInfo, myIdInter, RoleRankInfo } from "../../domain/rank"; import { gameData, getGVGVestigeLeagueRank, getGVGVestigePlayerRank, getGVGVestigeRange } from "../../pubUtils/data"; import { GVG } from "../../pubUtils/dicParam"; import { RewardInter } from "../../pubUtils/interface"; import { getTimeFun, nowSeconds } from "../../pubUtils/timeUtil"; import { getRandEelm } from "../../pubUtils/util"; import { getNumberArr, uniqueArr } from "../ladderService"; import { getHeroesAttributes } from "../playerCeService"; import { Rank } from "../rankService"; import { findKeys, getAllServerName, redisClient } from "../redisService"; import { isDevelopEnv } from "../utilService"; import { combinePushItem } from "./gvgItemService"; import { addVestigeLeagueRankRec, addVestigeRankMessage } from "./gvgRecService"; import { getGroupIdOfServer, getGroupKey, getGVGServerType, getPeriodByTime } from "./gvgService"; let gvgFightTime = undefined; // !!注意,这个函数会改动内存,线上不应该使用 export function setPeriodTime(startFightTime: number, endFightTime: number) { gvgFightTime = { startFightTime, endFightTime } } export function resetPeriodTime() { gvgFightTime = undefined; } // 备战期的遗迹和激战期的开始结束战斗时间 export function getFightTimeByPeriod(period: GVG_PERIOD, time?: number) { let dicPeriod = gameData.gvgPeriod.get(period); if(!dicPeriod) return { startFightTime: 0, endFightTime: 0 }; if(isDevelopEnv() && gvgFightTime) { return gvgFightTime; } return { startFightTime: getTimeFun(time).getTimeWithHour(dicPeriod.startHour, dicPeriod.startMinute, dicPeriod.startSecond), endFightTime: getTimeFun(time).getTimeWithHour(dicPeriod.endHour, dicPeriod.endMinute, dicPeriod.endSecond), } } export function checkFightTime() { let { startFightTime, endFightTime } = getFightTimeByPeriod(GVG_PERIOD.PREPARE); let now = nowSeconds(); return startFightTime <= now && endFightTime >= now; } // 征战中原每天随机的遗迹点 export async function getVestiges(serverId: number) { let groupKey = await getGroupKey(serverId); let serverType = await getGVGServerType(serverId); let vestige = await GVGVestigeModel.getVestige(groupKey); if(!vestige) { let dicGVGVestige = gameData.gvgVestigeByServerType.get(serverType)||[]; let cnt = gameData.gvgVestigeCntByServerType.get(serverType)||0; let randResult = getRandEelm(dicGVGVestige, cnt); vestige = await GVGVestigeModel.initTodayVestige(groupKey, randResult); } return vestige.vestiges||[]; } export async function getMyVestiges(serverId: number, roleId: string) { let vestiges = await getVestiges(serverId); let myRanks = await GVGVestigeRankModel.findAllByRole(roleId); let result: { vestigeId: number, position: string, myRank: number }[] = []; for(let vestige of vestiges) { let myRank = myRanks.find(cur => cur.vestigeId == vestige.vestigeId); result.push({ ...vestige, myRank: myRank?.rank||0 }); } return result; } export async function checkVestige(serverId: number, vestigeId: number) { let vestiges = await getVestiges(serverId); return !!vestiges.find(cur => cur.vestigeId == vestigeId); } // 根据自己排名随机出对手排名 export function refreshVestigeOppRanks(rank: number) { let dicRange = getGVGVestigeRange(rank); if(!dicRange) return []; let { rangeBeforeFrom, rangeBeforeTo, rangeBeforeNum, rangeAfterFrom, rangeAfterTo, rangeAfterNum } = dicRange; let beforeRanks = randomRank(rangeBeforeFrom == -1? rank: rangeBeforeFrom, rangeBeforeTo == -1? rank: rangeBeforeTo, rangeBeforeNum); let afterRanks = randomRank(rangeAfterFrom == -1? rank: rangeAfterFrom, rangeAfterTo == -1? rank: rangeAfterTo, rangeAfterNum); let topRanks = getNumberArr(dicRange.topChallengeFrom, dicRange.topChallengeTo); let ranks = [...topRanks, ...beforeRanks, ...afterRanks]; return uniqueArr(ranks.sort((a, b) => a - b)); } function randomRank(min: number, max: number, len: number) { if(min == max || len == 0) return []; let arr = getNumberArr(min + 1, max); return getRandEelm(arr, len); } // 根据ranks获取oppPlayer显示数据 export async function getOppPlayerByRanks(serverId: number, groupKey: string, vestigeId: number, ranks: number[]) { let serverNames = await getAllServerName(); let opps = await GVGVestigeRankModel.findByRanks(groupKey, vestigeId, ranks); let roleIds = opps.map(cur => cur.roleId); let roles = await RoleModel.findByRoleIds(roleIds, 'roleId roleName head frame spine heads frames spines title lv guildName serverId updatedAt') let dicRankMap = gameData.gvgVestige.get(vestigeId); if(!dicRankMap) return []; let result: GVGVestigeOppPlayer[] = []; for(let rank of ranks) { let obj = new GVGVestigeOppPlayer(rank); let opp = opps.find(cur => cur.rank == rank); let dicCurRank = dicRankMap.get(rank); if(opp) { let role = roles.find(cur => cur.roleId == opp.roleId); if(!role) continue; let lineupce = opp.lineup.reduce((pre, cur) => pre + cur.ce, 0); const { leagueCode, name } = opp; obj.setByRole(role, serverNames, lineupce, dicCurRank?.score||0, leagueCode, name); } else { obj.setByDic(dicCurRank, serverNames[serverId]); } result.push(obj); } return result; } // 获取其他遗迹里使用过的武将 export async function getVestigeUsedHeroes(roleId: string, curVestigeId: number) { let myDatas = await GVGVestigeRankModel.findAllByRole(roleId); let result: number[] = []; for(let { vestigeId, lineup = [] } of myDatas) { if(vestigeId == curVestigeId) continue; result.push(...lineup.map(cur => cur.actorId)); } return result; } // 是否在其他遗迹中使用过这个武将 export async function checkHeroIsUsedInOtherVestige(roleId: string, curVestigeId: number, heroes: { actorId: number }[]) { let usedHeroes = await getVestigeUsedHeroes(roleId, curVestigeId); for(let { actorId } of heroes) { if(usedHeroes.indexOf(actorId) != -1) return true; } return false; } // 检查我的排名是否可以挑战对方的排名 export function checkVestigeRank(myRank: number, targetRank: number) { let dicRange = getGVGVestigeRange(myRank); if(!dicRange) return false; if(myRank == targetRank || targetRank == 0) return false; // 是否可以挑战最前的几个排名 if(dicRange.topChallengeFrom <= targetRank && dicRange.topChallengeTo >= targetRank) return true; if(dicRange.rangeBeforeTo == -1) { if(dicRange.rangeBeforeFrom < targetRank && myRank > targetRank) return true; } else { if(dicRange.rangeBeforeFrom < targetRank && dicRange.rangeBeforeTo >= targetRank) return true; } if(dicRange.rangeAfterTo == -1) { if(dicRange.rangeAfterFrom < targetRank && myRank > targetRank) return true; } else { if(dicRange.rangeAfterFrom < targetRank && dicRange.rangeAfterTo >= targetRank) return true; } return false; } export function isRobot(roleId: string) { if(!roleId) roleId = ''; return roleId.startsWith('robot'); } export async function checkVestigeOppStatus(configId: number, groupKey: string, vestigeId: number, roleId: string, myRank: number, targetRoleId: string, rank: number) { let myData = await getMyVestigeRank(configId, groupKey, vestigeId, roleId); if(!myData || myData.rank != myRank) return VESTIGE_OPP_STATUS.MY_RANK_CHANGE; if(isRobot(targetRoleId)) { let targetData = await GVGVestigeRankModel.findByRank(configId, groupKey, vestigeId, rank); if(targetData) return VESTIGE_OPP_STATUS.OPP_RANK_CHANGE; } else { let targetData = await GVGVestigeRankModel.findByRole(vestigeId, targetRoleId); if(targetData?.rank != rank) return VESTIGE_OPP_STATUS.OPP_RANK_CHANGE; } let lockResult = await GVGVestigeLockModel.chooseOppLock(groupKey, vestigeId, rank, configId, roleId, targetRoleId); if(!lockResult) return VESTIGE_OPP_STATUS.OPP_IS_LOCKED; return VESTIGE_OPP_STATUS.BATTLE; } // 生成防守数据 export async function generateDefenseInfo(roleId: string, vestigeId: number, rank: number) { if(isRobot(roleId)) { let dicRank = gameData.gvgVestige.get(vestigeId)?.get(rank); // 调用之前已经验证过了 let dicWar = gameData.war.get(dicRank?.warId); let dicWarJson = gameData.warJson.get(dicWar?.dispatchJsonId); if(!dicWarJson) return new OppPlayerInfo(); let heroes: OppPlayerHeroInfo[] = []; for(let json of dicWarJson) { if(json.relation == 2) { let hero = new OppPlayerHeroInfo(); hero.setByWarJson(json); heroes.push(hero); } } let info = new OppPlayerInfo(); info.initByRobot(dicRank, heroes, true); return info; } else { let playerVestigeData = await GVGVestigeRankModel.findByRole(vestigeId, roleId); if(!playerVestigeData) return null; let lineup = playerVestigeData.lineup||[]; let hids = lineup.map(cur => cur.actorId); let role = await RoleModel.findByRoleId(playerVestigeData.roleId, 'roleId roleName heads head frames frame spines spine title lv'); let heroes = await HeroModel.findByHidRange(hids, playerVestigeData.roleId, 'hid skinId lv star colorStar quality ce'); let league = await GVGLeagueModel.findByCode(playerVestigeData.leagueCode); let info = new OppPlayerInfo(); info.initByPlayer(playerVestigeData.rank, role, league, true); info.setPlayerHeroes(heroes, lineup); return info; } } export async function generateAttackInfo(roleId: string, league: GVGLeagueType, rank: number) { let role = await RoleModel.findByRoleId(roleId, 'roleId roleName heads head frames frame spines spine title lv'); let info = new OppPlayerInfo(); info.initByPlayer(rank, role, league, false); return info; } export async function generateAttackHeroInfo(lineup: { actorId: number, dataId: number, order: number }[], dbHeroes: HeroType[]) { let info = new OppPlayerInfo(); info.setPlayerHeroes(dbHeroes, lineup); return info; } export async function getOppDetailData(rec: GVGVestigeRecType) { let isRobot = rec.defenseInfo.isRobot; let dicLadderDifficultRatio = gameData.ladderDifficultRatio.get(rec.defenseInfo.oldRank); let result = new OppDetailData(rec, getVestigeRecStatus(rec)); if(!isRobot) { const dicWar = gameData.war.get(dicLadderDifficultRatio.gkId); const dicWarJson = gameData.warJson.get(dicWar.dispatchJsonId); const defenseInfo = rec.defenseInfo.heroes||[]; const hids = defenseInfo.map(cur => cur.hid); const heroes = await HeroModel.findByHidRange(hids, rec.defenseRoleId, 'hid lv quality star colorStar skins ce skinId'); const artifactSeids = heroes.map(hero => hero.artifact); const artifacts = await ArtifactModel.findbySeqIds(rec.defenseRoleId, artifactSeids); result.setByPlayer(dicWarJson, defenseInfo, heroes, artifacts); let attrByHid = await getHeroesAttributes(rec.defenseRoleId); for(let [hid, attribute] of attrByHid) { result.setAttribute(hid, attribute.getAttributesToString()); } } return result; } // 根据记录里的时间判断当前状态 export function getVestigeRecStatus(rec: GVGVestigeRecType) { let { checkTime = 0, battleTime = 0, endTime = 0, cancel } = rec; if(cancel) return { status: VESTIGE_STATUS.COMPLETE, time: Math.floor(endTime/1000) }; if(endTime > 0 && endTime <= Date.now()) return { status: VESTIGE_STATUS.COMPLETE, time: Math.floor(endTime/1000) }; if(battleTime > 0 && battleTime <= Date.now() && battleTime + GVG.GVG_VESTIGE_BATTLE_COUNTDOWN * 1000 > Date.now()) return { status: VESTIGE_STATUS.BATTLE, time: Math.floor(battleTime/1000) + GVG.GVG_VESTIGE_BATTLE_COUNTDOWN }; if(checkTime > 0 && checkTime <= Date.now() && checkTime + GVG.GVG_VESTIGE_PREPARE_COUNTDOWN * 1000 > Date.now()) return { status: VESTIGE_STATUS.CHECK, time: Math.floor(checkTime/1000) + GVG.GVG_VESTIGE_PREPARE_COUNTDOWN }; // 超时的 if(battleTime > 0 && endTime == 0 && battleTime + GVG.GVG_VESTIGE_BATTLE_COUNTDOWN * 1000 <= Date.now()) return { status: VESTIGE_STATUS.COMPLETE, time: Math.floor(battleTime/1000) + GVG.GVG_VESTIGE_BATTLE_COUNTDOWN }; if(checkTime > 0 && battleTime == 0 && checkTime + GVG.GVG_VESTIGE_PREPARE_COUNTDOWN * 1000 <= Date.now()) return { status: VESTIGE_STATUS.COMPLETE, time: Math.floor(checkTime/1000) + GVG.GVG_VESTIGE_PREPARE_COUNTDOWN }; return { status: VESTIGE_STATUS.NO, time: 0 }; } // 将开始战斗时候的阵容存到防守阵容 export async function updateMyVestigeRank(isChange: boolean, atkData: GVGVestigeRankType, defData: GVGVestigeRankType, historyRank: number, rec: GVGVestigeRecType) { let update: GVGVestigeRankUpdate = {}, needUpdate = false; if(isChange) { if(atkData && !atkData.hasDefense) { // 只有第一次挑战且自己没有主动保存过阵容的时候才会这么保存 let heroes = rec.attackInfo.heroes||[]; let lineup = heroes.map(hero => ({ actorId: hero.hid, dataId: hero.dataId, order: hero.order, ce: hero.ce })); update.lineup = lineup; update.hasDefense = true; needUpdate = true; } if(atkData && (historyRank == 0 || atkData.rank < historyRank)) { // 更新历史最高排名 update.historyRank = atkData.rank; needUpdate = true; } if(atkData) { // 排名变化时刷新对手 let oppRanks = refreshVestigeOppRanks(atkData.rank); update.oppRanks = oppRanks; needUpdate = true; } } if(needUpdate) { atkData = await GVGVestigeRankModel.updateByRoleId(atkData.vestigeId, atkData.roleId, update); } if(isChange && defData) { let oppRanks = refreshVestigeOppRanks(defData.rank); defData = await GVGVestigeRankModel.updateByRoleId(defData.vestigeId, defData.roleId, { oppRanks }); } return atkData } export function calBreakGoods(vestige: number, historyRank: number, rank: number) { let dicRankMap = gameData.gvgVestige.get(vestige); if(!dicRankMap) return { rewards: [], leagueRewards: [] }; let rewards: RewardInter[] = [], leagueRewards: RewardInter[] = []; for(let i = historyRank + 1; i <= rank; i++) { let dicRank = dicRankMap.get(i); if(!dicRank) continue; combinePushItem(rewards, dicRank.onceReward); combinePushItem(leagueRewards, dicRank.onceLeagueReward); } return { rewards, leagueRewards } } // 玩家当天的驻扎记录,没有的话就创建一个 export async function getMyVestigeRank(configId: number, groupKey: string, vestigeId: number, roleId: string, myLeague?: GVGLeagueType) { let myVestigeRank = await GVGVestigeRankModel.findByRole(vestigeId, roleId); // 我在这个遗迹的排名 if(!myVestigeRank) { let role = await RoleModel.findByRoleId(roleId, 'roleId guildCode'); let oppRanks = refreshVestigeOppRanks(-1); if(!myLeague) { myLeague = await GVGLeagueModel.findLeagueByGuild(role.guildCode); if(!myLeague) return null } const { leagueCode, name } = myLeague; myVestigeRank = await GVGVestigeRankModel.initRank(configId, vestigeId, groupKey, role, leagueCode, name, oppRanks ); } return myVestigeRank; } // 保存排行榜 export async function saveScoreToRank(rec: GVGVestigeRecType) { await savePlayerRank(rec.configId, rec.groupKey, rec.vestigeId, rec.attackInfo); await savePlayerRank(rec.configId, rec.groupKey, rec.vestigeId, rec.defenseInfo); } export function getDayKeyInfo() { return moment().format('YYMMDD'); } export async function savePlayerRank(configId: number, groupKey: string, vestigeId: number, playerInfo: Partial) { if(playerInfo.isRobot) return let dicRankMap = gameData.gvgVestige.get(vestigeId); let newRank = dicRankMap?.get(playerInfo.newRank)?.score||0; let oldRank = dicRankMap?.get(playerInfo.oldRank)?.score||0; let nextWeek = getTimeFun().getAfterDayWithHour(7); let myScore = await GVGVestigeSumRankModel.incScore(playerInfo.roleId, playerInfo.leagueCode, configId, groupKey, newRank - oldRank); let r1 = new Rank(REDIS_KEY.GVG_VESTIGE_MEMBER_ALL, { groupKey, day: getDayKeyInfo() }); await r1.setExpire(nextWeek); await r1.setRankWithRoleInfo(playerInfo.roleId, myScore.score, myScore.updatedAt.getTime()); let leagueScore = await GVGVestigeLeagueRankModel.incScore(configId, playerInfo.leagueCode, groupKey, newRank - oldRank); let r2 = new Rank(REDIS_KEY.GVG_VESTIGE_LEAGUE, { groupKey, day: getDayKeyInfo() }); await r2.setExpire(nextWeek); await r2.setRankWithLeagueInfo(playerInfo.leagueCode, leagueScore.score, leagueScore.updatedAt.getTime()); } export async function getVestigeRank(redisKey: REDIS_KEY, isSimple: boolean, keyParam: KeyNameParam, myId: myIdInter, serverNames?: any) { let r = new Rank(redisKey, keyParam); r.setGenerFieldsFun((obj => { if(redisKey == REDIS_KEY.GVG_VESTIGE_MEMBER_ALL && obj instanceof RoleRankInfo) { if(isSimple) { return { rank: obj.rank, name: obj.roleName, serverName: serverNames[obj.serverId], score: obj.num } } else { return { ...obj, serverName: serverNames[obj.serverId], score: obj.num } } } if(redisKey == REDIS_KEY.GVG_VESTIGE_LEAGUE && obj instanceof LeagueRankInfo) { if(isSimple) { return { rank: obj.rank, name: obj.name, score: obj.num } } else { return { ...obj, score: obj.num, leader: {...obj.leader, serverName: serverNames[obj.leader.serverId]}}; } } return obj })); let { ranks, myRank } = await r.getRankListWithMyRank(myId); if (!myRank) { if(redisKey == REDIS_KEY.GVG_VESTIGE_MEMBER_ALL) { myRank = await r.generMyRankWithRole(myId.roleId, 0, 0); } else if (redisKey == REDIS_KEY.GVG_VESTIGE_LEAGUE) { myRank = await r.generMyRankWithLeague(myId.leagueCode, 0, 0); } } return { ranks, myRank } } // systimer 服 export async function saveVestigeRankSchedule() { let config = await GVGConfigModel.findConfig(); let period = getPeriodByTime(config.teamTime, config.prepareTime, config.battleTime, config.scheduleTime); if(period != GVG_PERIOD.PREPARE) return; let day = getDayKeyInfo(); let keys = await findKeys(`${REDIS_KEY.GVG_VESTIGE_LEAGUE}:${day}:`); for(let key of keys) { let [,, groupKey] = key.split(':'); let r = new Rank(REDIS_KEY.GVG_VESTIGE_LEAGUE, { groupKey, day}); let ranks = await r.getRankByRangeRaw(); await GVGVestigeLeagueRankModel.saveRank(groupKey, ranks); await addVestigeLeagueRankRec(config.configId, groupKey, ranks); await addVestigeRankMessage(ranks); } let memberKeys = await findKeys(`${REDIS_KEY.GVG_VESTIGE_MEMBER_ALL}:${day}:`); for(let key of memberKeys) { let [,, groupKey] = key.split(':'); let r = new Rank(REDIS_KEY.GVG_VESTIGE_MEMBER_ALL, { groupKey, day}); let ranks = await r.getRankByRangeRaw(); await GVGVestigeSumRankModel.saveRank(groupKey, ranks); } let playerSumRanks = await GVGVestigeSumRankModel.findAllScores(); await GVGUserDataModel.addVestigeScores(config.configId, playerSumRanks); } export function calVestigeLeagueBoxRewards(canReceiveRanks: GVGVestigeLeagueRankType[], canReceivePlayerRanks: GVGVestigeSumRankType[]) { let rewards: RewardInter[] = [], leagueReward: RewardInter[] = []; for(let { rank } of canReceiveRanks) { let dicRank = getGVGVestigeLeagueRank(rank); if(!dicRank) { console.error('dic_zyz_GVGVestigeLeagueRank error'); continue; } // console.log('###### league', rank, dicRank) combinePushItem(rewards, dicRank.rankReward); combinePushItem(leagueReward, dicRank.rankLeagueReward); } for(let { rank } of canReceivePlayerRanks) { let dicRank = getGVGVestigePlayerRank(rank); if(!dicRank) { console.error('dic_zyz_GVGVestigPlayerRank error'); continue; } // console.log('###### player', rank, dicRank) combinePushItem(rewards, dicRank.rankPlayerReward); combinePushItem(leagueReward, dicRank.rankPlayerLeagueReward); } let boxPreview: { id: number, count: number, itemType: number }[] = []; for(let { id, count } of rewards) boxPreview.push({ id, count, itemType: GVG_RETURN_ITEM_TYPE.NORMAL_ITEM }); for(let { id, count } of leagueReward) boxPreview.push({ id, count, itemType: GVG_RETURN_ITEM_TYPE.GVG_ITEM }); return { leagueReward, rewards, boxPreview, canReceiveBox: canReceiveRanks.length > 0 || canReceivePlayerRanks.length > 0 } }