--战斗逻辑轮询管理器 local EventSystem = require('Event/EventSystem') local FirstPersonEventDispatcher = require"Battle/Logic/Event/FirstPersonEventDispatcher" local LogicUpdateManager = {} local private = {} local DEBUG_SAVE_FRAME_DATA = false local LEVEL_CMD_PLAYERID = 0 -- 关卡使用的命令的entityIDInRoom 是 0 --加载需要的模块 local LuaUpdater = require "Main/LuaUpdater" local Command = require "Battle/Logic/MainLoop/Command" local MonsterItem = require "Battle/Logic/AI/MonsterItem" local ColliderManager = require("Battle/Logic/Level/ColliderManager") local SkillMissileSystem = require "Battle/Logic/Entity/System/SkillMissileSystem" local DeadSystem = require "Battle/Logic/Entity/System/DeadSystem" local TriangleMath = require"Common/TriangleMath" local table_insert = table.insert local table_removebyvalue = table.removebyvalue local table_sort = table.sort local math_ceil = math.ceil local math_clamp = math.clamp local LuaConst=LuaConst local SpecialCommandType_SoloFightNextRound = LuaConst.SpecialCommandType.SoloFightNextRound local FP=FP local FP_Zero = FP.Const.Zero local MaxBufferFrame = 3 -- buffer 的最大值 local MaxFastPlayFrameNum = 400 -- 最大的追帧速度 (400 帧/秒) function LogicUpdateManager.GetMaxFastPlayFrameNum() return MaxFastPlayFrameNum end function LogicUpdateManager.Init() --客户端收到的服务器帧 private._serverFrame = 0 --客户端处理到的帧 LogicUpdateManager._clientHandleFrame = 0 -- 客户端总共处理的帧命令数量 LogicUpdateManager._handleCmdCount = 0 LogicUpdateManager._handleFrameCount = 0--处理过的帧数量 private.handleFrame2CmdCountDic = {} -- 统计每帧执行了多少个cmd --客户端预测的帧 private._clientForecastFrame = 0 --客户端指令缓存大小 private._bufferFrame = 0 --重连过程中,服务器补帧的最新帧 ,也是快速播放要到的帧 LogicUpdateManager._serverReplenishFrame = -1 LogicUpdateManager._serverReplenishOverFrame = -1 -- 补帧结束的帧 ,如果补帧一定是 _serverReplenishOverFrame = _serverReplenishFrame + 1 (用内存换了个 + 运算) --客户端收到的Command --key是entityID,value是 帧号to commands Dic --每一帧每个单位可能有多个command,在网络延迟的状况下客户端先后多个command会被服务器合并到网络通畅后的第一帧一起响应 private._syncCommandDir = {} --客户端非同步单位的Command private._notSyncCommand = Command.TryGet() --客户端对当前操控的角色做出的操控指令 private._selfCommandList = {} --用于给每个Entity分配唯一ID private._tempEntityID = 0 --记录关卡中角色数量,退出关卡时候重置 private._playerDic = {} --当前玩家操控的hero类型的Entity(当操控载具时,_curEntity依然是之前的hero的Entity,而不是载具Entity。 --如果要获取当前操控的Entity,通过BattleDataGetter.GetMainPlayerManipulatingEntity() 获取 LogicUpdateManager._curEntity = nil --储存所有Entity,key是entityID,value是entity对象, 包括没有active的 LogicUpdateManager._entityDic = {} --存储所有群组 LogicUpdateManager.GroupAIEntityDir = {} --{[levelItemID] = leaderEntity} --存储所有MonsterItem LogicUpdateManager.MonsterItemTable = {} --{[levelItemID] = EntityTable} --NPC转向玩家操控Entity的距离 LogicUpdateManager.NPCTurnSqrDistance = DataSheetManager.ConstData:GetDataByID("NPCTurnAroundDistance").FloatValue:Square() --是否已经进入EnterLevel LogicUpdateManager.FightBegin = false -- 统计同步玩法在线的人数 LogicUpdateManager.OnlinePlayerCount = 0 --是否是在线(在线近还是离线进入) LogicUpdateManager.IsOnlinePlay = true LogicUpdateManager.EntityList_Normal = {} LogicUpdateManager.EntityList_Npc = {} LogicUpdateManager.toRemoveEntityList = {} --存储所有在同步玩法中,需要接收服务器消息才能行动的Entity(包括自己) 服务器创建的机器人不在这里 LogicUpdateManager.EntityList_Sync = {} --是否已经停止关卡内逻辑 private._stopElementTick = false --是否需要延迟删除normalEntity private._needDelayRemoveNormalEntity = false LuaUpdater.StartLateUpdate(LogicUpdateManager.OnLateUpdate) LuaUpdater.StartFixedUpdate(LogicUpdateManager.OnLogicUpdate) end function LogicUpdateManager.ExitLevel() ColliderManager.Clear() --客户端收到的服务器帧 private._serverFrame = 0 local syncServerFrame = KCPTransport.GetCurserverFrame() if syncServerFrame ~= 0 then logging.error(LTAG.ERROR ,string.format("重大错误:在切关卡前,服务器的当前帧数不为0,而是:%d,请查看在结束同步关卡的时候是否调用了self:StopLevel",syncServerFrame)) MultiplayManager.CloseKCP() -- 这是底线,一般不会触碰。 end --客户端处理到的帧 LogicUpdateManager._clientHandleFrame = 0 LogicUpdateManager._handleCmdCount = 0 LogicUpdateManager._handleFrameCount = 0 private.handleFrame2CmdCountDic = {} private._bufferFrame = 0 private._stopElementTick = false --客户端预测的帧 private._clientForecastFrame = 0 --客户端收到的Command if DEBUG_SAVE_FRAME_DATA then logging.error(LTAG.ERROR, "HYW : 储存帧数据") end private._syncCommandDir = {} --客户端非同步单位的Command Command.Reset(private._notSyncCommand) --客户端对当前操控的角色做出的操控指令 private._selfCommandList = {} --用于给每个Entity分配唯一ID private._tempEntityID = 0 --记录关卡中角色数量,退出关卡时候重置 private._playerDic = {} --销毁剩余的所有实体 for _, entity in pairs(LogicUpdateManager._entityDic) do SystemManager.OnUnload(entity,true) end --当前玩家操控的Entity LogicUpdateManager._curEntity = nil --储存所有Entity LogicUpdateManager._entityDic = {} --存储所有在同步玩法中,需要接收服务器消息才能行动的Entity(包括自己) 。(储存所有玩家的主将,不包括机器人的) LogicUpdateManager.EntityList_Sync = {} LogicUpdateManager.EntityList_Npc = {} LogicUpdateManager.EntityList_Normal = {} --存储所有群组 LogicUpdateManager.GroupAIEntityDir = {} --{[levelItemID] = leaderEntity} --存储所有MonsterItem LogicUpdateManager.MonsterItemTable = {} --{[levelItemID] = EntityTable} -- call 清空 SkillMissileSystem.Clear() --子弹都清空 MissileManager.Clear() -- buff 系统缓存清理 BuffManager.Dispose() --清理数据缓存 LuaDataManager.Dispose() end function LogicUpdateManager.ResetData(isReconnect) --客户端处理到的帧 LogicUpdateManager._clientHandleFrame = 0 LogicUpdateManager._handleCmdCount = 0 LogicUpdateManager._handleFrameCount = 0 private.handleFrame2CmdCountDic = {} --客户端预测的帧 private._clientForecastFrame = 0 --客户端对当前操控的角色做出的操控指令 private._selfCommandList = {} private._stopElementTick = false if LevelManager.IsSyncLevel() then private._bufferFrame = math_clamp( BattleExt.GetNetDelayTime()/ LuaConst.TimeSpanMs + 1, 0,MaxBufferFrame) else private._bufferFrame = 0 end end function LogicUpdateManager.GetPlayerCount() local count = 0 for k, v in pairs(private._playerDic) do -- 只有在非战斗关卡中,其他玩家的id都是-1,所以-1记录的是非战斗关中其他角色的数量; 其他类型关卡中,没有任何影响 if k == -1 then count = count + v else count = count + 1 end end return count end function LogicUpdateManager.RemoveSyncLevelEntity(playerID) if not (LogicUpdateManager._curEntity and LogicUpdateManager._curEntity:GetPlayerID() == playerID) then private._playerDic[playerID] = nil end end --[[ 添加Entity --]] function LogicUpdateManager.AddEntity(entity, forceSetEntityID) if LogicUpdateManager._entityDic[entity._logicInitData.EntityID] == nil then --TODO 这个判断逻辑有问题 private._tempEntityID = private._tempEntityID + 1 if forceSetEntityID then LogicUpdateManager._entityDic[forceSetEntityID] = entity entity._logicInitData.EntityID = forceSetEntityID else LogicUpdateManager._entityDic[private._tempEntityID] = entity entity._logicInitData.EntityID = private._tempEntityID end if entity._logicInitData.NeedSyncInfo then private.AddEntityInList(LogicUpdateManager.EntityList_Sync,entity) elseif not entity._logicInitData.isSyncMove then if entity._logicInitData.CharacterType == LuaConst.CharacterType.NPC then table_insert(LogicUpdateManager.EntityList_Npc,entity) else private.AddEntityInList(LogicUpdateManager.EntityList_Normal,entity) end end local bornItemID = entity._logicInitData.OriginBornItemID if bornItemID then local itemData = LevelItemManager.GetItemDataByID(bornItemID) if itemData then if not LogicUpdateManager.MonsterItemTable[bornItemID] then LogicUpdateManager.MonsterItemTable[bornItemID] = MonsterItem:new(itemData.LayoutType) end local entityTable = LogicUpdateManager.MonsterItemTable[bornItemID].EntityTable table_insert(entityTable, entity) if not entity.isPickupItem then entity.components.patrol.IndexInMonsterItem = #entityTable if itemData.IsLeader then local groupID = itemData.LevelItemID entity._logicInitData.IsLeader = itemData.IsLeader LogicUpdateManager.GroupAIEntityDir[groupID] = entity for key,tempEntity in ipairs(LogicUpdateManager.EntityList_Normal) do if tempEntity.components.group.GroupID == groupID then SystemManager.OnGroupAddEntity(groupID, entity, tempEntity) end end else local groupID = itemData.LeaderID if groupID and groupID ~= 0 then entity._logicInitData.LeaderID = groupID local leader = LogicUpdateManager.GroupAIEntityDir[groupID] SystemManager.OnGroupAddEntity(groupID, leader, entity) end end end end end if entity:IsHero() then local playerID = entity:GetPlayerID() if private._playerDic[playerID] == nil then private._playerDic[playerID] = 1 else private._playerDic[playerID] = private._playerDic[playerID] + 1 end end end end function private.sort(a,b) return a._logicInitData.EntityID < b._logicInitData.EntityID end function private.AddEntityInList(list, entity) table_insert(list, entity) if LevelManager.ShouldKeepSyncLevel() then table_sort(list, private.sort) end end function LogicUpdateManager.RemoveEntity(entity) if not entity then return end local bornItemID = entity._logicInitData.OriginBornItemID if bornItemID then local monsterItem = LogicUpdateManager.MonsterItemTable[bornItemID] if monsterItem then local entityTable = monsterItem.EntityTable table_removebyvalue(entityTable,entity,true) if entity.components.patrol then local tempIndex = entity.components.patrol.IndexInMonsterItem for _,tempEntity in pairs(entityTable) do if tempEntity.components.patrol.IndexInMonsterItem > tempIndex then tempEntity.components.patrol.IndexInMonsterItem = tempEntity.components.patrol.IndexInMonsterItem -1 end end end end if entity._logicInitData.IsLeader then LogicUpdateManager.GroupAIEntityDir[bornItemID] = nil elseif entity._logicInitData.LeaderID then local groupID = entity._logicInitData.LeaderID SystemManager.OnGroupRemoveEntity(LogicUpdateManager.GroupAIEntityDir[groupID], entity) end end if entity._logicInitData.NeedSyncInfo then table_removebyvalue(LogicUpdateManager.EntityList_Sync,entity,true) elseif not entity._logicInitData.isSyncMove then if entity._logicInitData.CharacterType == LuaConst.CharacterType.NPC then table_removebyvalue(LogicUpdateManager.EntityList_Npc,entity,true) else if private._needDelayRemoveNormalEntity then table_insert(LogicUpdateManager.toRemoveEntityList,entity) else table_removebyvalue(LogicUpdateManager.EntityList_Normal,entity,true) end end end LogicUpdateManager._entityDic[entity._logicInitData.EntityID] = nil if entity:IsHero() then local playerID = entity:GetPlayerID() if private._playerDic[playerID] ~= nil then private._playerDic[playerID] = private._playerDic[playerID] - 1 if private._playerDic[playerID] <= 0 then private._playerDic[playerID] = nil end end end end function LogicUpdateManager.StopFightUpdate() print("GWS : -- FightBegin = false") LogicUpdateManager.FightBegin = false if USE_LUA_STANDALONE then--- 服务器检验代码 LogicUpdateManager.stopFightTimes = LogicUpdateManager.stopFightTimes and LogicUpdateManager.stopFightTimes + 1 or 1 if LogicUpdateManager.stopFightTimes > 20 and LogicUpdateManager.stopFightTimes < 30 then logging.error(LTAG.ERROR , "GWS : ERROR : why execute StopFightUpdate more than 20 times! levelID = " ,LevelManager.GetCurLevelID()) end end end function LogicUpdateManager.StartFightUpdate() print("GWS : -- FightBegin = true ,levelID=" ,LevelManager.GetCurLevelID()) LogicUpdateManager.FightBegin = true if USE_LUA_STANDALONE then--- 服务器检验代码 LogicUpdateManager.startFightTimes = LogicUpdateManager.startFightTimes and LogicUpdateManager.startFightTimes + 1 or 1 if LogicUpdateManager.startFightTimes > 20 and LogicUpdateManager.startFightTimes < 30 then logging.error(LTAG.ERROR , "GWS : ERROR : why execute StartFightUpdate more than 20 times! levelID = " ,LevelManager.GetCurLevelID()) end end end function LogicUpdateManager.StopElementTick() private._stopElementTick = true end function LogicUpdateManager.StartElementTick() private._stopElementTick = false end --从C#的B2CFrameList 获取数据 function LogicUpdateManager.GetFrameDataFromKCPCache(isDealAll) private._serverFrame = KCPTransport.GetCurserverFrame() KCPTransport.GetB2CFrameList(private._syncCommandDir, isDealAll) end --从本地File的得到 B2CFrameList(回放个人数据使用) function LogicUpdateManager.GetFrameDataFromFile(maxFrame) private._serverFrame = maxFrame KCPTransport.GetB2CFrameList(private._syncCommandDir) end function LogicUpdateManager.OnLateUpdate(deltaTime, timeSinceUpdate) LevelManager.OnLateUpdate_InLevel() end local FrameOffsetLowThreshold = 5 local FrameOffsetHighThreshold = 10 local FrameLowHandleMax = 3 local FrameHighHandleRate = 3 --[[ 逻辑层的主循环 --]] function LogicUpdateManager.OnLogicUpdate(deltaTime, timeSinceUpdate) MultiReconnectManager.OnUpdate() if not LogicUpdateManager.FightBegin then LevelManager.OnUpdateLoading() return end if LevelManager.IsLevelPause() then return end if MultiplayManager.ForceWaitting then -- 发现了异常问题,需要等会儿再更新 return end local needHandleFrame = 0 --本次update要执行到的帧数 --- 服务器检验时 if USE_LUA_STANDALONE then needHandleFrame = private._serverFrame if LogicUpdateManager.IsAutoCheckLevel then -- 如果是自动跑关卡,则需自增 needHandleFrame = LogicUpdateManager._clientHandleFrame + 1 end --- 客户端重播时 elseif LogicUpdateManager.IsReplayOrServerCheck then -- 如果是分帧回放,则需先从文件中读取帧数据 BattleSimulator.TryReadSplitFrame() if GameAppInfo.IsVersionGreaterThan113100() then LogicUpdateManager.GetFrameDataFromKCPCache(true) end if LogicUpdateManager._clientHandleFrame < BattleSimulator.FastPlayFrame then needHandleFrame = BattleSimulator.FastPlayFrame else needHandleFrame = LogicUpdateManager._clientHandleFrame + 1 end --- 观战时 elseif LevelManager.IsInLevelWatchBattle() then LogicUpdateManager.GetFrameDataFromKCPCache() local maxPlayFrame = WatchBattleManager.GetMaxPlayFrame() if LogicUpdateManager._clientHandleFrame < maxPlayFrame then needHandleFrame = math.min(LogicUpdateManager._clientHandleFrame + MaxFastPlayFrameNum,maxPlayFrame) else needHandleFrame = math.min(LogicUpdateManager._clientHandleFrame + 1, private._serverFrame) end --- 快速追帧过程(断线重连) elseif LogicUpdateManager.IsInReconnectFastPlay() then--快速追帧过程中,由于逻辑代码时有消耗的,大于1帧2.5ms ,所以1秒能追400帧 LogicUpdateManager.GetFrameDataFromKCPCache() local replenishFrame = LogicUpdateManager._serverReplenishFrame if replenishFrame - LogicUpdateManager._clientHandleFrame > MaxFastPlayFrameNum then needHandleFrame = LogicUpdateManager._clientHandleFrame + MaxFastPlayFrameNum EventSystem.Dispatch(EventID.UI_Loading_HandleStep,LuaConst.Enum_LoadingStep.FastPlay) -- 更新进度条数字 else if LevelManager._levelLogicData.LogicLoadingStep == LuaConst.Enum_LoadingStep.FastPlay then EventSystem.Dispatch(EventID.UI_Loading_HandleStep,LuaConst.Enum_LoadingStep.FastPlay) -- 更新进度条数字 end if private._serverFrame - LogicUpdateManager._clientHandleFrame > FrameOffsetLowThreshold then -- 帧数大于3,继续延长补帧 LogicUpdateManager.SetServerReplenishFrame(KCPTransport.GetCurserverFrame()) logging.log(LTAG.LOG , "[gws]:追帧过程中,又额外产生帧数 :" ,private._serverFrame - replenishFrame , " | 设置追帧到 :" ,KCPTransport.GetCurserverFrame()) end local buffer = math_ceil(BattleExt.GetNetDelayTime()/ LuaConst.TimeSpanMs) private._bufferFrame = math_clamp( buffer, 0,MaxBufferFrame) needHandleFrame = math_clamp( LogicUpdateManager._clientHandleFrame + 1, private._serverFrame-private._bufferFrame ,private._serverFrame) if GameAppInfo.IsVersionGreaterThan113100() then if needHandleFrame > LogicUpdateManager._clientHandleFrame + MaxFastPlayFrameNum then needHandleFrame = LogicUpdateManager._clientHandleFrame + MaxFastPlayFrameNum end end end else --- 正常战斗时 if LevelManager.IsSyncLevel() then --同步玩法 LogicUpdateManager.GetFrameDataFromKCPCache() local buffer = math_ceil(BattleExt.GetNetDelayTime()/ LuaConst.TimeSpanMs) private._bufferFrame = math_clamp( buffer, 0,MaxBufferFrame) needHandleFrame = math_clamp( LogicUpdateManager._clientHandleFrame + 1, private._serverFrame-private._bufferFrame ,private._serverFrame) local frameOffset = needHandleFrame - LogicUpdateManager._clientHandleFrame if frameOffset < FrameOffsetLowThreshold then frameOffset = math.min(frameOffset, FrameLowHandleMax) elseif frameOffset >= FrameOffsetHighThreshold then local orgOffset = frameOffset frameOffset = math.max(math.ceil(frameOffset / FrameHighHandleRate), FrameOffsetHighThreshold) --TODO 使用BI打点,统计上传,修正统计精度 --logging.error(LTAG.ERROR,"logic frame handle delayed, client:", -- LogicUpdateManager._clientHandleFrame, "server:", private._serverFrame, "offset:", orgOffset, "net delay:", BattleExt.GetNetDelayTime()) else frameOffset = frameOffset - FrameOffsetLowThreshold + FrameLowHandleMax end needHandleFrame = LogicUpdateManager._clientHandleFrame + frameOffset else -- 单机玩法 --模拟接收服务器的指令 private.CheatSyncServerCommand() needHandleFrame = private._serverFrame end end local needExpBatchEntities = needHandleFrame > LogicUpdateManager._clientHandleFrame --遍历帧数据 while LogicUpdateManager._clientHandleFrame < needHandleFrame do if not LogicUpdateManager.FightBegin then -- 防止多个命令中间停止,导致后面的命令各种问题 break end LogicUpdateManager._clientHandleFrame = LogicUpdateManager._clientHandleFrame + 1 LogicUpdateManager._isIntegralSecond = LogicUpdateManager._clientHandleFrame % LuaConst.LogicFrameRate == 0 --是否是整秒 LogicUpdateManager.CheckFastPlayOver() -- 更新玩家在线状态 private.UpdateSyncLevelPlayerOnlineState() LevelManager.CurLevelFunc:PreUpdate() private.TryActiveSyncHero() if MultiplayManager.ForceWaitting then -- 发现了异常问题,需要等会儿再更新 return end if not private._stopElementTick then private.TraversalAllEntity() SkillMissileSystem.OnLogicUpdate() MissileManager.OnLogicUpdate() ColliderManager.OnUpdate() LevelItemManager.OnUpdate() LuaShoveManager.EntityShove() SystemManager.OnLogicUpdateGlobal() end LogicUpdateManager.FrameEndLog()-- 统计entity的关键帧内存数据 LevelManager.OnUpdate_InLevel() private.FilterLevelCommand() --这个要放在最后 end --客户端发送当前指令 private.HandleSelf() --处理输入 LuaInputManager.OnUpdate() --处理状态同步的单位移动 SyncMoveManager.HandleEntityMove() MultiplayManager.OnUpdate() if needExpBatchEntities then --批量同步位置到表现层 ExpUpdateManager.OnPostLogicUpdate() end end --[[ 出现异常时,错过的那一帧逻辑补上 --]] function LogicUpdateManager.FixedExceptionUpdate() if not LogicUpdateManager.FightBegin then -- 防止多个命令中间停止,导致后面的命令各种问题 return end private.TryActiveSyncHero() if not private._stopElementTick then private.TraversalAllEntity() SkillMissileSystem.OnLogicUpdate() MissileManager.OnLogicUpdate() ColliderManager.OnUpdate() LevelItemManager.OnUpdate() LuaShoveManager.EntityShove() end --if not LogicUpdateManager.IsInReconnectFastPlay() then LogicUpdateManager.FrameEndLog() --用到的时候再加 --end LevelManager.OnUpdate_InLevel() private.FilterLevelCommand() --客户端发送当前指令 private.HandleSelf() LuaInputManager.OnUpdate() SyncMoveManager.HandleEntityMove() ExpUpdateManager.OnPostLogicUpdate() end --[[ 模拟接收服务器的指令 --]] function private.CheatSyncServerCommand() private._serverFrame = private._serverFrame + 1 --local entityID = LogicUpdateManager._curEntity._logicInitData.EntityID 郭卫松 改为了playerID local playerID = LevelPlayerManager.MyPlayerIDInRoom if not private._syncCommandDir[playerID] then private._syncCommandDir[playerID] = {} end local frameCommandList = private._syncCommandDir[playerID] if not frameCommandList[private._serverFrame] then frameCommandList[private._serverFrame] = {} end local frameCommand = frameCommandList[private._serverFrame] local command = private._selfCommandList[private._serverFrame] if not command then command = Command.TryGet() command._frame = private._serverFrame end frameCommand[1] = command private._selfCommandList[private._serverFrame] = nil end --[[ 客户端发送当前指令 --]] function private.HandleSelf() private._clientForecastFrame = private._clientForecastFrame + 1 -- 重播不需要产生命令 if LogicUpdateManager.IsReplayOrServerCheck then return end -- 观战也不用产生命令 if LevelManager.IsInLevelWatchBattle() or LevelManager.IsReplayBattle() then return end --禁止玩家输入的关卡 if LevelManager.IsForbidPlayerInputLevel() then if LevelManager.IsNeedCheckNotSyncLevel() then --需要单机校验的关卡即便禁止输入也要插空帧,否则服务器无法进行校验 -- -- 特殊指令:LuaConst.SpecialCommandType.SoloFightNextRound 不能被禁 if LuaInputManager.SpecialCommand == SpecialCommandType_SoloFightNextRound then local command = Command.TryGet() command.Frame = private._clientForecastFrame + 1 command.SpecialCommand = LuaInputManager.SpecialCommand private._selfCommandList[command.Frame] = command --这个是离线模拟的数据,连服务器的战斗不会用这个 FirstPersonEventDispatcher.OnSendSpecialCommand(LogicUpdateManager._curEntity, command.SpecialCommand) LevelManager.RecordFrameCmd(command.Frame, 1, 0, 0, 0, LuaInputManager.SpecialCommand, false, 0, 0) else LevelManager.RecordFrameCmd(private._clientForecastFrame + 1, 1, 0, 0, 0, 0, false, 0, 0) end else if LuaInputManager.SpecialCommand == SpecialCommandType_SoloFightNextRound then local command = Command.TryGet() command.Frame = private._clientForecastFrame + 1 command.SpecialCommand = LuaInputManager.SpecialCommand private._selfCommandList[command.Frame] = command --这个是离线模拟的数据,连服务器的战斗不会用这个 FirstPersonEventDispatcher.OnSendSpecialCommand(LogicUpdateManager._curEntity, command.SpecialCommand) end end return end EventSystem.Dispatch(EventID.Cheat_TestAutoGetCommond) -- 模拟玩家自动战斗,尝试输入input local skillIndex = 0 local useJoyStick = 1 local moveType = 1 if LogicUpdateManager._curEntity then --主视角调用 local entityOfCurCmd = LogicUpdateManager._curEntity.components.drive.DriveEntity or LogicUpdateManager._curEntity skillIndex = SystemManager.GetSkillIndexByKeyPosition(entityOfCurCmd,LuaInputManager.KeyPosition) if LuaInputManager.SkillJoystickWorldOffsetX == 0 and LuaInputManager.SkillJoystickWorldOffsetZ == 0 then useJoyStick = 0 end if LuaInputManager.UseJoyStickType > LuaConst.UseJoystickType.UseSkillJoystick then useJoyStick = LuaInputManager.UseJoyStickType end moveType = LuaInputManager.MoveType end if LevelManager.IsSyncLevel() then if LevelManager.isPlayingTimeline then -- 如果正在播放timeline return end --主视角调用 if LogicUpdateManager._curEntity and LogicUpdateManager._curEntity.components.autoFight.IsAIAutoTest then --pvp 自动战斗脚本,客户端本地脚本,一致性测试 local command = Command.TryGet() command.SkillIndex = 0 command.SpecialCommand = 0 command.MoveDir:Reset() --主视角调用 SystemManager.HandleDecision(LogicUpdateManager._curEntity, command) -- 产生指令 LuaInputManager.SetSpecialCommand(command.SpecialCommand) --LuaInputManager.SpecialCommand = command.SpecialCommand skillIndex = command.SkillIndex LuaInputManager.FixVirtualMoveDir:Reset(command.MoveDir) useJoyStick = 0 end -- 向服务器发送指令 if LogicUpdateManager.FightBegin then -- x、z合并为1个angle值 local angle = 255 --255代表无效数据(无输入)>=181也是无效 if LuaInputManager.FixVirtualMoveDir ~= FP.Const.Vector2_Zero then angle = FP.V2.AngleToRight360(LuaInputManager.FixVirtualMoveDir) angle:Div(2) -- 这个值为了优化,从short改为了byte ,所有角度值都除以2 ,精度为2 。 angle = FP.tonumberV1(angle) end if LuaInputManager.SpecialCommand == LuaConst.SpecialCommandType.RunToPosByIndex then LuaInputManager.SkillJoystickWorldOffsetX = LuaInputManager.posIndex useJoyStick = 1 end KCPTransport.SendC2BFrame( skillIndex, LuaInputManager.SpecialCommand, moveType, angle, useJoyStick, LuaInputManager.SkillJoystickWorldOffsetX, LuaInputManager.SkillJoystickWorldOffsetZ) if LuaInputManager.SpecialCommand > 0 then FirstPersonEventDispatcher.OnSendSpecialCommand(LogicUpdateManager._curEntity, LuaInputManager.SpecialCommand) end end else local command = Command.TryGet() command.Frame = private._clientForecastFrame + 1 command.MoveType = moveType command.MoveDir:Reset(LuaInputManager.FixVirtualMoveDir) command.RotateDir:Reset(LuaInputManager.FixVirtualMoveDir) command.SkillIndex = skillIndex command.SpecialCommand = LuaInputManager.SpecialCommand command.UseJoyStick = useJoyStick command.SkillJoystickPosX:Rawset( LuaInputManager.SkillJoystickWorldOffsetX*10)-- Rawset乘10代表保留两位小数【传输的是short最大只能是32768,只能表示±327.68以内的值】 command.SkillJoystickPosZ:Rawset( LuaInputManager.SkillJoystickWorldOffsetZ*10)-- Rawset乘10代表保留两位小数【传输的是short最大只能是32768,只能表示±327.68以内的值】 if LuaInputManager.SpecialCommand == LuaConst.SpecialCommandType.RunToPosByIndex then command.SkillJoystickPosX:Reset(LuaInputManager.posIndex) end private._selfCommandList[command.Frame] = command --这个是离线模拟的数据,连服务器的战斗不会用这个 if command.SpecialCommand > 0 then FirstPersonEventDispatcher.OnSendSpecialCommand(LogicUpdateManager._curEntity, command.SpecialCommand) end if LevelManager.IsNeedCheckNotSyncLevel() then -- or LevelManager.IsRogueLike() LevelManager.RecordFrameCmd(command.Frame, moveType, LuaInputManager.FixVirtualMoveDir.x[1], LuaInputManager.FixVirtualMoveDir.z[1], skillIndex, LuaInputManager.SpecialCommand, useJoyStick, LuaInputManager.SkillJoystickWorldOffsetX, LuaInputManager.SkillJoystickWorldOffsetZ) end end LuaInputManager.ClearSkillJoystickInfo() LuaInputManager.SetMoveType(1) end -- 处理服务器给关卡的插帧 function private.FilterLevelCommand() local frameCommandList = private._syncCommandDir[LEVEL_CMD_PLAYERID] if frameCommandList then local frameCommands = frameCommandList[LogicUpdateManager._clientHandleFrame] if frameCommands then for j,command in ipairs(frameCommands) do FilterManager.FilterLevelCommand(command) end LogicUpdateManager._handleCmdCount = LogicUpdateManager._handleCmdCount + #frameCommands if LevelManager.NeedStatisticFrameCmdLevel() then -- 目前只在军团战+大乱斗统计 if private.handleFrame2CmdCountDic[LogicUpdateManager._clientHandleFrame] then private.handleFrame2CmdCountDic[LogicUpdateManager._clientHandleFrame] = private.handleFrame2CmdCountDic[LogicUpdateManager._clientHandleFrame] + #frameCommands else private.handleFrame2CmdCountDic[LogicUpdateManager._clientHandleFrame] = #frameCommands end end end if not DEBUG_SAVE_FRAME_DATA then frameCommandList[LogicUpdateManager._clientHandleFrame] = nil end end end --[[ 在一次Tick中,遍历所有Entity --]] function private.TraversalAllEntity() --在遍历前,设置输入信息 LuaInputManager.SetInputBeforeTraversal() private.TraversalSyncEntity() private.TraversalNotSyncEntity() --在遍历后,设置输入信息 LuaInputManager.SetInputAfterTraversal() end --[[ 在一次Tick中,遍历所有在同步玩法中,需要接收服务器消息才能行动的Entity --]] function private.TraversalSyncEntity() local cmdCount = 0 for i,entity in ipairs(LogicUpdateManager.EntityList_Sync) do local components=entity.components local baseComp = components.base if baseComp.IsActive or baseComp.HidingNeedUpdate then -- 被切换下场的武将不执行( 但是有特殊情况下需要更新:如驾驶投石车、暂时消失) local playerID = entity._logicInitData.PlayerIDInRoom local hasCommand = false -- 目的是及时无主动指令也要执行被动指令 local toHandleEntity = entity -- 记录要被处理的entity if entity._logicInitData.BattleMemberType ~= LuaConst.BattleMemberType.Robot_Hero and entity:IsMainEntityOfPlayer(playerID) then local frameCommandList = private._syncCommandDir[playerID] if frameCommandList then --frameCommand:当前帧对应的Entity的所有的Command,可能不止一个 --(这样支持玩家一帧进行多个操作,如果客户端在卡顿的多帧进行多个操作,会在之后通畅的一帧一起handle) local frameCommands = frameCommandList[LogicUpdateManager._clientHandleFrame] if frameCommands then for j,command in ipairs(frameCommands) do LogicUpdateManager._handleCmdCount = LogicUpdateManager._handleCmdCount + 1 cmdCount = cmdCount + 1 --log - 指令 -- CompareSyncLogManager.TryColectLog( LuaConst.CheckBattleLogType.OriginCmd , playerID , command ) LevelManager.CurLevelFunc:PreHandleCommand(entity, playerID, command) components.move.VirtualMoveDir:Reset(command.MoveDir) --摇杆方向(目前只给技能寻敌用) private.TrySetPassiveityCommand(entity,command) private.TrySetAutoFightCommand(entity,command) -- CompareSyncLogManager.TryColectLog( LuaConst.CheckBattleLogType.AfterCmd , playerID , command ) local driveEntity = components.drive:GetDrivedEntity() if driveEntity then toHandleEntity = driveEntity end private.HandleCommand(toHandleEntity, command) if not DEBUG_SAVE_FRAME_DATA then frameCommands[j] = nil end hasCommand = true Command.Recycle(command) end end if not DEBUG_SAVE_FRAME_DATA then frameCommandList[LogicUpdateManager._clientHandleFrame] = nil end end end if not hasCommand then -- 无主动指令也要执行被动指令,掉线的也走这 if private.NeedSetAutoFightCommand(entity) or private.NeedSetPassivityCommand(entity) then --这里提前判断,能省一个Command.TryGet() local command = Command.TryGet() private.TrySetPassiveityCommand(entity,command) private.TrySetAutoFightCommand(entity,command) if not command.DontHandle or command.SpecialCommand ~= LuaConst.SpecialCommandType.None then -- CompareSyncLogManager.TryColectLog( LuaConst.CheckBattleLogType.AfterCmd , playerID , command ) private.HandleCommand(toHandleEntity, command) end Command.Recycle(command) --else --do nothing end local driveEntity = components.drive:GetDrivedEntity() if driveEntity then toHandleEntity = driveEntity end end SystemManager.OnLogicUpdate(toHandleEntity) else --三切,被切换下场的武将(+死亡但是还存在的主将) SystemManager.OnDeActiveUpdate(entity) end end if LevelManager.NeedStatisticFrameCmdLevel() then -- 目前只在军团战+大乱斗统计 private.handleFrame2CmdCountDic[LogicUpdateManager._clientHandleFrame] = cmdCount end end --[[ 在一次Tick中,遍历所有不需要接收服务器消息即可行动的Entity --]] function private.TraversalNotSyncEntity() --目前只有两种频率,每帧刷新和每LuaConst.AIHandleTick刷新 --所以aiTickIndex可以统一计算,减少取余操作,提升性能 --如果将来每个单位的GetAiUpdateInterval()返回不同的值,比如1、2、3、4、5,那这里的 aiTickIndex 得用不同的单位的GetAiUpdateInterval()来计算 local aiTickIndex = LogicUpdateManager._clientHandleFrame % LuaConst.AIHandleTick private._needDelayRemoveNormalEntity = true local aiBaseComponent = nil for i,entity in ipairs(LogicUpdateManager.EntityList_Normal) do if entity.isPickupItem then DeadSystem.OnLogicUpdate(entity) else local components=entity.components aiBaseComponent = components.aiBase local beDrived = components.drive.BeDrived -- 被驾驶了 if components.base.IsActive then if beDrived then local driver = components.drive:GetLastDriver() local command = Command.TryGet() private.HandleCommand(driver, command) Command.Recycle(command) SystemManager.OnLogicUpdate(driver) else if aiBaseComponent.IsAIActive then if aiBaseComponent:GetAiUpdateInterval() == 1 or aiTickIndex == aiBaseComponent.AiUpdateBatchIndex then private.OnAiUpdate(entity) end end SystemManager.OnLogicUpdate(entity) end end end end private._needDelayRemoveNormalEntity = false local count = #LogicUpdateManager.toRemoveEntityList if count > 0 then for i = count, 1, -1 do local entity = LogicUpdateManager.toRemoveEntityList[i] table_removebyvalue(LogicUpdateManager.EntityList_Normal,entity,true) table.remove(LogicUpdateManager.toRemoveEntityList,i) end end for i, npcEntity in ipairs(LogicUpdateManager.EntityList_Npc) do aiBaseComponent = npcEntity.components.aiBase if aiBaseComponent.IsAIActive then if aiBaseComponent:GetAiUpdateInterval() == 1 or aiTickIndex == aiBaseComponent.AiUpdateBatchIndex then private.OnAiUpdate_NPC(npcEntity) end end SystemManager.OnLogicUpdate_NPC(npcEntity) end end function private.OnAiUpdate(entity) local notSyncCommand = private._notSyncCommand local loopCount = 0 local aiBase=entity.components.aiBase local lastDecisionData = nil --策划需求,瞬时decision需要可以配置一帧运行多个,直到运行到持续性decision。用IsCombine控制 while loopCount == 0 or (aiBase.CurDecisionNode and aiBase.CurDecisionNode.IsOnce and aiBase.CurDecisionData.IsCombine and (lastDecisionData == nil or lastDecisionData.NextDecisionData == aiBase.CurDecisionData)) do if loopCount > 20 then logging.error(LTAG.ERROR, "HYW : 循环超过20次,可能有死循环!是不是合并执行的瞬时节点没有接持续性节点?CharacterID : ", entity:GetCharacterID(), " aiid : ", aiBase:GetAIID(), " deicisionID : ", aiBase.CurDecisionData.ID) break end lastDecisionData = aiBase.CurDecisionData if not GlobalBattleData.Get_IsUseSelfAI() and entity._logicInitData.IsTest then --测试用的Entity GlobalBattleData.HandleDecision(entity, notSyncCommand) else SystemManager.HandleDecision(entity, notSyncCommand) end private.HandleCommand(entity, notSyncCommand) Command.Reset(notSyncCommand) loopCount = loopCount + 1 end aiBase:AfterAiUpdate() aiBase:ReSetLevelMessageID() end function private.OnAiUpdate_NPC(entity) local notSyncCommand = private._notSyncCommand SystemManager.HandleDecision(entity, notSyncCommand) private.HandleCommand_NPC(entity,notSyncCommand) entity.components.aiBase:ReSetLevelMessageID() Command.Reset(notSyncCommand) end function private.NeedSetAutoFightCommand(entity) return entity.components.autoFight:IsAuto() and entity.components.attr:IsAlive() end -- 尝试设置自动战斗指令 function private.TrySetAutoFightCommand(entity,command) if private.NeedSetAutoFightCommand(entity) then SystemManager.HandleDecision(entity, command) end end function private.NeedSetPassivityCommand(entity) return entity.components.record.PassivitySpecialCommand ~= LuaConst.SpecialCommandType.None end --尝试设置被动指令(目前仅有复活) function private.TrySetPassiveityCommand(entity,command) if private.NeedSetPassivityCommand(entity) then command.SpecialCommand = entity.components.record.PassivitySpecialCommand entity.components.record.PassivitySpecialCommand = LuaConst.SpecialCommandType.None end end -- 执行指令 function private.HandleCommand(entity, command) BattleEventSystem.BeforeFilter(entity ,command) --过滤层 local stateType,skillID,animName = FilterManager.FilterCommand(entity,command) --将指令转译为状态机信息,某些指令会被过滤,转译成空的状态机信息 if LuaInputManager.KeyPosResetAfterUse then LuaInputManager.TryKeyPosResetAfterUse(entity, skillID) end private.HandleState(entity,command,stateType,skillID,animName) --根据指令和状态机信息修改状态 end function private.HandleCommand_NPC(entity, command) --过滤层 local stateType,skillID,animName = FilterManager.FilterCommand_NPC(entity,command) private.HandleState(entity,command,stateType,skillID,animName) end --根据指令和状态机信息修改状态 function private.HandleState(entity,command,stateType,skillID,animName) --运动层 local moveComponent = entity.components.move if moveComponent.forbidJoystick then --如果禁用左摇杆 moveComponent:SetWantDir() moveComponent:SetWantRotateDir(nil) else --没有禁用左摇杆 moveComponent:SetHalfDir(command.MoveType == 0) moveComponent:SetWantDir(command.MoveDir) moveComponent:SetWantRotateDir(command.RotateDir) moveComponent.WantRotateDir:Normalize() end if command.TargetPos and not moveComponent.forbidJoystick then if moveComponent.WantPos then moveComponent.WantPos:Reset(command.TargetPos) else moveComponent.WantPos = command.TargetPos:cloneV2() end else if moveComponent.WantPos then FP.releaseV2(moveComponent.WantPos) moveComponent.WantPos = nil end end moveComponent:SetStepOnAccelerator(command.UseJoyStick) if entity.components.skillBase then entity.components.skillBase:SetSkillJoystickInfoByCommand(command) end --动画状态层 if stateType ~= LuaConst.StateType.None then SystemManager.SetNextState(stateType,entity,skillID,animName,command) end end ---同步玩法用的一些代码------ --- function private.TryActiveSyncHero() if not LevelManager.IsSyncLevel() then return end local activePlayerList = KCPTransport.GetPlayerActiveList(LogicUpdateManager._clientHandleFrame) if not activePlayerList then return end local hasException = false for i,playerID in ipairs(activePlayerList) do local playerHeroArmy = LevelPlayerManager.GetPlayerArmyByPlayerIDInRoom(playerID) if not playerHeroArmy then logging.warn(LTAG.WARN, "【GWS】sync玩法:创建主将时居然没有找到玩家战斗信息 playerID : ", tostring(playerID) ,",强制暂停游戏update ,等待TCP传来信息 。程序注意IsFreeToEnterAndLeaveLevel要=true") MultiplayManager.ForceWait(playerID) hasException = true end end if hasException then return end for i,playerID in ipairs(activePlayerList) do LevelManager.CurLevelFunc:SetPlayerActive(playerID) end end --重连后正在快速播放 function LogicUpdateManager.IsInReconnectFastPlay() return LevelManager.IsSyncLevel() and LogicUpdateManager._clientHandleFrame <= LogicUpdateManager._serverReplenishFrame end -- 补帧结束 function LogicUpdateManager.CheckFastPlayOver() if LogicUpdateManager._clientHandleFrame == LogicUpdateManager._serverReplenishOverFrame then if LogicUpdateManager.FightBegin then -- 如果追帧完毕时,都结束了 ,那么在syncLevelFunction.StopLevel中处理 (原因 :计算的帧基本都是 < _serverReplenishFrame) --logging.error(LTAG.ERROR, "FastPlayOver :" ,LogicUpdateManager._clientHandleFrame) SystemManager.OnFastPlayOver() --随进随出玩法,需要在自己创建成功后,广播一个SpecialCommand,让所有端同时active这个单位 。 if LevelManager.CurLevelFunc.isFreeToEnterAndLeave and LogicUpdateManager._curEntity == nil then -- LogicUpdateManager._curEntity == nil是为了防止断线重连后重复发送 EventSystem.Dispatch(EventID.BI_LevelMiddle,BIManager.BIType.SyncBattle,"[sync]FastPlayOverActiveSelf") LuaInputManager.SetSpecialCommand(LuaConst.SpecialCommandType.ActiveSelf) end end end end function LogicUpdateManager.SetServerReplenishFrame(frame) LogicUpdateManager._serverReplenishFrame = frame if frame < 0 then -- 终止补帧 LogicUpdateManager._serverReplenishOverFrame = -1 else LogicUpdateManager._serverReplenishOverFrame = frame + 1 end end function LogicUpdateManager.SetCurEntity(entity) LogicUpdateManager._curEntity = entity end --检测是否在线 function private.UpdateSyncLevelPlayerOnlineState() -- 更新玩家在线状态 ,并统计在线人数 LevelPlayerManager.UpdateSyncLevelPlayerOnlineState() end function LogicUpdateManager.GetServerFrame() return private._serverFrame end --打印log function LogicUpdateManager.FrameEndLog() -- CompareSyncLogManager.FrameEndLog() end function LogicUpdateManager.ChangeOnlineState(value) LogicUpdateManager.IsOnlinePlay = value end -- 只给校验使用 function LogicUpdateManager.SetSyncCommandDic(cmdDic,frameNum) private._syncCommandDir = cmdDic private._serverFrame = frameNum EventSystem.Dispatch(EventID.Level_UpdateServerFrame,private._serverFrame) -- 更新进度条数字 end function LogicUpdateManager.ClearHandleFrame2CmdCountDic() private.handleFrame2CmdCountDic = {} end function LogicUpdateManager.GetHandleFrame2CmdCountDic() return private.handleFrame2CmdCountDic end function LogicUpdateManager.PauseEntity(pause) if pause then for i, entity in pairs(LogicUpdateManager._entityDic) do if not entity.isPickupItem then SystemManager.OnAddTimeScale(entity,"level",FP.newV1(0),-1,LuaConst.TimeScaleBreakType.NeverBreak) SystemManager.OnBreakTimeScale(entity,true,LuaConst.TimeScaleBreakType.LevelPauseBreak) end end else for i, entity in pairs(LogicUpdateManager._entityDic) do if not entity.isPickupItem then SystemManager.OnDeleteTimeScale(entity,"level") SystemManager.OnBreakTimeScale(entity,false,LuaConst.TimeScaleBreakType.LevelPauseBreak) end end end end function LogicUpdateManager._validEntityPair(t,i) while true do local v i,v = next(t,i) if v then if v:IsValid() then return i,v end else break end end end function LogicUpdateManager._validMainEntityIPair(t, i) while true do i = i + 1 local v = t[i] if v then if v.index == 1 then -- 为了性能优化把函数展开,原来是v:IsMainEntityOfPlayer() and v:IsValid() local cps = v.components local base = cps.base local attr = cps.attr if (base.IsActive or base.HidingNeedUpdate) and attr.hp > FP_Zero then return i, v end end else break end end end function LogicUpdateManager._validMonsterIPair(t, i) while true do i = i + 1 local v = t[i] if v then if v._logicInitData.CharacterType == LuaConst.CharacterType.Monster and v:IsValid() then return i,v end else break end end end function LogicUpdateManager.ValidSyncEntityIter() return LogicUpdateManager._validMainEntityIPair, LogicUpdateManager.EntityList_Sync, 0 end function LogicUpdateManager.ValidAllEntityIter() return LogicUpdateManager._validEntityPair, LogicUpdateManager._entityDic, nil end function LogicUpdateManager.ValidMonsterEntityIter() return LogicUpdateManager._validMonsterIPair, LogicUpdateManager.EntityList_Normal, 0 end return LogicUpdateManager