Fixed several issues with the ghost (thanks to Learath2)

This commit is contained in:
Redix 2017-10-28 14:23:24 +02:00
parent 9289deb5c7
commit de1c0cf24d
10 changed files with 214 additions and 187 deletions

View file

@ -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;

View file

@ -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()
{

View file

@ -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);

View file

@ -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

View file

@ -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<IConsole>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
}
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<IConsole>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
}
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<IGhostRecorder>();
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;
}

View file

@ -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

View file

@ -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()

View file

@ -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();

View file

@ -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);

View file

@ -1801,7 +1801,7 @@ void CGameClient::OnPredict()
m_PredictedTick = Client()->PredGameTick();
if(m_NewPredictedTick)
m_pGhost->OnNewSnapshot(true);
m_pGhost->OnNewPredictedSnapshot();
}
void CGameClient::OnActivateEditor()