diff --git a/src/engine/client.h b/src/engine/client.h index f57edac11..d01363793 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -183,10 +183,6 @@ public: virtual void RaceRecord_Stop() = 0; virtual bool RaceRecord_IsRecording() = 0; - virtual void GhostRecorder_Start(const char *pFilename, const char *pPlayerName) = 0; - virtual bool GhostLoader_Load(const char *pFilename) = 0; - virtual bool GhostLoader_GetGhostInfo(const char *pFilename, struct CGhostHeader *pGhostHeader) = 0; - virtual void DemoSliceBegin() = 0; virtual void DemoSliceEnd() = 0; virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser) = 0; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index beddbc29b..514b24dd4 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -2570,6 +2570,9 @@ void CClient::InitInterfaces() m_Friends.Init(); m_Foes.Init(true); + + m_GhostRecorder.Init(); + m_GhostLoader.Init(); } void CClient::Run() @@ -3620,21 +3623,6 @@ bool CClient::RaceRecord_IsRecording() return m_DemoRecorder[RECORDER_RACE].IsRecording(); } -void CClient::GhostRecorder_Start(const char *pFilename, const char *pPlayerName) -{ - m_GhostRecorder.Start(Storage(), m_pConsole, pFilename, m_aCurrentMap, m_pMap->Crc(), pPlayerName); -} - -bool CClient::GhostLoader_Load(const char *pFilename) -{ - return m_GhostLoader.Load(Storage(), m_pConsole, pFilename, m_aCurrentMap, m_pMap->Crc()) == 0; -} - -bool CClient::GhostLoader_GetGhostInfo(const char *pFilename, struct CGhostHeader *pGhostHeader) -{ - return m_GhostLoader.GetGhostInfo(Storage(), m_pConsole, pFilename, pGhostHeader, m_aCurrentMap, m_pMap->Crc()); -} - void CClient::RequestDDNetInfo() { diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 7c969ce0f..85cd42a2b 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -391,10 +391,6 @@ public: void RaceRecord_Stop(); bool RaceRecord_IsRecording(); - void GhostRecorder_Start(const char *pFilename, const char *pPlayerName); - bool GhostLoader_Load(const char *pFilename); - bool GhostLoader_GetGhostInfo(const char *pFilename, struct CGhostHeader *pGhostHeader); - virtual void DemoSliceBegin(); virtual void DemoSliceEnd(); virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser); diff --git a/src/engine/ghost.h b/src/engine/ghost.h index 5195ae5fa..b9cd816c9 100644 --- a/src/engine/ghost.h +++ b/src/engine/ghost.h @@ -5,8 +5,9 @@ #include "kernel.h" -struct CGhostHeader +class CGhostHeader { +public: unsigned char m_aMarker[8]; unsigned char m_Version; char m_aOwner[MAX_NAME_LENGTH]; @@ -14,6 +15,16 @@ struct CGhostHeader unsigned char m_aCrc[4]; unsigned char m_aNumTicks[4]; unsigned char m_aTime[4]; + + int GetTime() const + { + return (m_aTime[0] << 24) | (m_aTime[1] << 16) | (m_aTime[2] << 8) | (m_aTime[3]); + } + + int GetTicks() const + { + return (m_aNumTicks[0] << 24) | (m_aNumTicks[1] << 16) | (m_aNumTicks[2] << 8) | (m_aNumTicks[3]); + } }; class IGhostRecorder : public IInterface @@ -21,9 +32,11 @@ class IGhostRecorder : public IInterface MACRO_INTERFACE("ghostrecorder", 0) public: virtual ~IGhostRecorder() {} + + virtual int Start(const char *pFilename, const char *pMap, unsigned MapCrc, const char *pName) = 0; virtual int Stop(int Ticks, int Time) = 0; - virtual void WriteData(int Type, const char *pData, int Size) = 0; + virtual void WriteData(int Type, const void *pData, int Size) = 0; virtual bool IsRecording() const = 0; }; @@ -32,15 +45,16 @@ class IGhostLoader : public IInterface MACRO_INTERFACE("ghostloader", 0) public: virtual ~IGhostLoader() {} + + virtual int Load(const char *pFilename, const char *pMap, unsigned Crc) = 0; virtual void Close() = 0; virtual const CGhostHeader *GetHeader() const = 0; virtual bool ReadNextType(int *pType) = 0; - virtual bool ReadData(int Type, char *pData, int Size) = 0; + virtual bool ReadData(int Type, void *pData, int Size) = 0; - virtual int GetTime(const CGhostHeader *pHeader) const = 0; - virtual int GetTicks(const CGhostHeader *pHeader) const = 0; + virtual bool GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc) = 0; }; #endif diff --git a/src/engine/shared/ghost.cpp b/src/engine/shared/ghost.cpp index 2b2a543bf..6c67f6c36 100644 --- a/src/engine/shared/ghost.cpp +++ b/src/engine/shared/ghost.cpp @@ -17,12 +17,16 @@ CGhostRecorder::CGhostRecorder() ResetBuffer(); } -// Record -int CGhostRecorder::Start(IStorage *pStorage, IConsole *pConsole, const char *pFilename, const char *pMap, unsigned Crc, const char* pName) +void CGhostRecorder::Init() { - m_pConsole = pConsole; + m_pConsole = Kernel()->RequestInterface(); + m_pStorage = Kernel()->RequestInterface(); +} - m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); +// Record +int CGhostRecorder::Start(const char *pFilename, const char *pMap, unsigned Crc, const char* pName) +{ + m_File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); if(!m_File) { char aBuf[256]; @@ -71,7 +75,7 @@ static void DiffItem(int *pPast, int *pCurrent, int *pOut, int Size) } } -void CGhostRecorder::WriteData(int Type, const char *pData, int Size) +void CGhostRecorder::WriteData(int Type, const void *pData, int Size) { if(!m_File || (unsigned)Size > MAX_ITEM_SIZE || Size <= 0 || Type == -1) return; @@ -167,6 +171,12 @@ CGhostLoader::CGhostLoader() ResetBuffer(); } +void CGhostLoader::Init() +{ + m_pConsole = Kernel()->RequestInterface(); + m_pStorage = Kernel()->RequestInterface(); +} + void CGhostLoader::ResetBuffer() { m_pBufferPos = m_aBuffer; @@ -175,10 +185,9 @@ void CGhostLoader::ResetBuffer() m_BufferPrevItem = -1; } -int CGhostLoader::Load(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pMap, unsigned Crc) +int CGhostLoader::Load(const char *pFilename, const char *pMap, unsigned Crc) { - m_pConsole = pConsole; - m_File = pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE); + m_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE); if(!m_File) { char aBuf[256]; @@ -299,7 +308,7 @@ static void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size) } } -bool CGhostLoader::ReadData(int Type, char *pData, int Size) +bool CGhostLoader::ReadData(int Type, void *pData, int Size) { if(!m_File || Size > MAX_ITEM_SIZE || Size <= 0 || Type == -1) return false; @@ -327,14 +336,14 @@ void CGhostLoader::Close() m_File = 0; } -bool CGhostLoader::GetGhostInfo(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc) const +bool CGhostLoader::GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc) { if(!pGhostHeader) return false; mem_zero(pGhostHeader, sizeof(CGhostHeader)); - IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE); + IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE); if(!File) return false; @@ -344,43 +353,29 @@ bool CGhostLoader::GetGhostInfo(class IStorage *pStorage, class IConsole *pConso { io_close(File); // old version... try to update - if(CGhostUpdater::Update(pStorage, pConsole, pFilename)) + IGhostRecorder *pRecorder = Kernel()->RequestInterface(); + if(CGhostUpdater::Update(pRecorder, m_pStorage, m_pConsole, pFilename)) { // try again - File = pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE); + File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE); io_read(File, pGhostHeader, sizeof(CGhostHeader)); } else return false; } + io_close(File); + if(mem_comp(pGhostHeader->m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) || (pGhostHeader->m_Version != gs_ActVersion && pGhostHeader->m_Version != 4)) - { - io_close(File); return false; - } unsigned GhostMapCrc = (pGhostHeader->m_aCrc[0] << 24) | (pGhostHeader->m_aCrc[1] << 16) | (pGhostHeader->m_aCrc[2] << 8) | (pGhostHeader->m_aCrc[3]); if(str_comp(pGhostHeader->m_aMap, pMap) != 0 || GhostMapCrc != Crc) - { - io_close(File); return false; - } - io_close(File); return true; } -int CGhostLoader::GetTime(const CGhostHeader *pHeader) const -{ - return (pHeader->m_aTime[0] << 24) | (pHeader->m_aTime[1] << 16) | (pHeader->m_aTime[2] << 8) | (pHeader->m_aTime[3]); -} - -int CGhostLoader::GetTicks(const CGhostHeader *pHeader) const -{ - return (pHeader->m_aNumTicks[0] << 24) | (pHeader->m_aNumTicks[1] << 16) | (pHeader->m_aNumTicks[2] << 8) | (pHeader->m_aNumTicks[3]); -} - inline void StrToInts(int *pInts, int Num, const char *pStr) { int Index = 0; @@ -398,9 +393,7 @@ inline void StrToInts(int *pInts, int Num, const char *pStr) pInts[-1] &= 0xffffff00; } -CGhostRecorder CGhostUpdater::ms_Recorder; - -bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename) +bool CGhostUpdater::Update(class IGhostRecorder *pRecorder, class IStorage *pStorage, class IConsole *pConsole, const char *pFilename) { pStorage->CreateFolder("ghosts/backup", IStorage::TYPE_SAVE); @@ -443,11 +436,11 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c Time = ExtHeader.m_Time * 1000; unsigned Crc = (ExtHeader.m_aCrc[0] << 24) | (ExtHeader.m_aCrc[1] << 16) | (ExtHeader.m_aCrc[2] << 8) | (ExtHeader.m_aCrc[3]); - ms_Recorder.Start(pStorage, pConsole, pFilename, ExtHeader.m_aMap, Crc, ExtHeader.m_aOwner); + pRecorder->Start(pFilename, ExtHeader.m_aMap, Crc, ExtHeader.m_aOwner); CGhostSkin Skin; mem_copy(&Skin, aSkinData + ms_SkinOffsetV2, sizeof(Skin)); - ms_Recorder.WriteData(0 /* GHOSTDATA_TYPE_SKIN */, (const char*)&Skin, sizeof(Skin)); + pRecorder->WriteData(0 /* GHOSTDATA_TYPE_SKIN */, &Skin, sizeof(Skin)); } else { @@ -459,14 +452,14 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c Time = ExtHeader.m_Time * 1000; unsigned Crc = (ExtHeader.m_aCrc[0] << 24) | (ExtHeader.m_aCrc[1] << 16) | (ExtHeader.m_aCrc[2] << 8) | (ExtHeader.m_aCrc[3]); - ms_Recorder.Start(pStorage, pConsole, pFilename, ExtHeader.m_aMap, Crc, ExtHeader.m_aOwner); + pRecorder->Start(pFilename, ExtHeader.m_aMap, Crc, ExtHeader.m_aOwner); CGhostSkin Skin; StrToInts(&Skin.m_Skin0, 6, ExtHeader.m_aSkinName); Skin.m_UseCustomColor = ExtHeader.m_UseCustomColor; Skin.m_ColorBody = ExtHeader.m_ColorBody; Skin.m_ColorFeet = ExtHeader.m_ColorFeet; - ms_Recorder.WriteData(0 /* GHOSTDATA_TYPE_SKIN */, (const char*)&Skin, sizeof(Skin)); + pRecorder->WriteData(0 /* GHOSTDATA_TYPE_SKIN */, &Skin, sizeof(Skin)); } // read data @@ -505,7 +498,7 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c char *pTmp = s_aData; for(int i = 0; i < DataSize / ms_GhostCharacterSize; i++) { - ms_Recorder.WriteData(1 /* GHOSTDATA_TYPE_CHARACTER_NO_TICK */, pTmp, ms_GhostCharacterSize); + pRecorder->WriteData(1 /* GHOSTDATA_TYPE_CHARACTER_NO_TICK */, pTmp, ms_GhostCharacterSize); pTmp += ms_GhostCharacterSize; Index++; } @@ -514,6 +507,6 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c io_close(File); bool Error = Ticks != Index; - ms_Recorder.Stop(Index, Error ? 0 : Time); + pRecorder->Stop(Index, Error ? 0 : Time); return !Error; } diff --git a/src/engine/shared/ghost.h b/src/engine/shared/ghost.h index 70e330ddd..ae63df0fa 100644 --- a/src/engine/shared/ghost.h +++ b/src/engine/shared/ghost.h @@ -12,7 +12,7 @@ enum class CGhostItem { public: - char m_aData[MAX_ITEM_SIZE]; + unsigned char m_aData[MAX_ITEM_SIZE]; int m_Type; CGhostItem() : m_Type(-1) {} @@ -24,6 +24,7 @@ class CGhostRecorder : public IGhostRecorder { IOHANDLE m_File; class IConsole *m_pConsole; + class IStorage *m_pStorage; CGhostItem m_LastItem; @@ -37,10 +38,12 @@ class CGhostRecorder : public IGhostRecorder public: CGhostRecorder(); - int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pMap, unsigned MapCrc, const char *pName); + void Init(); + + int Start(const char *pFilename, const char *pMap, unsigned MapCrc, const char *pName); int Stop(int Ticks, int Time); - void WriteData(int Type, const char *pData, int Size); + void WriteData(int Type, const void *pData, int Size); bool IsRecording() const { return m_File != 0; } }; @@ -48,6 +51,7 @@ class CGhostLoader : public IGhostLoader { IOHANDLE m_File; class IConsole *m_pConsole; + class IStorage *m_pStorage; CGhostHeader m_Header; @@ -65,16 +69,16 @@ class CGhostLoader : public IGhostLoader public: CGhostLoader(); - int Load(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pMap, unsigned Crc); + void Init(); + + int Load(const char *pFilename, const char *pMap, unsigned Crc); void Close(); const CGhostHeader *GetHeader() const { return &m_Header; } bool ReadNextType(int *pType); - bool ReadData(int Type, char *pData, int Size); + bool ReadData(int Type, void *pData, int Size); - bool GetGhostInfo(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc) const; - int GetTime(const CGhostHeader *pHeader) const; - int GetTicks(const CGhostHeader *pHeader) const; + bool GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc); }; class CGhostUpdater @@ -133,10 +137,8 @@ class CGhostUpdater static const int ms_GhostCharacterSize = 11 * sizeof(int); - static CGhostRecorder ms_Recorder; - public: - static bool Update(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename); + static bool Update(class IGhostRecorder *pRecorder, class IStorage *pStorage, class IConsole *pConsole, const char *pFilename); }; #endif diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp index 17e7bcc79..84a82580b 100644 --- a/src/game/client/components/ghost.cpp +++ b/src/game/client/components/ghost.cpp @@ -15,7 +15,7 @@ const char *CGhost::ms_pGhostDir = "ghosts"; -CGhost::CGhost() : m_NewRenderTick(-1), m_StartRenderTick(-1), m_LastDeathTick(-1), m_Recording(false), m_Rendering(false) {} +CGhost::CGhost() : m_NewRenderTick(-1), m_StartRenderTick(-1), m_LastDeathTick(-1), m_LastRaceTick(-1), m_Recording(false), m_Rendering(false) {} void CGhost::GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet) { @@ -139,19 +139,19 @@ void CGhost::AddInfos(const CNetObj_Character *pChar) if(g_Config.m_ClRaceSaveGhost && !GhostRecorder()->IsRecording() && NumTicks > 0) { GetPath(m_aTmpFilename, sizeof(m_aTmpFilename), m_CurGhost.m_aPlayer); - Client()->GhostRecorder_Start(m_aTmpFilename, m_CurGhost.m_aPlayer); + GhostRecorder()->Start(m_aTmpFilename, Client()->GetCurrentMap(), Client()->GetMapCrc(), m_CurGhost.m_aPlayer); - GhostRecorder()->WriteData(GHOSTDATA_TYPE_START_TICK, (const char*)&m_CurGhost.m_StartTick, sizeof(int)); - GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&m_CurGhost.m_Skin, sizeof(CGhostSkin)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_START_TICK, &m_CurGhost.m_StartTick, sizeof(int)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, &m_CurGhost.m_Skin, sizeof(CGhostSkin)); for(int i = 0; i < NumTicks; i++) - GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)m_CurGhost.m_Path.Get(i), sizeof(CGhostCharacter)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, m_CurGhost.m_Path.Get(i), sizeof(CGhostCharacter)); } CGhostCharacter GhostChar; GetGhostCharacter(&GhostChar, pChar); m_CurGhost.m_Path.Add(GhostChar); if(GhostRecorder()->IsRecording()) - GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)&GhostChar, sizeof(CGhostCharacter)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, &GhostChar, sizeof(CGhostCharacter)); } int CGhost::GetSlot() const @@ -162,7 +162,7 @@ int CGhost::GetSlot() const return -1; } -int CGhost::FreeSlot() const +int CGhost::FreeSlots() const { int Num = 0; for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++) @@ -171,104 +171,133 @@ int CGhost::FreeSlot() const return Num; } -void CGhost::OnNewSnapshot(bool Predicted) +void CGhost::CheckStart() +{ + int RaceTick = -m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer; + int RenderTick = m_NewRenderTick; + + if(m_LastRaceTick != RaceTick && Client()->GameTick() - RaceTick < Client()->GameTickSpeed()) + { + if(m_Rendering && m_RenderingStartedByServer) // race restarted: stop rendering + StopRender(); + if(m_Recording && m_LastRaceTick != -1) // race restarted: activate restarting for local start detection so we have a smooth transition + m_AllowRestart = true; + if(m_LastRaceTick == -1) // no restart: reset rendering preparations + m_NewRenderTick = -1; + if(GhostRecorder()->IsRecording()) // race restarted: stop recording + GhostRecorder()->Stop(0, -1); + int StartTick = RaceTick; + + CServerInfo ServerInfo; + Client()->GetServerInfo(&ServerInfo); + if(IsDDRace(&ServerInfo)) // the client recognizes the start one tick earlier than ddrace servers + StartTick--; + StartRecord(StartTick); + RenderTick = StartTick; + } + + TryRenderStart(RenderTick, true); +} + +void CGhost::CheckStartLocal(bool Predicted) +{ + if(Predicted) // rendering + { + int RenderTick = m_NewRenderTick; + + vec2 PrevPos = m_pClient->m_PredictedPrevChar.m_Pos; + vec2 Pos = m_pClient->m_PredictedChar.m_Pos; + if(((!m_Rendering && RenderTick == -1) || m_AllowRestart) && CRaceHelper::IsStart(m_pClient, PrevPos, Pos)) + { + if(m_Rendering && !m_RenderingStartedByServer) // race restarted: stop rendering + StopRender(); + RenderTick = Client()->PredGameTick(); + } + + TryRenderStart(RenderTick, false); + } + else // recording + { + int PrevTick = m_pClient->m_Snap.m_pLocalPrevCharacter->m_Tick; + int CurTick = m_pClient->m_Snap.m_pLocalCharacter->m_Tick; + vec2 PrevPos = vec2(m_pClient->m_Snap.m_pLocalPrevCharacter->m_X, m_pClient->m_Snap.m_pLocalPrevCharacter->m_Y); + vec2 Pos = vec2(m_pClient->m_Snap.m_pLocalCharacter->m_X, m_pClient->m_Snap.m_pLocalCharacter->m_Y); + + // detecting death, needed because race allows immediate respawning + if((!m_Recording || m_AllowRestart) && m_LastDeathTick < PrevTick) + { + // estimate the exact start tick + int RecordTick = -1; + int TickDiff = CurTick - PrevTick; + for(int i = 0; i < TickDiff; i++) + { + if(CRaceHelper::IsStart(m_pClient, mix(PrevPos, Pos, (float)i / TickDiff), mix(PrevPos, Pos, (float)(i + 1) / TickDiff))) + { + RecordTick = PrevTick + i + 1; + if(!m_AllowRestart) + break; + } + } + if(RecordTick != -1) + { + if(GhostRecorder()->IsRecording()) // race restarted: stop recording + GhostRecorder()->Stop(0, -1); + StartRecord(RecordTick); + } + } + } +} + +void CGhost::TryRenderStart(int Tick, bool ServerControl) +{ + // only restart rendering if it did not change since last tick to prevent stuttering + if(m_NewRenderTick != -1 && m_NewRenderTick == Tick) + { + StartRender(Tick); + Tick = -1; + m_RenderingStartedByServer = ServerControl; + } + m_NewRenderTick = Tick; +} + +void CGhost::OnNewSnapshot() { CServerInfo ServerInfo; Client()->GetServerInfo(&ServerInfo); if(!IsRace(&ServerInfo) || !g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE) return; - if(!m_pClient->m_Snap.m_pGameInfoObj || m_pClient->m_Snap.m_SpecInfo.m_Active || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->m_Snap.m_pLocalPrevCharacter) return; bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_RACETIME; bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl; + + if(!ServerControl) + CheckStartLocal(false); + else + CheckStart(); + + if(m_Recording) + AddInfos(m_pClient->m_Snap.m_pLocalCharacter); + int RaceTick = -m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer; + m_LastRaceTick = RaceFlag ? RaceTick : -1; +} - int RenderTick = m_NewRenderTick; +void CGhost::OnNewPredictedSnapshot() +{ + CServerInfo ServerInfo; + Client()->GetServerInfo(&ServerInfo); + if(!IsRace(&ServerInfo) || !g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE) + return; + if(!m_pClient->m_Snap.m_pGameInfoObj || m_pClient->m_Snap.m_SpecInfo.m_Active || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->m_Snap.m_pLocalPrevCharacter) + return; - static bool s_RenderingStartedByServer = false; + bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_RACETIME; + bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl; - if(!ServerControl && Predicted) - { - vec2 PrevPos = m_pClient->m_PredictedPrevChar.m_Pos; - vec2 Pos = m_pClient->m_PredictedChar.m_Pos; - if(((!m_Rendering && RenderTick == -1) || m_AllowRestart) && CRaceHelper::IsStart(m_pClient, PrevPos, Pos)) - { - if(m_Rendering && !s_RenderingStartedByServer) // race restarted: stop rendering - StopRender(); - RenderTick = Client()->PredGameTick(); - } - } - - if(!Predicted) - { - static int s_LastRaceTick = -1; - - if(ServerControl && s_LastRaceTick != RaceTick && Client()->GameTick() - RaceTick < Client()->GameTickSpeed()) - { - if(m_Rendering && s_RenderingStartedByServer) // race restarted: stop rendering - StopRender(); - if(m_Recording && s_LastRaceTick != -1) // race restarted: activate restarting for local start detection so we have a smooth transition - m_AllowRestart = true; - if(s_LastRaceTick == -1) // no restart: reset rendering preparations - m_NewRenderTick = -1; - if(GhostRecorder()->IsRecording()) // race restarted: stop recording - GhostRecorder()->Stop(0, -1); - int StartTick = RaceTick; - if(IsDDRace(&ServerInfo)) // the client recognizes the start one tick earlier than ddrace servers - StartTick--; - StartRecord(StartTick); - RenderTick = StartTick; - } - else if(!ServerControl) - { - int PrevTick = m_pClient->m_Snap.m_pLocalPrevCharacter->m_Tick; - int CurTick = m_pClient->m_Snap.m_pLocalCharacter->m_Tick; - vec2 PrevPos = vec2(m_pClient->m_Snap.m_pLocalPrevCharacter->m_X, m_pClient->m_Snap.m_pLocalPrevCharacter->m_Y); - vec2 Pos = vec2(m_pClient->m_Snap.m_pLocalCharacter->m_X, m_pClient->m_Snap.m_pLocalCharacter->m_Y); - - // detecting death, needed because race allows immediate respawning - if((!m_Recording || m_AllowRestart) && m_LastDeathTick < PrevTick) - { - // estimate the exact start tick - int RecordTick = -1; - int TickDiff = CurTick - PrevTick; - for(int i = 0; i < TickDiff; i++) - { - if(CRaceHelper::IsStart(m_pClient, mix(PrevPos, Pos, (float)i / TickDiff), mix(PrevPos, Pos, (float)(i + 1) / TickDiff))) - { - RecordTick = PrevTick + i + 1; - if(!m_AllowRestart) - break; - } - } - if(RecordTick != -1) - { - if(GhostRecorder()->IsRecording()) // race restarted: stop recording - GhostRecorder()->Stop(0, -1); - StartRecord(RecordTick); - } - } - } - - if(m_Recording) - AddInfos(m_pClient->m_Snap.m_pLocalCharacter); - - s_LastRaceTick = RaceFlag ? RaceTick : -1; - } - - if(ServerControl != Predicted) - { - // only restart rendering if it did not change since last tick to prevent stuttering - if(m_NewRenderTick != -1 && m_NewRenderTick == RenderTick) - { - StartRender(RenderTick); - RenderTick = -1; - s_RenderingStartedByServer = ServerControl; - } - m_NewRenderTick = RenderTick; - } + if(!ServerControl) + CheckStartLocal(true); } void CGhost::OnRender() @@ -282,7 +311,7 @@ void CGhost::OnRender() for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++) { CGhostItem *pGhost = &m_aActiveGhosts[i]; - if(pGhost->Empty() || pGhost->m_PlaybackPos < 0) + if(pGhost->Empty()) continue; int GhostTick = pGhost->m_StartTick + PlaybackTick; @@ -424,13 +453,13 @@ int CGhost::Load(const char *pFilename) if(Slot == -1) return -1; - if(!Client()->GhostLoader_Load(pFilename)) + if(GhostLoader()->Load(pFilename, Client()->GetCurrentMap(), Client()->GetMapCrc()) != 0) return -1; const CGhostHeader *pHeader = GhostLoader()->GetHeader(); - int NumTicks = GhostLoader()->GetTicks(pHeader); - int Time = GhostLoader()->GetTime(pHeader); + int NumTicks = pHeader->GetTicks(); + int Time = pHeader->GetTime(); if(NumTicks <= 0 || Time <= 0) { GhostLoader()->Close(); @@ -461,23 +490,23 @@ int CGhost::Load(const char *pFilename) if(Type == GHOSTDATA_TYPE_SKIN && !FoundSkin) { FoundSkin = true; - if(!GhostLoader()->ReadData(Type, (char*)&pGhost->m_Skin, sizeof(CGhostSkin))) + if(!GhostLoader()->ReadData(Type, &pGhost->m_Skin, sizeof(CGhostSkin))) Error = true; } else if(Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK) { NoTick = true; - if(!GhostLoader()->ReadData(Type, (char*)pGhost->m_Path.Get(Index++), sizeof(CGhostCharacter_NoTick))) + if(!GhostLoader()->ReadData(Type, pGhost->m_Path.Get(Index++), sizeof(CGhostCharacter_NoTick))) Error = true; } else if(Type == GHOSTDATA_TYPE_CHARACTER) { - if(!GhostLoader()->ReadData(Type, (char*)pGhost->m_Path.Get(Index++), sizeof(CGhostCharacter))) + if(!GhostLoader()->ReadData(Type, pGhost->m_Path.Get(Index++), sizeof(CGhostCharacter))) Error = true; } else if(Type == GHOSTDATA_TYPE_START_TICK) { - if(!GhostLoader()->ReadData(Type, (char*)&pGhost->m_StartTick, sizeof(int))) + if(!GhostLoader()->ReadData(Type, &pGhost->m_StartTick, sizeof(int))) Error = true; } } @@ -531,12 +560,12 @@ void CGhost::SaveGhost(CMenus::CGhostItem *pItem) int NumTicks = pGhost->m_Path.Size(); GetPath(pItem->m_aFilename, sizeof(pItem->m_aFilename), pItem->m_aPlayer, pItem->m_Time); - Client()->GhostRecorder_Start(pItem->m_aFilename, pItem->m_aPlayer); + GhostRecorder()->Start(pItem->m_aFilename, Client()->GetCurrentMap(), Client()->GetMapCrc(), pItem->m_aPlayer); - GhostRecorder()->WriteData(GHOSTDATA_TYPE_START_TICK, (const char*)&pGhost->m_StartTick, sizeof(int)); - GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&pGhost->m_Skin, sizeof(CGhostSkin)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_START_TICK, &pGhost->m_StartTick, sizeof(int)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, &pGhost->m_Skin, sizeof(CGhostSkin)); for(int i = 0; i < NumTicks; i++) - GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)pGhost->m_Path.Get(i), sizeof(CGhostCharacter)); + GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, pGhost->m_Path.Get(i), sizeof(CGhostCharacter)); GhostRecorder()->Stop(NumTicks, pItem->m_Time); } @@ -590,6 +619,7 @@ void CGhost::OnReset() StopRecord(); StopRender(); m_LastDeathTick = -1; + m_LastRaceTick = -1; } void CGhost::OnMapLoad() diff --git a/src/game/client/components/ghost.h b/src/game/client/components/ghost.h index b3a362919..b0884ca46 100644 --- a/src/game/client/components/ghost.h +++ b/src/game/client/components/ghost.h @@ -112,9 +112,12 @@ private: int m_NewRenderTick; int m_StartRenderTick; int m_LastDeathTick; + int m_LastRaceTick; bool m_Recording; bool m_Rendering; + bool m_RenderingStartedByServer; + static void GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet); static void GetGhostCharacter(CGhostCharacter *pGhostChar, const CNetObj_Character *pChar); static void GetNetObjCharacter(CNetObj_Character *pChar, const CGhostCharacter *pGhostChar); @@ -124,6 +127,10 @@ private: void AddInfos(const CNetObj_Character *pChar); int GetSlot() const; + void CheckStart(); + void CheckStartLocal(bool Predicted); + void TryRenderStart(int Tick, bool ServerControl); + void StartRecord(int Tick); void StopRecord(int Time = -1); void StartRender(int Tick); @@ -144,9 +151,10 @@ public: virtual void OnMessage(int MsgType, void *pRawMsg); virtual void OnMapLoad(); - void OnNewSnapshot(bool Predicted = false); + void OnNewSnapshot(); + void OnNewPredictedSnapshot(); - int FreeSlot() const; + int FreeSlots() const; int Load(const char *pFilename); void Unload(int Slot); void UnloadAll(); diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 801aed47a..f4ca98e67 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -844,13 +844,13 @@ int CMenus::GhostlistFetchCallback(const char *pName, int IsDir, int StorageType str_format(aFilename, sizeof(aFilename), "%s/%s", pSelf->m_pClient->m_pGhost->GetGhostDir(), pName); CGhostHeader Header; - if(!pSelf->Client()->GhostLoader_GetGhostInfo(aFilename, &Header)) + if(!pSelf->m_pClient->m_pGhost->GhostLoader()->GetGhostInfo(aFilename, &Header, pMap, pSelf->Client()->GetMapCrc())) return 0; CGhostItem Item; str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename)); str_copy(Item.m_aPlayer, Header.m_aOwner, sizeof(Item.m_aPlayer)); - Item.m_Time = pSelf->m_pClient->m_pGhost->GhostLoader()->GetTime(&Header); + Item.m_Time = Header.GetTime(); if(Item.m_Time > 0) pSelf->m_lGhosts.add(Item); return 0; @@ -1146,7 +1146,7 @@ void CMenus::RenderGhost(CUIRect MainView) CGhostItem *pOwnGhost = GetOwnGhost(); int ReservedSlots = !pGhost->m_Own && !(pOwnGhost && pOwnGhost->Active()); - if(pGhost->HasFile() && (pGhost->Active() || m_pClient->m_pGhost->FreeSlot() > ReservedSlots)) + if(pGhost->HasFile() && (pGhost->Active() || m_pClient->m_pGhost->FreeSlots() > ReservedSlots)) { Status.VSplitRight(120.0f, &Status, &Button); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 544ebf459..ddcf2ee2a 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -1801,7 +1801,7 @@ void CGameClient::OnPredict() m_PredictedTick = Client()->PredGameTick(); if(m_NewPredictedTick) - m_pGhost->OnNewSnapshot(true); + m_pGhost->OnNewPredictedSnapshot(); } void CGameClient::OnActivateEditor()