Added AutoDemoRecord and ghost made by Race mod team, implemented to DDRace by noother

This commit is contained in:
GreYFoXGTi 2011-02-04 19:25:04 +02:00
parent 3da1860654
commit 24a688b2c3
17 changed files with 1364 additions and 8 deletions

1
.gitignore vendored
View file

@ -47,4 +47,5 @@ DDRace-Server*
*.user
*.cmd
.settings
*.opensdf

View file

@ -131,6 +131,13 @@ public:
virtual const char *LatestVersion() = 0;
virtual bool ConnectionProblems() = 0;
//DDRace
virtual const char* GetCurrentMap() = 0;
virtual int GetCurrentMapCrc() = 0;
virtual const char* RaceRecordStart(const char *pFilename) = 0;
virtual void RaceRecordStop() = 0;
virtual bool DemoIsRecording() = 0;
virtual bool SoundInitFailed() = 0;
};

View file

@ -2354,3 +2354,38 @@ int main(int argc, const char **argv) // ignore_convention
return 0;
}
const char* CClient::GetCurrentMap()
{
return m_aCurrentMap;
}
int CClient::GetCurrentMapCrc()
{
return m_CurrentMapCrc;
}
const char* CClient::RaceRecordStart(const char *pFilename)
{
char aFilename[128];
str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", m_aCurrentMap, pFilename);
if(State() != STATE_ONLINE)
dbg_msg("demorec/record", "client is not online");
else
m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client");
return m_aCurrentMap;
}
void CClient::RaceRecordStop()
{
if(m_DemoRecorder.IsRecording())
m_DemoRecorder.Stop();
}
bool CClient::DemoIsRecording()
{
return m_DemoRecorder.IsRecording();
}

View file

@ -316,6 +316,13 @@ public:
static void Con_Record(IConsole::IResult *pResult, void *pUserData, int ClientID);
static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData, int ClientID);
//DDRace
virtual const char* GetCurrentMap();
virtual int GetCurrentMapCrc();
virtual const char* RaceRecordStart(const char *pFilename);
virtual void RaceRecordStop();
virtual bool DemoIsRecording();
void RegisterCommands();
const char *DemoPlayer_Play(const char *pFilename, int StorageType);

View file

@ -193,4 +193,11 @@ MACRO_CONFIG_INT(BrFilterTestServer, br_filter_test_server, 0, 0, 2, CFGFLAG_SAV
MACRO_CONFIG_INT(SvTeamAskTime, sv_team_ask_time, 10, 0, 9999, CFGFLAG_SERVER, "How much time the player has to accept or refuse", 3)
MACRO_CONFIG_INT(SvAllowTeamLeader, sv_allow_team_leader, 1, 0, 1, CFGFLAG_SERVER, "Whether the admin allows teams to have leaders or not", 4)
MACRO_CONFIG_INT(ClAutoRaceRecord, cl_auto_race_record, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Save the best demo of each race", -1)
MACRO_CONFIG_INT(ClDemoName, cl_demo_name, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Save the playername within the demo", -1)
MACRO_CONFIG_INT(ClRaceGhost, cl_race_ghost, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Enable ghost",-1)
MACRO_CONFIG_INT(ClRaceShowGhost, cl_race_show_ghost, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show ghost",-1)
MACRO_CONFIG_INT(ClRaceSaveGhost, cl_race_save_ghost, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Save ghost",-1)
#endif

View file

@ -63,6 +63,7 @@ public:
fs_makedir(GetPath(TYPE_SAVE, "downloadedmaps", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "demos", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "demos/auto", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "ghosts", aPath, sizeof(aPath)));
}
return m_NumPaths ? 0 : 1;

View file

@ -0,0 +1,615 @@
/* (c) Rajh, Redix and Sushi. */
#include <cstdio>
#include <engine/storage.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/shared/compression.h>
#include <engine/shared/network.h>
#include <game/generated/client_data.h>
#include <game/client/animstate.h>
#include "skins.h"
#include "menus.h"
#include "ghost.h"
/*
Note:
Freezing fucks up the ghost
the ghost isnt really sync
don't really get the client tick system for prediction
can used PrevChar and PlayerChar and it would be fluent and accurate but won't be predicted
so it will be affected by lags
*/
static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'G', 'H', 'O', 'S', 'T', 0};
static const unsigned char gs_ActVersion = 1;
CGhost::CGhost()
{
m_lGhosts.clear();
m_CurPath.clear();
m_CurPos = 0;
m_Recording = false;
m_Rendering = false;
m_RaceState = RACE_NONE;
m_NewRecord = false;
m_BestTime = -1;
m_StartRenderTick = -1;
m_StartRecordTick = -1;
}
void CGhost::AddInfos(CNetObj_Character Player)
{
if(!m_Recording)
return;
// Just to be sure it doesnt eat too much memory, the first test should be enough anyway
if((Client()->GameTick()-m_StartRecordTick) > Client()->GameTickSpeed()*60*13 || m_CurPath.size() > 50*15*60)
{
dbg_msg("ghost", "13 minutes elapsed. Stopping ghost record");
StopRecord();
m_CurPath.clear();
return;
}
m_CurPath.add(Player);
}
void CGhost::OnRender()
{
// only for race
if(!m_pClient->m_IsRace || !g_Config.m_ClRaceGhost)
return;
// Check if the race line is crossed then start the render of the ghost if one
if(m_RaceState != RACE_STARTED) {
bool start = false;
std::list < int > Indices = m_pClient->Collision()->GetMapIndices(m_pClient->m_PredictedPrevChar.m_Pos, m_pClient->m_LocalCharacterPos);
if(!Indices.empty()) {
for(std::list < int >::iterator i = Indices.begin(); i != Indices.end(); i++) {
if(m_pClient->Collision()->GetTileIndex(*i) == TILE_BEGIN) start = true;
}
} else {
int CurrentIndex = m_pClient->Collision()->GetPureMapIndex(m_pClient->m_LocalCharacterPos);
if(m_pClient->Collision()->GetTileIndex(CurrentIndex) == TILE_BEGIN) start = true;
}
if(start) {
dbg_msg("ghost", "race started");
m_RaceState = RACE_STARTED;
StartRender();
StartRecord();
}
}
if(m_RaceState == RACE_FINISHED)
{
int OwnIndex = -1;
for(int i = 0; i < m_lGhosts.size(); i++)
if(m_lGhosts[i].m_ID == -1)
{
OwnIndex = i;
break;
}
if(m_NewRecord)
{
dbg_msg("ghost", "new path saved");
m_NewRecord = false;
CGhostList Ghost;
Ghost.m_ID = -1;
Ghost.m_GhostInfo = m_CurInfo;
Ghost.m_BestPath.clear();
Ghost.m_BestPath = m_CurPath;
if(OwnIndex < 0)
m_lGhosts.add(Ghost);
else
m_lGhosts[OwnIndex] = Ghost;
Save();
}
StopRecord();
StopRender();
m_RaceState = RACE_NONE;
}
CNetObj_Character Player = m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalCid].m_Cur;
m_pClient->m_PredictedChar.Write(&Player);
if(m_pClient->m_NewPredictedTick)
AddInfos(Player);
// Play the ghost
if(!m_Rendering || !g_Config.m_ClRaceShowGhost)
return;
m_CurPos = Client()->PredGameTick()-m_StartRenderTick;
if(m_lGhosts.size() == 0 || m_CurPos < 0)
{
//dbg_msg("ghost", "Ghost path done");
m_Rendering = false;
return;
}
for(int i = 0; i < m_lGhosts.size(); i++)
{
RenderGhostHook(&m_lGhosts[i]);
RenderGhost(&m_lGhosts[i]);
}
}
void CGhost::RenderGhost(CGhostList *pGhost)
{
if(m_CurPos >= pGhost->m_BestPath.size())
return;
CNetObj_Character Player = pGhost->m_BestPath[m_CurPos];
CNetObj_Character Prev = pGhost->m_BestPath[m_CurPos];
if(m_CurPos > 0)
Prev = pGhost->m_BestPath[m_CurPos-1];
char aSkinName[64];
IntsToStr(&pGhost->m_GhostInfo.m_Skin0, 6, aSkinName);
int SkinId = m_pClient->m_pSkins->Find(aSkinName);
if(SkinId < 0)
{
SkinId = m_pClient->m_pSkins->Find("default");
if(SkinId < 0)
SkinId = 0;
}
CTeeRenderInfo RenderInfo;
RenderInfo.m_ColorBody = m_pClient->m_pSkins->GetColorV4(pGhost->m_GhostInfo.m_ColorBody);
RenderInfo.m_ColorFeet = m_pClient->m_pSkins->GetColorV4(pGhost->m_GhostInfo.m_ColorFeet);
if(pGhost->m_GhostInfo.m_UseCustomColor)
RenderInfo.m_Texture = m_pClient->m_pSkins->Get(SkinId)->m_ColorTexture;
else
{
RenderInfo.m_Texture = m_pClient->m_pSkins->Get(SkinId)->m_OrgTexture;
RenderInfo.m_ColorBody = vec4(1,1,1,1);
RenderInfo.m_ColorFeet = vec4(1,1,1,1);
}
RenderInfo.m_ColorBody.a = 0.5f;
RenderInfo.m_ColorFeet.a = 0.5f;
RenderInfo.m_Size = 64;
float Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, Client()->IntraGameTick())/256.0f;
vec2 Direction = GetDirection((int)(Angle*256.0f));
vec2 Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), Client()->PredIntraGameTick());
vec2 Vel = mix(vec2(Prev.m_VelX/256.0f, Prev.m_VelY/256.0f), vec2(Player.m_VelX/256.0f, Player.m_VelY/256.0f), Client()->PredIntraGameTick());
bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1;
bool InAir = !Collision()->CheckPoint(Player.m_X, Player.m_Y+16);
bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0);
float WalkTime = fmod(absolute(Position.x), 100.0f)/100.0f;
CAnimState State;
State.Set(&g_pData->m_aAnimations[ANIM_BASE], 0);
if(InAir)
State.Add(&g_pData->m_aAnimations[ANIM_INAIR], 0, 1.0f);
else if(Stationary)
State.Add(&g_pData->m_aAnimations[ANIM_IDLE], 0, 1.0f);
else if(!WantOtherDir)
State.Add(&g_pData->m_aAnimations[ANIM_WALK], WalkTime, 1.0f);
if (Player.m_Weapon == WEAPON_GRENADE)
{
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
Graphics()->QuadsBegin();
Graphics()->QuadsSetRotation(State.GetAttach()->m_Angle*pi*2+Angle);
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
// normal weapons
int iw = clamp(Player.m_Weapon, 0, NUM_WEAPONS-1);
RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[iw].m_pSpriteBody, Direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0);
vec2 Dir = Direction;
float Recoil = 0.0f;
// TODO: is this correct?
float a = (Client()->PredGameTick()-Player.m_AttackTick+Client()->PredIntraGameTick())/5.0f;
if(a < 1)
Recoil = sinf(a*pi);
vec2 p = Position + Dir * g_pData->m_Weapons.m_aId[iw].m_Offsetx - Direction*Recoil*10.0f;
p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety;
RenderTools()->DrawSprite(p.x, p.y, g_pData->m_Weapons.m_aId[iw].m_VisualSize);
Graphics()->QuadsEnd();
}
// Render ghost
RenderTools()->RenderTee(&State, &RenderInfo, 0, Direction, Position, true);
}
void CGhost::RenderGhostHook(CGhostList *pGhost)
{
if(m_CurPos >= pGhost->m_BestPath.size())
return;
CNetObj_Character Player = pGhost->m_BestPath[m_CurPos];
CNetObj_Character Prev = pGhost->m_BestPath[m_CurPos];
if(m_CurPos > 0)
Prev = pGhost->m_BestPath[m_CurPos-1];
if (Prev.m_HookState<=0 || Player.m_HookState<=0)
return;
float Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, Client()->IntraGameTick())/256.0f;
vec2 Direction = GetDirection((int)(Angle*256.0f));
vec2 Pos = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), Client()->PredIntraGameTick());
vec2 HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), Client()->PredIntraGameTick());
float d = distance(Pos, HookPos);
vec2 Dir = normalize(Pos-HookPos);
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
Graphics()->QuadsBegin();
Graphics()->QuadsSetRotation(GetAngle(Dir)+pi);
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
// render head
RenderTools()->SelectSprite(SPRITE_HOOK_HEAD);
IGraphics::CQuadItem QuadItem(HookPos.x, HookPos.y, 24, 16);
Graphics()->QuadsDraw(&QuadItem, 1);
// render chain
RenderTools()->SelectSprite(SPRITE_HOOK_CHAIN);
IGraphics::CQuadItem Array[1024];
int j = 0;
for(float f = 24; f < d && j < 1024; f += 24, j++)
{
vec2 p = HookPos + Dir*f;
Array[j] = IGraphics::CQuadItem(p.x, p.y, 24, 16);
}
Graphics()->QuadsDraw(Array, j);
Graphics()->QuadsSetRotation(0);
Graphics()->QuadsEnd();
}
void CGhost::StartRecord()
{
m_Recording = true;
m_CurPath.clear();
CNetObj_ClientInfo *pInfo = (CNetObj_ClientInfo *) Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_CLIENTINFO, m_pClient->m_Snap.m_LocalCid);
m_CurInfo = *pInfo;
m_StartRecordTick = Client()->GameTick();
}
void CGhost::StopRecord()
{
m_Recording = false;
}
void CGhost::StartRender()
{
m_CurPos = 0;
m_Rendering = true;
m_StartRenderTick = Client()->PredGameTick();
}
void CGhost::StopRender()
{
m_Rendering = false;
}
void CGhost::Save()
{
if(!g_Config.m_ClRaceSaveGhost)
return;
CGhostHeader Header;
// check the player name
char aName[MAX_NAME_LENGTH];
str_copy(aName, g_Config.m_PlayerName, sizeof(aName));
for(int i = 0; i < MAX_NAME_LENGTH; i++)
{
if(!aName[i])
break;
if(aName[i] == '\\' || aName[i] == '/' || aName[i] == '|' || aName[i] == ':' || aName[i] == '*' || aName[i] == '?' || aName[i] == '<' || aName[i] == '>' || aName[i] == '"')
aName[i] = '%';
}
char aFilename[256];
char aBuf[256];
str_format(aFilename, sizeof(aFilename), "%s_%s_%.3f_%08x.gho", Client()->GetCurrentMap(), aName, m_BestTime, Client()->GetCurrentMapCrc());
str_format(aBuf, sizeof(aBuf), "ghosts/%s", aFilename);
IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE);
if(!File)
return;
// write header
int Crc = Client()->GetCurrentMapCrc();
mem_zero(&Header, sizeof(Header));
mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker));
Header.m_Version = gs_ActVersion;
IntsToStr(&m_CurInfo.m_Name0, 6, Header.m_aOwner);
str_copy(Header.m_aMap, Client()->GetCurrentMap(), sizeof(Header.m_aMap));
Header.m_aCrc[0] = (Crc>>24)&0xff;
Header.m_aCrc[1] = (Crc>>16)&0xff;
Header.m_aCrc[2] = (Crc>>8)&0xff;
Header.m_aCrc[3] = (Crc)&0xff;
Header.m_Time = m_BestTime;
io_write(File, &Header, sizeof(Header));
// write client info
io_write(File, &m_CurInfo, sizeof(m_CurInfo));
// write data
int ItemsPerPackage = 500; // 500 ticks per package
int Num = m_CurPath.size();
CNetObj_Character *Data = &m_CurPath[0];
while(Num)
{
int Items = min(Num, ItemsPerPackage);
Num -= Items;
char aBuffer[100*500];
char aBuffer2[100*500];
unsigned char aSize[4];
int Size = sizeof(CNetObj_Character)*Items;
mem_copy(aBuffer2, Data, Size);
Data += Items;
Size = CVariableInt::Compress(aBuffer2, Size, aBuffer);
Size = CNetBase::Compress(aBuffer, Size, aBuffer2, sizeof(aBuffer2));
aSize[0] = (Size>>24)&0xff;
aSize[1] = (Size>>16)&0xff;
aSize[2] = (Size>>8)&0xff;
aSize[3] = (Size)&0xff;
io_write(File, aSize, sizeof(aSize));
io_write(File, aBuffer2, Size);
}
io_close(File);
// remove old ghost from list
for(int i = 0; i < m_pClient->m_pMenus->m_lGhosts.size(); i++)
{
CMenus::CGhostItem TmpItem = m_pClient->m_pMenus->m_lGhosts[i];
if(TmpItem.m_ID == -1)
{
char aFile[256];
str_format(aFile, sizeof(aFile), "ghosts/%s", TmpItem.m_aFilename);
Storage()->RemoveFile(aFile, IStorage::TYPE_SAVE);
m_pClient->m_pMenus->m_lGhosts.remove_index(i);
break; // TODO: remove other ghosts?
}
}
// add new ghost to ghost list
CMenus::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 = m_BestTime;
Item.m_Active = true;
Item.m_ID = -1;
m_pClient->m_pMenus->m_lGhosts.add(Item);
}
bool CGhost::GetHeader(IOHANDLE *pFile, CGhostHeader *pHeader)
{
if(!*pFile)
return 0;
CGhostHeader Header;
io_read(*pFile, &Header, sizeof(Header));
*pHeader = Header;
if(mem_comp(Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0)
return 0;
if(Header.m_Version > gs_ActVersion)
return 0;
int Crc = (Header.m_aCrc[0]<<24) | (Header.m_aCrc[1]<<16) | (Header.m_aCrc[2]<<8) | (Header.m_aCrc[3]);
if(str_comp(Header.m_aMap, Client()->GetCurrentMap()) != 0 || Crc != Client()->GetCurrentMapCrc())
return 0;
return 1;
}
bool CGhost::GetInfo(const char* pFilename, CGhostHeader *pHeader)
{
char aFilename[256];
str_format(aFilename, sizeof(aFilename), "ghosts/%s", pFilename);
IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
if(!File)
return 0;
bool Check = GetHeader(&File, pHeader);
io_close(File);
return Check;
}
void CGhost::Load(const char* pFilename, int ID)
{
char aFilename[256];
str_format(aFilename, sizeof(aFilename), "ghosts/%s", pFilename);
IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
if(!File)
return;
// read header
CGhostHeader Header;
if(!GetHeader(&File, &Header))
{
io_close(File);
return;
}
if(ID == -1)
m_BestTime = Header.m_Time;
// create ghost
CGhostList Ghost;
Ghost.m_ID = ID;
// read client info
io_read(File, &Ghost.m_GhostInfo, sizeof(Ghost.m_GhostInfo));
// read data
Ghost.m_BestPath.clear();
while(1)
{
static char aCompresseddata[100*500];
static char aDecompressed[100*500];
static char aData[100*500];
unsigned char aSize[4];
if(io_read(File, aSize, sizeof(aSize)) != sizeof(aSize))
break;
int Size = (aSize[0]<<24) | (aSize[1]<<16) | (aSize[2]<<8) | aSize[3];
if(io_read(File, aCompresseddata, Size) != (unsigned)Size)
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error reading chunk");
break;
}
Size = CNetBase::Decompress(aCompresseddata, Size, aDecompressed, sizeof(aDecompressed));
if(Size < 0)
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error during network decompression");
break;
}
Size = CVariableInt::Decompress(aDecompressed, Size, aData);
if(Size < 0)
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "error during intpack decompression");
break;
}
CNetObj_Character *Tmp = (CNetObj_Character*)aData;
for(int i = 0; i < (signed)(Size/sizeof(CNetObj_Character)); i++)
{
Ghost.m_BestPath.add(*Tmp);
Tmp++;
}
}
io_close(File);
m_lGhosts.add(Ghost);
}
void CGhost::Unload(int ID)
{
for(int i = 0; i < m_lGhosts.size(); i++)
{
if(m_lGhosts[i].m_ID == ID)
{
m_lGhosts.remove_index_fast(i);
break;
}
}
}
void CGhost::ConGPlay(IConsole::IResult *pResult, void *pUserData, int ClientId)
{
((CGhost *)pUserData)->StartRender();
}
void CGhost::OnConsoleInit()
{
Console()->Register("gplay","", CFGFLAG_CLIENT, ConGPlay, this, "", -1);
}
void CGhost::OnMessage(int MsgType, void *pRawMsg)
{
if(!g_Config.m_ClRaceGhost || m_pClient->m_Snap.m_Spectate)
return;
// only for race
if(!m_pClient->m_IsRace)
return;
// check for messages from server
if(MsgType == NETMSGTYPE_SV_KILLMSG)
{
CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalCid)
{
OnReset();
}
}
else if(MsgType == NETMSGTYPE_SV_CHAT)
{
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
if(pMsg->m_Cid == -1 && m_RaceState == RACE_STARTED)
{
const char* pMessage = pMsg->m_pMessage;
int Num = 0;
while(str_comp_num(pMessage, " finished in: ", 14))
{
pMessage++;
Num++;
if(!pMessage[0])
return;
}
// store the name
char aName[64];
str_copy(aName, pMsg->m_pMessage, Num+1);
// prepare values and state for saving
int Minutes;
float Seconds;
if(!str_comp(aName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalCid].m_aName) && sscanf(pMessage, " finished in: %d minute(s) %f", &Minutes, &Seconds) == 2)
{
m_RaceState = RACE_FINISHED;
if(m_Recording)
{
float CurTime = Minutes*60 + Seconds;
if(CurTime < m_BestTime || m_BestTime == -1)
{
m_NewRecord = true;
m_BestTime = CurTime;
}
}
}
}
}
}
void CGhost::OnReset()
{
StopRecord();
StopRender();
m_RaceState = RACE_NONE;
m_NewRecord = false;
m_CurPath.clear();
m_StartRenderTick = -1;
}
void CGhost::OnMapLoad()
{
OnReset();
m_BestTime = -1;
m_lGhosts.clear();
m_pClient->m_pMenus->GhostlistPopulate();
}

View file

@ -0,0 +1,80 @@
/* (c) Rajh, Redix and Sushi. */
#ifndef GAME_CLIENT_COMPONENTS_GHOST_H
#define GAME_CLIENT_COMPONENTS_GHOST_H
#include <game/client/component.h>
class CGhost : public CComponent
{
public:
struct CGhostHeader
{
unsigned char m_aMarker[8];
unsigned char m_Version;
char m_aOwner[MAX_NAME_LENGTH];
char m_aMap[64];
unsigned char m_aCrc[4];
float m_Time;
};
private:
struct CGhostList
{
int m_ID;
CNetObj_ClientInfo m_GhostInfo;
array<CNetObj_Character> m_BestPath;
};
array<CGhostList> m_lGhosts;
array<CNetObj_Character> m_CurPath;
CNetObj_ClientInfo m_CurInfo;
int m_StartRenderTick;
int m_StartRecordTick;
int m_CurPos;
bool m_Recording;
bool m_Rendering;
int m_RaceState;
float m_BestTime;
bool m_NewRecord;
enum
{
RACE_NONE = 0,
RACE_STARTED,
RACE_FINISHED,
};
void AddInfos(CNetObj_Character Player);
void StartRecord();
void StopRecord();
void StartRender();
void StopRender();
void RenderGhost(CGhostList *pGhost);
void RenderGhostHook(CGhostList *pGhost);
bool GetHeader(IOHANDLE *pFile, CGhostHeader *pHeader);
void Save();
static void ConGPlay(IConsole::IResult *pResult, void *pUserData, int ClientId);
public:
CGhost();
virtual void OnRender();
virtual void OnConsoleInit();
virtual void OnReset();
virtual void OnMessage(int MsgType, void *pRawMsg);
virtual void OnMapLoad();
void Load(const char* pFilename, int ID);
void Unload(int ID);
bool GetInfo(const char* pFilename, CGhostHeader *pHeader);
};
#endif

View file

@ -552,6 +552,15 @@ int CMenus::RenderMenubar(CUIRect r)
static int s_ServerInfoButton=0;
if(DoButton_MenuTab(&s_ServerInfoButton, Localize("Server info"), m_ActivePage==PAGE_SERVER_INFO, &Button, CUI::CORNER_T))
NewPage = PAGE_SERVER_INFO;
if(m_pClient->m_IsRace)
{
Box.VSplitLeft(4.0f, 0, &Box);
Box.VSplitLeft(100.0f, &Button, &Box);
static int s_GhostButton=0;
if(DoButton_MenuTab(&s_GhostButton, Localize("Ghost"), m_ActivePage==PAGE_GHOST, &Button, CUI::CORNER_T))
NewPage = PAGE_GHOST;
}
Box.VSplitLeft(4.0f, 0, &Box);
Box.VSplitLeft(140.0f, &Button, &Box);
@ -788,6 +797,8 @@ int CMenus::Render()
RenderGame(MainView);
else if(m_GamePage == PAGE_SERVER_INFO)
RenderServerInfo(MainView);
else if(m_GamePage == PAGE_GHOST)
RenderGhost(MainView);
else if(m_GamePage == PAGE_CALLVOTE)
RenderServerControl(MainView);
else if(m_GamePage == PAGE_BROWSER)

View file

@ -107,6 +107,7 @@ class CMenus : public CComponent
PAGE_NEWS=1,
PAGE_GAME,
PAGE_SERVER_INFO,
PAGE_GHOST,
PAGE_CALLVOTE,
PAGE_INTERNET,
PAGE_LAN,
@ -180,16 +181,16 @@ class CMenus : public CComponent
str_comp_filenames(m_aFilename, Other.m_aFilename) < 0; }
};
sorted_array<CDemoItem> m_lDemos;
char m_aCurrentDemoFolder[256];
int m_DemolistSelectedIndex;
bool m_DemolistSelectedIsDir;
int m_DemolistStorageType;
void DemolistOnUpdate(bool Reset);
void DemolistPopulate();
static void DemolistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
static void GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
// found in menus.cpp
int Render();
//void render_background();
@ -207,6 +208,7 @@ class CMenus : public CComponent
void RenderServerControl(CUIRect MainView);
void RenderServerControlKick(CUIRect MainView);
void RenderServerControlServer(CUIRect MainView);
void RenderGhost(CUIRect MainView);
void RenderInGameBrowser(CUIRect MainView);
// found in menus_browser.cpp
@ -250,5 +252,25 @@ public:
//DDRace
int DoButton_CheckBox_DontCare(const void *pID, const char *pText, int Checked, const CUIRect *pRect);
sorted_array<CDemoItem> m_lDemos;
void DemolistPopulate();
// ghost
struct CGhostItem
{
char m_aFilename[256];
char m_aPlayer[MAX_NAME_LENGTH];
float m_Time;
bool m_Active;
int m_ID;
bool operator<(const CGhostItem &Other) { return m_Time < Other.m_Time; }
};
sorted_array<CGhostItem> m_lGhosts;
void GhostlistPopulate();
};
#endif

View file

@ -20,6 +20,10 @@
#include "menus.h"
#include "motd.h"
#include "voting.h"
#include <engine/keys.h>
#include <engine/graphics.h>
#include <engine/storage.h>
#include "ghost.h"
void CMenus::RenderGame(CUIRect MainView)
{
@ -514,3 +518,298 @@ void CMenus::RenderInGameBrowser(CUIRect MainView)
RenderServerbrowser(MainView);
return;
}
// ghost stuff
void CMenus::GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser)
{
CMenus *pSelf = (CMenus *)pUser;
int Length = str_length(pName);
if((pName[0] == '.' && (pName[1] == 0 ||
(pName[1] == '.' && pName[2] == 0))) ||
(!IsDir && (Length < 4 || str_comp(pName+Length-4, ".gho"))))
return;
CGhost::CGhostHeader Header;
if(!pSelf->m_pClient->m_pGhost->GetInfo(pName, &Header))
return;
CGhostItem Item;
str_copy(Item.m_aFilename, pName, sizeof(Item.m_aFilename));
str_copy(Item.m_aPlayer, Header.m_aOwner, sizeof(Item.m_aPlayer));
Item.m_Time = Header.m_Time;
Item.m_Active = false;
Item.m_ID = pSelf->m_lGhosts.size();
pSelf->m_lGhosts.add(Item);
}
void CMenus::GhostlistPopulate()
{
m_lGhosts.clear();
Storage()->ListDirectory(IStorage::TYPE_ALL, "ghosts", GhostlistFetchCallback, this);
int OwnTime = -1;
int Own = -1;
for(int i = 0; i < m_lGhosts.size(); i++)
{
if(str_comp(m_lGhosts[i].m_aPlayer, g_Config.m_PlayerName) == 0 && (OwnTime == -1 || m_lGhosts[i].m_Time < OwnTime))
{
OwnTime = m_lGhosts[i].m_Time;
Own = i;
}
}
if(Own != -1)
{
m_lGhosts[Own].m_ID = -1;
m_lGhosts[Own].m_Active = true;
m_pClient->m_pGhost->Load(m_lGhosts[Own].m_aFilename, -1);
}
}
void CMenus::RenderGhost(CUIRect MainView)
{
// render background
RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B|CUI::CORNER_TL, 10.0f);
MainView.HSplitTop(10.0f, 0, &MainView);
MainView.HSplitBottom(5.0f, &MainView, 0);
MainView.VSplitLeft(5.0f, 0, &MainView);
MainView.VSplitRight(5.0f, &MainView, 0);
CUIRect Headers, Status;
CUIRect View = MainView;
View.HSplitTop(17.0f, &Headers, &View);
View.HSplitBottom(28.0f, &View, &Status);
// split of the scrollbar
RenderTools()->DrawUIRect(&Headers, vec4(1,1,1,0.25f), CUI::CORNER_T, 5.0f);
Headers.VSplitRight(20.0f, &Headers, 0);
struct CColumn
{
int m_Id;
CLocConstString m_Caption;
float m_Width;
CUIRect m_Rect;
CUIRect m_Spacer;
};
enum
{
COL_ACTIVE=0,
COL_NAME,
COL_TIME,
};
static CColumn s_aCols[] = {
{-1, " ", 2.0f, {0}, {0}},
{COL_ACTIVE, " ", 30.0f, {0}, {0}},
{COL_NAME, "Name", 300.0f, {0}, {0}},
{COL_TIME, "Time", 200.0f, {0}, {0}},
};
int NumCols = sizeof(s_aCols)/sizeof(CColumn);
// do layout
for(int i = 0; i < NumCols; i++)
{
Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers);
if(i+1 < NumCols)
Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers);
}
// do headers
for(int i = 0; i < NumCols; i++)
DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, 0, &s_aCols[i].m_Rect);
RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.15f), 0, 0);
CUIRect Scroll;
View.VSplitRight(15, &View, &Scroll);
int NumGhosts = m_lGhosts.size();
int Num = (int)(View.h/s_aCols[0].m_Rect.h) + 1;
static int s_ScrollBar = 0;
static float s_ScrollValue = 0;
Scroll.HMargin(5.0f, &Scroll);
s_ScrollValue = DoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue);
int ScrollNum = NumGhosts-Num+1;
if(ScrollNum > 0)
{
if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
s_ScrollValue -= 1.0f/ScrollNum;
if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
s_ScrollValue += 1.0f/ScrollNum;
}
else
ScrollNum = 0;
static int s_SelectedIndex = 0;
for(int i = 0; i < m_NumInputEvents; i++)
{
int NewIndex = -1;
if(m_aInputEvents[i].m_Flags&IInput::FLAG_PRESS)
{
if(m_aInputEvents[i].m_Key == KEY_DOWN) NewIndex = s_SelectedIndex + 1;
if(m_aInputEvents[i].m_Key == KEY_UP) NewIndex = s_SelectedIndex - 1;
}
if(NewIndex > -1 && NewIndex < NumGhosts)
{
//scroll
float IndexY = View.y - s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h + NewIndex*s_aCols[0].m_Rect.h;
int Scroll = View.y > IndexY ? -1 : View.y+View.h < IndexY+s_aCols[0].m_Rect.h ? 1 : 0;
if(Scroll)
{
if(Scroll < 0)
{
int NumScrolls = (View.y-IndexY+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h;
s_ScrollValue -= (1.0f/ScrollNum)*NumScrolls;
}
else
{
int NumScrolls = (IndexY+s_aCols[0].m_Rect.h-(View.y+View.h)+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h;
s_ScrollValue += (1.0f/ScrollNum)*NumScrolls;
}
}
s_SelectedIndex = NewIndex;
}
}
if(s_ScrollValue < 0) s_ScrollValue = 0;
if(s_ScrollValue > 1) s_ScrollValue = 1;
// set clipping
UI()->ClipEnable(&View);
CUIRect OriginalView = View;
View.y -= s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h;
for (int i = 0; i < NumGhosts; i++)
{
const CGhostItem *pItem = &m_lGhosts[i];
CUIRect Row;
CUIRect SelectHitBox;
View.HSplitTop(17.0f, &Row, &View);
SelectHitBox = Row;
// make sure that only those in view can be selected
if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h)
{
if(i == s_SelectedIndex)
{
CUIRect r = Row;
r.Margin(1.5f, &r);
RenderTools()->DrawUIRect(&r, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 4.0f);
}
// clip the selection
if(SelectHitBox.y < OriginalView.y) // top
{
SelectHitBox.h -= OriginalView.y-SelectHitBox.y;
SelectHitBox.y = OriginalView.y;
}
else if(SelectHitBox.y+SelectHitBox.h > OriginalView.y+OriginalView.h) // bottom
SelectHitBox.h = OriginalView.y+OriginalView.h-SelectHitBox.y;
if(UI()->DoButtonLogic(pItem, "", 0, &SelectHitBox))
{
s_SelectedIndex = i;
}
if(UI()->MouseInside(&Row) && Input()->MouseDoubleClick())
{
if(m_lGhosts[s_SelectedIndex].m_Active)
{
m_lGhosts[s_SelectedIndex].m_Active = false;
m_pClient->m_pGhost->Unload(m_lGhosts[s_SelectedIndex].m_ID);
}
else
{
m_lGhosts[s_SelectedIndex].m_Active = true;
m_pClient->m_pGhost->Load(m_lGhosts[s_SelectedIndex].m_aFilename, m_lGhosts[s_SelectedIndex].m_ID);
}
}
}
for(int c = 0; c < NumCols; c++)
{
CUIRect Button;
Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y;
Button.h = Row.h;
Button.w = s_aCols[c].m_Rect.w;
int Id = s_aCols[c].m_Id;
if(Id == COL_ACTIVE)
{
if(pItem->m_Active)
{
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id);
Graphics()->QuadsBegin();
RenderTools()->SelectSprite(SPRITE_OOP + 7);
IGraphics::CQuadItem QuadItem(Button.x+Button.w/2, Button.y+Button.h/2, 20.0f, 20.0f);
Graphics()->QuadsDraw(&QuadItem, 1);
Graphics()->QuadsEnd();
}
}
else if(Id == COL_NAME)
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w;
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "%s%s", pItem->m_aPlayer, (pItem->m_ID == -1)?" (own)":"");
TextRender()->TextEx(&Cursor, aBuf, -1);
}
else if(Id == COL_TIME)
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w;
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%02d:%06.3f", (int)pItem->m_Time/60, pItem->m_Time-((int)pItem->m_Time/60*60));
TextRender()->TextEx(&Cursor, aBuf, -1);
}
}
}
UI()->ClipDisable();
RenderTools()->DrawUIRect(&Status, vec4(1,1,1,0.25f), CUI::CORNER_B, 5.0f);
Status.Margin(5.0f, &Status);
CUIRect Button;
Status.VSplitRight(120.0f, &Status, &Button);
static int s_GhostButton = 0;
if(m_lGhosts[s_SelectedIndex].m_Active)
{
if(DoButton_Menu(&s_GhostButton, Localize("Deactivate"), 0, &Button))
{
m_lGhosts[s_SelectedIndex].m_Active = false;
m_pClient->m_pGhost->Unload(m_lGhosts[s_SelectedIndex].m_ID);
}
}
else
{
if(DoButton_Menu(&s_GhostButton, Localize("Activate"), 0, &Button))
{
m_lGhosts[s_SelectedIndex].m_Active = true;
m_pClient->m_pGhost->Load(m_lGhosts[s_SelectedIndex].m_aFilename, m_lGhosts[s_SelectedIndex].m_ID);
}
}
}

View file

@ -0,0 +1,202 @@
/* (c) Redix and Sushi */
#include <stdio.h>
#include <engine/shared/config.h>
#include <engine/serverbrowser.h>
#include <engine/storage.h>
#include "menus.h"
#include "race_demo.h"
CRaceDemo::CRaceDemo()
{
m_RaceState = RACE_NONE;
m_RecordStopTime = 0;
m_Time = 0;
m_DemoStartTick = 0;
}
void CRaceDemo::OnRender()
{
if(!g_Config.m_ClAutoRaceRecord || !m_pClient->m_Snap.m_pGameobj || m_pClient->m_Snap.m_Spectate)
{
m_Active = m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalCid].m_Active;
return;
}
// only for race
if(!m_pClient->m_IsRace)
return;
vec2 PlayerPos = m_pClient->m_LocalCharacterPos;
// start the demo
if(((!m_Active && m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalCid].m_Active)) && m_DemoStartTick < Client()->GameTick())
{
if(m_RaceState == RACE_STARTED)
OnReset();
m_pMap = Client()->RaceRecordStart("tmp");
m_DemoStartTick = Client()->GameTick() + Client()->GameTickSpeed();
m_RaceState = RACE_STARTED;
}
// stop the demo
if(m_RaceState == RACE_FINISHED && m_RecordStopTime < Client()->GameTick() && m_Time > 0)
{
CheckDemo();
OnReset();
}
m_Active = m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalCid].m_Active;
}
void CRaceDemo::OnReset()
{
if(Client()->DemoIsRecording())
Client()->RaceRecordStop();
char aFilename[512];
str_format(aFilename, sizeof(aFilename), "demos/%s_tmp.demo", m_pMap);
Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE);
m_Time = 0;
m_RaceState = RACE_NONE;
m_RecordStopTime = 0;
m_DemoStartTick = 0;
}
void CRaceDemo::OnShutdown()
{
if(Client()->DemoIsRecording())
Client()->RaceRecordStop();
char aFilename[512];
str_format(aFilename, sizeof(aFilename), "demos/%s_tmp.demo", m_pMap);
Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE);
}
void CRaceDemo::OnMessage(int MsgType, void *pRawMsg)
{
if(!g_Config.m_ClAutoRaceRecord || m_pClient->m_Snap.m_Spectate)
return;
// only for race
if(!m_pClient->m_IsRace)
return;
// check for messages from server
if(MsgType == NETMSGTYPE_SV_KILLMSG)
{
CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalCid && m_RaceState == RACE_FINISHED)
{
// check for new record
CheckDemo();
OnReset();
}
}
else if(MsgType == NETMSGTYPE_SV_CHAT)
{
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
if(pMsg->m_Cid == -1 && m_RaceState == RACE_STARTED)
{
const char* pMessage = pMsg->m_pMessage;
int Num = 0;
while(str_comp_num(pMessage, " finished in: ", 14))
{
pMessage++;
Num++;
if(!pMessage[0])
return;
}
// store the name
char aName[64];
str_copy(aName, pMsg->m_pMessage, Num+1);
// prepare values and state for saving
int Minutes;
float Seconds;
if(!str_comp(aName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalCid].m_aName) && sscanf(pMessage, " finished in: %d minute(s) %f", &Minutes, &Seconds) == 2)
{
m_RaceState = RACE_FINISHED;
m_RecordStopTime = Client()->GameTick() + Client()->GameTickSpeed();
m_Time = Minutes*60 + Seconds;
}
}
}
}
void CRaceDemo::CheckDemo()
{
// stop the demo recording
Client()->RaceRecordStop();
char aTmpDemoName[128];
str_format(aTmpDemoName, sizeof(aTmpDemoName), "%s_tmp", m_pMap);
// loop through demo files
m_pClient->m_pMenus->DemolistPopulate();
for(int i = 0; i < m_pClient->m_pMenus->m_lDemos.size(); i++)
{
if(!str_comp_num(m_pClient->m_pMenus->m_lDemos[i].m_aName, m_pMap, str_length(m_pMap)) && str_comp_num(m_pClient->m_pMenus->m_lDemos[i].m_aName, aTmpDemoName, str_length(aTmpDemoName)))
{
const char *pDemo = m_pClient->m_pMenus->m_lDemos[i].m_aName;
// set cursor
pDemo += str_length(m_pMap)+1;
float DemoTime = str_tofloat(pDemo);
if(m_Time < DemoTime)
{
// save new record
SaveDemo(m_pMap);
// delete old demo
char aFilename[512];
str_format(aFilename, sizeof(aFilename), "demos/%s.demo", m_pClient->m_pMenus->m_lDemos[i].m_aName);
Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE);
}
m_Time = 0;
return;
}
}
// save demo if there is none
SaveDemo(m_pMap);
m_Time = 0;
}
void CRaceDemo::SaveDemo(const char* pDemo)
{
char aNewFilename[512];
char aOldFilename[512];
if(g_Config.m_ClDemoName)
{
char aPlayerName[MAX_NAME_LENGTH];
str_copy(aPlayerName, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalCid].m_aName, sizeof(aPlayerName));
// check the player name
for(int i = 0; i < MAX_NAME_LENGTH; i++)
{
if(!aPlayerName[i])
break;
if(aPlayerName[i] == '\\' || aPlayerName[i] == '/' || aPlayerName[i] == '|' || aPlayerName[i] == ':' || aPlayerName[i] == '*' || aPlayerName[i] == '?' || aPlayerName[i] == '<' || aPlayerName[i] == '>' || aPlayerName[i] == '"')
aPlayerName[i] = '%';
str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%5.2f_%s.demo", pDemo, m_Time, aPlayerName);
}
}
else
str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%5.2f.demo", pDemo, m_Time);
str_format(aOldFilename, sizeof(aOldFilename), "demos/%s_tmp.demo", m_pMap);
Storage()->RenameFile(aOldFilename, aNewFilename, IStorage::TYPE_SAVE);
}

View file

@ -0,0 +1,40 @@
/* (c) Redix and Sushi */
#ifndef GAME_CLIENT_COMPONENTS_RACE_DEMO_H
#define GAME_CLIENT_COMPONENTS_RACE_DEMO_H
#include <game/client/gameclient.h>
#include <game/client/component.h>
class CRaceDemo : public CComponent
{
int m_RecordStopTime;
int m_DemoStartTick;
float m_Time;
const char *m_pMap;
bool m_Active;
public:
int m_RaceState;
enum
{
RACE_NONE = 0,
RACE_STARTED,
RACE_FINISHED,
};
CRaceDemo();
virtual void OnReset();
virtual void OnRender();
virtual void OnShutdown();
virtual void OnMessage(int MsgType, void *pRawMsg);
void CheckDemo();
void SaveDemo(const char* pDemo);
};
#endif

View file

@ -44,6 +44,8 @@
#include "components/skins.h"
#include "components/sounds.h"
#include "components/voting.h"
#include "components/race_demo.h"
#include "components/ghost.h"
#include <base/tl/sorted_array.h>
CGameClient g_GameClient;
@ -69,6 +71,8 @@ static CSounds gs_Sounds;
static CEmoticon gs_Emoticon;
static CDamageInd gsDamageInd;
static CVoting gs_Voting;
static CRaceDemo gs_RaceDemo;
static CGhost gs_Ghost;
static CPlayers gs_Players;
static CNamePlates gs_NamePlates;
@ -141,6 +145,9 @@ void CGameClient::OnConsoleInit()
m_pMapimages = &::gs_MapImages;
m_pVoting = &::gs_Voting;
m_pScoreboard = &::gs_Scoreboard;
m_pRaceDemo = &::gs_RaceDemo;
m_pGhost = &::gs_Ghost;
// make a list of all the systems, make sure to add them in the corrent render order
m_All.Add(m_pSkins);
@ -153,11 +160,13 @@ void CGameClient::OnConsoleInit()
m_All.Add(m_pSounds);
m_All.Add(m_pVoting);
m_All.Add(m_pParticles); // doesn't render anything, just updates all the particles
m_All.Add(m_pRaceDemo);
m_All.Add(&gs_MapLayersBackGround); // first to render
m_All.Add(&m_pParticles->m_RenderTrail);
m_All.Add(&gs_Items);
m_All.Add(&gs_Players);
m_All.Add(m_pGhost);
m_All.Add(&gs_MapLayersForeGround);
m_All.Add(&m_pParticles->m_RenderExplosions);
m_All.Add(&gs_NamePlates);
@ -308,6 +317,7 @@ void CGameClient::OnInit()
m_ServerMode = SERVERMODE_PURE;
m_IsRace = false;
m_DDRaceMsgSent = false;
}
@ -397,6 +407,7 @@ void CGameClient::OnReset()
m_All.m_paComponents[i]->OnReset();
m_Teams.Reset();
m_IsRace = false;
m_DDRaceMsgSent = false;
}
@ -623,7 +634,11 @@ void CGameClient::OnStateChange(int NewState, int OldState)
m_All.m_paComponents[i]->OnStateChange(NewState, OldState);
}
void CGameClient::OnShutdown() {}
void CGameClient::OnShutdown()
{
m_pRaceDemo->OnShutdown();
}
void CGameClient::OnEnterGame() {}
void CGameClient::OnGameOver()
@ -889,6 +904,8 @@ void CGameClient::OnNewSnapshot()
CNetMsg_Cl_IsDDRace Msg;
Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
m_DDRaceMsgSent = true;
if(!m_IsRace)
m_IsRace = true;
}
}

View file

@ -218,6 +218,12 @@ public:
class CMapImages *m_pMapimages;
class CVoting *m_pVoting;
class CScoreboard *m_pScoreboard;
//DDRace
//TODO: This is ugly
class CRaceDemo *m_pRaceDemo;
class CGhost *m_pGhost;
bool m_IsRace;
};

View file

@ -165,7 +165,7 @@ void CRenderTools::DrawUIRect(const CUIRect *r, vec4 Color, int Corners, float R
Graphics()->QuadsEnd();
}
void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos)
void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, bool Alpha)
{
vec2 Direction = Dir;
vec2 Position = Pos;
@ -192,7 +192,10 @@ void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote
Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle*pi*2);
// draw body
Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, 1.0f);
if(Alpha)
Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, pInfo->m_ColorBody.a);
else
Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, 1.0f);
vec2 BodyPos = Position + vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y)*AnimScale;
SelectSprite(OutLine?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, 0);
IGraphics::CQuadItem QuadItem(BodyPos.x, BodyPos.y, BaseSize, BaseSize);
@ -250,8 +253,11 @@ void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote
if(Indicate)
cs = 0.5f;
}
Graphics()->SetColor(pInfo->m_ColorFeet.r*cs, pInfo->m_ColorFeet.g*cs, pInfo->m_ColorFeet.b*cs, 1.0f);
if(Alpha)
Graphics()->SetColor(pInfo->m_ColorFeet.r*cs, pInfo->m_ColorFeet.g*cs, pInfo->m_ColorFeet.b*cs, pInfo->m_ColorFeet.a*cs);
else
Graphics()->SetColor(pInfo->m_ColorFeet.r*cs, pInfo->m_ColorFeet.g*cs, pInfo->m_ColorFeet.b*cs, 1.0f);
IGraphics::CQuadItem QuadItem(Position.x+pFoot->m_X*AnimScale, Position.y+pFoot->m_Y*AnimScale, w, h);
Graphics()->QuadsDraw(&QuadItem, 1);
}

View file

@ -66,7 +66,7 @@ public:
void RenderTilemapGenerateSkip(class CLayers *pLayers);
// object render methods (gc_render_obj.cpp)
void RenderTee(class CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos);
void RenderTee(class CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, bool Alpha=false);
// map render methods (gc_render_map.cpp)
static void RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, float Time, float *pResult);