From 19b70baf8c2d9e68614a3a2aa02a04bedaff9ed6 Mon Sep 17 00:00:00 2001 From: luying Date: Fri, 10 Feb 2023 21:23:52 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(gvg):=20=E6=BF=80=E6=88=98?= =?UTF-8?q?=E6=9C=9F=E5=86=85=E5=AD=98=E4=BB=A5=E5=8F=8A=E7=8E=A9=E5=AE=B6?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E3=80=81=E6=8C=91=E6=88=98=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servers/guild/handler/gvgBattleHandler.ts | 210 ++++++++++-------- .../app/services/gvg/gvgBattleMemory.ts | 86 +++++++ .../app/services/gvg/gvgBattleService.ts | 38 +++- shared/consts/statusCode.ts | 10 + shared/db/GVGBattleRec.ts | 79 +++++++ shared/db/GVGCity.ts | 46 ++-- shared/db/GVGLeagueFarm.ts | 2 +- shared/db/GVGTeam.ts | 97 +++++++- shared/db/GVGUserData.ts | 1 + shared/domain/battleField/gvgBattle.ts | 51 ++++- shared/domain/gvgField/returnData.ts | 2 +- shared/domain/roleField/hero.ts | 6 +- shared/pubUtils/data.ts | 10 +- shared/pubUtils/dicParam.ts | 7 +- shared/pubUtils/dictionary/DicGVGArea.ts | 6 +- shared/pubUtils/dictionary/DicGVGAreaPoint.ts | 10 + 16 files changed, 536 insertions(+), 125 deletions(-) create mode 100644 game-server/app/services/gvg/gvgBattleMemory.ts create mode 100644 shared/db/GVGBattleRec.ts diff --git a/game-server/app/servers/guild/handler/gvgBattleHandler.ts b/game-server/app/servers/guild/handler/gvgBattleHandler.ts index c09a1ddff..ba6105a08 100644 --- a/game-server/app/servers/guild/handler/gvgBattleHandler.ts +++ b/game-server/app/servers/guild/handler/gvgBattleHandler.ts @@ -1,5 +1,4 @@ import { GVGCityType } from './../../../db/GVGCity'; -import { LeagueCityPoint, GVGTeamMem } from './../../../domain/battleField/gvgBattle'; import { GVGRecModel } from '../../../db/GVGRec'; import { LeagueGood } from '../../../domain/gvgField/returnData'; import { GVGTeamModel } from '../../../db/GVGTeam'; @@ -10,7 +9,11 @@ import { DEBUG_MAGIC_WORD, GVG_PERIOD, STATUS } from "../../../consts"; import { LineupHero } from "../../../domain/roleField/hero"; import { resResult, genCode } from "../../../pubUtils/util"; import { GVGLeagueModel } from '../../../db/GVGLeague'; -import { checkGVGPeriod, getGVGPeriodData } from '../../../services/gvg/gvgService'; +import { checkGVGPeriod, getGroupIdOfServer, getGVGPeriodData, getGVGServerType } from '../../../services/gvg/gvgService'; +import { checkMoveStatus, getBirthAreaOfCity, initRobots } from '../../../services/gvg/gvgBattleService'; +import { getGVGBattleData } from '../../../services/gvg/gvgBattleMemory'; +import { nowSeconds } from '../../../pubUtils/timeUtil'; +import { GVGBattleRecModel } from '../../../db/GVGBattleRec'; export default function (app: Application) { new HandlerService(app, {}); @@ -20,15 +23,15 @@ export default function (app: Application) { export class GVGBattleHandler { channelService: ChannelService; - // 积分点占领情况,cityId -> LeagueCode -> LeagueCityPoint - private pointOccupy: Map> = new Map(); - // 城池队伍状态,cityId -> areaId -> GVGTeamMem - private cityTeamStatus: Map> = new Map(); - constructor(private app: Application) { this.channelService = app.get('channelService'); } + // 获取我的编队信息 + async getTeams(msg: {}, session: BackendSession) { + + } + // 保存队伍 // index: 队伍索引位置 // head: 头像 @@ -53,60 +56,63 @@ export class GVGBattleHandler { // 获取城池信息 async getCity(msg: { cityId: number }, session: BackendSession) { - if(checkGVGPeriod(GVG_PERIOD.BATTLE)) return resResult(STATUS.GVG_NOT_BATTLE_PERIOD); const { cityId } = msg; let { configId } = getGVGPeriodData(); const city = await GVGCityModel.findOne({ configId, cityId }).lean(); - return resResult(STATUS.SUCCESS, { city }); + return resResult(STATUS.SUCCESS, { + cityId, + guardLeague: city.guardLeague + }); + } + + // 进入城池之前的检查 + async checkMyTeam(msg: { cityId: number }, session: BackendSession) { + } // 进入城池 async enterCity(msg: { cityId: number }, session: BackendSession) { - if(checkGVGPeriod(GVG_PERIOD.BATTLE)) return resResult(STATUS.GVG_NOT_BATTLE_PERIOD); + if(!checkGVGPeriod(GVG_PERIOD.BATTLE)) return resResult(STATUS.GVG_NOT_BATTLE_PERIOD); - const roleId = session.get('roleId') - const guildCode = session.get('guildCode') + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + const guildCode = session.get('guildCode'); const { cityId } = msg; let { configId } = getGVGPeriodData(); - let city: GVGCityType = await GVGCityModel.findOne({ cityId, configId }).lean(); - if (!city) { - return resResult(STATUS.GVG_CITY_NOT_FOUND); - } + let groupId = await getGroupIdOfServer(serverId); + let serverType = await getGVGServerType(serverId); - // 检测是否已经在城池中,如果在城池中,直接返回城池信息 let myLeague = await GVGLeagueModel.findLeagueByGuild(guildCode); if(!myLeague) return resResult(STATUS.GVG_LEAGUE_NOT_EXIST); + + await initRobots(groupId, serverType, cityId); + + let city: GVGCityType = await GVGCityModel.findByCityId(configId, groupId, serverType, cityId); + let gvgUserData = await GVGUserDataModel.findByRole(configId, myLeague.leagueCode, roleId); if (!gvgUserData) { return resResult(STATUS.GVG_USER_NOT_FOUND); } - if (gvgUserData.cityId === cityId) { - return resResult(STATUS.SUCCESS, { city }); - } - // 不在城池则检测是否满员 - const { userCnt } = city; - // 检测是否满员 - if (userCnt >= 200) { - return resResult(STATUS.GVG_BATTLE_CITY_FULL); - } + // 检测是否已经在城池中,玩家不在这个城池中时进行处理 + if (gvgUserData.cityId !== cityId) { + if(gvgUserData.cityId > 0) { // 如果leaveCity没有退出成功,玩家还遗留在上一座城中,做一下处理 + await GVGCityModel.decreasePlayer(configId, groupId, serverType, gvgUserData.cityId, roleId); + } + city = await GVGCityModel.increasePlayer(configId, groupId, serverType, cityId, roleId, myLeague.leagueCode); + if(!city) return resResult(STATUS.GVG_BATTLE_CITY_FULL); - // 检测玩家是否已经在其他城池中,由 checkMyTeam 接口检测 - // if (gvgUserData.cityId) { - // } - - const roleTeamCnt = await GVGTeamModel.getTeamCnt(roleId); - city = await GVGCityModel.updateCityUser(configId, cityId, 1, roleTeamCnt); - - gvgUserData = await GVGUserDataModel.changeCity(configId, myLeague.leagueCode, roleId, cityId); - // ! 队伍默认进入的据点暂时设为 0;更新内存队伍信息 - const res = await GVGTeamModel.resetTeamsLoc(roleId, cityId, 0); - if (!res) { - return resResult(STATUS.GVG_RESET_TEAM_LOC_FAILED); + gvgUserData = await GVGUserDataModel.changeCity(configId, myLeague.leagueCode, roleId, cityId); + // 更新内存队伍信息 + let areaId = getBirthAreaOfCity(city, myLeague.leagueCode); + await GVGTeamModel.enterCity(roleId, cityId, areaId, groupId, serverType); + let teams = await GVGTeamModel.findByRole(roleId); + let teamObj = getGVGBattleData(groupId, serverType); + teamObj.enterCity(teams); } return resResult(STATUS.SUCCESS, { city }); @@ -114,11 +120,16 @@ export class GVGBattleHandler { // 离开城池 async leaveCity(msg: { cityId: number }, session: BackendSession) { - if(checkGVGPeriod(GVG_PERIOD.BATTLE)) return resResult(STATUS.GVG_NOT_BATTLE_PERIOD); - const roleId = session.get('roleId') - const guildCode = session.get('guildCode') + + if(!checkGVGPeriod(GVG_PERIOD.BATTLE)) return resResult(STATUS.GVG_NOT_BATTLE_PERIOD); + + const roleId = session.get('roleId'); + const guildCode = session.get('guildCode'); + const serverId = session.get('serverId'); const { cityId } = msg; let { configId } = getGVGPeriodData(); + let groupId = await getGroupIdOfServer(serverId); + let serverType = await getGVGServerType(serverId); // 检测是否已经在城池中 let myLeague = await GVGLeagueModel.findLeagueByGuild(guildCode); @@ -131,20 +142,17 @@ export class GVGBattleHandler { return resResult(STATUS.GVG_USER_NOT_IN_CITY); } - const roleTeamCnt = await GVGTeamModel.getTeamCnt(roleId); - const city = await GVGCityModel.updateCityUser(configId, cityId, -1, -roleTeamCnt); + const city = await GVGCityModel.decreasePlayer(configId, groupId, serverType, cityId, roleId); if (!city) { return resResult(STATUS.GVG_CITY_NOT_FOUND); } // 更新玩家城池和队伍城池 gvgUserData = await GVGUserDataModel.changeCity(configId, myLeague.leagueCode, roleId, 0); - const res = await GVGTeamModel.resetTeamsLoc(roleId, 0, 0); - if (!res) { - return resResult(STATUS.GVG_RESET_TEAM_LOC_FAILED); - } - - // ! 还需处理内存数据 + await GVGTeamModel.enterCity(roleId, 0, 0, groupId, serverType); + // 处理内存数据 + let teamObj = getGVGBattleData(groupId, serverType); + teamObj.leaveCity(roleId); return resResult(STATUS.SUCCESS); } @@ -152,19 +160,46 @@ export class GVGBattleHandler { // 开始移动 // areaId: 要移动的目标据点 id async startMove(msg: { cityId: number, areaId: number, teamCode: string }, session: BackendSession) { - const { areaId, cityId } = msg; - const moveTime = Date.now(); + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + const { areaId, cityId, teamCode } = msg; - return resResult(STATUS.SUCCESS, { areaId, cityId, moveTime }); + let groupId = await getGroupIdOfServer(serverId); + let serverType = await getGVGServerType(serverId); + + let team = await GVGTeamModel.findByTeamCode(roleId, teamCode); + let checkResult = checkMoveStatus(team, cityId, areaId); + if(checkResult.code != 0) return resResult(checkResult); + + team = await GVGTeamModel.startMove(teamCode, areaId); + + // 更新内存数据 + let teamObj = getGVGBattleData(groupId, serverType); + teamObj.move(teamCode, areaId, team.startMoveTime, team.stopMoveTime); + + return resResult(STATUS.SUCCESS, { areaId, cityId, stopMoveTime: team.stopMoveTime }); } // 停止移动 // areaId: 移动到的目标据点 id async stopMove(msg: { cityId: number, areaId: number, teamCode: string }, session: BackendSession) { + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); const { areaId, cityId, teamCode } = msg; - const players = await GVGUserDataModel.find({ cityId, areaId }).limit(20).lean(); - const curTeam = await GVGTeamModel.findOneAndUpdate({ teamCode }, { areaId }).lean(); - return resResult(STATUS.SUCCESS, { areaId, cityId, players, curTeam }); + + let groupId = await getGroupIdOfServer(serverId); + let serverType = await getGVGServerType(serverId); + + let team = await GVGTeamModel.findByTeamCode(roleId, teamCode); + if(!team) return resResult(STATUS.GVG_BATTLE_TEAM_NOT_FOUND); + if(team.cityId != cityId && team.areaId != areaId) return resResult(STATUS.GVG_BATTLE_IS_NOT_IN_CITY); + + team = await GVGTeamModel.stopMove(teamCode, areaId); + // 更新内存数据 + let teamObj = getGVGBattleData(groupId, serverType); + teamObj.move(teamCode, areaId, team.startMoveTime, team.stopMoveTime); + + return resResult(STATUS.SUCCESS, { areaId, cityId, curTeam: team }); } // 队伍入驻积分点 @@ -182,29 +217,36 @@ export class GVGBattleHandler { // 队伍开始攻击 // teamCode: 攻击方队伍 // oppoTeamCode: 防守方队伍 - async battleStart(msg: { teamCode: string, oppoTeamCode: string }, session: BackendSession) { + async battleStart(msg: { cityId: number, teamCode: string, oppoTeamCode: string, pointId: number }, session: BackendSession) { + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); const { teamCode, oppoTeamCode } = msg; - const teams = await GVGTeamModel.find({ teamCode: { $in: [ teamCode, oppoTeamCode ] } }).lean(); - let teamInvalid = false; - let invalidTeamCode = ''; - teams.forEach(team => { - if (team.attackTime > Date.now() - 1000 * 5) { - teamInvalid = true; - invalidTeamCode = team.teamCode; - } - }); - if (teamInvalid) { - return resResult(STATUS.GVG_BATTLE_TEAM_INVALID, { teamCode: invalidTeamCode }); - } - // 生成 battleCode - const battleCode = genCode(8); - return resResult(STATUS.SUCCESS, { battleCode }); + + let groupId = await getGroupIdOfServer(serverId); + let serverType = await getGVGServerType(serverId); + + let { attackTeam, defenseTeam } = await GVGTeamModel.findBattleTeams(teamCode, oppoTeamCode); + if(!attackTeam || !defenseTeam) return resResult(STATUS.GVG_BATTLE_TEAM_INVALID); + if(attackTeam.attackTime > nowSeconds()) return resResult(STATUS.GVG_TEAM_ATTACKING); + if(defenseTeam.defenseTime > nowSeconds()) return resResult(STATUS.GVG_TEAM_DEFENSEING); + + const battleRecord = await GVGBattleRecModel.createRec(attackTeam, defenseTeam); + + attackTeam = await GVGTeamModel.lockAttack(teamCode, groupId, serverType); + defenseTeam = await GVGTeamModel.lockDefense(oppoTeamCode, groupId, serverType); + // 内存处理 + let teamObj = getGVGBattleData(groupId, serverType); + teamObj.setTime(teamCode, attackTeam); + teamObj.setTime(teamCode, defenseTeam); + + return resResult(STATUS.SUCCESS, { battleCode: battleRecord.battleCode, curTeam: attackTeam }); } // 队伍停止攻击 - async battleEnd(msg: { battleCode: string, isSuccess: boolean }, session: BackendSession) { + async battleEnd(msg: { cityId: number, battleCode: string, isSuccess: boolean }, session: BackendSession) { const { battleCode, isSuccess } = msg; - // ! 根据 battleCode 获取 teamCode + const record = await GVGBattleRecModel.findByBattleCode(battleCode); + if(!record) return resResult(STATUS.GVG_BATTLEREC_NOT_FOUND); const teamCode = ''; const leagueGoods: LeagueGood[] = null; // ! 计算并更新两支队伍耐久 @@ -223,7 +265,7 @@ export class GVGBattleHandler { // 使用道具 // teamCode: 要使用道具的队伍 - async useItem(msg: { itemId: number, teamCode: string }, session: BackendSession) { + async useItem(msg: { cityId: number, itemId: number, teamCode: string }, session: BackendSession) { const { itemId, teamCode } = msg; // ! 检查道具是否存在 // ! 检查道具是否可以使用在该队伍 @@ -233,7 +275,7 @@ export class GVGBattleHandler { } // 复活队伍 - async reviveTeam(msg: { teamCode: string }, session: BackendSession) { + async reviveTeam(msg: { cityId: number, teamCode: string }, session: BackendSession) { const { teamCode } = msg; // ! 检查该队伍是否可以复活,可以的话更新队伍状态 const team = await GVGTeamModel.findOneAndUpdate({ teamCode }, {}).lean(); @@ -269,22 +311,4 @@ export class GVGBattleHandler { ]); return resResult(STATUS.SUCCESS, { teams }); } - - // debug 接口,创建城市 - async createCity(msg: { cityId: number, magicWord: string }, session: BackendSession) { - const { cityId, magicWord } = msg; - if (magicWord !== DEBUG_MAGIC_WORD) { - return resResult(STATUS.INTERNAL_ERR); - } - let { configId } = getGVGPeriodData(); - - // 检查城市是否存在 - let city = await GVGCityModel.getCityByCityId(configId, cityId); - if (city) { - return resResult(STATUS.SUCCESS, { city }); - } - - city = await GVGCityModel.createCity(configId, cityId); - return resResult(STATUS.SUCCESS, { city }); - } } diff --git a/game-server/app/services/gvg/gvgBattleMemory.ts b/game-server/app/services/gvg/gvgBattleMemory.ts new file mode 100644 index 000000000..f2c5d062f --- /dev/null +++ b/game-server/app/services/gvg/gvgBattleMemory.ts @@ -0,0 +1,86 @@ +// 存在激战期的内存数据 + +import { GVGTeamType } from "../../db/GVGTeam"; +import { GVGTeamMem } from "../../domain/battleField/gvgBattle"; + +// 积分点占领情况,groupId_serverType_cityId -> GVGBattleData +const gvgBattleMap: Map = new Map(); + +class GVGBattleData { + public groupId: number; // 战区 + public serverType: number; // 单服还是跨服 + + private teams: Map = new Map(); // 队伍, teamCode => team + private rolePoints: Map = new Map(); // roleId => pointId[],用于更新玩家的积分 + private roleToTeam: Map = new Map(); // roleId => teamCode + private areaToTeams: Map> = new Map(); // areaId => teamCode set,用于定时下发地图玩家数据 + + constructor(groupId: number, serverType: number) { + this.groupId = groupId; + this.serverType = serverType; + } + + public leaveCity(roleId: string) { + let teamCodes = this.roleToTeam.get(roleId)||[]; + for(let teamCode of teamCodes) { + let team = this.teams.get(teamCode); + if(team) team.setCity(0, 0); + } + this.rolePoints.delete(roleId); + } + + public enterCity(teams: GVGTeamType[]) { + for(let team of teams) { + if(!this.teams.has(team.teamCode)) { + this.teams.set(team.teamCode, new GVGTeamMem(team)) + } + let originTeam = this.teams.get(team.teamCode); + let fromAreaId = originTeam.areaId; + originTeam.setCity(team.cityId, team.areaId); + if(team.pointId > 0) { + if(!this.rolePoints.has(team.roleId)) this.rolePoints.set(team.roleId, []); + this.rolePoints.get(team.roleId).push(team.pointId); + } + let teamCodesOfRole = this.roleToTeam.get(team.roleId)||[]; + if(teamCodesOfRole.indexOf(team.teamCode) == -1) teamCodesOfRole.push(team.teamCode); + this.roleToTeam.set(team.roleId, teamCodesOfRole); + this.setAreaMap(team.teamCode, fromAreaId, team.areaId); + } + } + + private setAreaMap(teamCode: string, fromAreaId: number, toAreaId: number) { + if(fromAreaId == toAreaId) return; + let fromAreaSet = this.areaToTeams.get(fromAreaId); + if(fromAreaSet && fromAreaSet.has(teamCode)) fromAreaSet.delete(teamCode); + + if(!this.areaToTeams.has(toAreaId)) { + this.areaToTeams.set(toAreaId, new Set()); + } + if(!this.areaToTeams.get(toAreaId).has(teamCode)) { + this.areaToTeams.get(toAreaId).add(teamCode); + } + } + + public move(teamCode: string, areaId: number, startMoveTime: number, stopMoveTime: number) { + let team = this.teams.get(teamCode); + if(!team) return; + let fromAreaId = team.areaId; + team.moveToArea(areaId, startMoveTime, stopMoveTime); + this.setAreaMap(teamCode, fromAreaId, areaId); + } + + public setTime(teamCode: string, teamUpdate: GVGTeamType) { + let team = this.teams.get(teamCode); + if(!team) return; + team.attackTime = teamUpdate.attackTime; + team.defenseTime = teamUpdate.defenseTime; + } +} + +export function getGVGBattleData(groupId: number, serverType: number) { + let key = `${groupId}_${serverType}`; + if(!gvgBattleMap.has(key)) { + gvgBattleMap.set(key, new GVGBattleData(groupId, serverType)); + } + return gvgBattleMap.get(key); +} \ No newline at end of file diff --git a/game-server/app/services/gvg/gvgBattleService.ts b/game-server/app/services/gvg/gvgBattleService.ts index d84280aac..d1404b55c 100644 --- a/game-server/app/services/gvg/gvgBattleService.ts +++ b/game-server/app/services/gvg/gvgBattleService.ts @@ -1,6 +1,11 @@ import { GVGTeamMem } from "../../domain/battleField/gvgBattle"; import { GVGLeagueType } from "../../db/GVGLeague"; -import { GVGTeamType } from "../../db/GVGTeam"; +import { GVGTeamModel, GVGTeamType } from "../../db/GVGTeam"; +import { GVGCityType } from "../../db/GVGCity"; +import { gameData } from "../../pubUtils/data"; +import { STATUS } from "../../consts"; +import { nowSeconds } from "../../pubUtils/timeUtil"; +import { DicGVGAreaPoint } from "../../pubUtils/dictionary/DicGVGAreaPoint"; /** @@ -33,9 +38,38 @@ export async function getGVGCitiesInfo(league: GVGLeagueType): Promise<{cityId: * 获取内存中队伍的数据结构 */ export function getGVGTeamMemInfo(team: GVGTeamType): GVGTeamMem { - const teamMem = team as GVGTeamMem; + const teamMem = new GVGTeamMem(team); teamMem.isMoving = false; teamMem.startMoveTime = 0; teamMem.stopMoveTime = 0; return teamMem; } + +export function getBirthAreaOfCity(city: GVGCityType, leagueCode: string) { + let isGuard = city.guardLeague == leagueCode; + let dicGVGCity = gameData.gvgCity.get(city.cityId); + return isGuard? dicGVGCity.defenseBirth: dicGVGCity.attackBirth; +} + +export function checkMoveStatus(team: GVGTeamType, cityId: number, areaId: number) { + if(!team) return STATUS.GVG_BATTLE_TEAM_NOT_FOUND; + if(team.cityId != cityId) return STATUS.GVG_BATTLE_IS_NOT_IN_CITY; + if(team.pointId > 0) return STATUS.GVG_BATTLE_TEAM_IS_SELLTED; + if(team.stopMoveTime > 0 && team.stopMoveTime < nowSeconds()) return STATUS.GVG_BATTLE_IS_MOVING; + let dicArea = gameData.gvgArea.get(areaId); + if(!dicArea) return STATUS.DIC_DATA_NOT_FOUND; + if(dicArea.relateArea.indexOf(team.areaId) == -1) return STATUS.GVG_BATTLE_AREA_NOT_RELATE; + return STATUS.SUCCESS; +} + +export async function initRobots(groupId: number, serverType: number, cityId: number) { + let hasRobot = await GVGTeamModel.checkRobot(groupId, serverType, cityId); + if(!hasRobot) { + let dicPoints: DicGVGAreaPoint[] = []; + let { areaIds = []} = gameData.gvgCity.get(cityId); + for(let [_, point] of gameData.gvgAreaPoint) { + if(areaIds.indexOf(point.areaId) != -1) dicPoints.push(point); + } + await GVGTeamModel.initRobots(groupId, serverType, cityId, dicPoints); + } +} \ No newline at end of file diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index 97c975723..02f1d1b40 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -358,6 +358,16 @@ export const STATUS = { GVG_RECEIVE_NO_RANK_REWARD: { code: 21368, simStr: '没有可领取的奖励' }, GVG_VESTIGE_TIME_OUT: { code: 21369, simStr: '今天遗迹已结算,不可再挑战' }, + // GVG激战期 + GVG_BATTLE_TEAM_NOT_FOUND: { code: 21400, simStr: '未找到该编队' }, + GVG_BATTLE_TEAM_IS_SELLTED: { code: 21401, simStr: '编队正在驻守据点,不可移动' }, + GVG_BATTLE_AREA_NOT_RELATE: { code: 21402, simStr: '该据点与原据点不相连' }, + GVG_BATTLE_IS_MOVING: { code: 21403, simStr: '编队正在移动中' }, + GVG_BATTLE_IS_NOT_IN_CITY: { code: 21404, simStr: '编队不在这个城池中' }, + GVG_TEAM_ATTACKING: { code: 21405, simStr: '您的编队正在攻击冷却中' }, + GVG_TEAM_DEFENSEING: { code: 21406, simStr: '选择的编队正在防守冷却中' }, + GVG_BATTLEREC_NOT_FOUND: { code: 21407, simStr: '未找到该记录' }, + // 通用 30000 - 30099 DIC_DATA_NOT_FOUND: { code: 30000, simStr: '数据表未找到' }, ROLE_MATERIAL_NOT_ENOUGH: { code: 30001, simStr: '材料数量不足' }, diff --git a/shared/db/GVGBattleRec.ts b/shared/db/GVGBattleRec.ts new file mode 100644 index 000000000..381fd9b9d --- /dev/null +++ b/shared/db/GVGBattleRec.ts @@ -0,0 +1,79 @@ +// GVGBattleRec 数据库表,挑战记录信息 +import BaseModel from "./BaseModel"; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; +import { genCode } from "../pubUtils/util"; +import { LineupHero } from './../domain/roleField/hero'; +import { GVGTeamType } from "./GVGTeam"; + +class TeamInfo { + @prop({ required: true }) + roleId: string; // 玩家id + + @prop({ required: true }) + teamCode: string; // 玩家队伍唯一标识 + + @prop({ required: true }) + teamId: number; // 队伍id,1-3,玩家的第几个队伍 + + @prop({ required: true }) + leagueCode: string; // 联军 + + @prop({ required: true }) + guildCode: string; // 军团 + + @prop({ required: false }) + areaId: number; + + @prop({ required: false }) + cityId: number; + + @prop({ required: false }) + pointId: number; + + @prop({ required: true }) + head: number; // 头像 + + @prop({ required: true }) + spine: number; // 形象 + + @prop({ required: true }) + frame: number; // 相框 + + @prop({ required: true, default: 0 }) + durability: number; // 耐久 + + @prop({ required: true, type: () => LineupHero, _id: false }) + lineup: LineupHero[] +} + +@index({ battleCode: 1 }) +export default class GVGBattleRec extends BaseModel { + @prop({ required: true }) + battleCode: string; // 战斗记录 + + @prop({ required: true, type: TeamInfo, _id: false }) + attackTeam: TeamInfo; // 挑战队伍 + + @prop({ required: true, type: TeamInfo, _id: false }) + defenseTeam: TeamInfo; // 防守队伍 + + @prop({ required: true }) + isSuccess: boolean; + + public static async createRec(attackTeam: GVGTeamType, defenseTeam: GVGTeamType) { + const battleCode = genCode(8); + const result: GVGBattleRecType = await GVGBattleRecModel.findOneAndUpdate({ battleCode }, { $set: { attackTeam, defenseTeam, isSuccess: false } }).lean(); + return result; + } + + public static async findByBattleCode(battleCode: string) { + const result: GVGBattleRecType = await GVGBattleRecModel.findOne({ battleCode }).lean(); + return result; + } +} + +export const GVGBattleRecModel = getModelForClass(GVGBattleRec); + +export interface GVGBattleRecType extends Pick, keyof GVGBattleRec> {}; + +export type GVGBattleRecUpdate = Partial; // 将所有字段变成可选项 \ No newline at end of file diff --git a/shared/db/GVGCity.ts b/shared/db/GVGCity.ts index 0d319800d..783c36e57 100644 --- a/shared/db/GVGCity.ts +++ b/shared/db/GVGCity.ts @@ -1,17 +1,31 @@ import BaseModel from "./BaseModel"; import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; -@index({ configId: 1, cityId: 1 }) +class Player { + @prop({ required: true }) + leagueCode: string; + @prop({ required: true }) + roleId: string; +} + + +@index({ configId: 1, cityId: 1, groupId: 1, serverType: 1 }) // GVGCity 类,继承自 BaseModel export default class GVGCity extends BaseModel { @prop({ required: true, default: 1 }) configId: number; // config唯一id + @prop({ required: true, default: 1 }) + groupId: number; // 战区 + + @prop({ required: true, default: 1 }) + serverType: number; // 1-单服 2-跨服 + @prop({ required: true }) cityId: number; // 城池id - @prop({ required: true, default: [] }) - leagueCodes: string[]; // 联军 + @prop({ required: true, default: [], type: Player, _id: false }) + players: Player[]; // 联军 @prop({ required: false }) guardLeague: string; // 占领的联军 @@ -19,29 +33,35 @@ export default class GVGCity extends BaseModel { @prop({ required: false }) guardLeagueName: string; // 占领的联军 + @prop({ required: false }) + guardLeagueIcon: number; // 占领的联军 + @prop({ required: true, default: 0 }) userCnt: number; // 城池人数 - @prop({ required: true, default: 0 }) - teamCnt: number; // 城池队伍数 - // 创建城市 - public static async createCity(configId: number, cityId: number) { + public static async createCity(configId: number, groupId: number, serverType: number, cityId: number) { let doc = new GVGCityModel(); let update = Object.assign(doc.toJSON(), { configId, cityId}); - const city: GVGCityType | null = await GVGCityModel.findOneAndUpdate({ configId, cityId }, { $setOnInsert: update }, { upsert: true, new: true }).lean(); + const city: GVGCityType = await GVGCityModel.findOneAndUpdate({ configId, cityId, groupId, serverType }, { $setOnInsert: update }, { upsert: true, new: true }).lean(); return city; } // 通过 cityId 获取城市 - public static async getCityByCityId(configId: number, cityId: number) { - const city: GVGCityType | null = await GVGCityModel.findOne({ configId, cityId }).lean(); + public static async findByCityId(configId: number, groupId: number, serverType: number, cityId: number) { + const city: GVGCityType = await GVGCityModel.findOne({ configId, groupId, serverType, cityId }).lean(); return city; } - // 更新城市字段 - public static async updateCityUser(configId: number, cityId: number, userInc: number, teamInc: number) { - const city: GVGCityType | null = await GVGCityModel.findOneAndUpdate({ configId, cityId}, { $inc: {userCnt: userInc, teamCnt: teamInc} }, { new: true }).lean(); + // 添加人数 + public static async increasePlayer(configId: number, groupId: number, serverType: number, cityId: number, roleId: string, leagueCode: string) { + const city: GVGCityType = await GVGCityModel.findOneAndUpdate({ configId, groupId, serverType, cityId, userCnt: { $lt: 200 } }, { $inc: { userCnt: 1 }, $push: { players: { roleId, leagueCode } }}, { new: true, upsert: true }).lean(); + return city; + } + + // 减少人数 + public static async decreasePlayer(configId: number, groupId: number, serverType: number, cityId: number, roleId: string) { + const city: GVGCityType = await GVGCityModel.findOneAndUpdate({ configId, groupId, serverType, cityId, 'players.roleId': roleId }, { $inc: { userCnt: -1 }, $pull: { players: { roleId } }}, { new: true, upsert: true }).lean(); return city; } } diff --git a/shared/db/GVGLeagueFarm.ts b/shared/db/GVGLeagueFarm.ts index d33552c1b..9a56548bc 100644 --- a/shared/db/GVGLeagueFarm.ts +++ b/shared/db/GVGLeagueFarm.ts @@ -105,7 +105,7 @@ export default class GVGLeagueFarm extends BaseModel { public static async lockFields(configId: number, leagueCode: string, farmId: number, type: number, roleId: string, roleName: string, lands: { fieldId: number, addType: number }[]) { // 先创建 await GVGLeagueFarmModel.bulkWrite(lands.map(({ fieldId }) => { - return { updateOne: { filter: { configId, leagueCode, farmId, fieldId }, update: { $setOnInsert: { unlockTime: 0, harvestTime: 0, seedType: 0, addType: 0, addTypes: [], type } }, upsert: true} } + return { updateOne: { filter: { configId, leagueCode, farmId, fieldId }, update: { $setOnInsert: { unlockTime: 0, harvestTime: 0, seedType: 0, addType: 0, addTypes: [], type, index: 0 } }, upsert: true} } })); const result = await GVGLeagueFarmModel.bulkWrite(lands.map(({ fieldId, addType }) => { if(addType > 0) { diff --git a/shared/db/GVGTeam.ts b/shared/db/GVGTeam.ts index 1cf29365d..727858e50 100644 --- a/shared/db/GVGTeam.ts +++ b/shared/db/GVGTeam.ts @@ -3,10 +3,14 @@ import BaseModel from "./BaseModel"; import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; import { genCode } from "../pubUtils/util"; import { LineupHero } from './../domain/roleField/hero'; +import { nowSeconds } from "../pubUtils/timeUtil"; +import { GVG } from "../pubUtils/dicParam"; +import { DicGVGAreaPoint } from "../pubUtils/dictionary/DicGVGAreaPoint"; @index({ roleId: 1, teamId: 1 }) @index({ teamCode: 1 }) export default class GVGTeam extends BaseModel { + @prop({ required: true }) roleId: string; // 玩家id @@ -19,6 +23,9 @@ export default class GVGTeam extends BaseModel { @prop({ required: true }) leagueCode: string; // 联军 + @prop({ required: true }) + guildCode: string; // 军团 + @prop({ required: false }) areaId: number; @@ -47,13 +54,31 @@ export default class GVGTeam extends BaseModel { attackTime: number; // 进攻冷却时间 @prop({ required: true, default: 0 }) - moveTime: number; // 移动冷却时间 + startMoveTime: number; + + @prop({ required: true, default: 0 }) + stopMoveTime: number; // 移动冷却时间 @prop({ required: true, default: 0 }) defenseTime: number; // 防守保护时间 + @prop({ required: true, default: 0 }) + point: number; // 积分 + + @prop({ required: true, type: () => LineupHero, _id: false }) + lineup: LineupHero[]; + @prop({ required: true }) - lineup: [LineupHero] + lineupCe: number; + + @prop({ required: true }) + groupId: number; // 战区 + + @prop({ required: true }) + serverType: number; // 单服还是跨服 + + @prop({ required: true, default: false }) + isRobot: boolean; // 是否是机器人 // 创建队伍 public static async createTeam(roleId: string, leagueCode: string, teamId: number, head: number, spine: number, frame: number, lineup: [LineupHero]) { @@ -87,10 +112,72 @@ export default class GVGTeam extends BaseModel { } // 玩家切换城池更新队伍信息 - public static async resetTeamsLoc(roleId: string, cityId: number, areaId: number) { - const res = await GVGTeamModel.updateMany({ roleId }, { cityId, areaId, pointId: 0 }).lean(); + public static async enterCity(roleId: string, cityId: number, areaId: number, groupId: number, serverType: number) { + const res = await GVGTeamModel.updateMany({ roleId }, { cityId, areaId, pointId: 0, groupId, serverType }).lean(); return !!res['ok']; } + + // 获取我的编队 + public static async findByRole(roleId: string, select = '') { + const teams: GVGTeamType[] = await GVGTeamModel.find({ roleId }).select(select).lean(); + return teams; + } + + // 获取我的编队 + public static async findByTeamCode(roleId: string, teamCode: string, select = '') { + const res: GVGTeamType = await GVGTeamModel.findOne({ roleId, teamCode }).select(select).lean(); + return res; + } + + // 移动 + public static async startMove(teamCode: string, areaId: number) { + const res: GVGTeamType = await GVGTeamModel.findOneAndUpdate({ teamCode }, { $set: { areaId, stopMoveTime: nowSeconds() + GVG.GVG_DEFAULT_MOVE_CD } }).lean(); + return res; + } + + public static async stopMove(teamCode: string, areaId: number) { + const res: GVGTeamType = await GVGTeamModel.findOneAndUpdate({ teamCode }, { $set: { areaId, stopMoveTime: nowSeconds() } }).lean(); + return res; + } + + // 查询挑战和防守的编队 + public static async findBattleTeams(teamCode: string, oppoTeamCode: string) { + const teams: GVGTeamType[] = await GVGTeamModel.find({ teamCode: { $in: [ teamCode, oppoTeamCode ] } }).lean(); + let attackTeam: GVGTeamType = null, defenseTeam: GVGTeamType = null; + for(let team of teams) { + if(team.teamCode == teamCode) attackTeam = team; + if(team.teamCode == oppoTeamCode) defenseTeam = team; + } + return { attackTeam, defenseTeam } + } + + public static async checkRobot(groupId: number, serverType: number, cityId: number) { + return await GVGTeamModel.exists({ groupId, serverType, cityId, isRobot: true }); + } + + public static async initRobots(groupId: number, serverType: number, cityId: number, dicPoints: DicGVGAreaPoint[]) { + await GVGTeamModel.bulkWrite(dicPoints.map(dic => { + return { + updateOne: { + filter: { groupId, serverType, cityId, pointId: dic.pointId }, + update: { $setOnInsert: { teamCode: `robot${dic.pointId}`, ...dic, isRobot: true } }, upsert: true + } + } + })); + } + + // 攻击方攻击cd + public static async lockAttack(teamCode: string, groupId: number, serverType: number) { + const team: GVGTeamType = await GVGTeamModel.findOneAndUpdate({ teamCode }, { $set: { attackTime: nowSeconds() + GVG.GVG_DEFAULT_ATTACK_CD, groupId, serverType } }, { new: true }).lean(); + return team; + } + + // 防守方防守cd + public static async lockDefense(teamCode: string, groupId: number, serverType: number) { + const team: GVGTeamType = await GVGTeamModel.findOneAndUpdate({ teamCode }, { $set: { attackTime: nowSeconds() + GVG.GVG_DEFAULT_DEFENSE_CD, groupId, serverType } }, { new: true }).lean(); + return team; + } + } export const GVGTeamModel = getModelForClass(GVGTeam); @@ -98,3 +185,5 @@ export const GVGTeamModel = getModelForClass(GVGTeam); export interface GVGTeamType extends Pick, keyof GVGTeam> { id: number; }; + +export type GVGTeamUpdate = Partial; // 将所有字段变成可选项 \ No newline at end of file diff --git a/shared/db/GVGUserData.ts b/shared/db/GVGUserData.ts index 7b4a65bb1..63092928a 100644 --- a/shared/db/GVGUserData.ts +++ b/shared/db/GVGUserData.ts @@ -129,6 +129,7 @@ export default class GVGUserData extends BaseModel { const result: GVGUserDataType = await GVGUserDataModel.findOneAndUpdate({ configId, leagueCode, roleId }, { $set: { cityId }}, { new: true }).lean(); return result; } + } export const GVGUserDataModel = getModelForClass(GVGUserData); diff --git a/shared/domain/battleField/gvgBattle.ts b/shared/domain/battleField/gvgBattle.ts index 25de0a355..5078e472b 100644 --- a/shared/domain/battleField/gvgBattle.ts +++ b/shared/domain/battleField/gvgBattle.ts @@ -1,7 +1,14 @@ -import { GVGTeamType } from "../../db/GVGTeam"; +import { GVGTeamType, GVGTeamUpdate } from "../../db/GVGTeam"; // 积分点分类统计,1-3级积分点代表从小到大 -export interface LeagueCityPoint { +export class LeagueCityPoint { + // 玩家id + roleId: string; + // 军团id + guildCode: string; + // 联军id + leagueCode: string; + // 计分点数 pointCnt1: number; pointCnt2: number; @@ -13,11 +20,43 @@ export interface LeagueCityPoint { } // 队伍状态 -export interface GVGTeamMem extends GVGTeamType { - // 开始移动时间戳 +export class GVGTeamMem implements GVGTeamUpdate { + roleId: string; // 玩家id + id: number; + teamCode: string; + teamId: number; + leagueCode: string; + guildCode: string; + areaId: number; + cityId: number; + pointId: number; + head: number; + spine: number; + frame: number; + durability: number; + restartTime: number; + attackTime: number; + defenseTime: number; startMoveTime: number; - // 停止移动时间戳 stopMoveTime: number; - // 是否正在移动 isMoving: boolean; + point: number; + + constructor(team: GVGTeamType) { + for(let key in team) { + this[key] = team[key]; + } + } + + public setCity(cityId: number, areaId = 0) { + this.cityId = cityId; + this.areaId = areaId; + this.pointId = 0; + } + + public moveToArea(areaId: number, startMoveTime: number, stopMoveTime: number) { + this.areaId = areaId; + this.startMoveTime = startMoveTime; + this.stopMoveTime = stopMoveTime; + } } diff --git a/shared/domain/gvgField/returnData.ts b/shared/domain/gvgField/returnData.ts index da6c17e86..ab87ab916 100644 --- a/shared/domain/gvgField/returnData.ts +++ b/shared/domain/gvgField/returnData.ts @@ -363,7 +363,7 @@ export class LeagueFarmMember { setByFields(fields: GVGLeagueFarmType[]) { this.count = fields.length; this.harvestTime = Math.min(...fields.map(cur => cur.harvestTime))||0; - this.canHelp = !!fields.find(cur => cur.harvestTime < nowSeconds()); + this.canHelp = !!fields.find(cur => cur.harvestTime > 0 && cur.harvestTime < nowSeconds()); this.output = fields.reduce((pre, cur) => (pre + cur.output||0), 0); } diff --git a/shared/domain/roleField/hero.ts b/shared/domain/roleField/hero.ts index 83dae552a..0e0b76b32 100644 --- a/shared/domain/roleField/hero.ts +++ b/shared/domain/roleField/hero.ts @@ -1,4 +1,5 @@ +import { prop } from 'typegoose'; import { ArtifactModelType } from '../../db/Artifact'; import { Connect, EPlace, HeroSkin, HeroType, HeroUpdate, Talent } from '../../db/Hero'; import { JewelSe, JewelType, RandSe } from '../../db/Jewel'; @@ -155,8 +156,11 @@ export class ArtifactParam { } // 阵容 -export interface LineupHero { +export class LineupHero { + @prop({ required: true }) actorId: number; // 武将 + @prop({ required: true }) dataId: number; // 出兵表上的位置 + @prop({ required: true }) order: number; // 行动 } diff --git a/shared/pubUtils/data.ts b/shared/pubUtils/data.ts index 2c9ddbf71..17c2a49fa 100644 --- a/shared/pubUtils/data.ts +++ b/shared/pubUtils/data.ts @@ -57,7 +57,7 @@ import { dicCityActivityReward, loadCityActivityReward } from "./dictionary/DicC import { dicRaceActivity, dicRaceTypes, loadRaceActivity } from './dictionary/DicRaceActivity'; import { GUILDACTIVITY, RECRUIT } from "./dicParam"; import * as param from "./dicParam"; -import { decodeIdCntArrayStr, parseGoodStr, decodeArrayListStr, getRandEelm, readTsFile, getRandSingleEelm } from "./util"; +import { decodeIdCntArrayStr, parseGoodStr, decodeArrayListStr, getRandEelm, readTsFile, getRandSingleEelm, parseNumberList } from "./util"; import { RACE_EVENT_TYPE } from "../consts"; import { dicShopByType, dicShopItem, loadShop } from "./dictionary/DicShop"; import { dicShopType, loadShopType } from "./dictionary/DicShopType"; @@ -334,6 +334,7 @@ export const gameData = { gvgVestigeName: dicGVGVestigeName, gvgAreaPoint: dicGVGAreaPoint, gvgBattleRankReward: dicGVGBattleRankReward, + gvgBattleDurabilityMinus: { win: 0, fail: 0 }, }; // 在此提供一些原先在gamedata中提供的方法,以便更方便获取gameData数据 @@ -1213,6 +1214,12 @@ export function getGVGBattleRankReward(type: number, rank: number) { return lastRank } +function parseGVGDurabilityMinus() { + const range = parseNumberList(param.GVG.GVG_DEFAULT_DURABILITY_MINUS); + gameData.gvgBattleDurabilityMinus.win = range[0]; + gameData.gvgBattleDurabilityMinus.fail = range[0]; +} + // 初始加载 function initDatas() { parseDicParam(); @@ -1236,6 +1243,7 @@ function parseDicParam() { parseGVGActive(); parseGVGFieldAdd(); parseGVGVestigeCnt(); + parseGVGDurabilityMinus(); } /** diff --git a/shared/pubUtils/dicParam.ts b/shared/pubUtils/dicParam.ts index 32f506ba9..0a5733e90 100644 --- a/shared/pubUtils/dicParam.ts +++ b/shared/pubUtils/dicParam.ts @@ -376,7 +376,7 @@ export const GVG = { GVG_LEAGUE_COMPOSE: 3, // 一个联军有最多多少军团组成 GVG_CROSS_SERVICE_STARTTIME: 4, // 开服几周后开始跨服玩法 GVG_ROLE_TOTAL_RATIO: 1.2, // 贤臣+猛将总人数上限系数 - GVG_ROLE_RATIO: 1, // 贤臣/猛将各职能选择人数上限系数 + GVG_ROLE_RATIO: 1.2, // 贤臣/猛将各职能选择人数上限系数 GVG_PRODUCER_GET: '10&4|11&2', // 贤臣每天发多少令 GVG_FIGHTER_GET: '10&2|11&4', // 猛将每天发多少令 GVG_LEAGUE_TECH_LIST: 3, // 科技树解锁队列数量 @@ -401,4 +401,9 @@ export const GVG = { GVG_VESTIGE_BATTLE_COUNTDOWN: 300, // 遗迹战斗界面倒计时(s) GVG_VESTIGE_BGMAP_GKID: 80001, // GVG备战期遗迹防守阵容地图所用的关卡id GVG_CITY_BGMAP_GKID: 85001, // GVG激战期防守阵容地图所用的关卡id + GVG_DEFAULT_REVIVE_CD: 30, // GVG激战期默认的复活CD + GVG_DEFAULT_MOVE_CD: 5, // GVG激战期默认的移动CD + GVG_DEFAULT_ATTACK_CD: 5, // GVG激战期默认的攻击CD + GVG_DEFAULT_DEFENSE_CD: 2, // GVG激战期默认的防御CD + GVG_DEFAULT_DURABILITY_MINUS: '5&100', // GVG激战期默认的队伍耐扣除规则,胜&败:num&num }; diff --git a/shared/pubUtils/dictionary/DicGVGArea.ts b/shared/pubUtils/dictionary/DicGVGArea.ts index 138c062de..664d327fa 100644 --- a/shared/pubUtils/dictionary/DicGVGArea.ts +++ b/shared/pubUtils/dictionary/DicGVGArea.ts @@ -22,7 +22,7 @@ export interface DicGVGArea { } export const dicGVGArea = new Map(); -export const dicGVGCity = new Map(); +export const dicGVGCity = new Map(); export function loadGVGArea() { dicGVGArea.clear(); dicGVGCity.clear(); @@ -31,9 +31,11 @@ export function loadGVGArea() { arr.forEach(o => { o.relateArea = parseNumberList(o.relateArea); if(!dicGVGCity.has(o.cityId)) { - dicGVGCity.set(o.cityId, { cityType: o.cityType, areaIds: [] }); + dicGVGCity.set(o.cityId, { cityType: o.cityType, areaIds: [], defenseBirth: 0, attackBirth: 0 }); } dicGVGCity.get(o.cityId)?.areaIds.push(o.areaId); + if(o.areaType == 1) dicGVGCity.get(o.cityId).defenseBirth = o.areaId; + if(o.areaType == 2) dicGVGCity.get(o.cityId).attackBirth = o.areaId; dicGVGArea.set(o.areaId, o); }); arr = undefined; diff --git a/shared/pubUtils/dictionary/DicGVGAreaPoint.ts b/shared/pubUtils/dictionary/DicGVGAreaPoint.ts index af0ef3438..b86bf6af6 100644 --- a/shared/pubUtils/dictionary/DicGVGAreaPoint.ts +++ b/shared/pubUtils/dictionary/DicGVGAreaPoint.ts @@ -11,6 +11,16 @@ export interface DicGVGAreaPoint { readonly score: number; // 位置 readonly pointPosition: string; + // 守卫名 + readonly name: number; + // 守卫头像 + readonly head: number; + // 守卫形象 + readonly spine: number; + // 守卫战力 + readonly ce: number; + // 守卫耐久 + readonly durability: number; } export const dicGVGAreaPoint = new Map();