Improved ghost and race recorder file handling

This commit is contained in:
Redix 2017-09-28 19:13:20 +02:00
parent 66cc527af4
commit e9a0271c29
8 changed files with 143 additions and 112 deletions

View file

@ -177,14 +177,13 @@ public:
virtual const char *GetCurrentMap() = 0;
virtual const char *GetCurrentMapPath() = 0;
virtual unsigned GetMapCrc() = 0;
virtual void RaceRecord_GetName(char *pBuf, int Size, int Time = -1) = 0;
virtual void RaceRecord_Start() = 0;
virtual void RaceRecord_Start(const char *pFilename) = 0;
virtual void RaceRecord_Stop() = 0;
virtual bool RaceRecord_IsRecording() = 0;
virtual void Ghost_GetPath(char *pBuf, int Size, const char *pPlayerName, int Time = -1) = 0;
virtual void GhostRecorder_Start(const char *pPlayerName, int Time = -1) = 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;

View file

@ -3596,35 +3596,17 @@ const char *CClient::GetCurrentMapPath()
return m_aCurrentMapPath;
}
void CClient::RaceRecord_GetName(char *pBuf, int Size, int Time)
unsigned CClient::GetMapCrc()
{
// check the player name
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, g_Config.m_PlayerName, sizeof(aPlayerName));
str_sanitize_filename(aPlayerName);
if(Time < 0)
str_format(pBuf, Size, "%s_tmp_%d", m_aCurrentMap, pid());
else if(g_Config.m_ClDemoName)
str_format(pBuf, Size, "%s_%d.%03d_%s", m_aCurrentMap, Time / 1000, Time % 1000, aPlayerName);
else
str_format(pBuf, Size, "%s_%d.%03d", m_aCurrentMap, Time / 1000, Time % 1000);
return m_pMap->Crc();
}
void CClient::RaceRecord_Start()
void CClient::RaceRecord_Start(const char *pFilename)
{
if(State() != IClient::STATE_ONLINE)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
else
{
char aDemoName[128];
char aFilename[128];
RaceRecord_GetName(aDemoName, sizeof(aDemoName));
str_format(aFilename, sizeof(aFilename), "demos/%s.demo", aDemoName);
m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
}
m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, pFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
}
void CClient::RaceRecord_Stop()
@ -3638,24 +3620,9 @@ bool CClient::RaceRecord_IsRecording()
return m_DemoRecorder[RECORDER_RACE].IsRecording();
}
void CClient::Ghost_GetPath(char *pBuf, int Size, const char *pPlayerName, int Time)
void CClient::GhostRecorder_Start(const char *pFilename, const char *pPlayerName)
{
// check the player name
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, pPlayerName, sizeof(aPlayerName));
str_sanitize_filename(aPlayerName);
if(Time < 0)
str_format(pBuf, Size, "ghosts/%s_%s_%08x_tmp_%d.gho", m_aCurrentMap, aPlayerName, m_pMap->Crc(), pid());
else
str_format(pBuf, Size, "ghosts/%s_%s_%d.%03d_%08x.gho", m_aCurrentMap, aPlayerName, Time / 1000, Time % 1000, m_pMap->Crc());
}
void CClient::GhostRecorder_Start(const char *pPlayerName, int Time)
{
char aFilename[128];
Ghost_GetPath(aFilename, sizeof(aFilename), pPlayerName, Time);
m_GhostRecorder.Start(Storage(), m_pConsole, aFilename, m_aCurrentMap, m_pMap->Crc(), pPlayerName);
m_GhostRecorder.Start(Storage(), m_pConsole, pFilename, m_aCurrentMap, m_pMap->Crc(), pPlayerName);
}
bool CClient::GhostLoader_Load(const char *pFilename)

View file

@ -385,14 +385,13 @@ public:
const char *GetCurrentMap();
const char *GetCurrentMapPath();
unsigned GetMapCrc();
void RaceRecord_GetName(char *pBuf, int Size, int Time = -1);
void RaceRecord_Start();
void RaceRecord_Start(const char *pFilename);
void RaceRecord_Stop();
bool RaceRecord_IsRecording();
void Ghost_GetPath(char *pBuf, int Size, const char *pPlayerName, int Time = -1);
void GhostRecorder_Start(const char *pPlayerName, int Time = -1);
void GhostRecorder_Start(const char *pFilename, const char *pPlayerName);
bool GhostLoader_Load(const char *pFilename);
bool GhostLoader_GetGhostInfo(const char *pFilename, struct CGhostHeader *pGhostHeader);

View file

@ -12,6 +12,8 @@
#include "menus.h"
#include "ghost.h"
const char *CGhost::ms_pGhostDir = "ghosts";
CGhost::CGhost() : m_StartRenderTick(-1), m_LastDeathTick(-1), m_Recording(false), m_Rendering(false) {}
void CGhost::GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet)
@ -113,6 +115,21 @@ CGhostCharacter *CGhost::CGhostPath::Get(int Index)
return &m_lChunks[Chunk][Pos];
}
void CGhost::GetPath(char *pBuf, int Size, const char *pPlayerName, int Time) const
{
const char *pMap = Client()->GetCurrentMap();
unsigned Crc = Client()->GetMapCrc();
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, pPlayerName, sizeof(aPlayerName));
str_sanitize_filename(aPlayerName);
if(Time < 0)
str_format(pBuf, Size, "%s/%s_%s_%08x_tmp_%d.gho", ms_pGhostDir, pMap, aPlayerName, Crc, pid());
else
str_format(pBuf, Size, "%s/%s_%s_%d.%03d_%08x.gho", ms_pGhostDir, pMap, aPlayerName, Time / 1000, Time % 1000, Crc);
}
void CGhost::AddInfos(const CNetObj_Character *pChar)
{
int NumTicks = m_CurGhost.m_Path.Size();
@ -120,7 +137,8 @@ void CGhost::AddInfos(const CNetObj_Character *pChar)
// do not start writing to file as long as we still touch the start line
if(g_Config.m_ClRaceSaveGhost && !GhostRecorder()->IsRecording() && NumTicks > 0)
{
Client()->GhostRecorder_Start(m_CurGhost.m_aPlayer);
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename), m_CurGhost.m_aPlayer);
Client()->GhostRecorder_Start(m_aTmpFilename, 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));
@ -347,9 +365,6 @@ void CGhost::StopRecord(int Time)
if(RecordingToFile)
GhostRecorder()->Stop(m_CurGhost.m_Path.Size(), Time);
char aTmpFilename[128];
Client()->Ghost_GetPath(aTmpFilename, sizeof(aTmpFilename), m_CurGhost.m_aPlayer);
CMenus::CGhostItem *pOwnGhost = m_pClient->m_pMenus->GetOwnGhost();
if(Time > 0 && (!pOwnGhost || Time < pOwnGhost->m_Time))
{
@ -364,20 +379,22 @@ void CGhost::StopRecord(int Time)
// create ghost item
CMenus::CGhostItem Item;
if(RecordingToFile)
Client()->Ghost_GetPath(Item.m_aFilename, sizeof(Item.m_aFilename), m_CurGhost.m_aPlayer, Time);
GetPath(Item.m_aFilename, sizeof(Item.m_aFilename), m_CurGhost.m_aPlayer, Time);
str_copy(Item.m_aPlayer, m_CurGhost.m_aPlayer, sizeof(Item.m_aPlayer));
Item.m_Time = Time;
Item.m_Slot = Slot;
// save new ghost file
if(Item.HasFile())
Storage()->RenameFile(aTmpFilename, Item.m_aFilename, IStorage::TYPE_SAVE);
Storage()->RenameFile(m_aTmpFilename, Item.m_aFilename, IStorage::TYPE_SAVE);
// add item to menu list
m_pClient->m_pMenus->UpdateOwnGhost(Item);
}
else if(RecordingToFile) // no new record
Storage()->RemoveFile(aTmpFilename, IStorage::TYPE_SAVE);
Storage()->RemoveFile(m_aTmpFilename, IStorage::TYPE_SAVE);
m_aTmpFilename[0] = 0;
m_CurGhost.Reset();
}
@ -507,7 +524,8 @@ void CGhost::SaveGhost(CMenus::CGhostItem *pItem)
CGhostItem *pGhost = &m_aActiveGhosts[Slot];
int NumTicks = pGhost->m_Path.Size();
Client()->GhostRecorder_Start(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()->WriteData(GHOSTDATA_TYPE_START_TICK, (const char*)&pGhost->m_StartTick, sizeof(int));
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&pGhost->m_Skin, sizeof(CGhostSkin));
@ -515,7 +533,6 @@ void CGhost::SaveGhost(CMenus::CGhostItem *pItem)
GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)pGhost->m_Path.Get(i), sizeof(CGhostCharacter));
GhostRecorder()->Stop(NumTicks, pItem->m_Time);
Client()->Ghost_GetPath(pItem->m_aFilename, sizeof(pItem->m_aFilename), pItem->m_aPlayer, pItem->m_Time);
}
void CGhost::ConGPlay(IConsole::IResult *pResult, void *pUserData)

View file

@ -99,12 +99,16 @@ private:
}
};
static const char *ms_pGhostDir;
class IGhostLoader *m_pGhostLoader;
class IGhostRecorder *m_pGhostRecorder;
CGhostItem m_aActiveGhosts[MAX_ACTIVE_GHOSTS];
CGhostItem m_CurGhost;
char m_aTmpFilename[128];
int m_StartRenderTick;
int m_LastDeathTick;
bool m_Recording;
@ -114,6 +118,8 @@ private:
static void GetGhostCharacter(CGhostCharacter *pGhostChar, const CNetObj_Character *pChar);
static void GetNetObjCharacter(CNetObj_Character *pChar, const CGhostCharacter *pGhostChar);
void GetPath(char *pBuf, int Size, const char *pPlayerName, int Time = -1) const;
void AddInfos(const CNetObj_Character *pChar);
int GetSlot() const;

View file

@ -836,10 +836,8 @@ int CMenus::GhostlistFetchCallback(const char *pName, int IsDir, int StorageType
{
CMenus *pSelf = (CMenus *)pUser;
int Length = str_length(pName);
const char *pMap = pSelf->m_pClient->Client()->GetCurrentMap();
if((pName[0] == '.' && (pName[1] == 0 ||
(pName[1] == '.' && pName[2] == 0))) ||
(!IsDir && (Length < 4 || str_comp(pName+Length-4, ".gho") || str_comp_num(pName, pMap, str_length(pMap)))))
const char *pMap = pSelf->Client()->GetCurrentMap();
if(IsDir || Length < 4 || str_comp(pName+Length-4, ".gho") != 0 || str_comp_num(pName, pMap, str_length(pMap)) != 0)
return 0;
char aFilename[256];

View file

@ -1,16 +1,47 @@
/* (c) Redix and Sushi */
#include <ctype.h>
#include <base/system.h>
#include <engine/shared/config.h>
#include <engine/serverbrowser.h>
#include <engine/storage.h>
#include "menus.h"
#include "race.h"
#include "race_demo.h"
const char *CRaceDemo::ms_pRaceDemoDir = "demos";
struct CDemoItem
{
char m_aName[128];
int m_Time;
};
struct CDemoListParam
{
std::vector<CDemoItem> *m_plDemos;
const char *pMap;
};
CRaceDemo::CRaceDemo() : m_RaceState(RACE_NONE), m_RaceStartTick(-1), m_RecordStopTick(-1), m_Time(0) {}
void CRaceDemo::GetPath(char *pBuf, int Size, int Time) const
{
const char *pMap = Client()->GetCurrentMap();
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, g_Config.m_PlayerName, sizeof(aPlayerName));
str_sanitize_filename(aPlayerName);
if(Time < 0)
str_format(pBuf, Size, "%s/%s_tmp_%d.demo", ms_pRaceDemoDir, pMap, pid());
else if(g_Config.m_ClDemoName)
str_format(pBuf, Size, "%s/%s_%d.%03d_%s.demo", ms_pRaceDemoDir, pMap, Time / 1000, Time % 1000, aPlayerName);
else
str_format(pBuf, Size, "%s/%s_%d.%03d.demo", ms_pRaceDemoDir, pMap, Time / 1000, Time % 1000);
}
void CRaceDemo::OnStateChange(int NewState, int OldState)
{
if(OldState == IClient::STATE_ONLINE)
@ -47,7 +78,10 @@ void CRaceDemo::OnRender()
if(m_RaceState == RACE_STARTED)
Client()->RaceRecord_Stop();
if(m_RaceState != RACE_PREPARE) // start recording again
Client()->RaceRecord_Start();
{
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename));
Client()->RaceRecord_Start(m_aTmpFilename);
}
m_RaceStartTick = Client()->GameTick();
m_RaceState = RACE_STARTED;
}
@ -56,7 +90,8 @@ void CRaceDemo::OnRender()
// start recording before the player passes the start line, so we can see some preparation steps
if(m_RaceState == RACE_NONE)
{
Client()->RaceRecord_Start();
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename));
Client()->RaceRecord_Start(m_aTmpFilename);
m_RaceStartTick = Client()->GameTick();
m_RaceState = RACE_PREPARE;
}
@ -64,7 +99,7 @@ void CRaceDemo::OnRender()
// stop recording if the player did not pass the start line after 20 seconds
if(m_RaceState == RACE_PREPARE && Client()->GameTick() - m_RaceStartTick >= Client()->GameTickSpeed() * 20)
{
OnReset();
StopRecord();
m_RaceState = RACE_IDLE;
}
@ -125,22 +160,18 @@ void CRaceDemo::StopRecord(int Time)
if(Client()->RaceRecord_IsRecording())
Client()->RaceRecord_Stop();
char aDemoName[128];
char aTmpFilename[512];
Client()->RaceRecord_GetName(aDemoName, sizeof(aDemoName));
str_format(aTmpFilename, sizeof(aTmpFilename), "demos/%s.demo", aDemoName);
if(Time > 0 && CheckDemo(Time))
{
// save file
char aNewFilename[512];
Client()->RaceRecord_GetName(aDemoName, sizeof(aDemoName), m_Time);
str_format(aNewFilename, sizeof(aNewFilename), "demos/%s.demo", aDemoName);
GetPath(aNewFilename, sizeof(aNewFilename), m_Time);
Storage()->RenameFile(aTmpFilename, aNewFilename, IStorage::TYPE_SAVE);
Storage()->RenameFile(m_aTmpFilename, aNewFilename, IStorage::TYPE_SAVE);
}
else // no new record
Storage()->RemoveFile(aTmpFilename, IStorage::TYPE_SAVE);
Storage()->RemoveFile(m_aTmpFilename, IStorage::TYPE_SAVE);
m_aTmpFilename[0] = 0;
m_Time = 0;
m_RaceState = RACE_NONE;
@ -148,51 +179,57 @@ void CRaceDemo::StopRecord(int Time)
m_RecordStopTick = -1;
}
int CRaceDemo::RaceDemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser)
{
CDemoListParam *pParam = (CDemoListParam*) pUser;
int Length = str_length(pName);
int MapLen = str_length(pParam->pMap);
if(IsDir || Length < 5 || str_comp(pName + Length - 5, ".demo") != 0 || str_comp_num(pName, pParam->pMap, MapLen) != 0 || pName[MapLen] != '_')
return 0;
CDemoItem Item;
str_copy(Item.m_aName, pName, min(static_cast<int>(sizeof(Item.m_aName)), Length - 4));
const char *pTime = Item.m_aName + MapLen + 1;
const char *pTEnd = pTime;
while(isdigit(*pTEnd) || *pTEnd == ' ' || *pTEnd == '.' || *pTEnd == ',')
pTEnd++;
if(g_Config.m_ClDemoName)
{
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, g_Config.m_PlayerName, sizeof(aPlayerName));
str_sanitize_filename(aPlayerName);
if(pTEnd[0] != '_' || str_comp(pTEnd + 1, aPlayerName) != 0)
return 0;
}
else if(pTEnd[0])
return 0;
Item.m_Time = CRaceHelper::TimeFromSecondsStr(pTime);
if(Item.m_Time > 0)
pParam->m_plDemos->push_back(Item);
return 0;
}
bool CRaceDemo::CheckDemo(int Time) const
{
if(str_comp(m_pClient->m_pMenus->GetCurrentDemoFolder(), "demos") != 0)
return true;
char aTmpDemoName[128];
Client()->RaceRecord_GetName(aTmpDemoName, sizeof(aTmpDemoName));
std::vector<CDemoItem> lDemos;
CDemoListParam Param = { &lDemos, Client()->GetCurrentMap() };
Storage()->ListDirectoryInfo(IStorage::TYPE_SAVE, ms_pRaceDemoDir, RaceDemolistFetchCallback, &Param);
// loop through demo files
m_pClient->m_pMenus->DemolistPopulate();
for(int i = 0; i < m_pClient->m_pMenus->m_lDemos.size(); i++)
for(int i = 0; i < lDemos.size(); i++)
{
// skip temp file
const char *pDemoName = m_pClient->m_pMenus->m_lDemos[i].m_aName;
if(str_comp(pDemoName, aTmpDemoName) == 0)
continue;
if(Time >= lDemos[i].m_Time) // found a better demo
return false;
int MapLen = str_length(Client()->GetCurrentMap());
if(str_comp_num(pDemoName, Client()->GetCurrentMap(), MapLen) != 0 || pDemoName[MapLen] != '_')
continue;
// set cursor
pDemoName += MapLen + 1;
if(g_Config.m_ClDemoName)
{
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, g_Config.m_PlayerName, sizeof(aPlayerName));
str_sanitize_filename(aPlayerName);
if(!str_find(pDemoName, aPlayerName))
continue;
}
int DemoTime = CRaceHelper::TimeFromSecondsStr(pDemoName);
if(DemoTime > 0)
{
if(Time >= DemoTime) // found a better demo
return false;
// delete old demo
char aFilename[512];
str_format(aFilename, sizeof(aFilename), "demos/%s", m_pClient->m_pMenus->m_lDemos[i].m_aFilename);
Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE);
}
// delete old demo
char aFilename[512];
str_format(aFilename, sizeof(aFilename), "%s/%s.demo", ms_pRaceDemoDir, lDemos[i].m_aName);
Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE);
}
return true;

View file

@ -16,11 +16,19 @@ class CRaceDemo : public CComponent
RACE_FINISHED,
};
static const char *ms_pRaceDemoDir;
char m_aTmpFilename[128];
int m_RaceState;
int m_RaceStartTick;
int m_RecordStopTick;
int m_Time;
static int RaceDemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser);
void GetPath(char *pBuf, int Size, int Time = -1) const;
void StopRecord(int Time = -1);
bool CheckDemo(int Time) const;