From d2b91e8b1e32e22023f5d67df9d7ad2f538a537c Mon Sep 17 00:00:00 2001 From: Anchur <809595156@qq.com> Date: Sun, 14 Feb 2021 23:54:27 +0800 Subject: [PATCH] Make HalfLife 1 support clinet side ragdoll. --- README.md | 13 +- cl_dll/GameStudioModelRenderer.cpp | 267 +++++++++++++++++++++++- cl_dll/GameStudioModelRenderer.h | 8 +- cl_dll/cdll_int.cpp | 9 + cl_dll/phy_corpse.h | 39 ++++ cl_dll/physics.cpp | 117 +++++++++++ cl_dll/physics.h | 149 +++++++++++++ projects/vs2019/hl_cdll.vcxproj | 7 +- projects/vs2019/hl_cdll.vcxproj.filters | 9 + 9 files changed, 613 insertions(+), 5 deletions(-) create mode 100644 cl_dll/phy_corpse.h create mode 100644 cl_dll/physics.cpp create mode 100644 cl_dll/physics.h diff --git a/README.md b/README.md index aef5edf..fec9e04 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,15 @@ -Half Life 1 SDK LICENSE +# HalfLife 1 Ragdoll MOD +This repo shows how to make client side ragdoll effect using GoldsrcPhysics (a lib written in C#). + +All changes in these flies: + +* M GameStudioModelRenderer.h/cpp +* A physics.h/cpp +* A phy_corpse.h +* cdll_int.cpp +> git diff tells you what I do. + +# Half Life 1 SDK LICENSE ====================== Half Life 1 SDK Copyright© Valve Corp. diff --git a/cl_dll/GameStudioModelRenderer.cpp b/cl_dll/GameStudioModelRenderer.cpp index 68eba2a..bbc7d7f 100644 --- a/cl_dll/GameStudioModelRenderer.cpp +++ b/cl_dll/GameStudioModelRenderer.cpp @@ -1,4 +1,4 @@ -//========= Copyright 1996-2002, Valve LLC, All rights reserved. ============ +//========= Copyright ?1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -28,6 +28,9 @@ #include "GameStudioModelRenderer.h" #include "Exports.h" +#include "phy_corpse.h" +#include"physics.h" + // // Override the StudioModelRender virtual member functions here to implement custom bone // setup, blending, etc. @@ -47,6 +50,206 @@ CGameStudioModelRenderer CGameStudioModelRenderer::CGameStudioModelRenderer( void ) { } +void CGameStudioModelRenderer::Init(void) +{ + CStudioModelRenderer::Init(); + InitPhysicsInterface(NULL); + gPhysics.InitSystem(&m_clTime, m_plighttransform, &IEngineStudio); +} +int CGameStudioModelRenderer::StudioDrawRagdoll(int flags) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes(&m_nFrameCount, &m_clTime, &m_clOldTime); + IEngineStudio.GetViewInfo(m_vRenderOrigin, m_vUp, m_vRight, m_vNormal); + IEngineStudio.GetAliasScale(&m_fSoftwareXScale, &m_fSoftwareYScale); + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t*)IEngineStudio.Mod_Extradata(m_pRenderModel); + IEngineStudio.StudioSetHeader(m_pStudioHeader); + IEngineStudio.SetRenderModel(m_pRenderModel); + + StudioSetUpTransform(0); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones(m_pRenderModel); + } + else + { + StudioSetupBones(); + gPhysics.SetupBonesPhysically(m_pCurrentEntity->index); + m_pCurrentEntity->origin.x = (*m_pbonetransform)[1][0][3]; + m_pCurrentEntity->origin.y = (*m_pbonetransform)[1][1][3]; + m_pCurrentEntity->origin.z = (*m_pbonetransform)[1][2][3]; + } + StudioSaveBones(); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments(); + IEngineStudio.StudioClientEvents(); + // copy attachments into global entity array + if (m_pCurrentEntity->index > 0) + { + cl_entity_t* ent = gEngfuncs.GetEntityByIndex(m_pCurrentEntity->index); + + memcpy(ent->attachment, m_pCurrentEntity->attachment, sizeof(vec3_t) * 4); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting); + + IEngineStudio.StudioEntityLight(&lighting); + + // model and frame independant + IEngineStudio.StudioSetupLighting(&lighting); + + // get remap colors + + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + IEngineStudio.StudioSetRemapColors(m_nTopColor, m_nBottomColor); + + StudioRenderModel(); + } + + return 1; +} +cvar_t* _drawOriginalDead = NULL; +int CGameStudioModelRenderer::StudioDrawModel(int flags) +{ + // we need a better place to call the physics update function +#pragma region physics world update + static int lastFrame = -1; + IEngineStudio.GetTimes(&lastFrame, &m_clTime, &m_clOldTime); + if (lastFrame != m_nFrameCount) + { + gPhysics.Update(m_clTime - m_clOldTime); + lastFrame = m_nFrameCount; + } +#pragma endregion + + // init + if (!_drawOriginalDead) { + gEngfuncs.Cvar_SetValue("r_corpse", 0); + _drawOriginalDead = IEngineStudio.GetCvar("r_corpse"); + } + + /*if (!_studioOn) + { + gEngfuncs.Cvar_SetValue("studio_on", 1); + gEngfuncs.Cvar_SetValue("_fade", 1); + gEngfuncs.Cvar_SetValue("_die", 3000); + gEngfuncs.Cvar_SetValue("play_explosion", 0); + + _studioOn = IEngineStudio.GetCvar("studio_on"); + _fade= IEngineStudio.GetCvar("_fade"); + _die = IEngineStudio.GetCvar("_die"); + play_explosion= IEngineStudio.GetCvar("play_explosion"); + }*/ + +#pragma region Explosion + /*if (play_explosion->value != 0) + { + cl_entity_t* local = gEngfuncs.GetLocalPlayer(); + gPhysics.Explosion((Vector3*)(&local->origin), 90); + play_explosion->value = 0; + gEngfuncs.Con_DPrintf("play explosion at local player.\n"); + }*/ +#pragma endregion + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + + if (pgCorpseMgr->IsRagdollCorpse(m_pCurrentEntity)) + return StudioDrawRagdoll(flags); + + // hard coded here, we need a better way. + if ((31 <= m_pCurrentEntity->curstate.sequence && + m_pCurrentEntity->curstate.sequence <= 43 && + strstr(m_pCurrentEntity->model->name, "scientist"))|| + (15 <= m_pCurrentEntity->curstate.sequence && + m_pCurrentEntity->curstate.sequence <= 19 && + strstr(m_pCurrentEntity->model->name, "zombie"))) + { + // 如果实体原来没死 + if (!pgCorpseMgr->IsEntityDead(m_pCurrentEntity)) + { + // init pose + CStudioModelRenderer::StudioDrawModel(0); + + pgCorpseMgr->CreateRagdollCorpse(m_pCurrentEntity); + pgCorpseMgr->EntityDie(m_pCurrentEntity); + } + else + { + if (_drawOriginalDead->value == 0) + return 0; + } + } + else + { + pgCorpseMgr->EntityRespawn(m_pCurrentEntity); + } + +#pragma region Note + + //if (IsRagdollEntity(m_pCurrentEntity)) + // return StudioDrawRagdoll(flags); + + //bool IsRagdollModelAndInDeathSequence;//TODO + //// 如果当前模型支持布娃娃 且 处于死亡动画 + //if (IsRagdollModelAndInDeathSequence) + //{ + // // 如果实体原来处于非死亡状态 + // if (!IsEntityDying(m_pCurrentEntity->index)) + // { + // // 到此说明正在播放首帧死亡动画 + // // TODO: 创建临时实体,赋给它一个布娃娃控制器,激活布娃娃控制器 + // SetEntityDying(m_pCurrentEntity->index, true); + // } + // else + // { + // // 到此说明实体早已死了,已经给它创建过布娃娃尸体实体了,就不渲染这个实体了。 + // return 0; + // } + //} + //else + //{ + // // 对于不支持的动画来说,这个步骤不影响任何东西; + // // 对于支持布娃娃的模型 但 正在播放非死亡动画,说明实体活着。 + // SetEntityDying(m_pCurrentEntity->index, false); + //} + +#pragma endregion + + // 正常渲染 + return CStudioModelRenderer::StudioDrawModel(flags); +} + +int CGameStudioModelRenderer::StudioDrawPlayer(int flags, entity_state_s* pplayer) +{ + return CStudioModelRenderer::StudioDrawPlayer(flags, pplayer); +} //////////////////////////////////// // Hooks to class implementation @@ -119,3 +322,65 @@ int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interfac // Success return 1; } + +#pragma region phy_corpse.cpp +CorpseManager* pgCorpseMgr = nullptr; + +CorpseManager::CorpseManager(void) +{ + memset(_entityDead, 0, MAX_ENTITIES); +} + +bool CorpseManager::IsEntityDead(cl_entity_t* ent) +{ + return _entityDead[ent->index]; +} + +void CorpseManager::EntityDie(cl_entity_t* ent) +{ + _entityDead[ent->index] = true; +} + +void CorpseManager::EntityRespawn(cl_entity_t* ent) +{ + _entityDead[ent->index] = false; +} + +TEMPENTITY* CorpseManager::CreateRagdollCorpse(cl_entity_t* ent) +{ + TEMPENTITY* tempent = gEngfuncs.pEfxAPI->CL_TempEntAlloc(ent->curstate.origin, ent->model); + tempent->entity.curstate.iuser1 = ent->index; + tempent->entity.curstate.iuser3 = PhyCorpseFlag1; + tempent->entity.curstate.iuser4 = PhyCorpseFlag2; + tempent->entity.curstate.body = ent->curstate.body; + tempent->entity.curstate.skin = ent->curstate.skin; + entity_state_t* entstate = &ent->curstate; + entity_state_t* tempstate = &tempent->entity.curstate; + tempent->entity.angles = ent->angles; + tempent->entity.latched = ent->latched; + tempstate->angles = entstate->angles; + tempstate->animtime = entstate->animtime; + tempstate->sequence = entstate->sequence; + tempstate->aiment = entstate->aiment; + tempstate->frame = entstate->frame; + + + tempent->die = 3000; + tempent->fadeSpeed = 1; + tempent->entity.index = _corpseIndex++; + gPhysics.CreateRagdollControllerHeader(tempent->entity.index, IEngineStudio.Mod_Extradata(ent->model)); + gPhysics.StartRagdoll(tempent->entity.index); + gPhysics.SetVelocity(tempent->entity.index, (Vector3*)&ent->curstate.velocity); + + gEngfuncs.Con_DPrintf("corpse [%d]'s velocity is %f\n", tempent->entity.index, ent->curstate.velocity.Length()); + gEngfuncs.Con_DPrintf("create corpse [%d] for entity [%d]\n", tempent->entity.index, ent->index); + return tempent; +} + +bool CorpseManager::IsRagdollCorpse(cl_entity_t* ent) +{ + return (ent->curstate.iuser3 == PhyCorpseFlag1 && + ent->curstate.iuser4 == PhyCorpseFlag2); +} +#pragma endregion + diff --git a/cl_dll/GameStudioModelRenderer.h b/cl_dll/GameStudioModelRenderer.h index 7d06f70..79e4c19 100644 --- a/cl_dll/GameStudioModelRenderer.h +++ b/cl_dll/GameStudioModelRenderer.h @@ -1,4 +1,4 @@ -//========= Copyright 1996-2002, Valve LLC, All rights reserved. ============ +//========= Copyright ?1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -21,6 +21,12 @@ class CGameStudioModelRenderer : public CStudioModelRenderer { public: CGameStudioModelRenderer( void ); + // override public interfaces + virtual int StudioDrawModel(int flags); + virtual int StudioDrawPlayer(int flags, struct entity_state_s* pplayer); + + virtual void Init(void); + int StudioDrawRagdoll(int flags); }; #endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/cl_dll/cdll_int.cpp b/cl_dll/cdll_int.cpp index afff446..8b80448 100644 --- a/cl_dll/cdll_int.cpp +++ b/cl_dll/cdll_int.cpp @@ -38,6 +38,8 @@ #include "tri.h" #include "vgui_TeamFortressViewport.h" #include "../public/interface.h" +#include "phy_corpse.h" +#include"physics.h" cl_enginefunc_t gEngfuncs; CHud gHUD; @@ -171,6 +173,13 @@ int DLLEXPORT HUD_VidInit( void ) VGui_Startup(); + const char* pLevelName=gEngfuncs.pfnGetLevelName(); + gPhysics.ChangeLevel(pLevelName); + + if (!pgCorpseMgr) + delete pgCorpseMgr; + pgCorpseMgr = new CorpseManager(); + return 1; } diff --git a/cl_dll/phy_corpse.h b/cl_dll/phy_corpse.h new file mode 100644 index 0000000..1554c83 --- /dev/null +++ b/cl_dll/phy_corpse.h @@ -0,0 +1,39 @@ +#pragma once + +#include"APIProxy.h" +#include + +// magic num +#define PhyCorpseFlag1 (753951) +#define PhyCorpseFlag2 (152358) + +#define MAX_ENTITIES 512 + +class CorpseManager +{ +public: + CorpseManager(void); + + // check if the entity is already dead + bool IsEntityDead(cl_entity_t* ent); + + // tells the mgr that the entity died just now. + void EntityDie(cl_entity_t* ent); + + // if entity plays any sequences other than death sequences, + // we tells the mgr this entity is alive. + void EntityRespawn(cl_entity_t* ent); + + // create ragdoll corpse for specified entity + TEMPENTITY* CreateRagdollCorpse(cl_entity_t* ent); + + // check if the entity is a ragdoll corpse (temp entity) + bool IsRagdollCorpse(cl_entity_t* ent); + +private: + // max server side entity count elements + bool _entityDead[MAX_ENTITIES]; + int _corpseIndex = MAX_ENTITIES; +}; + +extern CorpseManager* pgCorpseMgr; \ No newline at end of file diff --git a/cl_dll/physics.cpp b/cl_dll/physics.cpp new file mode 100644 index 0000000..1996d4e --- /dev/null +++ b/cl_dll/physics.cpp @@ -0,0 +1,117 @@ + +#include"physics.h" +#include +#include +#include +//#include + +#pragma comment(lib, "mscoree.lib") + +PhsicsAPI gPhysics; + + +#ifdef _DEBUG +const wchar_t PhyDllPath[] = L".\\gsphysics\\bin\\GoldsrcPhysics.dll"; +#else// _DEBUG +const wchar_t PhyDllPath[] = L"\\gsphysics\\GoldsrcPhysics.dll"; +#endif + +//globle CLR handle +ICLRMetaHost* pMetaHost = nullptr; +ICLRMetaHostPolicy* pMetaHostPolicy = nullptr; +ICLRRuntimeHost* pRuntimeHost = nullptr; +ICLRRuntimeInfo* pRuntimeInfo = nullptr; + +void ReleaseCLR() +{ + if (pRuntimeInfo != nullptr) + { + pRuntimeInfo->Release(); + pRuntimeInfo = nullptr; + } + + if (pRuntimeHost != nullptr) + { + pRuntimeHost->Release(); + pRuntimeHost = nullptr; + } + + if (pMetaHost != nullptr) + { + pMetaHost->Release(); + pMetaHost = nullptr; + } +} + +int InitCLR() +{ + if (pRuntimeInfo != nullptr) + return 0; + HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost); + hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo)); + + if (FAILED(hr)) { + ReleaseCLR(); + } + + hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pRuntimeHost)); + hr = pRuntimeHost->Start(); + return (int)hr; +} + +int ExitCLR() +{ + pRuntimeHost->Stop();//return HRESULT + ReleaseCLR(); + return 0; +} + +void* GetFunctionPointer(const LPCWSTR name) +{ + void* pfn = NULL; + + wchar_t buffer[64];//marshal args to [0xXXXX|MethodName] format + swprintf(buffer, 64, L"%p|%s", &pfn, name); + + DWORD dwRet = 0; + HRESULT hr = pRuntimeHost->ExecuteInDefaultAppDomain(PhyDllPath, + L"GoldsrcPhysics.ExportAPIs.PhysicsMain", + L"GetFunctionPointer", + buffer, + &dwRet); + + if (hr != S_OK) + exit(12345); + if ((DWORD)pfn != dwRet) + exit(54321); + + return pfn; +} + +//auto generated +extern "C" void InitPhysicsInterface(char* msg) +{ + InitCLR(); + gPhysics.Test = (void(_stdcall*)())GetFunctionPointer(L"Test"); + gPhysics.InitSystem = (void(_stdcall*)(void* pStudioRenderer, void* lastFieldAddress, void* engineStudioAPI))GetFunctionPointer(L"InitSystem"); + gPhysics.ChangeLevel = (void(_stdcall*)(const char* mapName))GetFunctionPointer(L"ChangeLevel"); + gPhysics.LevelReset = (void(_stdcall*)())GetFunctionPointer(L"LevelReset"); + gPhysics.Update = (void(_stdcall*)(float delta))GetFunctionPointer(L"Update"); + gPhysics.Pause = (void(_stdcall*)())GetFunctionPointer(L"Pause"); + gPhysics.Resume = (void(_stdcall*)())GetFunctionPointer(L"Resume"); + gPhysics.ShotDown = (void(_stdcall*)())GetFunctionPointer(L"ShotDown"); + gPhysics.ShowConfigForm = (void(_stdcall*)())GetFunctionPointer(L"ShowConfigForm"); + gPhysics.CreateRagdollController = (void(_stdcall*)(int entityId, const char* modelName))GetFunctionPointer(L"CreateRagdollController"); + gPhysics.CreateRagdollControllerIndex = (void(_stdcall*)(int entityId, int index))GetFunctionPointer(L"CreateRagdollControllerIndex"); + gPhysics.CreateRagdollControllerHeader = (void(_stdcall*)(int entityId, void * hdr))GetFunctionPointer(L"CreateRagdollControllerHeader"); + gPhysics.StartRagdoll = (void(_stdcall*)(int entityId))GetFunctionPointer(L"StartRagdoll"); + gPhysics.StopRagdoll = (void(_stdcall*)(int entityId))GetFunctionPointer(L"StopRagdoll"); + gPhysics.SetupBonesPhysically = (void(_stdcall*)(int entityId))GetFunctionPointer(L"SetupBonesPhysically"); + gPhysics.ChangeOwner = (void(_stdcall*)(int oldEntity, int newEntity))GetFunctionPointer(L"ChangeOwner"); + gPhysics.SetVelocity = (void(_stdcall*)(int entityId, Vector3 * v))GetFunctionPointer(L"SetVelocity"); + gPhysics.DisposeRagdollController = (void(_stdcall*)(int entityId))GetFunctionPointer(L"DisposeRagdollController"); + gPhysics.Explosion = (void(_stdcall*)(Vector3 * pos, float intensity))GetFunctionPointer(L"Explosion"); + gPhysics.Shoot = (void(_stdcall*)(Vector3 * from, Vector3 * force))GetFunctionPointer(L"Shoot"); + gPhysics.PickBody = (void(_stdcall*)())GetFunctionPointer(L"PickBody"); + gPhysics.ReleaseBody = (void(_stdcall*)())GetFunctionPointer(L"ReleaseBody"); +} \ No newline at end of file diff --git a/cl_dll/physics.h b/cl_dll/physics.h new file mode 100644 index 0000000..47b13d6 --- /dev/null +++ b/cl_dll/physics.h @@ -0,0 +1,149 @@ +#pragma once + +typedef struct +{ + float x, y, z; +}Vector3; + + +typedef struct PhsicsAPI_s +{ + /* + + */ + void(_stdcall* Test)(); + + /* + + Init physics system + if the struct layout is different from default layout, that will throw a fatal error. + the address of StudioModelRenderer's first field. (m_clTime)>the address of StudioModelRenderer's last field. (m_plighttransform)pIEngineStudio + */ + void(_stdcall* InitSystem)(void* pStudioRenderer, void* lastFieldAddress, void* engineStudioAPI); + + /* + + Load map geomitry collider. + + */ + void(_stdcall* ChangeLevel)(const char* mapName); + + /* + + ͼ䣬ãϷж̬ĸCollisionObjects + csÿһֽԵ + + */ + void(_stdcall* LevelReset)(); + + /* + + Physics world update + + */ + void(_stdcall* Update)(float delta); + + /* + + */ + void(_stdcall* Pause)(); + + /* + + */ + void(_stdcall* Resume)(); + + /* + + Close physics system and release physics resources. + + */ + void(_stdcall* ShotDown)(); + + /* + + Show configration form. + Using cvar to call this is recommended. + + */ + void(_stdcall* ShowConfigForm)(); + + /* + + */ + void(_stdcall* CreateRagdollController)(int entityId, const char* modelName); + + /* + + */ + void(_stdcall* CreateRagdollControllerIndex)(int entityId, int index); + + /* + + */ + void(_stdcall* CreateRagdollControllerHeader)(int entityId, void* hdr); + + /* + + */ + void(_stdcall* StartRagdoll)(int entityId); + + /* + + */ + void(_stdcall* StopRagdoll)(int entityId); + + /* + + */ + void(_stdcall* SetupBonesPhysically)(int entityId); + + /* + + */ + void(_stdcall* ChangeOwner)(int oldEntity, int newEntity); + + /* + + */ + void(_stdcall* SetVelocity)(int entityId, Vector3* v); + + /* + + */ + void(_stdcall* DisposeRagdollController)(int entityId); + + /* + + Set an explosion on the specified position. + The impact range is calculated automatically via intensity. + + */ + void(_stdcall* Explosion)(Vector3* pos, float intensity); + + /* + + Shoot an invisable bullet to apply impulse to the rigidbody it hits. + eye pos.contains direction and intensity. + */ + void(_stdcall* Shoot)(Vector3* from, Vector3* force); + + /* + + */ + void(_stdcall* PickBody)(); + + /* + + */ + void(_stdcall* ReleaseBody)(); + +}PhsicsAPI; + + +// Containts all the physics system API +// Call [InitPhysicsInterface] before using these API +extern PhsicsAPI gPhysics; + +// Call this function to initialize [gPhysics]. +extern "C" void InitPhysicsInterface(char* msg); \ No newline at end of file diff --git a/projects/vs2019/hl_cdll.vcxproj b/projects/vs2019/hl_cdll.vcxproj index 3aeaa11..03f5040 100644 --- a/projects/vs2019/hl_cdll.vcxproj +++ b/projects/vs2019/hl_cdll.vcxproj @@ -21,7 +21,7 @@ DynamicLibrary true NotSet - v141_xp + v142 DynamicLibrary @@ -42,7 +42,7 @@ true - C:\Program Files (x86)\Steam\steamapps\common\Half-Life\updated\cl_dlls\ + $(Configuration)\$(ProjectName) client $(Configuration)\$(ProjectName)\int\ @@ -141,6 +141,7 @@ + @@ -210,6 +211,8 @@ + + diff --git a/projects/vs2019/hl_cdll.vcxproj.filters b/projects/vs2019/hl_cdll.vcxproj.filters index ec62548..f970f57 100644 --- a/projects/vs2019/hl_cdll.vcxproj.filters +++ b/projects/vs2019/hl_cdll.vcxproj.filters @@ -306,6 +306,9 @@ Source Files\pm_shared + + Source Files\cl_dll + @@ -431,6 +434,12 @@ Header Files\common + + Header Files\cl_dll + + + Header Files\cl_dll +