2011-02-04 17:25:04 +00:00
|
|
|
/* (c) Rajh, Redix and Sushi. */
|
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
#include <engine/ghost.h>
|
2011-02-04 17:25:04 +00:00
|
|
|
#include <engine/graphics.h>
|
2017-09-09 18:28:38 +00:00
|
|
|
#include <engine/serverbrowser.h>
|
2017-09-09 15:04:19 +00:00
|
|
|
#include <engine/storage.h>
|
2011-02-04 17:25:04 +00:00
|
|
|
#include <engine/shared/config.h>
|
|
|
|
|
2017-09-09 22:57:32 +00:00
|
|
|
#include "players.h"
|
2017-08-30 19:44:27 +00:00
|
|
|
#include "race.h"
|
2011-02-04 17:25:04 +00:00
|
|
|
#include "skins.h"
|
|
|
|
#include "menus.h"
|
|
|
|
#include "ghost.h"
|
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
CGhost::CGhost() : m_StartRenderTick(-1), m_LastDeathTick(-1), m_Rendering(false), m_Recording(false) {}
|
2011-02-04 17:25:04 +00:00
|
|
|
|
2017-09-09 22:57:32 +00:00
|
|
|
void CGhost::GetGhostCharacter(CGhostCharacter *pGhostChar, const CNetObj_Character *pChar)
|
2017-09-09 16:25:32 +00:00
|
|
|
{
|
|
|
|
pGhostChar->m_X = pChar->m_X;
|
|
|
|
pGhostChar->m_Y = pChar->m_Y;
|
|
|
|
pGhostChar->m_VelX = pChar->m_VelX;
|
|
|
|
pGhostChar->m_VelY = 0;
|
|
|
|
pGhostChar->m_Angle = pChar->m_Angle;
|
|
|
|
pGhostChar->m_Direction = pChar->m_Direction;
|
|
|
|
pGhostChar->m_Weapon = pChar->m_Weapon;
|
|
|
|
pGhostChar->m_HookState = pChar->m_HookState;
|
|
|
|
pGhostChar->m_HookX = pChar->m_HookX;
|
|
|
|
pGhostChar->m_HookY = pChar->m_HookY;
|
|
|
|
pGhostChar->m_AttackTick = pChar->m_AttackTick;
|
2017-09-09 22:57:32 +00:00
|
|
|
pGhostChar->m_Tick = pChar->m_Tick;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::GetNetObjCharacter(CNetObj_Character *pChar, const CGhostCharacter *pGhostChar)
|
|
|
|
{
|
|
|
|
mem_zero(pChar, sizeof(CNetObj_Character));
|
|
|
|
pChar->m_X = pGhostChar->m_X;
|
|
|
|
pChar->m_Y = pGhostChar->m_Y;
|
|
|
|
pChar->m_VelX = pGhostChar->m_VelX;
|
|
|
|
pChar->m_VelY = 0;
|
|
|
|
pChar->m_Angle = pGhostChar->m_Angle;
|
|
|
|
pChar->m_Direction = pGhostChar->m_Direction;
|
|
|
|
pChar->m_Weapon = pGhostChar->m_Weapon == WEAPON_GRENADE ? WEAPON_GRENADE : WEAPON_GUN;
|
|
|
|
pChar->m_HookState = pGhostChar->m_HookState;
|
|
|
|
pChar->m_HookX = pGhostChar->m_HookX;
|
|
|
|
pChar->m_HookY = pGhostChar->m_HookY;
|
|
|
|
pChar->m_AttackTick = pGhostChar->m_AttackTick;
|
|
|
|
pChar->m_HookedPlayer = -1;
|
|
|
|
pChar->m_Tick = pGhostChar->m_Tick;
|
2017-09-09 16:25:32 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
void CGhost::AddInfos(const CNetObj_Character *pChar)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
|
|
|
// Just to be sure it doesnt eat too much memory, the first test should be enough anyway
|
2017-09-09 19:38:41 +00:00
|
|
|
int NumTicks = m_CurGhost.m_lPath.size();
|
|
|
|
if(NumTicks > Client()->GameTickSpeed()*60*20)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
|
|
|
StopRecord();
|
|
|
|
return;
|
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 19:38:41 +00:00
|
|
|
// 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();
|
|
|
|
|
|
|
|
const CGameClient::CClientData *pClientData = &m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID];
|
|
|
|
CGhostSkin Skin;
|
|
|
|
StrToInts(&Skin.m_Skin0, 6, pClientData->m_aSkinName);
|
|
|
|
Skin.m_UseCustomColor = pClientData->m_UseCustomColor;
|
|
|
|
Skin.m_ColorBody = pClientData->m_ColorBody;
|
|
|
|
Skin.m_ColorFeet = pClientData->m_ColorFeet;
|
|
|
|
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&Skin, sizeof(Skin));
|
|
|
|
|
|
|
|
for(int i = 0; i < NumTicks; i++)
|
|
|
|
GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)&m_CurGhost.m_lPath[i], sizeof(CGhostCharacter));
|
|
|
|
}
|
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
CGhostCharacter GhostChar;
|
2017-09-09 16:25:32 +00:00
|
|
|
GetGhostCharacter(&GhostChar, pChar);
|
2017-09-09 15:04:19 +00:00
|
|
|
m_CurGhost.m_lPath.add(GhostChar);
|
2017-09-09 16:25:32 +00:00
|
|
|
if(GhostRecorder()->IsRecording())
|
2017-09-09 19:38:41 +00:00
|
|
|
GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)&GhostChar, sizeof(CGhostCharacter));
|
2017-09-09 16:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int CGhost::GetSlot() const
|
|
|
|
{
|
|
|
|
for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++)
|
|
|
|
if(m_aActiveGhosts[i].Empty())
|
|
|
|
return i;
|
|
|
|
return -1;
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::OnRender()
|
|
|
|
{
|
2017-09-09 18:28:38 +00:00
|
|
|
// only for race
|
|
|
|
CServerInfo ServerInfo;
|
|
|
|
Client()->GetServerInfo(&ServerInfo);
|
|
|
|
if(!IsRace(&ServerInfo) || !g_Config.m_ClRaceGhost)
|
2011-02-04 17:25:04 +00:00
|
|
|
return;
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
if(m_pClient->m_Snap.m_pLocalCharacter && m_pClient->m_Snap.m_pLocalPrevCharacter)
|
2015-04-18 20:29:28 +00:00
|
|
|
{
|
2017-09-09 18:28:38 +00:00
|
|
|
if(m_pClient->m_NewPredictedTick)
|
|
|
|
{
|
|
|
|
vec2 PrevPos = m_pClient->m_PredictedPrevChar.m_Pos;
|
|
|
|
vec2 Pos = m_pClient->m_PredictedChar.m_Pos;
|
2017-09-09 19:38:41 +00:00
|
|
|
if((!m_Rendering || !m_IsSolo) && CRaceHelper::IsStart(m_pClient, PrevPos, Pos))
|
2017-09-09 18:28:38 +00:00
|
|
|
StartRender();
|
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
if(m_pClient->m_NewTick)
|
|
|
|
{
|
|
|
|
int PrevTick = m_pClient->m_Snap.m_pLocalPrevCharacter->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);
|
2011-02-04 17:25:04 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
// detecting death, needed because race allows immediate respawning
|
2017-09-09 19:38:41 +00:00
|
|
|
if((!m_Recording || !m_IsSolo) && CRaceHelper::IsStart(m_pClient, PrevPos, Pos) && m_LastDeathTick < PrevTick)
|
|
|
|
{
|
|
|
|
if(m_Recording)
|
|
|
|
GhostRecorder()->Stop(0, -1);
|
2017-09-09 18:28:38 +00:00
|
|
|
StartRecord();
|
2017-09-09 19:38:41 +00:00
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
if(m_Recording)
|
|
|
|
AddInfos(m_pClient->m_Snap.m_pLocalCharacter);
|
|
|
|
}
|
|
|
|
}
|
2011-02-04 17:25:04 +00:00
|
|
|
|
|
|
|
// Play the ghost
|
|
|
|
if(!m_Rendering || !g_Config.m_ClRaceShowGhost)
|
|
|
|
return;
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
int ActiveGhosts = 0;
|
|
|
|
int PlaybackTick = Client()->PredGameTick() - m_StartRenderTick;
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 16:25:32 +00:00
|
|
|
for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 16:25:32 +00:00
|
|
|
CGhostItem *pGhost = &m_aActiveGhosts[i];
|
2017-09-09 18:28:38 +00:00
|
|
|
if(pGhost->Empty())
|
2011-02-11 06:00:12 +00:00
|
|
|
continue;
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
bool End = false;
|
|
|
|
int GhostTick = pGhost->m_lPath[0].m_Tick + PlaybackTick;
|
|
|
|
while(pGhost->m_lPath[pGhost->m_PlaybackPos].m_Tick < GhostTick && !End)
|
|
|
|
{
|
|
|
|
if(pGhost->m_PlaybackPos < pGhost->m_lPath.size() - 1)
|
|
|
|
pGhost->m_PlaybackPos++;
|
|
|
|
else
|
|
|
|
End = true;
|
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
if(End)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ActiveGhosts++;
|
|
|
|
|
|
|
|
int CurPos = pGhost->m_PlaybackPos;
|
|
|
|
int PrevPos = max(0, CurPos - 1);
|
2017-09-09 22:57:32 +00:00
|
|
|
CNetObj_Character Player, Prev;
|
|
|
|
GetNetObjCharacter(&Player, &pGhost->m_lPath[CurPos]);
|
|
|
|
GetNetObjCharacter(&Prev, &pGhost->m_lPath[PrevPos]);
|
2017-09-09 18:28:38 +00:00
|
|
|
|
|
|
|
int TickDiff = Player.m_Tick - Prev.m_Tick;
|
|
|
|
float IntraTick = 0.f;
|
|
|
|
if(TickDiff > 0)
|
|
|
|
IntraTick = (GhostTick - Prev.m_Tick - 1 + Client()->PredIntraGameTick()) / TickDiff;
|
|
|
|
|
|
|
|
Player.m_AttackTick += Client()->GameTick() - GhostTick;
|
|
|
|
|
2017-09-09 22:57:32 +00:00
|
|
|
m_pClient->m_pPlayers->RenderHook(&Prev, &Player, &pGhost->m_RenderInfo , -2, vec2(), vec2(), IntraTick);
|
|
|
|
m_pClient->m_pPlayers->RenderPlayer(&Prev, &Player, &pGhost->m_RenderInfo, -2, vec2(), IntraTick);
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
2017-09-09 18:28:38 +00:00
|
|
|
|
|
|
|
if(!ActiveGhosts)
|
|
|
|
StopRender();
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
void CGhost::InitRenderInfos(CTeeRenderInfo *pRenderInfo, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet)
|
2011-02-11 06:00:12 +00:00
|
|
|
{
|
2017-09-09 15:04:19 +00:00
|
|
|
int SkinId = m_pClient->m_pSkins->Find(pSkinName);
|
|
|
|
if(SkinId < 0)
|
|
|
|
{
|
|
|
|
SkinId = m_pClient->m_pSkins->Find("default");
|
|
|
|
if(SkinId < 0)
|
|
|
|
SkinId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pRenderInfo->m_ColorBody = m_pClient->m_pSkins->GetColorV4(ColorBody);
|
|
|
|
pRenderInfo->m_ColorFeet = m_pClient->m_pSkins->GetColorV4(ColorFeet);
|
|
|
|
|
|
|
|
if(UseCustomColor)
|
|
|
|
pRenderInfo->m_Texture = m_pClient->m_pSkins->Get(SkinId)->m_ColorTexture;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pRenderInfo->m_Texture = m_pClient->m_pSkins->Get(SkinId)->m_OrgTexture;
|
|
|
|
pRenderInfo->m_ColorBody = vec4(1, 1, 1, 1);
|
|
|
|
pRenderInfo->m_ColorFeet = vec4(1, 1, 1, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
pRenderInfo->m_ColorBody.a = 0.5f;
|
|
|
|
pRenderInfo->m_ColorFeet.a = 0.5f;
|
|
|
|
pRenderInfo->m_Size = 64;
|
2011-02-11 06:00:12 +00:00
|
|
|
}
|
|
|
|
|
2011-02-04 17:25:04 +00:00
|
|
|
void CGhost::StartRecord()
|
|
|
|
{
|
|
|
|
m_Recording = true;
|
2017-09-09 15:04:19 +00:00
|
|
|
m_CurGhost.Reset();
|
|
|
|
|
2017-09-09 19:38:41 +00:00
|
|
|
const CGameClient::CClientData *pClientData = &m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID];
|
|
|
|
InitRenderInfos(&m_CurGhost.m_RenderInfo, pClientData->m_aSkinName, pClientData->m_UseCustomColor, pClientData->m_ColorBody, pClientData->m_ColorFeet);
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 16:25:32 +00:00
|
|
|
void CGhost::StopRecord(int Time)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
|
|
|
m_Recording = false;
|
2017-09-09 16:25:32 +00:00
|
|
|
bool RecordingToFile = GhostRecorder()->IsRecording();
|
|
|
|
|
|
|
|
if(RecordingToFile)
|
|
|
|
GhostRecorder()->Stop(m_CurGhost.m_lPath.size(), Time);
|
|
|
|
|
|
|
|
char aTmpFilename[128];
|
|
|
|
Client()->Ghost_GetPath(aTmpFilename, sizeof(aTmpFilename));
|
|
|
|
|
|
|
|
CMenus::CGhostItem *pOwnGhost = m_pClient->m_pMenus->GetOwnGhost();
|
|
|
|
if(Time > 0 && (!pOwnGhost || Time < pOwnGhost->m_Time))
|
|
|
|
{
|
|
|
|
// add to active ghosts
|
|
|
|
int Slot = pOwnGhost ? pOwnGhost->m_Slot : GetSlot();
|
|
|
|
if(Slot != -1)
|
|
|
|
m_aActiveGhosts[Slot] = m_CurGhost;
|
|
|
|
|
|
|
|
char aFilename[128] = { 0 };
|
|
|
|
if(RecordingToFile)
|
|
|
|
Client()->Ghost_GetPath(aFilename, sizeof(aFilename), Time);
|
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
// create ghost item
|
|
|
|
CMenus::CGhostItem Item;
|
|
|
|
str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename));
|
|
|
|
str_copy(Item.m_aPlayer, g_Config.m_PlayerName, sizeof(Item.m_aPlayer));
|
|
|
|
Item.m_Time = Time;
|
|
|
|
Item.m_Slot = Slot;
|
2017-09-10 01:48:22 +00:00
|
|
|
|
|
|
|
// save new ghost file
|
|
|
|
if(Item.HasFile())
|
|
|
|
Storage()->RenameFile(aTmpFilename, aFilename, IStorage::TYPE_SAVE);
|
2017-09-09 18:28:38 +00:00
|
|
|
|
|
|
|
// add item to menu list
|
2017-09-10 01:48:22 +00:00
|
|
|
m_pClient->m_pMenus->UpdateOwnGhost(Item);
|
2017-09-09 16:25:32 +00:00
|
|
|
}
|
|
|
|
else if(RecordingToFile) // no new record
|
|
|
|
Storage()->RemoveFile(aTmpFilename, IStorage::TYPE_SAVE);
|
|
|
|
|
|
|
|
m_CurGhost.Reset();
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::StartRender()
|
|
|
|
{
|
|
|
|
m_Rendering = true;
|
|
|
|
m_StartRenderTick = Client()->PredGameTick();
|
2017-09-09 18:28:38 +00:00
|
|
|
for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++)
|
|
|
|
m_aActiveGhosts[i].m_PlaybackPos = 0;
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::StopRender()
|
|
|
|
{
|
|
|
|
m_Rendering = false;
|
|
|
|
}
|
|
|
|
|
2017-09-09 16:25:32 +00:00
|
|
|
int CGhost::Load(const char *pFilename)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 16:25:32 +00:00
|
|
|
int Slot = GetSlot();
|
|
|
|
if(Slot == -1)
|
|
|
|
return -1;
|
2011-02-11 06:00:12 +00:00
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
if(!Client()->GhostLoader_Load(pFilename))
|
2017-09-09 16:25:32 +00:00
|
|
|
return -1;
|
2011-02-04 17:25:04 +00:00
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
const CGhostHeader *pHeader = GhostLoader()->GetHeader();
|
|
|
|
|
|
|
|
int NumTicks = GhostLoader()->GetTicks(pHeader);
|
|
|
|
int Time = GhostLoader()->GetTime(pHeader);
|
|
|
|
if(NumTicks <= 0 || Time <= 0)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 15:04:19 +00:00
|
|
|
GhostLoader()->Close();
|
2017-09-09 16:25:32 +00:00
|
|
|
return -1;
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
// select ghost
|
2017-09-09 16:25:32 +00:00
|
|
|
CGhostItem *pGhost = &m_aActiveGhosts[Slot];
|
|
|
|
pGhost->m_lPath.set_size(NumTicks);
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2011-02-11 06:00:12 +00:00
|
|
|
int Index = 0;
|
2017-09-09 15:04:19 +00:00
|
|
|
bool FoundSkin = false;
|
2017-09-09 18:28:38 +00:00
|
|
|
bool NoTick = false;
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
int Type;
|
|
|
|
while(GhostLoader()->ReadNextType(&Type))
|
|
|
|
{
|
2017-09-09 18:28:38 +00:00
|
|
|
if(Index == NumTicks && (Type == GHOSTDATA_TYPE_CHARACTER || Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK))
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 15:04:19 +00:00
|
|
|
Index = -1;
|
2011-02-04 17:25:04 +00:00
|
|
|
break;
|
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
if(Type == GHOSTDATA_TYPE_SKIN)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 15:04:19 +00:00
|
|
|
CGhostSkin Skin;
|
|
|
|
if(GhostLoader()->ReadData(Type, (char*)&Skin, sizeof(Skin)) && !FoundSkin)
|
|
|
|
{
|
|
|
|
FoundSkin = true;
|
|
|
|
char aSkinName[64];
|
|
|
|
IntsToStr(&Skin.m_Skin0, 6, aSkinName);
|
2017-09-09 16:25:32 +00:00
|
|
|
InitRenderInfos(&pGhost->m_RenderInfo, aSkinName, Skin.m_UseCustomColor, Skin.m_ColorBody, Skin.m_ColorFeet);
|
2017-09-09 15:04:19 +00:00
|
|
|
}
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
2017-09-09 15:04:19 +00:00
|
|
|
else if(Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 18:28:38 +00:00
|
|
|
NoTick = true;
|
|
|
|
GhostLoader()->ReadData(Type, (char*)&pGhost->m_lPath[Index++], sizeof(CGhostCharacter_NoTick));
|
|
|
|
}
|
|
|
|
else if(Type == GHOSTDATA_TYPE_CHARACTER)
|
|
|
|
{
|
|
|
|
GhostLoader()->ReadData(Type, (char*)&pGhost->m_lPath[Index++], sizeof(CGhostCharacter));
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
}
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
GhostLoader()->Close();
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2017-09-09 16:25:32 +00:00
|
|
|
if(Index != NumTicks)
|
|
|
|
{
|
|
|
|
pGhost->Reset();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-09-09 18:28:38 +00:00
|
|
|
if(NoTick)
|
|
|
|
{
|
|
|
|
int StartTick = 0;
|
|
|
|
for(int i = 1; i < NumTicks; i++) // estimate start tick
|
|
|
|
if(pGhost->m_lPath[i].m_AttackTick != pGhost->m_lPath[i - 1].m_AttackTick)
|
|
|
|
StartTick = pGhost->m_lPath[i].m_AttackTick - i;
|
|
|
|
for(int i = 0; i < NumTicks; i++)
|
|
|
|
pGhost->m_lPath[i].m_Tick = StartTick + i;
|
|
|
|
}
|
|
|
|
|
2017-09-09 15:04:19 +00:00
|
|
|
if(!FoundSkin)
|
2017-09-09 16:25:32 +00:00
|
|
|
InitRenderInfos(&pGhost->m_RenderInfo, "default", 0, 0, 0);
|
2017-09-09 15:04:19 +00:00
|
|
|
|
2017-09-09 16:25:32 +00:00
|
|
|
return Slot;
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 16:25:32 +00:00
|
|
|
void CGhost::Unload(int Slot)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 16:25:32 +00:00
|
|
|
m_aActiveGhosts[Slot].Reset();
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
2017-09-10 01:48:22 +00:00
|
|
|
void CGhost::UnloadAll()
|
|
|
|
{
|
|
|
|
for(int i = 0; i < MAX_ACTIVE_GHOSTS; i++)
|
|
|
|
Unload(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::SaveGhost(CMenus::CGhostItem *pItem)
|
|
|
|
{
|
|
|
|
int Slot = pItem->m_Slot;
|
|
|
|
if(Slot < 0 || pItem->HasFile() || m_aActiveGhosts[Slot].Empty() || GhostRecorder()->IsRecording())
|
|
|
|
return;
|
|
|
|
|
|
|
|
int NumTicks = m_aActiveGhosts[Slot].m_lPath.size();
|
|
|
|
Client()->GhostRecorder_Start(pItem->m_Time);
|
|
|
|
|
|
|
|
const CGameClient::CClientData *pClientData = &m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID];
|
|
|
|
CGhostSkin Skin;
|
|
|
|
StrToInts(&Skin.m_Skin0, 6, pClientData->m_aSkinName);
|
|
|
|
Skin.m_UseCustomColor = pClientData->m_UseCustomColor;
|
|
|
|
Skin.m_ColorBody = pClientData->m_ColorBody;
|
|
|
|
Skin.m_ColorFeet = pClientData->m_ColorFeet;
|
|
|
|
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, (const char*)&Skin, sizeof(Skin));
|
|
|
|
|
|
|
|
for(int i = 0; i < NumTicks; i++)
|
|
|
|
GhostRecorder()->WriteData(GHOSTDATA_TYPE_CHARACTER, (const char*)&m_aActiveGhosts[Slot].m_lPath[i], sizeof(CGhostCharacter));
|
|
|
|
|
|
|
|
GhostRecorder()->Stop(NumTicks, pItem->m_Time);
|
|
|
|
Client()->Ghost_GetPath(pItem->m_aFilename, sizeof(pItem->m_aFilename), pItem->m_Time);
|
|
|
|
}
|
|
|
|
|
2011-08-13 00:11:06 +00:00
|
|
|
void CGhost::ConGPlay(IConsole::IResult *pResult, void *pUserData)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
|
|
|
((CGhost *)pUserData)->StartRender();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::OnConsoleInit()
|
|
|
|
{
|
2017-09-09 15:04:19 +00:00
|
|
|
m_pGhostLoader = Kernel()->RequestInterface<IGhostLoader>();
|
|
|
|
m_pGhostRecorder = Kernel()->RequestInterface<IGhostRecorder>();
|
|
|
|
|
2011-08-13 00:11:06 +00:00
|
|
|
Console()->Register("gplay", "", CFGFLAG_CLIENT, ConGPlay, this, "");
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::OnMessage(int MsgType, void *pRawMsg)
|
|
|
|
{
|
2017-09-09 18:28:38 +00:00
|
|
|
// only for race
|
|
|
|
CServerInfo ServerInfo;
|
|
|
|
Client()->GetServerInfo(&ServerInfo);
|
|
|
|
|
|
|
|
if(!IsRace(&ServerInfo) || m_pClient->m_Snap.m_SpecInfo.m_Active)
|
2011-02-04 17:25:04 +00:00
|
|
|
return;
|
2015-04-18 20:29:28 +00:00
|
|
|
|
2011-02-04 17:25:04 +00:00
|
|
|
// check for messages from server
|
|
|
|
if(MsgType == NETMSGTYPE_SV_KILLMSG)
|
|
|
|
{
|
|
|
|
CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
|
2011-02-13 05:35:13 +00:00
|
|
|
if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalClientID)
|
2017-09-09 18:28:38 +00:00
|
|
|
{
|
|
|
|
if(m_Recording)
|
|
|
|
StopRecord();
|
|
|
|
StopRender();
|
|
|
|
m_LastDeathTick = Client()->GameTick();
|
|
|
|
}
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
else if(MsgType == NETMSGTYPE_SV_CHAT)
|
|
|
|
{
|
|
|
|
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
|
2017-09-09 15:04:19 +00:00
|
|
|
if(pMsg->m_ClientID == -1 && m_Recording)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
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)
|
2011-02-04 17:25:04 +00:00
|
|
|
{
|
2017-09-09 16:25:32 +00:00
|
|
|
StopRecord(Time);
|
2017-09-09 15:04:19 +00:00
|
|
|
StopRender();
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::OnReset()
|
|
|
|
{
|
|
|
|
StopRecord();
|
|
|
|
StopRender();
|
2017-09-09 18:28:38 +00:00
|
|
|
m_LastDeathTick = -1;
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGhost::OnMapLoad()
|
|
|
|
{
|
|
|
|
OnReset();
|
2017-09-10 01:48:22 +00:00
|
|
|
UnloadAll();
|
2011-02-04 17:25:04 +00:00
|
|
|
m_pClient->m_pMenus->GhostlistPopulate();
|
2017-09-09 19:38:41 +00:00
|
|
|
m_IsSolo = true;
|
2011-02-04 17:25:04 +00:00
|
|
|
}
|