diff --git a/CMakeLists.txt b/CMakeLists.txt index d26e8d06..56b04278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1306,6 +1306,7 @@ set_src(GAME_SHARED GLOB src/game layers.cpp layers.h mapitems.h + resource.h tuning.h variables.h version.h @@ -1415,6 +1416,8 @@ if(CLIENT) components/particles.h components/players.cpp components/players.h + components/resource.cpp + components/resource.h components/scoreboard.cpp components/scoreboard.h components/skins.cpp @@ -1559,6 +1562,8 @@ set_src(GAME_SERVER GLOB_RECURSE src/game/server gameworld.h player.cpp player.h + resource.cpp + resource.h ) set(GAME_GENERATED_SERVER src/generated/server_data.cpp diff --git a/datasrc/datatypes.py b/datasrc/datatypes.py index 6eb340d7..4e4597b2 100644 --- a/datasrc/datatypes.py +++ b/datasrc/datatypes.py @@ -304,6 +304,33 @@ def emit_unpack(self): def emit_unpack_check(self): return [] + +class NetRawData(NetVariable): + def emit_declaration(self): + return [f"const void *{self.name};", f"int {self.name}Size;"] + def emit_unpack(self): + return [f"pMsg->{self.name}Size = pUnpacker->GetInt();", f"pMsg->{self.name} = pUnpacker->GetRaw(pMsg->{self.name}Size);"] + def emit_pack(self): + return [f"pPacker->AddInt({self.name}Size);", f"pPacker->AddRaw({self.name}, {self.name}Size);"] + +class NetRawDataFixed(NetVariable): + def __init__(self, name, data_size, default=None): + NetVariable.__init__(self,name,default=default) + self.data_size = data_size + def emit_declaration(self): + return [f"const void *{self.name};"] + def emit_unpack(self): + return [f"pMsg->{self.name} = pUnpacker->GetRaw({self.data_size});"] + def emit_pack(self): + return [f"pPacker->AddRaw({self.name}, {self.data_size});"] + +class NetRawDataFixedSnapshot(NetVariable): + def __init__(self, name, data_size, default=None): + NetVariable.__init__(self,name,default=default) + self.data_size = data_size + def emit_declaration(self): + return [f"unsigned char {self.name}[{self.data_size}];"] + class NetString(NetVariable): def emit_declaration(self): return ["const char *%s;"%self.name] diff --git a/datasrc/network.py b/datasrc/network.py index c6780fca..e4c99b2f 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -22,8 +22,12 @@ GamePredictionFlags = Flags("GAMEPREDICTIONFLAG", ["EVENT", "INPUT"]) +Resources = Enum("RESOURCE", ["SOUND", "IMAGE"]) + RawHeader = ''' +#include +#include #include #include @@ -75,6 +79,7 @@ Votes, ChatModes, GameMsgIDs, + Resources, ] Flags = [ @@ -274,7 +279,32 @@ NetObjectEx("GameDataPrediction", "game-data-prediction@netobj.teeworlds.wiki", [ NetFlag("m_PredictionFlags", GamePredictionFlags), - ]) + ]), + + NetEventEx("CustomSoundWorld:Common", "custom-sound-world@netevent.teeworlds.wiki", [ + NetRawDataFixedSnapshot("m_Uuid", "sizeof(Uuid)"), + ]), + + NetObjectEx("CustomObject", "custom-entity@netobj.teeworlds.wiki", [ + NetRawDataFixedSnapshot("m_Uuid", "sizeof(Uuid)"), + NetIntAny("m_X"), + NetIntAny("m_Y"), + ]), + + NetObjectEx("CustomImageEntity:CustomObject", "custom-image-entity@netobj.teeworlds.wiki", [ + NetIntAny("m_Angle"), + NetIntAny("m_Width"), + NetIntAny("m_Height"), + ]), + + NetObjectEx("CustomSoundEntity:CustomObject", "custom-sound-entity@netobj.teeworlds.wiki", [ + NetIntAny("m_Vol"), + NetIntAny("m_Offset"), + ]), + + NetObjectEx("CharacterGameTexture", "character-game-texture@netobj.teeworlds.wiki", [ + NetRawDataFixedSnapshot("m_Uuid", "sizeof(Uuid)"), + ]), ] Messages = [ @@ -490,4 +520,21 @@ NetStringStrict("m_Arguments") ]), + NetMessageEx("Sv_CustomResource", "custom-resource@netmsg.teeworlds.wiki", [ + NetRawDataFixed("m_Uuid", "sizeof(Uuid)"), + NetEnum("m_Type", Resources), + NetStringStrict("m_Name"), + NetIntAny("m_Crc"), + NetIntRange("m_ChunkPerRequest", 0, 'max_int'), + NetIntRange("m_Size", 0, 'max_int'), + NetRawDataFixed("m_Sha256", "sizeof(SHA256_DIGEST)"), + ]), + NetMessageEx("Sv_CustomResourceData", "custom-resource-data@netmsg.teeworlds.wiki", [ + NetRawDataFixed("m_Uuid", "sizeof(Uuid)"), + NetIntRange("m_ChunkIndex", 0, 'max_int'), + NetRawData("m_Data"), + ]), + NetMessageEx("Cl_ReqeustCustomResource", "request-custom-resource@netmsg.teeworlds.wiki", [ + NetRawDataFixed("m_Uuid", "sizeof(Uuid)"), + ]), ] diff --git a/src/base/uuid.h b/src/base/uuid.h index 1392a05c..a4218780 100644 --- a/src/base/uuid.h +++ b/src/base/uuid.h @@ -46,6 +46,16 @@ inline bool operator!=(const Uuid &that, const Uuid &other) { return !(that == other); } + +inline bool operator<(const Uuid &that, const Uuid &other) +{ + return uuid_comp(that, other) < 0; +} + +inline bool operator<=(const Uuid &that, const Uuid &other) +{ + return uuid_comp(that, other) < 1; +} #endif #endif // BASE_UUID_H diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 442dcc0e..f12041ad 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -332,11 +332,10 @@ void CSound::RateConvert(int SampleID) pSample->m_NumFrames = NumFrames; } -ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) +ISound::CSampleHandle CSound::LoadOpusMemory(const char *pContext, const unsigned char *pData, int DataSize) { CSample *pSample; int SampleID = -1; - // don't waste memory on sound when we are stress testing #ifdef CONF_DEBUG if(m_pConfig->m_DbgStress) @@ -349,23 +348,16 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) if(!m_pStorage) return CSampleHandle(); - - lock_wait(m_SoundLock); - unsigned char *pFileData; - unsigned FileSize; - if(!m_pStorage->ReadFile(pFilename, IStorage::TYPE_ALL, (void **) &pFileData, &FileSize)) - { - dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); - lock_unlock(m_SoundLock); + + if(!pContext || !pData || DataSize <= 0) return CSampleHandle(); - } + lock_wait(m_SoundLock); int Error = 0; - OggOpusFile *pOpusFile = op_open_memory(pFileData, FileSize, &Error); + OggOpusFile *pOpusFile = op_open_memory(pData, DataSize, &Error); if(!pOpusFile) { - dbg_msg("sound/opus", "failed to open opus file '%s': error %d", pFilename, Error); - mem_free(pFileData); + dbg_msg("sound/opus", "failed to open opus '%s': error %d", pContext, Error); lock_unlock(m_SoundLock); return CSampleHandle(); } @@ -374,9 +366,8 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) ; if(NumChannels < 0 || NumChannels > 2) { - dbg_msg("sound/opus", "only mono/stereo supported. channels=%d, file='%s'", NumChannels, pFilename); + dbg_msg("sound/opus", "only mono/stereo supported. channels=%d, file='%s'", NumChannels, pContext); op_free(pOpusFile); - mem_free(pFileData); lock_unlock(m_SoundLock); return CSampleHandle(); } @@ -384,9 +375,8 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) int TotalSamples = op_pcm_total(pOpusFile, -1); if(TotalSamples < 0) { - dbg_msg("sound/opus", "failed to get number of samples, error %d. file='%s'", TotalSamples, pFilename); + dbg_msg("sound/opus", "failed to get number of samples, error %d. file='%s'", TotalSamples, pContext); op_free(pOpusFile); - mem_free(pFileData); lock_unlock(m_SoundLock); return CSampleHandle(); } @@ -395,7 +385,6 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) if(SampleID < 0) { op_free(pOpusFile); - mem_free(pFileData); lock_unlock(m_SoundLock); return CSampleHandle(); } @@ -404,7 +393,6 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) if(!pSample->m_pData) { op_free(pOpusFile); - mem_free(pFileData); lock_unlock(m_SoundLock); return CSampleHandle(); } @@ -417,8 +405,7 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) { mem_free(pSample->m_pData); op_free(pOpusFile); - mem_free(pFileData); - dbg_msg("sound/opus", "op_read error %d at %d. file='%s'", Read, Pos, pFilename); + dbg_msg("sound/opus", "op_read error %d at %d. file='%s'", Read, Pos, pContext); return CSampleHandle(); } else if(Read == 0) // EOF @@ -428,7 +415,6 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) } op_free(pOpusFile); - mem_free(pFileData); pSample->m_Channels = NumChannels; pSample->m_Rate = 48000; @@ -438,13 +424,56 @@ ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) pSample->m_PausedAt = 0; if(m_pConfig->m_Debug) - dbg_msg("sound/opus", "loaded %s (%d samples, %d channels)", pFilename, TotalSamples, NumChannels); + dbg_msg("sound/opus", "loaded %s (%d samples, %d channels)", pContext, TotalSamples, NumChannels); RateConvert(SampleID); lock_unlock(m_SoundLock); return CreateSampleHandle(SampleID); } +ISound::CSampleHandle CSound::LoadOpus(const char *pFilename) +{ + // don't waste memory on sound when we are stress testing +#ifdef CONF_DEBUG + if(m_pConfig->m_DbgStress) + return CSampleHandle(); +#endif + + // no need to load sound when we are running with no sound + if(!m_SoundEnabled) + return CSampleHandle(); + + if(!m_pStorage) + return CSampleHandle(); + + unsigned char *pFileData; + unsigned FileSize; + if(!m_pStorage->ReadFile(pFilename, IStorage::TYPE_ALL, (void **) &pFileData, &FileSize)) + { + dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); + return CSampleHandle(); + } + + ISound::CSampleHandle Sample = LoadOpusMemory(pFilename, pFileData, FileSize); + mem_free(pFileData); + return Sample; +} + +bool CSound::UnloadSample(CSampleHandle *pSampleID) +{ + if(!pSampleID) + return false; + + if(m_aSamples[pSampleID->Id()].m_pData) + { + mem_free(m_aSamples[pSampleID->Id()].m_pData); + m_aSamples[pSampleID->Id()].m_pData = 0; + pSampleID->Invalidate(); + return true; + } + return false; +} + void CSound::SetListenerPos(float x, float y) { m_CenterX = (int) x; diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 2a3e2bf1..d3d3b0b2 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -24,7 +24,9 @@ class CSound : public IEngineSound virtual bool IsSoundEnabled() { return m_SoundEnabled != 0; } + virtual CSampleHandle LoadOpusMemory(const char *pContext, const unsigned char *pData, int DataSize); virtual CSampleHandle LoadOpus(const char *pFilename); + virtual bool UnloadSample(CSampleHandle *pSampleID); virtual void SetListenerPos(float x, float y); virtual void SetChannelVolume(int ChannelID, float Vol); diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index cf880dad..ebc15d1d 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -69,6 +69,7 @@ class CStorage : public IStorage fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "maps", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "downloadedmaps", aPath, sizeof(aPath))); + fs_makedir(GetPath(TYPE_SAVE, "downloadedres", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "skins", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "editor", aPath, sizeof(aPath))); } diff --git a/src/engine/sound.h b/src/engine/sound.h index 7a95f4ea..e4a193e7 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -28,11 +28,14 @@ class ISound : public IInterface bool IsValid() const { return Id() >= 0; } int Id() const { return m_Id; } + void Invalidate() { m_Id = -1; } }; virtual bool IsSoundEnabled() = 0; + virtual CSampleHandle LoadOpusMemory(const char *pContext, const unsigned char *pData, int DataSize) = 0; virtual CSampleHandle LoadOpus(const char *pFilename) = 0; + virtual bool UnloadSample(CSampleHandle *pSampleID) = 0; virtual void SetChannelVolume(int ChannelID, float Volume) = 0; virtual void SetListenerPos(float x, float y) = 0; diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index cbc1b879..cac1b5af 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -45,7 +45,7 @@ void CPlayers::RenderHook( // draw hook if(Prev.m_HookState > 0 && Player.m_HookState > 0) { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); + Graphics()->TextureSet(m_pClient->m_Snap.m_aCharacters[ClientID].m_GameTexture); Graphics()->QuadsBegin(); vec2 HookPos; @@ -195,7 +195,7 @@ void CPlayers::RenderPlayer( // draw gun if(Player.m_Weapon >= 0) { - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); + Graphics()->TextureSet(m_pClient->m_Snap.m_aCharacters[ClientID].m_GameTexture); Graphics()->QuadsBegin(); Graphics()->QuadsSetRotation(State.GetAttach()->m_Angle * pi * 2 + Angle); diff --git a/src/game/client/components/resource.cpp b/src/game/client/components/resource.cpp new file mode 100644 index 00000000..27ef14c3 --- /dev/null +++ b/src/game/client/components/resource.cpp @@ -0,0 +1,229 @@ +#include +#include +#include + +#include "resource.h" +#include "sounds.h" + +static void FormatResourcePath(char *pBuffer, int BufferSize, const char *pName, bool Temp, const SHA256_DIGEST *pSha256, const unsigned int *pCrc) +{ + char aSha256[SHA256_MAXSTRSIZE]; + sha256_str(*pSha256, aSha256, sizeof(aSha256)); + if(Temp) + str_format(pBuffer, BufferSize, "downloadedres/%s_%08x%s.twres.%d.tmp", pName, *pCrc, aSha256, pid()); + else + str_format(pBuffer, BufferSize, "downloadedres/%s_%08x%s.twres", pName, *pCrc, aSha256); +} + +CClientResManager::CClientResManager() +{ + m_lResources.clear(); +} + +void CClientResManager::RequestDownload(const Uuid *pRequest) +{ + if(!pRequest) + return; + + CNetMsg_Cl_ReqeustCustomResource Msg; + Msg.m_Uuid = pRequest; + Client()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH | MSGFLAG_NORECORD); +} + +bool CClientResManager::LoadResource(CClientResource *pResource) +{ + IOHANDLE File = Storage()->OpenFile(pResource->m_aPath, IOFLAG_READ, IStorage::TYPE_SAVE, 0, 0, CDataFileReader::CheckSha256, &pResource->m_Sha256); + if(!File) + return false; + pResource->m_pData = (unsigned char *) mem_alloc(pResource->m_DataSize); + io_read(File, pResource->m_pData, pResource->m_DataSize); + io_close(File); + if(pResource->m_Type == RESOURCE_SOUND) + { + pResource->m_Sample = m_pClient->m_pSounds->LoadSampleMemory(pResource->m_aPath, pResource->m_pData, pResource->m_DataSize); + } + else if(pResource->m_Type == RESOURCE_IMAGE) + { + pResource->m_Texture = Graphics()->LoadTexture(pResource->m_aPath, IStorage::TYPE_SAVE, CImageInfo::FORMAT_AUTO, 0); + } + mem_free(pResource->m_pData); + pResource->m_pData = 0; + return true; +} + +void CClientResManager::RenderImageEntity(const CNetObj_CustomImageEntity *pPrev, const CNetObj_CustomImageEntity *pCur) +{ + Uuid TextureID; + mem_copy(&TextureID, pCur->m_Uuid, sizeof(Uuid)); + IGraphics::CTextureHandle Texture = GetResourceTexture(TextureID); + Texture = Texture.IsValid() ? Texture : g_pData->m_aImages[IMAGE_DEADTEE].m_Id; // fallback + vec2 Pos = mix(vec2(pPrev->m_X, pPrev->m_Y), vec2(pCur->m_X, pCur->m_Y), Client()->IntraGameTick()); + vec2 Size = mix(vec2(pPrev->m_Width, pPrev->m_Height), vec2(pCur->m_Width, pCur->m_Height), Client()->IntraGameTick()); + float Angle = mix(pPrev->m_Angle / 256.0f, pCur->m_Angle / 256.0f, Client()->IntraGameTick()); + + Graphics()->BlendNormal(); + Graphics()->TextureSet(Texture); + Graphics()->QuadsBegin(); + Graphics()->QuadsSetRotation(Angle); + IGraphics::CQuadItem QuadItem(Pos.x, Pos.y, Size.x, Size.y); + Graphics()->QuadsDraw(&QuadItem, 1); + Graphics()->QuadsEnd(); +} + +void CClientResManager::OnRender() +{ + int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); + for(int i = 0; i < Num; i++) + { + IClient::CSnapItem Item; + const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item); + + if(Item.m_Type == NETOBJTYPE_CUSTOMIMAGEENTITY) + { + const void *pPrev = Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID); + if(pPrev) + RenderImageEntity((const CNetObj_CustomImageEntity *) pPrev, (const CNetObj_CustomImageEntity *) pData); + } + } +} + +void CClientResManager::OnMessage(int MsgType, void *pRawMsg) +{ + if(MsgType == NETMSGTYPE_SV_CUSTOMRESOURCE) + { + CNetMsg_Sv_CustomResource *pMsg = (CNetMsg_Sv_CustomResource *) pRawMsg; + + // protect the player from nasty map names + for(int i = 0; pMsg->m_Name[i]; i++) + { + if(pMsg->m_Name[i] == '/' || pMsg->m_Name[i] == '\\') + { + char aBuf[IO_MAX_PATH_LENGTH + 64]; + str_format(aBuf, sizeof(aBuf), "got strange path from custom resource '%s'", pMsg->m_Name); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "resource", aBuf); + return; + } + } + + if(pMsg->m_Size <= 0) + { + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "resource", "invalid resource size"); + return; + } + + if(FindResource(*static_cast(pMsg->m_Uuid))) // there couldn't be uuid collision, if that happened, then the server-side resource name must be wrong. + { + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "resource", "invalid resource uuid"); + return; + } + CClientResource Resource; + str_copy(Resource.m_aName, pMsg->m_Name, sizeof(Resource.m_aName)); + Resource.m_Crc = pMsg->m_Crc; + Resource.m_DataSize = pMsg->m_Size; + Resource.m_Uuid = *static_cast(pMsg->m_Uuid); + Resource.m_pData = 0; + Resource.m_DownloadedSize = 0; + Resource.m_Sample = ISound::CSampleHandle(); + Resource.m_Texture = IGraphics::CTextureHandle(); + Resource.m_Type = pMsg->m_Type; + Resource.m_ChunkPerRequest = pMsg->m_ChunkPerRequest; + mem_copy(&Resource.m_Sha256, pMsg->m_Sha256, sizeof(SHA256_DIGEST)); + FormatResourcePath(Resource.m_aPath, sizeof(Resource.m_aPath), Resource.m_aName, false, &Resource.m_Sha256, &Resource.m_Crc); + FormatResourcePath(Resource.m_aTempPath, sizeof(Resource.m_aTempPath), Resource.m_aName, true, &Resource.m_Sha256, &Resource.m_Crc); + Resource.m_DownloadTemp = 0; + + int Index = m_lResources.add(Resource); + if(!LoadResource(&m_lResources[Index])) + { + m_lResources[Index].m_DownloadTemp = Storage()->OpenFile(m_lResources[Index].m_aTempPath, IOFLAG_WRITE, IStorage::TYPE_SAVE); + RequestDownload(&m_lResources[Index].m_Uuid); + } + } + else if(MsgType == NETMSGTYPE_SV_CUSTOMRESOURCEDATA) + { + CNetMsg_Sv_CustomResourceData *pMsg = (CNetMsg_Sv_CustomResourceData *) pRawMsg; + Uuid TargetResource = *static_cast(pMsg->m_Uuid); + CClientResource *pResource = FindResource(TargetResource); + if(!pResource) + return; + pResource->m_DownloadedSize += pMsg->m_DataSize; + if(pResource->m_DownloadedSize > pResource->m_DataSize) + { + io_close(pResource->m_DownloadTemp); + pResource->m_DownloadTemp = 0; + Storage()->RemoveFile(pResource->m_aTempPath, IStorage::TYPE_SAVE); + m_lResources.remove(*pResource); + return; // invalid! + } + io_write(pResource->m_DownloadTemp, pMsg->m_Data, pMsg->m_DataSize); + if(pResource->m_DownloadedSize == pResource->m_DataSize) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), Localize("Resource '%s': download complete"), pResource->m_aName); + UI()->DoToast(aBuf); + io_close(pResource->m_DownloadTemp); + pResource->m_DownloadTemp = 0; + + Storage()->RemoveFile(pResource->m_aPath, IStorage::TYPE_SAVE); + Storage()->RenameFile(pResource->m_aTempPath, pResource->m_aPath, IStorage::TYPE_SAVE); + + if(!LoadResource(pResource)) + { + Storage()->RemoveFile(pResource->m_aPath, IStorage::TYPE_SAVE); + m_lResources.remove(*pResource); + return; // invalid! + } + } + else if((pMsg->m_ChunkIndex + 1) % pResource->m_ChunkPerRequest == 0) + RequestDownload(&TargetResource); + } +} + +void CClientResManager::OnStateChange(int NewState, int OldState) +{ + if(NewState == IClient::STATE_OFFLINE) + { + if(OldState >= IClient::STATE_CONNECTING && NewState <= IClient::STATE_ONLINE) + { + for(int i = 0; i < m_lResources.size(); i++) + { + if(m_lResources[i].m_Sample.IsValid()) + m_pClient->m_pSounds->UnloadSample(&m_lResources[i].m_Sample); + if(m_lResources[i].m_Texture.IsValid()) + Graphics()->UnloadTexture(&m_lResources[i].m_Texture); + if(m_lResources[i].m_DownloadTemp) + { + io_close(m_lResources[i].m_DownloadTemp); + Storage()->RemoveFile(m_lResources[i].m_aTempPath, IStorage::TYPE_SAVE); + } + } + m_lResources.clear(); + } + } +} + +CClientResManager::CClientResource *CClientResManager::FindResource(Uuid ResourceID) +{ + for(int i = 0; i < m_lResources.size(); i++) + { + if(m_lResources[i].m_Uuid == ResourceID) + return &m_lResources[i]; + } + return nullptr; +} + +ISound::CSampleHandle CClientResManager::GetResourceSample(Uuid ResID) +{ + CClientResource *pResource = FindResource(ResID); + if(pResource) + return pResource->m_Sample; + return ISound::CSampleHandle(); +} + +IGraphics::CTextureHandle CClientResManager::GetResourceTexture(Uuid ResID) +{ + CClientResource *pResource = FindResource(ResID); + if(pResource) + return pResource->m_Texture; + return IGraphics::CTextureHandle(); +} diff --git a/src/game/client/components/resource.h b/src/game/client/components/resource.h new file mode 100644 index 00000000..6f5bad1f --- /dev/null +++ b/src/game/client/components/resource.h @@ -0,0 +1,40 @@ +#ifndef GAME_CLIENT_COMPONENTS_RESOURCE_H +#define GAME_CLIENT_COMPONENTS_RESOURCE_H + +#include +#include +#include +#include +#include +#include + +class CClientResManager : public CComponent +{ + struct CClientResource : public CResource + { + char m_aPath[IO_MAX_PATH_LENGTH]; + char m_aTempPath[IO_MAX_PATH_LENGTH]; + int m_DownloadedSize; + int m_ChunkPerRequest; + ISound::CSampleHandle m_Sample; + IGraphics::CTextureHandle m_Texture; + IOHANDLE m_DownloadTemp; + }; + + array m_lResources; + void RequestDownload(const Uuid *pRequest); + bool LoadResource(CClientResource *pResource); + + void RenderImageEntity(const CNetObj_CustomImageEntity *pPrev, const CNetObj_CustomImageEntity *pCur); +public: + CClientResManager(); + virtual void OnRender(); + virtual void OnMessage(int MsgType, void *pRawMsg); + virtual void OnStateChange(int NewState, int OldState); + CClientResource *FindResource(Uuid ResourceID); + + ISound::CSampleHandle GetResourceSample(Uuid ResID); + IGraphics::CTextureHandle GetResourceTexture(Uuid ResID); +}; + +#endif // GAME_CLIENT_COMPONENTS_RESOURCE_H \ No newline at end of file diff --git a/src/game/client/components/sounds.cpp b/src/game/client/components/sounds.cpp index 66cc4bb3..2c83fa2a 100644 --- a/src/game/client/components/sounds.cpp +++ b/src/game/client/components/sounds.cpp @@ -128,7 +128,7 @@ void CSounds::OnRender() int64 Now = time_get(); if(m_QueueWaitTime <= Now) { - Play(m_aQueue[0].m_Channel, m_aQueue[0].m_SetId, 1.0f); + PlaySample(m_aQueue[0].m_Channel, m_aQueue[0].m_Sample, 1.0f); m_QueueWaitTime = Now + time_freq() * 3 / 10; // wait 300ms before playing the next one if(--m_QueuePos > 0) mem_move(m_aQueue, m_aQueue + 1, m_QueuePos * sizeof(QueueEntry)); @@ -143,7 +143,7 @@ void CSounds::ClearQueue() m_QueueWaitTime = time_get(); } -void CSounds::Enqueue(int Channel, int SetId) +void CSounds::EnqueueSample(int Channel, ISound::CSampleHandle Sample) { if(m_pClient->m_SuppressEvents) return; @@ -151,45 +151,75 @@ void CSounds::Enqueue(int Channel, int SetId) return; if(Channel != CHN_MUSIC && Config()->m_ClEditor) return; + if(!Sample.IsValid()) + return; m_aQueue[m_QueuePos].m_Channel = Channel; - m_aQueue[m_QueuePos++].m_SetId = SetId; + m_aQueue[m_QueuePos++].m_Sample = Sample; } -void CSounds::Play(int Chn, int SetId, float Vol) +void CSounds::PlaySample(int Channel, ISound::CSampleHandle Sample, float Vol) { if(m_pClient->m_SuppressEvents) return; - if(Chn == CHN_MUSIC && !Config()->m_SndMusic) + if(Channel == CHN_MUSIC && !Config()->m_SndMusic) return; - ISound::CSampleHandle SampleId = GetSampleId(SetId); - if(!SampleId.IsValid()) + if(!Sample.IsValid()) return; int Flags = 0; - if(Chn == CHN_MUSIC) + if(Channel == CHN_MUSIC) Flags = ISound::FLAG_LOOP; - Sound()->Play(Chn, SampleId, Flags); + Sound()->Play(Channel, Sample, Flags); } -void CSounds::PlayAt(int Chn, int SetId, float Vol, vec2 Pos) +void CSounds::PlaySampleAt(int Channel, ISound::CSampleHandle Sample, float Vol, vec2 Pos) { if(m_pClient->m_SuppressEvents) return; - if(Chn == CHN_MUSIC && !Config()->m_SndMusic) + if(Channel == CHN_MUSIC && !Config()->m_SndMusic) return; - ISound::CSampleHandle SampleId = GetSampleId(SetId); - if(!SampleId.IsValid()) + if(!Sample.IsValid()) return; int Flags = 0; - if(Chn == CHN_MUSIC) + if(Channel == CHN_MUSIC) Flags = ISound::FLAG_LOOP; - Sound()->PlayAt(Chn, SampleId, Flags, Pos.x, Pos.y); + Sound()->PlayAt(Channel, Sample, Flags, Pos.x, Pos.y); +} + +void CSounds::StopSample(ISound::CSampleHandle Sample) +{ + if(m_WaitForSoundJob) + return; + + Sound()->Stop(Sample); +} + +bool CSounds::IsPlayingSample(ISound::CSampleHandle Sample) +{ + if(m_WaitForSoundJob) + return false; + return Sound()->IsPlaying(Sample); +} + +void CSounds::Enqueue(int Channel, int SetId) +{ + EnqueueSample(Channel, GetSampleId(SetId)); +} + +void CSounds::Play(int Channel, int SetId, float Vol) +{ + PlaySample(Channel, GetSampleId(SetId), Vol); +} + +void CSounds::PlayAt(int Channel, int SetId, float Vol, vec2 Pos) +{ + PlaySampleAt(Channel, GetSampleId(SetId), Vol, Pos); } void CSounds::Stop(int SetId) @@ -200,7 +230,7 @@ void CSounds::Stop(int SetId) CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; for(int i = 0; i < pSet->m_NumSounds; i++) - Sound()->Stop(pSet->m_aSounds[i].m_Id); + StopSample(pSet->m_aSounds[i].m_Id); } bool CSounds::IsPlaying(int SetId) @@ -211,8 +241,18 @@ bool CSounds::IsPlaying(int SetId) CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; for(int i = 0; i < pSet->m_NumSounds; i++) { - if(Sound()->IsPlaying(pSet->m_aSounds[i].m_Id)) + if(IsPlayingSample(pSet->m_aSounds[i].m_Id)) return true; } return false; } + +ISound::CSampleHandle CSounds::LoadSampleMemory(const char *pContext, const unsigned char *pData, int DataSize) +{ + return m_pClient->Sound()->LoadOpusMemory(pContext, pData, DataSize); +} + +bool CSounds::UnloadSample(ISound::CSampleHandle *pSample) +{ + return Sound()->UnloadSample(pSample); +} diff --git a/src/game/client/components/sounds.h b/src/game/client/components/sounds.h index 0f56593b..b2e7b67d 100644 --- a/src/game/client/components/sounds.h +++ b/src/game/client/components/sounds.h @@ -16,7 +16,7 @@ class CSounds : public CComponent struct QueueEntry { int m_Channel; - int m_SetId; + ISound::CSampleHandle m_Sample; } m_aQueue[QUEUE_SIZE]; int m_QueuePos; int64 m_QueueWaitTime; @@ -42,11 +42,20 @@ class CSounds : public CComponent virtual void OnRender(); void ClearQueue(); + void EnqueueSample(int Channel, ISound::CSampleHandle Sample); + void PlaySample(int Channel, ISound::CSampleHandle Sample, float Vol); + void PlaySampleAt(int Channel, ISound::CSampleHandle Sample, float Vol, vec2 Pos); + void StopSample(ISound::CSampleHandle Sample); + bool IsPlayingSample(ISound::CSampleHandle Sample); + void Enqueue(int Channel, int SetId); void Play(int Channel, int SetId, float Vol); void PlayAt(int Channel, int SetId, float Vol, vec2 Pos); void Stop(int SetId); bool IsPlaying(int SetId); + + ISound::CSampleHandle LoadSampleMemory(const char *pContext, const unsigned char *pData, int DataSize); + bool UnloadSample(ISound::CSampleHandle *pSample); }; #endif diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 9289cf1a..2558b547 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -46,6 +46,7 @@ #include "components/notifications.h" #include "components/particles.h" #include "components/players.h" +#include "components/resource.h" #include "components/scoreboard.h" #include "components/skins.h" #include "components/sounds.h" @@ -118,6 +119,7 @@ static CPlayers gs_Players; static CNamePlates gs_NamePlates; static CItems gs_Items; static CMapImages gs_MapImages; +static CClientResManager gs_ResourceManager; static CMapLayers gs_MapLayersBackGround(CMapLayers::TYPE_BACKGROUND); static CMapLayers gs_MapLayersForeGround(CMapLayers::TYPE_FOREGROUND); @@ -254,6 +256,7 @@ void CGameClient::OnConsoleInit() m_pMapLayersBackGround = &::gs_MapLayersBackGround; m_pMapLayersForeGround = &::gs_MapLayersForeGround; m_pStats = &::gs_Stats; + m_pResourceManager = &::gs_ResourceManager; // make a list of all the systems, make sure to add them in the corrent render order m_All.Add(m_pSkins); @@ -271,6 +274,7 @@ void CGameClient::OnConsoleInit() m_All.Add(&gs_MapLayersBackGround); // first to render m_All.Add(&m_pParticles->m_RenderTrail); m_All.Add(m_pItems); + m_All.Add(m_pResourceManager); m_All.Add(&gs_Players); m_All.Add(&gs_MapLayersForeGround); m_All.Add(&m_pParticles->m_RenderExplosions); @@ -1089,6 +1093,11 @@ void CGameClient::ProcessEvents() CNetEvent_SoundWorld *ev = (CNetEvent_SoundWorld *) pData; m_pSounds->PlayAt(CSounds::CHN_WORLD, ev->m_SoundID, 1.0f, vec2(ev->m_X, ev->m_Y)); } + else if(Item.m_Type == NETEVENTTYPE_CUSTOMSOUNDWORLD) + { + CNetEvent_CustomSoundWorld *ev = (CNetEvent_CustomSoundWorld *) pData; + m_pSounds->PlaySampleAt(CSounds::CHN_WORLD, m_pResourceManager->GetResourceSample(*reinterpret_cast(&ev->m_Uuid)), 1.0f, vec2(ev->m_X, ev->m_Y)); + } } } @@ -1306,6 +1315,17 @@ void CGameClient::OnNewSnapshot() } } } + else if(Item.m_Type == NETOBJTYPE_CHARACTERGAMETEXTURE) + { + if(Item.m_ID < MAX_CLIENTS) + { + CSnapState::CCharacterInfo *pCharInfo = &m_Snap.m_aCharacters[Item.m_ID]; + Uuid GameTextureID; + mem_copy(&GameTextureID, ((const CNetObj_CharacterGameTexture *) pData)->m_Uuid, sizeof(Uuid)); + pCharInfo->m_UseCustomGameTexture = true; + pCharInfo->m_GameTexture = m_pResourceManager->GetResourceTexture(GameTextureID); + } + } else if(Item.m_Type == NETOBJTYPE_SPECTATORINFO) { m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *) pData; @@ -1441,6 +1461,8 @@ void CGameClient::OnNewSnapshot() EvolveCharacter(&m_Snap.m_aCharacters[i].m_Prev, EvolvePrevTick); if(m_Snap.m_aCharacters[i].m_Cur.m_Tick) EvolveCharacter(&m_Snap.m_aCharacters[i].m_Cur, EvolveCurTick); + if(!m_Snap.m_aCharacters[i].m_UseCustomGameTexture || !m_Snap.m_aCharacters[i].m_GameTexture.IsValid()) + m_Snap.m_aCharacters[i].m_GameTexture = g_pData->m_aImages[IMAGE_GAME].m_Id; m_aClients[i].m_Evolved = m_Snap.m_aCharacters[i].m_Cur; if(i != m_LocalClientID || !Config()->m_ClPredict || Client()->State() == IClient::STATE_DEMOPLAYBACK || !GameDataPredictInput() || !GameDataPredictEvent()) diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index eea47326..f43f14eb 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -190,6 +190,8 @@ class CGameClient : public IGameClient // interpolated position vec2 m_Position; + bool m_UseCustomGameTexture; + IGraphics::CTextureHandle m_GameTexture; }; CCharacterInfo m_aCharacters[MAX_CLIENTS]; @@ -345,6 +347,7 @@ class CGameClient : public IGameClient class CItems *m_pItems; class CMapLayers *m_pMapLayersBackGround; class CMapLayers *m_pMapLayersForeGround; + class CClientResManager *m_pResourceManager; }; void FormatTime(char *pBuf, int Size, int Time, int Precision); diff --git a/src/game/resource.h b/src/game/resource.h new file mode 100644 index 00000000..5e58e796 --- /dev/null +++ b/src/game/resource.h @@ -0,0 +1,27 @@ +#ifndef GAME_RESOURCE_H +#define GAME_RESOURCE_H + +#include +#include + +struct CResource +{ + enum + { + CHUNK_SIZE = 1200, + }; + + char m_aName[64]; + SHA256_DIGEST m_Sha256; + unsigned m_Crc; + unsigned char *m_pData; + int m_DataSize; + int m_Type; + Uuid m_Uuid; + + bool operator<(const CResource &Other) const { return m_Uuid < Other.m_Uuid; } + bool operator<=(const CResource &Other) const { return m_Uuid <= Other.m_Uuid; } + bool operator==(const CResource &Other) const { return m_Uuid == Other.m_Uuid; } +}; + +#endif // GAME_RESOURCE_H \ No newline at end of file diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 3f8da342..686fbae2 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -184,6 +184,21 @@ void CGameContext::CreateSound(vec2 Pos, int Sound, int64 Mask) } } +void CGameContext::CreateCustomSound(vec2 Pos, Uuid Sound, int64 Mask) +{ + if(!ResourceManager()->FindResource(Sound)) + return; + + // create a sound + CNetEvent_CustomSoundWorld *pEvent = (CNetEvent_CustomSoundWorld *) m_Events.Create(NETEVENTTYPE_CUSTOMSOUNDWORLD, sizeof(CNetEvent_CustomSoundWorld), Mask); + if(pEvent) + { + pEvent->m_X = (int) Pos.x; + pEvent->m_Y = (int) Pos.y; + mem_copy(pEvent->m_Uuid, &Sound, sizeof(Uuid)); + } +} + // ----- send functions ----- void CGameContext::SendChat(int ChatterClientID, int Mode, int To, const char *pText) { @@ -542,6 +557,7 @@ void CGameContext::OnTick() { m_apPlayers[i]->Tick(); m_apPlayers[i]->PostTick(); + m_ResourceManager.TrySendResourceInfo(i); } } @@ -729,6 +745,8 @@ void CGameContext::OnClientEnter(int ClientID) Msg.m_Team = NewClientInfoMsg.m_Team; Server()->SendPackMsg(&Msg, MSGFLAG_NOSEND, -1); } + + ResourceManager()->OnClientEnter(ClientID); } void CGameContext::OnClientConnected(int ClientID, bool Dummy, bool AsSpec) @@ -1131,6 +1149,11 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) CNetMsg_Cl_Command *pMsg = (CNetMsg_Cl_Command *) pRawMsg; CommandManager()->OnCommand(pMsg->m_Name, pMsg->m_Arguments, ClientID); } + else if(MsgID == NETMSGTYPE_CL_REQEUSTCUSTOMRESOURCE) + { + CNetMsg_Cl_ReqeustCustomResource *pMsg = (CNetMsg_Cl_ReqeustCustomResource *) pRawMsg; + ResourceManager()->SendResourceData(ClientID, *static_cast(pMsg->m_Uuid)); + } } else { @@ -1600,6 +1623,7 @@ void CGameContext::OnInit() m_World.SetGameServer(this); m_Events.SetGameServer(this); m_CommandManager.Init(m_pConsole, this, NewCommandHook, RemoveCommandHook); + m_ResourceManager.Init(this); // HACK: only set static size for items, which were available in the first 0.7 release // so new items don't break the snapshot delta diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 85acaa60..ed80fec7 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -12,6 +12,7 @@ #include "eventhandler.h" #include "gameworld.h" +#include "resource.h" /* Tick @@ -92,9 +93,11 @@ class CGameContext : public IGameServer class CPlayer *m_apPlayers[MAX_CLIENTS]; class IGameController *m_pController; + CServerResManager m_ResourceManager; CGameWorld m_World; CCommandManager m_CommandManager; + CServerResManager *ResourceManager() { return &m_ResourceManager; } CCommandManager *CommandManager() { return &m_CommandManager; } // helper functions @@ -139,6 +142,7 @@ class CGameContext : public IGameServer void CreatePlayerSpawn(vec2 Pos); void CreateDeath(vec2 Pos, int Who); void CreateSound(vec2 Pos, int Sound, int64 Mask = -1); + void CreateCustomSound(vec2 Pos, Uuid Sound, int64 Mask = -1); // ----- send functions ----- void SendChat(int ChatterClientID, int Mode, int To, const char *pText); diff --git a/src/game/server/gamemodes/infection/reinfected.cpp b/src/game/server/gamemodes/infection/reinfected.cpp index 69619b28..5584f6b1 100644 --- a/src/game/server/gamemodes/infection/reinfected.cpp +++ b/src/game/server/gamemodes/infection/reinfected.cpp @@ -374,6 +374,11 @@ void CGameControllerReinfected::Tick() DoWincheckMatch(); } +void CGameControllerReinfected::Snap(int SnappingClient) +{ + IGameController::Snap(SnappingClient); +} + bool CGameControllerReinfected::DoWincheckMatch() { if(GetRealPlayerNum() < Config()->m_RiPlayersMin) diff --git a/src/game/server/gamemodes/infection/reinfected.h b/src/game/server/gamemodes/infection/reinfected.h index 79b626cc..e2fca776 100644 --- a/src/game/server/gamemodes/infection/reinfected.h +++ b/src/game/server/gamemodes/infection/reinfected.h @@ -37,6 +37,7 @@ class CGameControllerReinfected : public IGameController virtual int OnCharacterFireWeapon(class CCharacter *pChr, vec2 Direction, int Weapon); virtual void Tick(); + virtual void Snap(int SnappingClient); virtual bool DoWincheckMatch(); virtual void DoTeamChange(class CPlayer *pPlayer, int Team, bool DoChatMsg); diff --git a/src/game/server/resource.cpp b/src/game/server/resource.cpp new file mode 100644 index 00000000..dd603e9d --- /dev/null +++ b/src/game/server/resource.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include + +#include "gamecontext.h" +#include "resource.h" + +IConsole *CServerResManager::Console() { return m_pGameContext->Console(); } +IServer *CServerResManager::Server() { return m_pGameContext->Server(); } +IStorage *CServerResManager::Storage() { return m_pGameContext->Storage(); } +CConfig *CServerResManager::Config() { return m_pGameContext->Config(); } + +CServerResManager::CServerResource *CServerResManager::FindResource(Uuid ResourceID) +{ + for(int i = 0; i < m_lResources.size(); i++) + { + if(m_lResources[i].m_Uuid == ResourceID) + return &m_lResources[i]; + } + return nullptr; +} + +CServerResManager::CServerResManager() +{ +} + +CServerResManager::~CServerResManager() +{ + Clear(); +} + +void CServerResManager::Init(CGameContext *pGameContext) +{ + m_pGameContext = pGameContext; + m_ChunksPerRequest = Config()->m_SvResDownloadSpeed; +} + +void CServerResManager::SendResourceData(int ClientID, const Uuid RequestUuid) +{ + int ChunkSize = CResource::CHUNK_SIZE; + CServerResource *pTarget = FindResource(RequestUuid); + if(!pTarget) + return; + + // send resource chunks, copied from map download + for(int i = 0; i < Config()->m_SvResDownloadSpeed && pTarget->m_aDownloadChunks[ClientID] >= 0; ++i) + { + int Chunk = pTarget->m_aDownloadChunks[ClientID]; + int Offset = Chunk * ChunkSize; + + // check for last part + if(Offset + ChunkSize >= pTarget->m_DataSize) + { + ChunkSize = pTarget->m_DataSize - Offset; + pTarget->m_aDownloadChunks[ClientID] = -1; + } + else + pTarget->m_aDownloadChunks[ClientID]++; + + CNetMsg_Sv_CustomResourceData Msg; + Msg.m_Uuid = &pTarget->m_Uuid; + Msg.m_ChunkIndex = Chunk; + Msg.m_Data = &pTarget->m_pData[Offset]; + Msg.m_DataSize = ChunkSize; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH | MSGFLAG_NORECORD, ClientID); + + if(Config()->m_Debug) + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "sending chunk %d with size %d", Chunk, ChunkSize); + Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "resource", aBuf); + } + } +} + +void CServerResManager::AddResource(const char *pPath, const char *pName, const Uuid ResourceID) +{ + char aBuf[256]; + if(!str_endswith(pPath, ".png") && !str_endswith(pPath, ".opus")) + { + str_format(aBuf, sizeof(aBuf), "failed to load resource with wrong extension '%s'(%s)", pName, pPath); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "resource", aBuf); + return; + } + + IOHANDLE File = Storage()->OpenFile(pPath, IOFLAG_READ, IStorage::TYPE_ALL); + if(!File) + { + str_format(aBuf, sizeof(aBuf), "failed to load resource '%s'(%s)", pName, pPath); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "resource", aBuf); + return; + } + CServerResource NewRes; + unsigned int DataSize; + (void) Storage()->GetHashAndSize(pPath, IStorage::TYPE_ALL, &NewRes.m_Sha256, &NewRes.m_Crc, &DataSize); + str_copy(NewRes.m_aName, pName, sizeof(NewRes.m_aName)); + NewRes.m_DataSize = static_cast(DataSize); + NewRes.m_Type = str_endswith(pPath, ".png") ? RESOURCE_IMAGE : RESOURCE_SOUND; + NewRes.m_Uuid = ResourceID; + NewRes.m_pData = (unsigned char *) mem_alloc(NewRes.m_DataSize); + io_read(File, NewRes.m_pData, NewRes.m_DataSize); + io_close(File); + m_lResources.add(NewRes); + + str_format(aBuf, sizeof(aBuf), "loaded resource '%s'(%s)", pName, pPath); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "resource", aBuf); +} + +void CServerResManager::OnClientEnter(int ClientID) +{ + if(Server()->GetClientVersion(ClientID) < 0x0706) + return; + m_aResourceSendIndex[ClientID] = 0; +} + +void CServerResManager::Clear() +{ + for(int i = 0; i < m_lResources.size(); i++) + { + if(m_lResources[i].m_pData) + mem_free(m_lResources[i].m_pData); + m_lResources[i].m_pData = 0; + } + for(int i = 0; i < MAX_CLIENTS; i++) + { + m_aResourceSendIndex[i] = -1; + } +} + +void CServerResManager::TrySendResourceInfo(int ClientID) +{ + if(!GameServer()->m_apPlayers[ClientID]) + return; + + int Index = m_aResourceSendIndex[ClientID]; + if(Index < 0 || Index >= m_lResources.size()) + return; + CNetMsg_Sv_CustomResource Resource; + Resource.m_Uuid = &m_lResources[Index].m_Uuid; + Resource.m_Type = m_lResources[Index].m_Type; + Resource.m_Name = m_lResources[Index].m_aName; + Resource.m_Crc = m_lResources[Index].m_Crc; + Resource.m_Sha256 = &m_lResources[Index].m_Sha256; + Resource.m_Size = m_lResources[Index].m_DataSize; + Resource.m_ChunkPerRequest = m_ChunksPerRequest; + Server()->SendPackMsg(&Resource, MSGFLAG_VITAL | MSGFLAG_FLUSH | MSGFLAG_NORECORD, ClientID); + + m_lResources[Index].m_aDownloadChunks[ClientID] = 0; + m_aResourceSendIndex[ClientID]++; +} diff --git a/src/game/server/resource.h b/src/game/server/resource.h new file mode 100644 index 00000000..86f72275 --- /dev/null +++ b/src/game/server/resource.h @@ -0,0 +1,38 @@ +#ifndef GAME_SERVER_RESOURCE_H +#define GAME_SERVER_RESOURCE_H + +#include +#include + +class CServerResManager +{ + class CGameContext *m_pGameContext; + class CGameContext *GameServer() const { return m_pGameContext; } + class IConsole *Console(); + class IServer *Server(); + class IStorage *Storage(); + class CConfig *Config(); + + struct CServerResource : public CResource + { + int m_aDownloadChunks[MAX_CLIENTS]; + }; + + int m_aResourceSendIndex[MAX_CLIENTS]; + array m_lResources; + int m_ChunksPerRequest; + +public: + CServerResManager(); + ~CServerResManager(); + + void Init(class CGameContext *pGameContext); + void AddResource(const char *pPath, const char *pName, const Uuid ResourceID); + void SendResourceData(int ClientID, const Uuid RequestUuid); + void OnClientEnter(int ClientID); + void Clear(); + void TrySendResourceInfo(int ClientID); + CServerResource *FindResource(Uuid ResourceID); +}; + +#endif // GAME_SERVER_RESOURCE_H \ No newline at end of file diff --git a/src/game/variables.h b/src/game/variables.h index c612e177..ef1a775b 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -144,6 +144,8 @@ MACRO_CONFIG_INT(SvVoteKickMin, sv_vote_kick_min, 0, 0, MAX_CLIENTS, CFGFLAG_SAV MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SAVE | CFGFLAG_SERVER, "The time to ban a player if kicked by vote. 0 makes it just use kick") MACRO_CONFIG_INT(SvAllowSpecVoting, sv_allow_spec_voting, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_SERVER, "Allow voting by spectators") +MACRO_CONFIG_INT(SvResDownloadSpeed, sv_res_download_speed, 8, 1, 16, CFGFLAG_SAVE | CFGFLAG_SERVER, "Number of custom resource data packages a client gets on each request") + MACRO_CONFIG_INT(RiPlayersMin, ri_players_min, 2, 0, MAX_CLIENTS, CFGFLAG_SAVE | CFGFLAG_SERVER, "Minimum number of players required to start reinfected game") MACRO_CONFIG_INT(RiInfectionStartTime, ri_infection_start_time, 10, 0, MAX_CLIENTS, CFGFLAG_SAVE | CFGFLAG_SERVER, "timer for reinfected game to start infection")