2014-08-22 12:18:16 +00:00
|
|
|
/* (c) Redix and Sushi */
|
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
#include <ctype.h>
|
|
|
|
|
2014-08-22 12:18:16 +00:00
|
|
|
#include <base/system.h>
|
|
|
|
#include <engine/shared/config.h>
|
|
|
|
#include <engine/serverbrowser.h>
|
|
|
|
#include <engine/storage.h>
|
|
|
|
|
2017-10-06 20:10:29 +00:00
|
|
|
#include <game/client/race.h>
|
|
|
|
|
2014-08-22 12:18:16 +00:00
|
|
|
#include "race_demo.h"
|
|
|
|
|
2017-09-28 17:14:36 +00:00
|
|
|
const char *CRaceDemo::ms_pRaceDemoDir = "demos/auto/race";
|
2017-09-28 17:13:20 +00:00
|
|
|
|
|
|
|
struct CDemoItem
|
|
|
|
{
|
|
|
|
char m_aName[128];
|
|
|
|
int m_Time;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CDemoListParam
|
|
|
|
{
|
|
|
|
std::vector<CDemoItem> *m_plDemos;
|
|
|
|
const char *pMap;
|
|
|
|
};
|
|
|
|
|
2017-09-10 21:54:29 +00:00
|
|
|
CRaceDemo::CRaceDemo() : m_RaceState(RACE_NONE), m_RaceStartTick(-1), m_RecordStopTick(-1), m_Time(0) {}
|
2015-08-22 13:09:19 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-08-22 13:09:19 +00:00
|
|
|
void CRaceDemo::OnStateChange(int NewState, int OldState)
|
|
|
|
{
|
|
|
|
if(OldState == IClient::STATE_ONLINE)
|
2017-09-09 21:10:42 +00:00
|
|
|
StopRecord();
|
2015-08-22 13:09:19 +00:00
|
|
|
}
|
|
|
|
|
2017-10-06 20:01:33 +00:00
|
|
|
void CRaceDemo::OnNewSnapshot()
|
2014-08-22 12:18:16 +00:00
|
|
|
{
|
2019-06-03 19:52:14 +00:00
|
|
|
if(!GameClient()->m_GameInfo.m_Race || !g_Config.m_ClAutoRaceRecord || Client()->State() != IClient::STATE_ONLINE)
|
2017-10-06 20:01:33 +00:00
|
|
|
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)
|
2014-08-22 12:18:16 +00:00
|
|
|
return;
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-16 12:56:01 +00:00
|
|
|
static int s_LastRaceTick = -1;
|
|
|
|
|
|
|
|
bool RaceFlag = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_RACETIME;
|
2017-09-16 13:19:26 +00:00
|
|
|
bool ServerControl = RaceFlag && g_Config.m_ClRaceRecordServerControl;
|
2017-09-16 12:56:01 +00:00
|
|
|
int RaceTick = -m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer;
|
|
|
|
|
2014-08-22 12:18:16 +00:00
|
|
|
// start the demo
|
2017-09-28 14:57:39 +00:00
|
|
|
bool ForceStart = ServerControl && s_LastRaceTick != RaceTick && Client()->GameTick() - RaceTick < Client()->GameTickSpeed();
|
2017-09-16 12:56:01 +00:00
|
|
|
bool AllowRestart = (m_AllowRestart || ForceStart) && m_RaceStartTick + 10 * Client()->GameTickSpeed() < Client()->GameTick();
|
2017-09-10 21:54:29 +00:00
|
|
|
if(m_RaceState == RACE_IDLE || m_RaceState == RACE_PREPARE || (m_RaceState == RACE_STARTED && AllowRestart))
|
2014-08-22 12:18:16 +00:00
|
|
|
{
|
2017-09-09 21:10:42 +00:00
|
|
|
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);
|
|
|
|
|
2017-09-16 13:19:26 +00:00
|
|
|
if(ForceStart || (!ServerControl && CRaceHelper::IsStart(m_pClient, PrevPos, Pos)))
|
2017-09-09 21:10:42 +00:00
|
|
|
{
|
|
|
|
if(m_RaceState == RACE_STARTED)
|
2017-09-10 21:54:29 +00:00
|
|
|
Client()->RaceRecord_Stop();
|
|
|
|
if(m_RaceState != RACE_PREPARE) // start recording again
|
2017-09-28 17:13:20 +00:00
|
|
|
{
|
|
|
|
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename));
|
|
|
|
Client()->RaceRecord_Start(m_aTmpFilename);
|
|
|
|
}
|
2017-09-10 21:54:29 +00:00
|
|
|
m_RaceStartTick = Client()->GameTick();
|
2017-09-09 21:10:42 +00:00
|
|
|
m_RaceState = RACE_STARTED;
|
|
|
|
}
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-10 21:54:29 +00:00
|
|
|
// start recording before the player passes the start line, so we can see some preparation steps
|
|
|
|
if(m_RaceState == RACE_NONE)
|
|
|
|
{
|
2017-09-28 17:13:20 +00:00
|
|
|
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename));
|
|
|
|
Client()->RaceRecord_Start(m_aTmpFilename);
|
2017-09-10 21:54:29 +00:00
|
|
|
m_RaceStartTick = Client()->GameTick();
|
|
|
|
m_RaceState = RACE_PREPARE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
2017-09-28 17:13:20 +00:00
|
|
|
StopRecord();
|
2017-09-10 21:54:29 +00:00
|
|
|
m_RaceState = RACE_IDLE;
|
|
|
|
}
|
|
|
|
|
2014-08-22 12:18:16 +00:00
|
|
|
// stop the demo
|
2017-09-09 21:10:42 +00:00
|
|
|
if(m_RaceState == RACE_FINISHED && m_RecordStopTick <= Client()->GameTick())
|
|
|
|
StopRecord(m_Time);
|
2017-09-16 12:56:01 +00:00
|
|
|
|
|
|
|
s_LastRaceTick = RaceFlag ? RaceTick : -1;
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CRaceDemo::OnReset()
|
|
|
|
{
|
2017-09-09 21:10:42 +00:00
|
|
|
StopRecord();
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CRaceDemo::OnMessage(int MsgType, void *pRawMsg)
|
|
|
|
{
|
|
|
|
// check for messages from server
|
|
|
|
if(MsgType == NETMSGTYPE_SV_KILLMSG)
|
|
|
|
{
|
|
|
|
CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
|
2017-10-06 20:01:33 +00:00
|
|
|
if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalClientID && Client()->RaceRecord_IsRecording())
|
2017-09-09 21:10:42 +00:00
|
|
|
StopRecord(m_Time);
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|
|
|
|
else if(MsgType == NETMSGTYPE_SV_CHAT)
|
|
|
|
{
|
|
|
|
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
|
|
|
|
if(pMsg->m_ClientID == -1 && m_RaceState == RACE_STARTED)
|
|
|
|
{
|
2016-11-15 14:28:11 +00:00
|
|
|
char aName[MAX_NAME_LENGTH];
|
2017-08-30 19:44:27 +00:00
|
|
|
int Time = CRaceHelper::TimeFromFinishMessage(pMsg->m_pMessage, aName, sizeof(aName));
|
2017-08-30 22:43:46 +00:00
|
|
|
if(Time > 0 && str_comp(aName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName) == 0)
|
2014-08-22 12:18:16 +00:00
|
|
|
{
|
|
|
|
m_RaceState = RACE_FINISHED;
|
2017-09-09 21:10:42 +00:00
|
|
|
m_RecordStopTick = Client()->GameTick() + Client()->GameTickSpeed();
|
2017-08-30 23:59:22 +00:00
|
|
|
m_Time = Time;
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-09 19:38:41 +00:00
|
|
|
void CRaceDemo::OnMapLoad()
|
|
|
|
{
|
2017-09-12 20:54:29 +00:00
|
|
|
m_AllowRestart = false;
|
2017-09-09 19:38:41 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 21:10:42 +00:00
|
|
|
void CRaceDemo::StopRecord(int Time)
|
2014-08-22 12:18:16 +00:00
|
|
|
{
|
2017-09-09 21:10:42 +00:00
|
|
|
if(Client()->RaceRecord_IsRecording())
|
|
|
|
Client()->RaceRecord_Stop();
|
|
|
|
|
2019-01-07 13:36:17 +00:00
|
|
|
if(m_aTmpFilename[0] != '\0')
|
2017-09-09 21:10:42 +00:00
|
|
|
{
|
2019-01-07 13:36:17 +00:00
|
|
|
if(Time > 0 && CheckDemo(Time))
|
|
|
|
{
|
|
|
|
// save file
|
|
|
|
char aNewFilename[512];
|
|
|
|
GetPath(aNewFilename, sizeof(aNewFilename), m_Time);
|
2017-09-09 21:10:42 +00:00
|
|
|
|
2019-01-07 13:36:17 +00:00
|
|
|
Storage()->RenameFile(m_aTmpFilename, aNewFilename, IStorage::TYPE_SAVE);
|
|
|
|
}
|
|
|
|
else // no new record
|
|
|
|
Storage()->RemoveFile(m_aTmpFilename, IStorage::TYPE_SAVE);
|
2017-09-28 17:13:20 +00:00
|
|
|
|
2019-01-07 13:36:17 +00:00
|
|
|
m_aTmpFilename[0] = '\0';
|
|
|
|
}
|
2017-09-09 21:10:42 +00:00
|
|
|
|
|
|
|
m_Time = 0;
|
|
|
|
m_RaceState = RACE_NONE;
|
2017-09-10 21:54:29 +00:00
|
|
|
m_RaceStartTick = -1;
|
2017-09-09 21:10:42 +00:00
|
|
|
m_RecordStopTick = -1;
|
|
|
|
}
|
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
int CRaceDemo::RaceDemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser)
|
2017-09-09 21:10:42 +00:00
|
|
|
{
|
2017-09-28 17:13:20 +00:00
|
|
|
CDemoListParam *pParam = (CDemoListParam*) pUser;
|
|
|
|
int Length = str_length(pName);
|
|
|
|
int MapLen = str_length(pParam->pMap);
|
2018-07-25 08:29:05 +00:00
|
|
|
if(IsDir || !str_endswith(pName, ".demo") || !str_startswith(pName, pParam->pMap) || pName[MapLen] != '_')
|
2017-09-28 17:13:20 +00:00
|
|
|
return 0;
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
CDemoItem Item;
|
2019-04-26 19:36:49 +00:00
|
|
|
str_copy(Item.m_aName, pName, minimum(static_cast<int>(sizeof(Item.m_aName)), Length - 4));
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
const char *pTime = Item.m_aName + MapLen + 1;
|
|
|
|
const char *pTEnd = pTime;
|
|
|
|
while(isdigit(*pTEnd) || *pTEnd == ' ' || *pTEnd == '.' || *pTEnd == ',')
|
|
|
|
pTEnd++;
|
|
|
|
|
|
|
|
if(g_Config.m_ClDemoName)
|
2014-08-22 12:18:16 +00:00
|
|
|
{
|
2017-09-28 17:13:20 +00:00
|
|
|
char aPlayerName[MAX_NAME_LENGTH];
|
|
|
|
str_copy(aPlayerName, g_Config.m_PlayerName, sizeof(aPlayerName));
|
|
|
|
str_sanitize_filename(aPlayerName);
|
2017-09-09 21:10:42 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
if(pTEnd[0] != '_' || str_comp(pTEnd + 1, aPlayerName) != 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if(pTEnd[0])
|
|
|
|
return 0;
|
2014-11-24 17:29:38 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
Item.m_Time = CRaceHelper::TimeFromSecondsStr(pTime);
|
|
|
|
if(Item.m_Time > 0)
|
|
|
|
pParam->m_plDemos->push_back(Item);
|
2017-09-09 21:10:42 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
bool CRaceDemo::CheckDemo(int Time) const
|
|
|
|
{
|
|
|
|
std::vector<CDemoItem> lDemos;
|
|
|
|
CDemoListParam Param = { &lDemos, Client()->GetCurrentMap() };
|
|
|
|
Storage()->ListDirectoryInfo(IStorage::TYPE_SAVE, ms_pRaceDemoDir, RaceDemolistFetchCallback, &Param);
|
2014-08-22 12:18:16 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
// loop through demo files
|
2017-09-28 17:27:15 +00:00
|
|
|
for(unsigned i = 0; i < lDemos.size(); i++)
|
2017-09-28 17:13:20 +00:00
|
|
|
{
|
|
|
|
if(Time >= lDemos[i].m_Time) // found a better demo
|
|
|
|
return false;
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-28 17:13:20 +00:00
|
|
|
// 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);
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|
2014-11-24 18:10:19 +00:00
|
|
|
|
2017-09-09 21:10:42 +00:00
|
|
|
return true;
|
2014-08-22 12:18:16 +00:00
|
|
|
}
|