local Replay_Controller = {} local EventSystem = require 'Event/EventSystem' local BattleSimulator = require("Battle/Logic/MainLoop/BattleSimulator") local battle_match_pb = require 'Proto/battle_match_pb' local battle_common_pb = require 'Proto/battle_common_pb' local private = { lastRoleEnterSceneMsg = nil, -- 需要保存的 RoleEnterScene 协议(boss 挑战用) allReplayMsg = nil, --回放的数据 curPlayer = 1, -- 当前视角的玩家 playRate = 1, -- 当前播放速率 isPlayAgain = false, -- 是否又重播 gRoomId = nil, -- 个人回放使用 :回放文件标识 (同步玩法都是 gRoomId ,非同步玩法是battleID) curReplayIsFromMyRecords = false, -- 当前回放是否 个人记录(个人记录一定是从本地文件获取到的) curReplayMyPlayerID = 1, -- 个人回放:当前回放的战斗我的playerID } local FrameFileDirPath = LuaConst.SaveReplayFileDirPath -- 回放文件存在的目录 --排序参数 Replay_Controller.SortStateEnum = { SortByScore = 1, --按积分排序 SortByHot = 2, --按热度排序 } function Replay_Controller.SetMyPlayerIDOfMyRecords(myPlayerID) private.curReplayMyPlayerID = myPlayerID end function Replay_Controller.IsReplayFromMyRecords() return private.curReplayIsFromMyRecords end --region 播放控制台 function Replay_Controller.StartReplay(dataFilePath, playerID, startFrame) local dataBytes = LuaExt.FileReadBytes(dataFilePath or "ReplayData/replaydata") playerID = playerID or 1 BattleSimulator.FastPlayFrame = startFrame or 0 Replay_Controller.StartPlay(dataBytes, playerID) end --开始播放 function Replay_Controller.StartPlay(dataBytes, playerID) private.allReplayMsg = dataBytes private.curPlayer = playerID private.playRate = 1 private.isPlayAgain = false private.curReplayIsFromMyRecords = false BattleSimulator.SetPlayerID(playerID) BattleSimulator.StartServerData(dataBytes) LevelManager.SetIsReplayBattle(true) end -- 换人 function Replay_Controller.SwitchPlayer(useLast) local watchPlayerIDResult = NotSyncBattleSetter.SwitchWatchPlayer(useLast) private.curPlayer = watchPlayerIDResult EventSystem.Dispatch(EventID.UI_DragonArtery_ForceRefreshPlayerHead,watchPlayerIDResult) end -- 退出 function Replay_Controller.Exit() if not LevelManager.CurLevelFunc:GetHadStopLevel() then BattleSimulator.EndPlay(true) LevelManager.CurLevelFunc:StopLevel() end LevelManager.SetIsReplayBattle(false) EventSystem.Dispatch(EventID.Level_SendRoleLeaveFuben ,true) end -- 重播 function Replay_Controller.ReplayAgain() if not LevelManager.CurLevelFunc:GetHadStopLevel() then LevelManager.CurLevelFunc:StopLevel() end private.isPlayAgain = true BattleSimulator.SetPlayerID(private.curPlayer) BattleSimulator.StartServerData(private.allReplayMsg) end -- 暂停游戏 ,这个只能用于回放使用 一定要谨慎 ,因为有 设置 UnityEngine.Time.timeScale function Replay_Controller.PauseReplayBattle(isPause) LevelManager.PauseLevel(isPause) -- 所有entity不能动(时间缩放变为0),关卡停止tick UnityEngine.Time.timeScale = isPause and 0 or private.playRate end -- 设置播放速率 function Replay_Controller.SetPlayRate(rate, isPlaying) private.playRate = rate if isPlaying then UnityEngine.Time.timeScale = private.playRate end end function Replay_Controller.GetPlayRate() return private.playRate end function Replay_Controller.EndPlay(noReplayMessageBox) if UnityEngine.Time.timeScale ~=1 then UnityEngine.Time.timeScale = 1 end private.playRate = 1 -- 当前播放速率 private.isPlayAgain = false -- 是否又重播 if not noReplayMessageBox then -- 打开一个退出提示框 (退出和回放) local replayAgainFunc = nil if Replay_Controller.IsReplayFromMyRecords() then replayAgainFunc = Replay_Controller.ReplayAgainMyRecords else replayAgainFunc = MDL.Replay.ReplayAgain end UTL.MessageBox.ShowOKCancelNoCloseBox("", Const_IDS.IDS_FairPlayReplay_VideoFinishTips, Replay_Controller.Exit, replayAgainFunc, Const_IDS.IDS_FairPlayReplay_Quit, Const_IDS.IDS_FairPlayReplay_Replay) end end function Replay_Controller.IsPlayAgain() return private.isPlayAgain end --endregion --打开主界面 function Replay_Controller.OpenMainPanel() UIManager.OpenView(Const_UI_Lua.UIEnum.UIReplayMain) end --计算热度 -- videoPlayCount 播放量,averageScore 平均分 function Replay_Controller.GetHot(videoPlayCount,averageScore) local ReplayVCoef = FP.tonumberV1(DataSheetManager.ConstData:GetDataByID("ReplayVCoef").FloatValue) --播放量系数 local ReplayPowNum = FP.tonumberV1(DataSheetManager.ConstData:GetDataByID("ReplayPowNum").FloatValue) --播放量幂次 local ReplayACoef = FP.tonumberV1(DataSheetManager.ConstData:GetDataByID("ReplayACoef").FloatValue) --平均分系数 local result = ReplayVCoef * videoPlayCount^ReplayPowNum + ReplayACoef * averageScore local hot = math.ceil(result) return hot end --根据英雄id 筛选数据 function Replay_Controller.GetRankInfoByHeroId(data,heroId) if heroId == nil then return data end local result = {} for i,v in ipairs(data) do local isContainHero = false local replayInfo = MDL.Replay.ParseRankPerRecordReplayInfoFromBytes(v.extraParamBytes) for i = 1, #replayInfo.infos1 do local heroIds = replayInfo.infos1[i].heroIds for j = 1, #heroIds do if heroIds[2*j-1] == heroId then isContainHero = true end end end for i = 1, #replayInfo.infos2 do local heroIds = replayInfo.infos2[i].heroIds for j = 1, #heroIds do if heroIds[2*j-1] == heroId then isContainHero = true end end end if isContainHero then table.insert(result,v) end end return result end --重新排序数据 function Replay_Controller.ResortRankInfo(records,sortType) if sortType == Replay_Controller.SortStateEnum.SortByHot then --按热度排序 table.sort(records,private.SortRankInfoByHot) elseif sortType == Replay_Controller.SortStateEnum.SortByScore then --按积分排序 table.sort(records,private.SortRankInfoBySore) end return records end function private.SortRankInfoBySore(a,b) return a.param1 > b.param1 end function private.SortRankInfoByHot(a,b) local hotA = CTL.Replay.GetHot(a.thumbsUpNum,a.param1) local hotB = CTL.Replay.GetHot(b.thumbsUpNum,b.param1) return hotA > hotB end --打开我的战绩界面 function Replay_Controller.OnMyRecordsPanel() MDL.Replay.FairPlayMyRecordPanelReq(MDL.Replay.FairPlayType.Solo) UIManager.OpenView(Const_UI_Lua.UIEnum.UIFairPlayHistoryMain, {isFromMainPanel = true}) end --武将赛季信息 function Replay_Controller.OnMyHistoryPopUpPanel(seasonId,typ,lists) UIManager.OpenView(Const_UI_Lua.UIEnum.UIReplayHistoryPopUp,{seasonId = seasonId,typ = typ,lists = lists}) end --是否为当前赛季 function Replay_Controller.IsCurrentSeason(type,seasonId) if type == MDL.Replay.FairPlayType.Solo then return MDL.SyncSolo.GetSeasonId() == seasonId elseif type == MDL.Replay.FairPlayType.TVT then return MDL.SyncTeamPVP.GetSeasonId() == seasonId end return false end --获取当前赛季索引 function Replay_Controller.GetCurrentSeasonIndex(type) local seasonInfo = MDL.Replay.GetSeasonInfoByType(type) if type == MDL.Replay.FairPlayType.Solo then for i,v in ipairs(seasonInfo) do if v.seasonId == MDL.SyncSolo.GetSeasonId() then return i end end elseif type == MDL.Replay.FairPlayType.TVT then for i,v in ipairs(seasonInfo) do if v.seasonId == MDL.SyncTeamPVP.GetSeasonId() then return i end end end return 1 end --保留两位小数 local function KeepTwoDecimalPlaces(decimal) decimal = decimal * 10000 if decimal % 1 >= 0.5 then decimal= math.ceil(decimal) else decimal= math.floor(decimal) end return decimal * 0.01 end --输出胜率,要求保留两位小数四舍五入,0%,100% 特殊显示,不保留小数 function Replay_Controller.GetWinRateShow(winCount,gameCount) local temp = winCount/gameCount if winCount == 0 then return "0%" elseif winCount == gameCount then return "100%" end local result= string.format("%s%%", KeepTwoDecimalPlaces(temp)) return result end function Replay_Controller.ShowBattleResult(type, battleResultBytes) if type == MDL.Replay.FairPlayType.Solo then --1v1 local soloMsg = battle_match_pb.G2CPushSyncSoloBattleResult() soloMsg:ParseFromString(battleResultBytes) UIManager.OpenView(Const_UI_Lua.UIEnum.UISyncSoloBattleOverPanel, soloMsg) elseif type == MDL.Replay.FairPlayType.TVT then -- 3v3 local tvtMsg = battle_match_pb.G2CPushBattleResult() tvtMsg:ParseFromString(battleResultBytes) UIManager.OpenView(Const_UI_Lua.UIEnum.UISyncTeamPVPBattleOverPanel, tvtMsg) EventSystem.Dispatch(EventID.UI_SyncTeamPVPBattleOver_ShowResult, tvtMsg.ourCaptureProgress, tvtMsg.enemyCaptureProgress) end end function Replay_Controller.Is3V3battleMVP(gRoomId) local records = MDL.Replay.GetAllRecordsByType(MDL.Replay.FairPlayType.TVT) if records then for _, myRecord in ipairs(records) do if myRecord.gRoomId == gRoomId then local tvtMsg = battle_match_pb.G2CPushBattleResult() tvtMsg:ParseFromString(myRecord.battleResult) local playerID = Replay_Controller.GetMySelfPlayerID(tvtMsg.Icons,tvtMsg.selfACID) return playerID == tvtMsg.ourBestFighter, tvtMsg.isWin end end end return false, false end function Replay_Controller.GetMySelfPlayerID(iconsInfo, selfACID) for i, v in ipairs(iconsInfo) do if v.ACID == selfACID then return v.playerID end end logging.error(LTAG.ERROR,"未找到自己的playerID") end --region 删除本地文件 function Replay_Controller.TryDeleteFiles(deleteIDs) if not LuaExt.DirectoryExists(FrameFileDirPath) then return end local deleteFileRecords --读取回放文件夹下的所有文件 for i,str in ipairs(deleteIDs) do local filePath = FrameFileDirPath .. str LuaExt.FileDelete(filePath) if deleteFileRecords then deleteFileRecords = string.Concat(" ; ","filePath:", filePath) else deleteFileRecords = string.Concat("filePath:", filePath) end end BIManager.DispatchBIClientEvent(BIManager.EventName.DeleteAllMyRecords, "deleteFileRecords", deleteFileRecords, "isExistDirectory", LuaExt.DirectoryExists(FrameFileDirPath)) end function Replay_Controller.TryDeleteFileButRecords(butRecords) if not LuaExt.DirectoryExists(FrameFileDirPath) then return end local fileRecords --读取回放文件夹下的所有文件 local fileList = LuaExt.DirectoryGetFiles(FrameFileDirPath,"*", true) for i,filePath in ipairs(fileList) do local fileName = string.match(filePath, "([^/\\]+)$") local find = false for i,str in ipairs(butRecords) do if str == fileName then find = true if fileRecords then fileRecords = string.Concat(" ; ","filename:", fileName, " find:", find) else fileRecords = string.Concat("filename:", fileName, " find:", find) end break end end if not find then LuaExt.InfraFileDelete(filePath) end end BIManager.DispatchBIClientEvent(BIManager.EventName.DeleteExceptRecords, "butRecords", fileRecords, "isExistDirectory", LuaExt.DirectoryExists(FrameFileDirPath)) end -- 读取帧数据文件 function Replay_Controller.GetFrameBytesFromFile(gRoomId) local datas = LuaExt.FileReadBytes(FrameFileDirPath .. gRoomId) return datas end --endregion --region 保存非同步玩法数据(boss挑战) function Replay_Controller.TrySaveAsyncEnterMsg(roleEnterSceneMsg) if not LevelManager.NeedSaveFrameDatasLevel() then return end private.lastRoleEnterSceneMsg = roleEnterSceneMsg end -- 保存非同步玩法的帧数据 + 初始数据(boss挑战)-- 用 OfflineBattleCheckInfo 协议包装 function Replay_Controller.SaveAsyncLevelFrameDatas(frameBytes, battleId, checkInfo) if not LevelManager.NeedSaveFrameDatasLevel() or private.lastRoleEnterSceneMsg == nil then return end local saveMsg = battle_common_pb.OfflineBattleCheckInfo() saveMsg.roleEnterScene = private.lastRoleEnterSceneMsg:SerializeToString() saveMsg.frames = frameBytes saveMsg.clientCheckStr = checkInfo local bytes = saveMsg:SerializeToString() local filePath = FrameFileDirPath .. battleId if not LuaExt.DirectoryExists(FrameFileDirPath) then LuaExt.DirectoryCreate(FrameFileDirPath) end LuaExt.FileWriteBytes(filePath, false, bytes) BIManager.DispatchBIClientEvent(BIManager.EventName.SaveAsyncLevelFrameDatas, "filePath", filePath) end --endregion --region 个人记录: 回放数据读取文件 ; function Replay_Controller.TryReplayMyRecords(gRoomId, type, dataBytes) local filePath = FrameFileDirPath .. gRoomId if not LuaExt.FileExists(filePath) then if string.isNilOrEmpty(dataBytes) then logging.error(LTAG.ERROR, "【gws】获取个人回放数据错误:本地不存在文件且服务器返回的回放数据为空,gRoomId=", gRoomId, " type =", type) return end LuaExt.FileWriteBytes(filePath, false, dataBytes) BIManager.DispatchBIClientEvent(BIManager.EventName.StartPlayByServerData, "filePath", filePath) else BIManager.DispatchBIClientEvent(BIManager.EventName.StartPlayByLocalData, "filePath", filePath, "isExist", LuaExt.FileExists(filePath)) end if type == MDL.Replay.FairPlayType.Boss then Replay_Controller.StartPlayAsyncLevel_ByFile(gRoomId) else Replay_Controller.StartPlaySyncLevel_ByFile(gRoomId) end end function Replay_Controller.ReplayAgainMyRecords() if BattleDataGetter.IsSyncLevel() then Replay_Controller.ReplayAgain_SyncLevel_ByFile() else Replay_Controller.ReplayAgain_AsyncLevel_ByFile() end end -- 回放非同步战斗(boss挑战) function Replay_Controller.StartPlayAsyncLevel_ByFile(gRoomId) private.curPlayer = 1 private.playRate = 1 private.isPlayAgain = false private.gRoomId = gRoomId private.curReplayIsFromMyRecords = true BattleSimulator.SetPlayerID(private.curPlayer) local offlineBattleCheckInfoBytes = LuaExt.FileReadBytes(FrameFileDirPath .. gRoomId) BattleSimulator.ClientReplayAsyncLevel(offlineBattleCheckInfoBytes) LevelManager.SetIsReplayBattle(true) end -- 重新回放非同步战斗(boss挑战) function Replay_Controller.ReplayAgain_AsyncLevel_ByFile() if not LevelManager.CurLevelFunc:GetHadStopLevel() then LevelManager.CurLevelFunc:StopLevel() end private.isPlayAgain = true BattleSimulator.SetPlayerID(private.curPlayer) local offlineBattleCheckInfoBytes = LuaExt.FileReadBytes(FrameFileDirPath .. private.gRoomId) BattleSimulator.ClientReplayAsyncLevel(offlineBattleCheckInfoBytes) LevelManager.SetIsReplayBattle(true) end -- 使用本地文件播放 同步战斗 (1v1,3v3) function Replay_Controller.StartPlaySyncLevel_ByFile(gRoomId) private.curPlayer = private.curReplayMyPlayerID private.playRate = 1 private.isPlayAgain = false private.gRoomId = gRoomId private.curReplayIsFromMyRecords = true BattleSimulator.SetPlayerID(private.curReplayMyPlayerID) local bytes = LuaExt.FileReadBytes(FrameFileDirPath .. gRoomId) BattleSimulator.ClientReplaySyncLevel_File(bytes, gRoomId) LevelManager.SetIsReplayBattle(true) end -- 重新回放同步战斗(1v1 ,3v3) function Replay_Controller.ReplayAgain_SyncLevel_ByFile() if not LevelManager.CurLevelFunc:GetHadStopLevel() then LevelManager.CurLevelFunc:StopLevel() end private.isPlayAgain = true BattleSimulator.SetPlayerID(private.curReplayMyPlayerID) local bytes = LuaExt.FileReadBytes(FrameFileDirPath .. private.gRoomId) BattleSimulator.ClientReplaySyncLevel_File(bytes, private.gRoomId) end --endregion return Replay_Controller