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 void RaceRecord_Stop() = 0;
virtual bool RaceRecord_IsRecording() = 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 DemoSliceBegin() = 0;
virtual void DemoSliceEnd() = 0; virtual void DemoSliceEnd() = 0;
virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser) = 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_Friends.Init();
m_Foes.Init(true); m_Foes.Init(true);
m_GhostRecorder.Init();
m_GhostLoader.Init();
} }
void CClient::Run() void CClient::Run()
@ -3620,21 +3623,6 @@ bool CClient::RaceRecord_IsRecording()
return m_DemoRecorder[RECORDER_RACE].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() void CClient::RequestDDNetInfo()
{ {

View file

@ -391,10 +391,6 @@ public:
void RaceRecord_Stop(); void RaceRecord_Stop();
bool RaceRecord_IsRecording(); 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 DemoSliceBegin();
virtual void DemoSliceEnd(); virtual void DemoSliceEnd();
virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser); virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser);

View file

@ -5,8 +5,9 @@
#include "kernel.h" #include "kernel.h"
struct CGhostHeader class CGhostHeader
{ {
public:
unsigned char m_aMarker[8]; unsigned char m_aMarker[8];
unsigned char m_Version; unsigned char m_Version;
char m_aOwner[MAX_NAME_LENGTH]; char m_aOwner[MAX_NAME_LENGTH];
@ -14,6 +15,16 @@ struct CGhostHeader
unsigned char m_aCrc[4]; unsigned char m_aCrc[4];
unsigned char m_aNumTicks[4]; unsigned char m_aNumTicks[4];
unsigned char m_aTime[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 class IGhostRecorder : public IInterface
@ -21,9 +32,11 @@ class IGhostRecorder : public IInterface
MACRO_INTERFACE("ghostrecorder", 0) MACRO_INTERFACE("ghostrecorder", 0)
public: public:
virtual ~IGhostRecorder() {} 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 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; virtual bool IsRecording() const = 0;
}; };
@ -32,15 +45,16 @@ class IGhostLoader : public IInterface
MACRO_INTERFACE("ghostloader", 0) MACRO_INTERFACE("ghostloader", 0)
public: public:
virtual ~IGhostLoader() {} virtual ~IGhostLoader() {}
virtual int Load(const char *pFilename, const char *pMap, unsigned Crc) = 0;
virtual void Close() = 0; virtual void Close() = 0;
virtual const CGhostHeader *GetHeader() const = 0; virtual const CGhostHeader *GetHeader() const = 0;
virtual bool ReadNextType(int *pType) = 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 bool GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc) = 0;
virtual int GetTicks(const CGhostHeader *pHeader) const = 0;
}; };
#endif #endif

View file

@ -17,12 +17,16 @@ CGhostRecorder::CGhostRecorder()
ResetBuffer(); ResetBuffer();
} }
// Record void CGhostRecorder::Init()
int CGhostRecorder::Start(IStorage *pStorage, IConsole *pConsole, const char *pFilename, const char *pMap, unsigned Crc, const char* pName)
{ {
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) if(!m_File)
{ {
char aBuf[256]; 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) if(!m_File || (unsigned)Size > MAX_ITEM_SIZE || Size <= 0 || Type == -1)
return; return;
@ -167,6 +171,12 @@ CGhostLoader::CGhostLoader()
ResetBuffer(); ResetBuffer();
} }
void CGhostLoader::Init()
{
m_pConsole = Kernel()->RequestInterface<IConsole>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
}
void CGhostLoader::ResetBuffer() void CGhostLoader::ResetBuffer()
{ {
m_pBufferPos = m_aBuffer; m_pBufferPos = m_aBuffer;
@ -175,10 +185,9 @@ void CGhostLoader::ResetBuffer()
m_BufferPrevItem = -1; 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 = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
m_File = pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
if(!m_File) if(!m_File)
{ {
char aBuf[256]; 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) if(!m_File || Size > MAX_ITEM_SIZE || Size <= 0 || Type == -1)
return false; return false;
@ -327,14 +336,14 @@ void CGhostLoader::Close()
m_File = 0; 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) if(!pGhostHeader)
return false; return false;
mem_zero(pGhostHeader, sizeof(CGhostHeader)); 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) if(!File)
return false; return false;
@ -344,43 +353,29 @@ bool CGhostLoader::GetGhostInfo(class IStorage *pStorage, class IConsole *pConso
{ {
io_close(File); io_close(File);
// old version... try to update // 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 // 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)); io_read(File, pGhostHeader, sizeof(CGhostHeader));
} }
else else
return false; 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)) 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; return false;
}
unsigned GhostMapCrc = (pGhostHeader->m_aCrc[0] << 24) | (pGhostHeader->m_aCrc[1] << 16) | (pGhostHeader->m_aCrc[2] << 8) | (pGhostHeader->m_aCrc[3]); 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) if(str_comp(pGhostHeader->m_aMap, pMap) != 0 || GhostMapCrc != Crc)
{
io_close(File);
return false; return false;
}
io_close(File);
return true; 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) inline void StrToInts(int *pInts, int Num, const char *pStr)
{ {
int Index = 0; int Index = 0;
@ -398,9 +393,7 @@ inline void StrToInts(int *pInts, int Num, const char *pStr)
pInts[-1] &= 0xffffff00; pInts[-1] &= 0xffffff00;
} }
CGhostRecorder CGhostUpdater::ms_Recorder; bool CGhostUpdater::Update(class IGhostRecorder *pRecorder, class IStorage *pStorage, class IConsole *pConsole, const char *pFilename)
bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename)
{ {
pStorage->CreateFolder("ghosts/backup", IStorage::TYPE_SAVE); 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; 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]); 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; CGhostSkin Skin;
mem_copy(&Skin, aSkinData + ms_SkinOffsetV2, sizeof(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 else
{ {
@ -459,14 +452,14 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c
Time = ExtHeader.m_Time * 1000; 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]); 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; CGhostSkin Skin;
StrToInts(&Skin.m_Skin0, 6, ExtHeader.m_aSkinName); StrToInts(&Skin.m_Skin0, 6, ExtHeader.m_aSkinName);
Skin.m_UseCustomColor = ExtHeader.m_UseCustomColor; Skin.m_UseCustomColor = ExtHeader.m_UseCustomColor;
Skin.m_ColorBody = ExtHeader.m_ColorBody; Skin.m_ColorBody = ExtHeader.m_ColorBody;
Skin.m_ColorFeet = ExtHeader.m_ColorFeet; 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 // read data
@ -505,7 +498,7 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c
char *pTmp = s_aData; char *pTmp = s_aData;
for(int i = 0; i < DataSize / ms_GhostCharacterSize; i++) 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; pTmp += ms_GhostCharacterSize;
Index++; Index++;
} }
@ -514,6 +507,6 @@ bool CGhostUpdater::Update(class IStorage *pStorage, class IConsole *pConsole, c
io_close(File); io_close(File);
bool Error = Ticks != Index; bool Error = Ticks != Index;
ms_Recorder.Stop(Index, Error ? 0 : Time); pRecorder->Stop(Index, Error ? 0 : Time);
return !Error; return !Error;
} }

View file

@ -12,7 +12,7 @@ enum
class CGhostItem class CGhostItem
{ {
public: public:
char m_aData[MAX_ITEM_SIZE]; unsigned char m_aData[MAX_ITEM_SIZE];
int m_Type; int m_Type;
CGhostItem() : m_Type(-1) {} CGhostItem() : m_Type(-1) {}
@ -24,6 +24,7 @@ class CGhostRecorder : public IGhostRecorder
{ {
IOHANDLE m_File; IOHANDLE m_File;
class IConsole *m_pConsole; class IConsole *m_pConsole;
class IStorage *m_pStorage;
CGhostItem m_LastItem; CGhostItem m_LastItem;
@ -37,10 +38,12 @@ class CGhostRecorder : public IGhostRecorder
public: public:
CGhostRecorder(); 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); 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; } bool IsRecording() const { return m_File != 0; }
}; };
@ -48,6 +51,7 @@ class CGhostLoader : public IGhostLoader
{ {
IOHANDLE m_File; IOHANDLE m_File;
class IConsole *m_pConsole; class IConsole *m_pConsole;
class IStorage *m_pStorage;
CGhostHeader m_Header; CGhostHeader m_Header;
@ -65,16 +69,16 @@ class CGhostLoader : public IGhostLoader
public: public:
CGhostLoader(); 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(); void Close();
const CGhostHeader *GetHeader() const { return &m_Header; } const CGhostHeader *GetHeader() const { return &m_Header; }
bool ReadNextType(int *pType); 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; bool GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc);
int GetTime(const CGhostHeader *pHeader) const;
int GetTicks(const CGhostHeader *pHeader) const;
}; };
class CGhostUpdater class CGhostUpdater
@ -133,10 +137,8 @@ class CGhostUpdater
static const int ms_GhostCharacterSize = 11 * sizeof(int); static const int ms_GhostCharacterSize = 11 * sizeof(int);
static CGhostRecorder ms_Recorder;
public: 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 #endif

View file

@ -15,7 +15,7 @@
const char *CGhost::ms_pGhostDir = "ghosts"; 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) 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) if(g_Config.m_ClRaceSaveGhost && !GhostRecorder()->IsRecording() && NumTicks > 0)
{ {
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename), m_CurGhost.m_aPlayer); 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_START_TICK, &m_CurGhost.m_StartTick, sizeof(int));
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&m_CurGhost.m_Skin, sizeof(CGhostSkin)); GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, &m_CurGhost.m_Skin, sizeof(CGhostSkin));
for(int i = 0; i < NumTicks; i++) 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; CGhostCharacter GhostChar;
GetGhostCharacter(&GhostChar, pChar); GetGhostCharacter(&GhostChar, pChar);
m_CurGhost.m_Path.Add(GhostChar); m_CurGhost.m_Path.Add(GhostChar);
if(GhostRecorder()->IsRecording()) 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 int CGhost::GetSlot() const
@ -162,7 +162,7 @@ int CGhost::GetSlot() const
return -1; return -1;
} }
int CGhost::FreeSlot() const int CGhost::FreeSlots() const
{ {
int Num = 0; int Num = 0;
for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++) for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++)
@ -171,104 +171,133 @@ int CGhost::FreeSlot() const
return Num; 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; CServerInfo ServerInfo;
Client()->GetServerInfo(&ServerInfo); Client()->GetServerInfo(&ServerInfo);
if(!IsRace(&ServerInfo) || !g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE) if(!IsRace(&ServerInfo) || !g_Config.m_ClRaceGhost || Client()->State() != IClient::STATE_ONLINE)
return; 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) 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; return;
bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_RACETIME; bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_RACETIME;
bool ServerControl = RaceFlag && g_Config.m_ClRaceGhostServerControl; 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; 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) if(!ServerControl)
{ CheckStartLocal(true);
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;
}
} }
void CGhost::OnRender() void CGhost::OnRender()
@ -282,7 +311,7 @@ void CGhost::OnRender()
for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++) for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++)
{ {
CGhostItem *pGhost = &m_aActiveGhosts[i]; CGhostItem *pGhost = &m_aActiveGhosts[i];
if(pGhost->Empty() || pGhost->m_PlaybackPos < 0) if(pGhost->Empty())
continue; continue;
int GhostTick = pGhost->m_StartTick + PlaybackTick; int GhostTick = pGhost->m_StartTick + PlaybackTick;
@ -424,13 +453,13 @@ int CGhost::Load(const char *pFilename)
if(Slot == -1) if(Slot == -1)
return -1; return -1;
if(!Client()->GhostLoader_Load(pFilename)) if(GhostLoader()->Load(pFilename, Client()->GetCurrentMap(), Client()->GetMapCrc()) != 0)
return -1; return -1;
const CGhostHeader *pHeader = GhostLoader()->GetHeader(); const CGhostHeader *pHeader = GhostLoader()->GetHeader();
int NumTicks = GhostLoader()->GetTicks(pHeader); int NumTicks = pHeader->GetTicks();
int Time = GhostLoader()->GetTime(pHeader); int Time = pHeader->GetTime();
if(NumTicks <= 0 || Time <= 0) if(NumTicks <= 0 || Time <= 0)
{ {
GhostLoader()->Close(); GhostLoader()->Close();
@ -461,23 +490,23 @@ int CGhost::Load(const char *pFilename)
if(Type == GHOSTDATA_TYPE_SKIN && !FoundSkin) if(Type == GHOSTDATA_TYPE_SKIN && !FoundSkin)
{ {
FoundSkin = true; FoundSkin = true;
if(!GhostLoader()->ReadData(Type, (char*)&pGhost->m_Skin, sizeof(CGhostSkin))) if(!GhostLoader()->ReadData(Type, &pGhost->m_Skin, sizeof(CGhostSkin)))
Error = true; Error = true;
} }
else if(Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK) else if(Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK)
{ {
NoTick = true; 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; Error = true;
} }
else if(Type == GHOSTDATA_TYPE_CHARACTER) 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; Error = true;
} }
else if(Type == GHOSTDATA_TYPE_START_TICK) 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; Error = true;
} }
} }
@ -531,12 +560,12 @@ void CGhost::SaveGhost(CMenus::CGhostItem *pItem)
int NumTicks = pGhost->m_Path.Size(); int NumTicks = pGhost->m_Path.Size();
GetPath(pItem->m_aFilename, sizeof(pItem->m_aFilename), pItem->m_aPlayer, pItem->m_Time); 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_START_TICK, &pGhost->m_StartTick, sizeof(int));
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&pGhost->m_Skin, sizeof(CGhostSkin)); GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, &pGhost->m_Skin, sizeof(CGhostSkin));
for(int i = 0; i < NumTicks; i++) 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); GhostRecorder()->Stop(NumTicks, pItem->m_Time);
} }
@ -590,6 +619,7 @@ void CGhost::OnReset()
StopRecord(); StopRecord();
StopRender(); StopRender();
m_LastDeathTick = -1; m_LastDeathTick = -1;
m_LastRaceTick = -1;
} }
void CGhost::OnMapLoad() void CGhost::OnMapLoad()

View file

@ -112,9 +112,12 @@ private:
int m_NewRenderTick; int m_NewRenderTick;
int m_StartRenderTick; int m_StartRenderTick;
int m_LastDeathTick; int m_LastDeathTick;
int m_LastRaceTick;
bool m_Recording; bool m_Recording;
bool m_Rendering; bool m_Rendering;
bool m_RenderingStartedByServer;
static void GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet); 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 GetGhostCharacter(CGhostCharacter *pGhostChar, const CNetObj_Character *pChar);
static void GetNetObjCharacter(CNetObj_Character *pChar, const CGhostCharacter *pGhostChar); static void GetNetObjCharacter(CNetObj_Character *pChar, const CGhostCharacter *pGhostChar);
@ -124,6 +127,10 @@ private:
void AddInfos(const CNetObj_Character *pChar); void AddInfos(const CNetObj_Character *pChar);
int GetSlot() const; int GetSlot() const;
void CheckStart();
void CheckStartLocal(bool Predicted);
void TryRenderStart(int Tick, bool ServerControl);
void StartRecord(int Tick); void StartRecord(int Tick);
void StopRecord(int Time = -1); void StopRecord(int Time = -1);
void StartRender(int Tick); void StartRender(int Tick);
@ -144,9 +151,10 @@ public:
virtual void OnMessage(int MsgType, void *pRawMsg); virtual void OnMessage(int MsgType, void *pRawMsg);
virtual void OnMapLoad(); virtual void OnMapLoad();
void OnNewSnapshot(bool Predicted = false); void OnNewSnapshot();
void OnNewPredictedSnapshot();
int FreeSlot() const; int FreeSlots() const;
int Load(const char *pFilename); int Load(const char *pFilename);
void Unload(int Slot); void Unload(int Slot);
void UnloadAll(); 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); str_format(aFilename, sizeof(aFilename), "%s/%s", pSelf->m_pClient->m_pGhost->GetGhostDir(), pName);
CGhostHeader Header; 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; return 0;
CGhostItem Item; CGhostItem Item;
str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename)); str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename));
str_copy(Item.m_aPlayer, Header.m_aOwner, sizeof(Item.m_aPlayer)); 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) if(Item.m_Time > 0)
pSelf->m_lGhosts.add(Item); pSelf->m_lGhosts.add(Item);
return 0; return 0;
@ -1146,7 +1146,7 @@ void CMenus::RenderGhost(CUIRect MainView)
CGhostItem *pOwnGhost = GetOwnGhost(); CGhostItem *pOwnGhost = GetOwnGhost();
int ReservedSlots = !pGhost->m_Own && !(pOwnGhost && pOwnGhost->Active()); 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); Status.VSplitRight(120.0f, &Status, &Button);

View file

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