-- 控制多人玩法的manager ,是从之前的mainupdatemanager中拆出来的通信相关的逻辑 local EventSystem = require('Event/EventSystem') local BattleLogSetter = require("Battle/BattleLogSetter") local MultiplayManager = { IsFromStartEnter = false, -- 是否是从房间建好后被服务器主动拉入即首次进入( 断线 | 闪退 = false) ForceWaitPlayer = {}, -- 强制等待某人进入的列表 【解决TCP的newfighter落后KCP的playerActive】 ForceWaitting = false, -- 强制等人中.. 即 #ForceWaitPlayer ~= 0 CurConnectType = LuaConst.SyncConnectType.None, -- 当前连接流程 (0 正常进入流程 ,1- 战斗内kcp断线重连 ,2- 战斗内TCP重连 ,3- 战斗外TCP重连 ) } local private = { logList = {}, -- 战斗的log CompareData sendLogTick = 0 ,--控制发送log的频率用 curBattleKCPInfo = nil, -- 记录当前KCP连接的信息 verifyRoomID = nil, -- 有的关卡进入新人需要确认下是否是这个房间的 serverID = nil, -- 战斗所在的服务器id(战斗服id) cacheBattleFrames = {}, -- 战斗帧数据 } local battle_compare_pb = require 'Proto/battle_compare_pb' local reconnect_pb = require "Proto/battle_reconnect_pb" local LuaConst=LuaConst local table_insert=table.insert local enter_world_pb = require 'Proto/enter_world_pb' function MultiplayManager.Init() private.RegisterMessageCallBack() MultiplayManager.TVTPointSpeed = DataSheetManager.ConstData:GetDataByID("TVTPointSpeed").FloatValue end --注册所有的push协议的方法 function private.RegisterMessageCallBack() NetManager.RegisterMsgCB(MessageEnums.MessageG2CBattleConnectInfo, private.OnG2CBattleConnectInfo ) NetManager.RegisterMsgCB(MessageEnums.MessageG2CInitFubenInfo,private.OnG2CInitFubenInfo) NetManager.RegisterMsgCB(MessageEnums.MessageG2CNewEnterFighter,MultiplayManager.OnNewEnterFighter) NetManager.RegisterMsgCB(MessageEnums.MessageG2CPushBattleErrorResult, private.OnG2CPushBattleErrorResult) NetManager.RegisterMsgCB(MessageEnums.MessageG2CBattleFrameDatas, private.OnG2CBattleFrameDatas) NetManager.RegisterMsgCB(MessageEnums.MessageCheckBattleRoom, private.OnCheckBattleRoom) end --接收到连接KCP的协议 function private.OnG2CBattleConnectInfo(info) -- 如果正在一个同步玩法中,收到了这个协议,玩家还没结束 ,此时需要把缓存的帧数据都执行完毕后,才能连接新IP . -- 解决问题 如:挖宝玩法,强制校验,服务器还没等客户端发结果就已经让客户端进入下一层 ,且在initfuben回来前,本地又执行了LevelSuccess,停掉了刚连的新IP,则服务器永远收不到ready了,导致流程卡死。 -- 解决问题时不能一刀切(强制关停此房间进入下一房间),否则这个房间的战斗结果服务器就统计不到了 if LevelManager._levelLogicData.LogicLoadingStep ~= LuaConst.Enum_LoadingStep.None then -- loading时: 收到这个协议 有两种case 1.服务器强制下一个关卡(进入一个关卡没完成时,强制进入下一关卡) 2. 断线重连时玩家还没真正进入战斗房间,服务器让重新进入 private.TryConnectIP(info) else -- 非loading时 : if LevelManager.IsSyncLevel() and not LevelManager.CurLevelFunc:GetHadStopLevel() and not MultiReconnectManager.HasKCPNeedReconnect then MultiplayManager.cacheG2CBattleConnectInfo = info MultiplayManager.cacheOverFrame = KCPTransport.GetCurserverFrame() logging.warn(LTAG.WARN, "[切场景] :目前是同步玩法中,且同步玩法还没停StopLevel ,等本场战斗结束后再连IP") EventSystem.Dispatch(EventID.BI_LevelMiddle, BIManager.BIType.SyncBattle, "[sync]BeforeStopGetNextLevelIP") else private.TryConnectIP(info) end end --解决问题:开始loading后kcp断开,卡在loading了 --问题原因:kcp断了服务器没收到ready。 重新连IP收到initfuben 但LogicLoadingStep非none,则不能切换Level,导致卡住了。 if MultiReconnectManager.HasKCPNeedReconnect then -- 标记是否第一次没能到loading结束(即发送过Ready且服务器接收成功)而发生了断线重连。 LevelManager._levelLogicData.LogicLoadingStep = LuaConst.Enum_LoadingStep.None end MultiReconnectManager.HasKCPNeedReconnect = false end function private.TryConnectIP(info) info.ip = "185.238.251.18" local ip = info.ip local port = info.port local roomId = info.battleRoomId --不是打点的battleid local playerId = info.playerId local gRoomID = info.gRoomId --是打点上的battleid , 也是帧数据命名使用的battleid MultiplayManager.CurConnectType = LuaConst.SyncConnectType.Normal if LevelManager._levelLogicData.LogicLoadingStep ~= LuaConst.Enum_LoadingStep.None then --发现上一关卡没进入服务器就让客户端进入下一关卡 。客户端则强制走下一关卡,防止卡住 EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]KCPConnectIP_lastPlaying" , LevelManager._levelLogicData.LogicLoadingStep ,LevelManager.GetCurLevelID()) logging.warn(LTAG.WARN, "[sync] force goto next level,battleRoomId =" .. roomId ,",gRoomID =",gRoomID,",LoadingStep = ",LevelManager._levelLogicData.LogicLoadingStep ,"CurLevelID = ",LevelManager.GetCurLevelID()) LevelManager.SetLoadingLevelEnd() KCPTransport.Close() KCPTransport.ClearKCPData() end MultiplayManager.SetKCPConnectInfo(ip,port,roomId,playerId,gRoomID, info.agentInfo) logging.log(LTAG.LOG, "kcp 连接 :",ip ,",port =",port ,",room=",roomId, ", gRoomID=",gRoomID ) KCPTransport.Connect(ip ,port ,roomId ,playerId ,true) MultiplayManager.SetServerID(info.serverId) EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]KCPConnectIP") end function MultiplayManager.OnUpdate() if MultiplayManager.cacheOverFrame then if LogicUpdateManager._clientHandleFrame >= MultiplayManager.cacheOverFrame then MultiplayManager.CloseKCP() private.TryConnectIP(MultiplayManager.cacheG2CBattleConnectInfo) MultiplayManager.cacheG2CBattleConnectInfo = nil MultiplayManager.cacheOverFrame = nil end end end --接收进入副本的协议 --只有联机关卡才会走这 function private.OnG2CInitFubenInfo(msg) logging.log(LTAG.LOG, msg) if LevelManager.IsReplayBattle() then -- 保底代码 ,防止玩家在回放时,服务器有什么问题直接下发切场景 CTL.Replay.EndPlay(true) LevelManager.SetIsReplayBattle(false) -- 保底代码 ,防止玩家在回放异常后未正常退出,导致界面打开错误。 logging.error(LTAG.ERROR, "[切场景错误]: 目前还在看回放呢,怎么就直接进入到一个同步玩法了呢? 做下保底保护") end MultiplayManager.HandleInitFubenInfo(msg , false) BattleExt.KCPFilterTankInit() -- 重置帧命令过滤功能,开始处理帧 SaveSyncBattleInfoManager.Init(msg) MultiplayManager.IsFromStartEnter = true LogicUpdateManager.SetServerReplenishFrame(-1) EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]BeginLoad") if msg.randomSeed == nil or msg.randomSeed == 0 then logging.error(LTAG.ERROR, "[gws]: 联机玩法:服务器下发的随机种子是空的") end LevelManager.SwitchLevel(msg.mapCfgId, msg.randomSeed, false, false) end --处理init副本协议的 断线和正常的通用代码 [一定注意:这个方法中每加一句代码 ,都要考虑三种情况:正常、断线重连 、服务器校验程序(反作弊);反作弊需要在replayManager 中单独加代码的 。 ] --这个方法跟逻辑数据相关的修改 要跟 BattleSimulator里的 private.GetServerInfo 保持一致! function MultiplayManager.HandleInitFubenInfo(initFubenInfo ,isReconnect) private.forceWaitOtherTimer = nil LevelManager.BeforeHandleInitFubenInfo() --派发事件,界面MODEL模块用于接收事件,储存数据记录该关卡是否通关过 EventSystem.Dispatch(EventID.UIBattle_LevelFirstPassUpdate, initFubenInfo.mapCfgId, initFubenInfo.isPass) LevelPlayerManager.MyPlayerIDInRoom = initFubenInfo.playerID LevelManager.SetServerLevel(initFubenInfo.serverLevel) LevelManager.SetHasNextLevel(initFubenInfo.hasNext) LevelManager.SetMatchStage(initFubenInfo.battleInfo.matchStage) LevelManager.SetServerGs(initFubenInfo.battleInfo.serverFightScore) LevelManager.SetServerAttrInfos(initFubenInfo.battleInfo.attrInfos) LevelManager.SetServerClientIntermediateParamsBytes(initFubenInfo.clientIntermediateParams) --clientIntermediateParams是上一个副本带给下一个副本的参数,由客户端发给服务器再转发给客户端 LevelManager.SetBattleStatueInfoList(initFubenInfo.battleInfo.roleList) LevelManager.SetPreBattleResult(initFubenInfo.battleInfo.preBattleResults) LevelManager.SetMonsterHpInfos(initFubenInfo.battleInfo.monsterHpInfos) LevelManager.SetBattleTime(initFubenInfo.battleInfo.battleTime) LevelManager.SetBattleEndTime(initFubenInfo.battleInfo.battleEndTime) LevelManager.SetNewBattleInfoIntParam(initFubenInfo.battleInfo.paramInt) LevelManager.SetNotBattleInfo(initFubenInfo.notBattleInfo) LevelManager.SetSwitchInGMS(initFubenInfo.battleInfo.switchInGMS) LevelManager.SetOpenServerDays(initFubenInfo.battleInfo.serverOpenDays) MultiplayManager.SetVerifyRoomID(initFubenInfo.battleRoomId) local levelInfoData = DataSheetManager.LevelInfoData:GetDataByLevelID(initFubenInfo.mapCfgId) if levelInfoData.LevelType == LuaConst.LevelType.SyncPlanesEscort or levelInfoData.LevelType == LuaConst.LevelType.SyncCountryPVP then LevelManager.SetBattleAnchorPos(initFubenInfo.cartPos) -- cartPos 这个地段服务器说先不改了,以后这个字段就是BattleAnchorPos end if levelInfoData.LevelType == LuaConst.LevelType.SyncTeamPVP or levelInfoData.LevelType == LuaConst.LevelType.SyncSolo or levelInfoData.LevelType == LuaConst.LevelType.SyncTeamPVPPractice or levelInfoData.LevelType == LuaConst.LevelType.SyncSoloPractice or levelInfoData.LevelType == LuaConst.LevelType.SyncFairSoloTournament or levelInfoData.LevelType == LuaConst.LevelType.SyncTeamFairChampionship or levelInfoData.LevelType == LuaConst.LevelType.SyncFourCountryChaos then --3v3、联机1v1特殊处理 if levelInfoData.LevelType == LuaConst.LevelType.SyncTeamPVP or levelInfoData.LevelType == LuaConst.LevelType.SyncTeamPVPPractice or levelInfoData.LevelType == LuaConst.LevelType.SyncTeamFairChampionship then LevelManager.debugTVTPointSpeed = initFubenInfo.TVTPointSpeed -- 调试的3v3占领进度增长速度 end LevelPlayerManager.SetSyncAllPlayersArmyTable(initFubenInfo.roleList,true) else --除了3v3的情况 LevelPlayerManager.SetSyncAllPlayersArmyTable(initFubenInfo.roleList,false) if LevelManager.IsGuildWarLevelType(levelInfoData.LevelType) then --军团战信息读取,主要是军团徽记相关 LevelManager.emblemContentRed = initFubenInfo.emblemContentRed LevelManager.emblemFrameRed = initFubenInfo.emblemFrameRed LevelManager.emblemContentBlue = initFubenInfo.emblemContentBlue LevelManager.emblemFrameBlue = initFubenInfo.emblemFrameBlue --logging.error(LTAG.ERROR, "HYW : ", LevelManager.emblemContentRed, LevelManager.emblemFrameRed, LevelManager.emblemContentBlue, LevelManager.emblemFrameBlue) end end table.clear(private.cacheBattleFrames) end --接收到了帧数据 -- FDNormal = 0;//正常 -- FDTcpReconnect = 1;//Tcp重连 -- FDKcpReconnect = 2;//Kcp重连 function private.OnG2CBattleFrameDatas(msg) logging.log(LTAG.LOG, " OnG2CBattleFrameDatas : type = ", msg.FrameDataType , "isEnd=", msg.IsEnd) if msg.FrameDataType == reconnect_pb.FDNormal then private.OnBattleFrameDatas(msg) elseif msg.FrameDataType == reconnect_pb.FDTcpReconnect then TCPReconnectManager.OnBattleFrameDatas(msg) elseif msg.FrameDataType == reconnect_pb.FDKcpReconnect then KCPReconnectManager.OnBattleFrameDatas(msg) elseif msg.FrameDataType == reconnect_pb.FDWatchTick or msg.FrameDataType == reconnect_pb.FDWatch then WatchBattleManager.OnBattleFrameDatas(msg, msg.FrameDataType) end end --接收到了帧数据 协议 (正常进入流程,非断线重连) function private.OnBattleFrameDatas(msg) table.insert(private.cacheBattleFrames, msg.BattleData) if not msg.IsEnd then return end local battleFrameBytes = table.concat(private.cacheBattleFrames) BattleExt.SetServerMsgCount(0) local length, startFrame, endFrame = BattleExt.ParseBeginBattleData(battleFrameBytes) --先将补帧的数据放入cache中,再从cache放入到B2CFrameList中 。如果msg.BattleData的长度为0(即不用补帧),则startFrame, endFrame都是-1 logging.log(LTAG.LOG, "------OnG2CBattleBegin----- length:",length ,",startFrame" ,startFrame,",endFrame" ,endFrame) EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]G2CBattleBegin" , length ,startFrame,endFrame) ---local CacheB2CFrameListCount, CacheB2CFrameListStartFrame, CacheB2CFrameListEndFrame = BattleExt.GetCacheB2CFrameData(nil, nil, nil) -- 这句话就是获取cache数据的一些信息 ,只是查询,没有任何其他操作 table.clear(private.cacheBattleFrames) LogicUpdateManager.ResetData() --3v3中能一起出场 ,组队也能-但是组队相机从远处飘过来 ,没好办法,先这样写了TODO 应该是组队也用这 if LevelManager.IsSyncTeamPVP() or LevelManager.IsSyncSolo() then MultiplayManager.SetAllRolesActive() -- 关闭loading界面等 ExpLevelManager.EnterLevel(LevelManager.GetCurInfoData()) LevelManager.SetLoadingLevelEnd() else if LevelManager.CurLevelFunc.isFreeToEnterAndLeave then -- 随进随出功能的设置快进 LogicUpdateManager.SetServerReplenishFrame(KCPTransport.GetCurserverFrame()) --设置快速补帧的值 ,不做表现 ; else -- 同时进入的关卡 ,直接开始就可以了 -- 关闭loading界面等 ExpLevelManager.EnterLevel(LevelManager.GetCurInfoData()) LevelManager.SetLoadingLevelEnd() end end LevelManager.EnterSyncLevel() MultiReconnectManager.SetInBattleReconnect(true) end --收到世界boss的新人进入的协议 function MultiplayManager.OnNewEnterFighter(msg) logging.crit(LTAG.CRIT, "[gws] : OnNewEnterFighter : playerID=" , msg.newFighter.playerID ,"新人房间=" ,msg.battleRoomId ,";自己房间=" ,private.verifyRoomID) local playerHeroArmy = LevelPlayerManager.GetPlayerArmyByPlayerIDInRoom(msg.newFighter.playerID) if playerHeroArmy then logging.log(LTAG.LOG,"OnNewEnterFighter:已在InitfubenMsg中发现此玩家信息,所以不再处理! 。playerID = ",msg.newFighter.playerID) return end if LevelManager.IsFreeToEnterAndLeaveLevel() then if msg.battleRoomId == private.verifyRoomID then LevelManager.CurLevelFunc:SetNewPlayerInfo(msg.newFighter) end end --从强制等待中移除 local find = false for i,playID in ipairs(MultiplayManager.ForceWaitPlayer) do if playID == msg.newFighter.playerID then find = true end end if find then LevelManager.CurLevelFunc:SetPlayerActive(msg.newFighter.playerID) logging.warn(LTAG.WARN,"[[gws] : 同步异常特殊处理:终于等到你,还好没放弃 ,player =" ,msg.newFighter.playerID ) table.removebyvalue(MultiplayManager.ForceWaitPlayer,msg.newFighter.playerID) if #MultiplayManager.ForceWaitPlayer == 0 then logging.warn(LTAG.WARN,"[gws] : sync 要等的人都来了,可以开始战斗了!") MultiplayManager.ForceWaitting = false if private.forceWaitOtherTimer then TimerUtil.Remove(private.forceWaitOtherTimer) private.forceWaitOtherTimer = nil end LogicUpdateManager.FixedExceptionUpdate() -- 不上之前没执行完的那一帧 end end end function MultiplayManager.ForceWait(playerID) logging.warn(LTAG.WARN, "[gws] sync 需要强制等待玩家,player = " ,playerID) MultiplayManager.ForceWaitting = true table.insert(MultiplayManager.ForceWaitPlayer ,playerID) private.forceWaitOtherTimer = TimerUtil.Interval(private.SendForceWaitOtherBILog, 3, 1) -- 3秒后再发送 end function private.SendForceWaitOtherBILog() EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]ForceWait") end function MultiplayManager.OnLoadFinished() if MultiplayManager.CurConnectType == LuaConst.SyncConnectType.WatchBattle then WatchBattleManager.SendReady() else MultiplayManager.SendReady() end end -- kcp发送ready ;用途:1.正常进入获取一次性补帧;2. TCP断线重连 战斗内和战斗外 请求大补帧数据 function MultiplayManager.SendReady() if LogicUpdateManager.IsReplayOrServerCheck then BattleSimulator.ReadyOver() return end KCPTransport.SendReady() EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]SendReady") end -- 重连时 :请求二次补帧 function MultiplayManager.SendReconnectInitReady() if LogicUpdateManager.IsReplayOrServerCheck then BattleSimulator.ReadyOver() return end local msg = reconnect_pb.C2BReconnectInitReady() msg.EndFrameLen = BattleExt.GetServerMsgCount() NetManager.SendMessage(MessageEnums.MessageC2BReconnectInitReady, msg) EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]ReconnectInitReady" ,msg.EndFrameLen) end -- 获取连接kcp的信息 function MultiplayManager.GetKCPConnectInfo() return private.curBattleKCPInfo end function MultiplayManager.SetKCPConnectInfo(ip,port,roomId,playerId ,gRoomId, agentInfo) private.curBattleKCPInfo = {["ip"] = ip ,["port"] = port ,["roomId"] = roomId ,["playerId"] = playerId,["gRoomId"] = gRoomId, ["agentInfo"] = agentInfo} BIManager.myTVTPlayerId = playerId BIManager.SetSyncBattleGRoomID(gRoomId, agentInfo) end --返回gRoomId,这个值是打点使用的值,也是帧数据命名使用的值 function MultiplayManager.GetBattleID() if private.curBattleKCPInfo then return private.curBattleKCPInfo.gRoomId end return -1 end --关闭kcp function MultiplayManager.CloseKCP() KCPTransport.Close() KCPTransport.ClearKCPData() --private.SendLog() -- CompareSyncLogManager.TryColectLog(LuaConst.CheckBattleLogType.CollectEid2HeroID) BattleLogSetter.ForceWriteLog() end function MultiplayManager.GetTVTPointSpeed() if LevelManager.debugTVTPointSpeed == nil or LevelManager.debugTVTPointSpeed == 0 then return MultiplayManager.TVTPointSpeed else return LevelManager.debugTVTPointSpeed end end function MultiplayManager.SetAllRolesActive() for _, playerID in ipairs(LevelPlayerManager.SyncPlayerIDList) do local entitys = LevelPlayerManager.GetPlayerEntitysByPlayerIDInRoom(playerID) SystemManager.Active(entitys[1] ,LevelPlayerManager.GetPlayerSetting(playerID ,LuaConst.BattleSettingEnum.AutoRide)) end end function MultiplayManager.SetVerifyRoomID(battleRoomId) print("设置自己房间ID: = " , battleRoomId) private.verifyRoomID = battleRoomId if battleRoomId and not LogicUpdateManager.IsReplayOrServerCheck then EventSystem.Dispatch(EventID.JoinBattleTeamChatChannel ,private.verifyRoomID ,true) end end function MultiplayManager.GetVerifyRoomID() return private.verifyRoomID end function MultiplayManager.SetServerID(serverID) private.serverID = serverID end function MultiplayManager.GetServerID() return private.serverID end function MultiplayManager.ExitLevel() MultiReconnectManager.SetInBattleReconnect(false) MultiplayManager.CurConnectType = LuaConst.SyncConnectType.None end --下发了不一致的协议 function private.OnG2CPushBattleErrorResult(msg) logging.log(LTAG.LOG, "------------战斗无效:", "错误码-", msg.ErrCode, "----------------") if msg.ErrCode == 1 then UTL.MessageBox.ShowOnlyOkNoCloseBox("",Const_IDS.IDS_ERROR_OUT_OF_SYNC,private.OnErrorConfirm) elseif msg.ErrCode == 2 then UTL.MessageBox.ShowOnlyOkNoCloseBox("",Const_IDS.IDS_CheatCheckFailed,private.OnErrorConfirm) MDL.Level.SetCheatCheckFailedFlag(true) EventSystem.Dispatch(EventID.Level_CheatCheckFailed) end end function private.OnErrorConfirm() CameraController.Instance:SetGray(false) MDL.Level.SendRoleLeaveFuben() end -- 检查下房间是否还存在 ↓ local _battleRoomIsFindCallBack = nil function MultiplayManager.CheckBattleRoom(callBack) local msg = enter_world_pb.CheckBattleRoomReq() NetManager.SendMessage(MessageEnums.MessageCheckBattleRoom, msg) _battleRoomIsFindCallBack = callBack end function private.OnCheckBattleRoom(msg) if msg.isFind then -- 发现有房间 ,则重连 logging.crit(LTAG.CRIT, "【gws】:战斗房间还存在 ,执行重连或特定逻辑" ) _battleRoomIsFindCallBack() else -- 房间不存在 logging.crit(LTAG.CRIT, "【gws】:战斗房间已不存在 ,请求回主城" ) if LevelManager.IsSyncLevel() then LevelManager.CurLevelFunc:StopLevel() end -- 下面代码目的:若在loading时,房间不存在了,则且主城,但是会被拦截进入关卡队列 ,但是执行关卡队列需要服务器的后续协议,但是现在又后续协议不会下发的,故次代码防止回主城进入切关队列 if LevelManager._levelLogicData.LogicLoadingStep ~= LuaConst.Enum_LoadingStep.None then LevelManager._levelLogicData.LogicLoadingStep = LuaConst.Enum_LoadingStep.None end CTL.Tips.AddTipsMsg(Const_IDS.IDS_GuildWar_BattleEnded) EventSystem.Dispatch(EventID.Level_SendRoleLeaveFuben,false) end end -- 检查房间 ↑----------------------- return MultiplayManager