From f85e1fa09a2ec2429086d0556177307b76d4cd90 Mon Sep 17 00:00:00 2001 From: wootguy Date: Sun, 3 Nov 2024 03:31:02 -0800 Subject: [PATCH] add reloadplugin command + update plugin api exposed more code and added more hooks --- CMakeLists.txt | 8 ++- dlls/PluginHooks.h | 13 +++- dlls/PluginManager.cpp | 46 +++++++++++-- dlls/PluginManager.h | 4 ++ dlls/Scheduler.h | 2 +- dlls/client.cpp | 4 ++ dlls/eng_wrappers.cpp | 16 +++++ dlls/eng_wrappers.h | 23 ++++--- dlls/enginecallback.h | 1 - dlls/env/CAmbientGeneric.h | 6 +- dlls/func/CBaseButton.h | 16 ++--- dlls/game.cpp | 9 +++ dlls/game.h | 128 ++++++++++++++++++------------------ dlls/sound.cpp | 35 ++++++---- dlls/util.h | 14 +++- game_shared/shared_util.h | 5 +- scripts/hlcoop_plugin.cmake | 4 +- 17 files changed, 220 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9e45f59..0620433e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ include_directories(${CMAKE_SOURCE_DIR}/dlls/monster) include_directories(${CMAKE_SOURCE_DIR}/dlls/item) include_directories(${CMAKE_SOURCE_DIR}/dlls/path) include_directories(${CMAKE_SOURCE_DIR}/dlls/weapon) +include_directories(${CMAKE_SOURCE_DIR}/dlls/net) include_directories(${CMAKE_SOURCE_DIR}/dlls/) project(sevenkewp) @@ -45,8 +46,11 @@ if (BUILD_PLUGINS) include(${CMAKE_SOURCE_DIR}/scripts/hlcoop_plugin.cmake) set(PLUGIN_DIR "${CMAKE_CURRENT_LIST_DIR}/plugins") file(GLOB ALL_ITEMS "${PLUGIN_DIR}/*") - message(STATUS "Note: Local plugin changes are discarded when built in BUILD_PLUGINS mode") - + + if (UPDATE_PLUGINS) + message(STATUS "Note: Local plugin changes are discarded when built in UPDATE_PLUGINS mode") + endif() + foreach(ITEM ${ALL_ITEMS}) if(IS_DIRECTORY ${ITEM}) get_filename_component(REPO_NAME ${ITEM} NAME) diff --git a/dlls/PluginHooks.h b/dlls/PluginHooks.h index d3b25180..b65386d4 100644 --- a/dlls/PluginHooks.h +++ b/dlls/PluginHooks.h @@ -1,4 +1,6 @@ #pragma once +#include "weaponinfo.h" +#include "entity_state.h" #define HLCOOP_API_VERSION 2 @@ -77,6 +79,9 @@ struct HLCOOP_PLUGIN_HOOKS { // called when a sound is about to be played, after the mod has handled sound replacement HOOK_RETURN_DATA (*pfnEmitSound)(edict_t* pEntity, int channel, const char* pszSample, float volume, float attenuation, int fFlags, int pitch); + + // called when an ambient sound is about to be played, after the mod has handled sound replacement + HOOK_RETURN_DATA (*pfnEmitAmbientSound)(edict_t* pEntity, const float* vecPos, const char* pszSample, float vol, float attenuation, int fFlags, int pitch); // called when the mod registers a custom user message HOOK_RETURN_DATA (*pfnRegUserMsg)(const char* name, int size); @@ -118,7 +123,13 @@ struct HLCOOP_PLUGIN_HOOKS { HOOK_RETURN_DATA (*pfnPrecacheModelPost)(const char* model); // called before an event is played - HOOK_RETURN_DATA(*pfnPlaybackEvent)(int flags, const edict_t* pInvoker, unsigned short eventindex, float delay, float* origin, float* angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2); + HOOK_RETURN_DATA (*pfnPlaybackEvent)(int flags, const edict_t* pInvoker, unsigned short eventindex, float delay, float* origin, float* angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2); + + // called before weapon data is updated + HOOK_RETURN_DATA (*pfnGetWeaponData)(edict_t* player, weapon_data_t* info); + + // called after client data is updated by the mod and before it is returned to the engine + HOOK_RETURN_DATA (*pfnUpdateClientDataPost)(const edict_t* ent, int sendweapons, clientdata_t* cd); }; EXPORT void RegisterPlugin(void* plugin, HLCOOP_PLUGIN_HOOKS* hooks, const char* name); diff --git a/dlls/PluginManager.cpp b/dlls/PluginManager.cpp index 2ee36104..096070b2 100644 --- a/dlls/PluginManager.cpp +++ b/dlls/PluginManager.cpp @@ -68,6 +68,15 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { plugin.fpath = fpath; plugin.id = g_plugin_id++; + if (LoadPlugin(plugin)) { + plugins.push_back(plugin); + return true; + } + + return false; +} + +bool PluginManager::LoadPlugin(Plugin& plugin) { #ifdef _WIN32 plugin.h_module = LoadLibraryA(plugin.fpath.c_str()); #else @@ -76,7 +85,7 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { if (!plugin.h_module) { ALERT(at_error, "Plugin load failed '%s' (" LOADLIB_FUNC_NAME " error code %d)\n", - fpath, GetLastError()); + plugin.fpath.c_str(), GetLastError()); return false; } @@ -87,7 +96,8 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { int apiVersion = HLCOOP_API_VERSION; if (apiFunc(&plugin, apiVersion)) { ALERT(at_console, "Loaded plugin '%s'\n", plugin.fpath.c_str()); - } else { + } + else { ALERT(at_error, "PluginInit call failed in plugin '%s'.\n", plugin.fpath.c_str()); FreeLibrary((HMODULE)plugin.h_module); return false; @@ -99,7 +109,6 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { return false; } - plugins.push_back(plugin); return true; } @@ -155,6 +164,31 @@ void PluginManager::RemovePlugin(const char* name) { } } +void PluginManager::ReloadPlugin(const char* name) { + int bestIdx = -1; + int numFound = 0; + + std::string lowerName = toLowerCase(name); + + for (int i = 0; i < (int)plugins.size(); i++) { + if (toLowerCase(plugins[i].fpath).find(lowerName) != std::string::npos) { + bestIdx = i; + numFound++; + } + } + + if (numFound == 1) { + UnloadPlugin(plugins[bestIdx]); + LoadPlugin(plugins[bestIdx]); + } + else if (numFound > 1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Multiple plugins contain '%s'. Be more specific.\n", name)); + } + else { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("No plugin found by name '%s'\n", name)); + } +} + void PluginManager::RemovePlugins(bool mapPluginsOnly) { std::vector newPluginList; @@ -481,7 +515,11 @@ void RegisterPlugin(void* pluginptr, HLCOOP_PLUGIN_HOOKS* hooks, const char* nam Plugin* plugin = (Plugin*)pluginptr; plugin->name = name; - memcpy(&plugin->hooks, hooks, sizeof(HLCOOP_PLUGIN_HOOKS)); + + if (hooks) + memcpy(&plugin->hooks, hooks, sizeof(HLCOOP_PLUGIN_HOOKS)); + else + memset(&plugin->hooks, 0, sizeof(HLCOOP_PLUGIN_HOOKS)); } // custom entity loader called by the engine during map load diff --git a/dlls/PluginManager.h b/dlls/PluginManager.h index 0e3d3a4a..a9ee1013 100644 --- a/dlls/PluginManager.h +++ b/dlls/PluginManager.h @@ -34,12 +34,16 @@ class PluginManager { // set isMapPlugin to true if the plugin should only run on the current map bool AddPlugin(const char* fpath, bool isMapPlugin); + bool LoadPlugin(Plugin& plugin); + void UnloadPlugin(const Plugin& plugin); void RemovePlugin(const Plugin& plugin); void RemovePlugin(const char* name); + void ReloadPlugin(const char* name); + void RemovePlugins(bool mapPluginsOnly); // will conditionally load/unload plugins if the config has been updated since the last call, unless forceUpdate=true diff --git a/dlls/Scheduler.h b/dlls/Scheduler.h index 270204e2..2f39f576 100644 --- a/dlls/Scheduler.h +++ b/dlls/Scheduler.h @@ -73,7 +73,7 @@ class Scheduler { void Think(); - void RemoveTimer(ScheduledFunction schedule); + EXPORT void RemoveTimer(ScheduledFunction schedule); void RemoveTimers(const char* owner); }; diff --git a/dlls/client.cpp b/dlls/client.cpp index 5d996018..b0ac7be5 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -1599,6 +1599,8 @@ void RegisterEncoders( void ) int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) { + CALL_HOOKS(int, pfnGetWeaponData, player, info); + #if defined( CLIENT_WEAPONS ) int i; weapon_data_t *item; @@ -1779,6 +1781,8 @@ void UpdateClientData ( const edict_t *ent, int sendweapons, struct clientdata_s } } #endif + + CALL_HOOKS_VOID(pfnUpdateClientDataPost, ent, sendweapons, cd); } /* diff --git a/dlls/eng_wrappers.cpp b/dlls/eng_wrappers.cpp index 81b03d16..60a5e146 100644 --- a/dlls/eng_wrappers.cpp +++ b/dlls/eng_wrappers.cpp @@ -476,7 +476,11 @@ int PRECACHE_EVENT(int id, const char* path) { bool SET_MODEL(edict_t* edict, const char* model) { if (model && model[0] == '*') { // BSP model. No special handling. + CALL_HOOKS(bool, pfnSetModel, edict, model); g_engfuncs.pfnSetModel(edict, model); + if (!g_serveractive) + g_precachedModels[model] = model; // engine precaches entity BSP models automatically + CALL_HOOKS(bool, pfnSetModelPost, edict, model); return false; } @@ -537,6 +541,13 @@ int MODEL_INDEX(const char* model) { return g_engfuncs.pfnModelIndex(model); } +int SOUND_INDEX(const char* sound) { + std::string lowerPath = toLowerCase(sound); + sound = lowerPath.c_str(); + return g_engfuncs.pfnPrecacheSound(sound); +} + + void* GET_MODEL_PTR(edict_t* edict) { studiohdr_t* header = (studiohdr_t*)g_engfuncs.pfnGetModelPtr(edict); @@ -630,6 +641,11 @@ void EMIT_SOUND_DYN2(edict_t* pEntity, int channel, const char* pszSample, float g_engfuncs.pfnEmitSound(pEntity, channel, pszSample, volume, attenuation, fFlags, pitch); } +void EMIT_AMBIENT_SOUND(edict_t* pEntity, const float* vecPos, const char* pszSample, float vol, float attenuation, int fFlags, int pitch) { + CALL_HOOKS_VOID(pfnEmitAmbientSound, pEntity, vecPos, pszSample, vol, attenuation, fFlags, pitch); + g_engfuncs.pfnEmitAmbientSound(pEntity, vecPos, pszSample, vol, attenuation, fFlags, pitch); +} + void SetClientMaxspeed(const edict_t* pEntity, float maxspeed) { CALL_HOOKS_VOID(pfnSetClientMaxspeed, pEntity, maxspeed); g_engfuncs.pfnSetClientMaxspeed(pEntity, maxspeed); diff --git a/dlls/eng_wrappers.h b/dlls/eng_wrappers.h index 612bc9b2..54b46332 100644 --- a/dlls/eng_wrappers.h +++ b/dlls/eng_wrappers.h @@ -5,20 +5,20 @@ class CBaseEntity; -extern Bsp g_bsp; +EXPORT extern Bsp g_bsp; // resources that were successfully precached -extern std::unordered_map g_precachedModels; // storing values so GET_MODEL can be used with MAKE_STRING -extern std::unordered_set g_missingModels; // storing values so GET_MODEL can be used with MAKE_STRING -extern std::unordered_set g_precachedSounds; -extern std::unordered_set g_precachedGeneric; -extern std::unordered_map g_precachedEvents; +EXPORT extern std::unordered_map g_precachedModels; // storing values so GET_MODEL can be used with MAKE_STRING +EXPORT extern std::unordered_set g_missingModels; // storing values so GET_MODEL can be used with MAKE_STRING +EXPORT extern std::unordered_set g_precachedSounds; +EXPORT extern std::unordered_set g_precachedGeneric; +EXPORT extern std::unordered_map g_precachedEvents; // resources that attempted to precache but may have been replaced with a failure model -extern std::unordered_set g_tryPrecacheModels; -extern std::unordered_set g_tryPrecacheSounds; -extern std::unordered_set g_tryPrecacheGeneric; -extern std::unordered_set g_tryPrecacheEvents; +EXPORT extern std::unordered_set g_tryPrecacheModels; +EXPORT extern std::unordered_set g_tryPrecacheSounds; +EXPORT extern std::unordered_set g_tryPrecacheGeneric; +EXPORT extern std::unordered_set g_tryPrecacheEvents; #ifdef CLIENT_DLL #define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) @@ -42,6 +42,7 @@ inline void MESSAGE_BEGIN(int msg_dest, int msg_type, const float* pOrigin = NUL #define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) #define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) #define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) #define CMD_ARGS (*g_engfuncs.pfnCmd_Args) #define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) #define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) @@ -59,6 +60,7 @@ EXPORT bool SET_MODEL(edict_t* edict, const char* model); // returns true if the EXPORT bool SET_MODEL_MERGED(edict_t* edict, const char* model, int mergeId); // will set the merged model and body if the given model was not replaced EXPORT const char* GET_MODEL(const char* model); // return replacement model, if one exists, or the given model EXPORT int MODEL_INDEX(const char* model); +EXPORT int SOUND_INDEX(const char* model); EXPORT void* GET_MODEL_PTR(edict_t* edict); EXPORT edict_t* CREATE_NAMED_ENTITY(string_t cname); #define PRECACHE_SOUND(path) PRECACHE_SOUND_ENT(this, path) @@ -80,6 +82,7 @@ EXPORT void WRITE_STRING(const char* sValue); EXPORT void WRITE_ENTITY(int iValue); EXPORT void EMIT_SOUND_DYN2(edict_t* pEntity, int channel, const char* pszSample, float volume, float attenuation, int fFlags, int pitch); +EXPORT void EMIT_AMBIENT_SOUND(edict_t* pEntity, const float* vecPos, const char* pszSample, float vol, float attenuation, int fFlags, int pitch); EXPORT void SetClientMaxspeed(const edict_t* pEntity, float maxspeed); EXPORT void SetClientKeyValue(int clientIndex, char* pszInfoBuffer, const char* pszKey, const char* pszValue); diff --git a/dlls/enginecallback.h b/dlls/enginecallback.h index bf28e389..7712ceea 100644 --- a/dlls/enginecallback.h +++ b/dlls/enginecallback.h @@ -120,7 +120,6 @@ inline void *GET_PRIVATE( const edict_t *pent ) #define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) #define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) #define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) // Doesn't work as expected in multiplayer. -#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) #define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) #define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) #define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) diff --git a/dlls/env/CAmbientGeneric.h b/dlls/env/CAmbientGeneric.h index cefe224a..0fcf4c8c 100644 --- a/dlls/env/CAmbientGeneric.h +++ b/dlls/env/CAmbientGeneric.h @@ -63,15 +63,15 @@ typedef struct dynpitchvol // presets for runtime pitch and vol modulation of ambient sounds -class CAmbientGeneric : public CBaseEntity +class EXPORT CAmbientGeneric : public CBaseEntity { public: virtual int GetEntindexPriority() { return ENTIDX_PRIORITY_HIGH; } void KeyValue(KeyValueData* pkvd); void Spawn(void); void Precache(void); - void EXPORT ToggleUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value); - void EXPORT RampThink(void); + void ToggleUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value); + void RampThink(void); void InitModulationParms(void); void InitSoundForNewJoiner(edict_t* target); diff --git a/dlls/func/CBaseButton.h b/dlls/func/CBaseButton.h index f1414b27..aa1155b5 100644 --- a/dlls/func/CBaseButton.h +++ b/dlls/func/CBaseButton.h @@ -14,7 +14,7 @@ void DoSpark(entvars_t* pev, const Vector& location); // // Generic Button // -class CBaseButton : public CBaseToggle +class EXPORT CBaseButton : public CBaseToggle { public: void Spawn(void); @@ -25,13 +25,13 @@ class CBaseButton : public CBaseToggle void ButtonActivate(); void SparkSoundCache(void); - void EXPORT ButtonShot(void); - void EXPORT ButtonTouch(CBaseEntity* pOther); - void EXPORT ButtonSpark(void); - void EXPORT TriggerAndWait(void); - void EXPORT ButtonReturn(void); - void EXPORT ButtonBackHome(void); - void EXPORT ButtonUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value); + void ButtonShot(void); + void ButtonTouch(CBaseEntity* pOther); + void ButtonSpark(void); + void TriggerAndWait(void); + void ButtonReturn(void); + void ButtonBackHome(void); + void ButtonUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value); virtual int TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); virtual int Save(CSave& save); virtual int Restore(CRestore& restore); diff --git a/dlls/game.cpp b/dlls/game.cpp index 5fbcaea3..ebb939da 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -220,6 +220,14 @@ void remove_plugin() { g_pluginManager.RemovePlugin(CMD_ARGV(1)); } +void reload_plugin() { + if (CMD_ARGC() < 2) { + return; + } + + g_pluginManager.ReloadPlugin(CMD_ARGV(1)); +} + void test_command() { } @@ -239,6 +247,7 @@ void GameDLLInit( void ) g_engfuncs.pfnAddServerCommand("reloadplugins", reload_plugins); g_engfuncs.pfnAddServerCommand("listplugins", list_plugins); g_engfuncs.pfnAddServerCommand("removeplugin", remove_plugin); + g_engfuncs.pfnAddServerCommand("reloadplugin", reload_plugin); // Register cvars here: g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); diff --git a/dlls/game.h b/dlls/game.h index 0d31a855..60c246c7 100644 --- a/dlls/game.h +++ b/dlls/game.h @@ -34,71 +34,71 @@ extern void GameDLLInit( void ); #define FRIEND_TEAM_COLOR 4 #define OBSERVER_TEAM_COLOR 6 // spectator color (white on windows, still blue on linux?) -extern cvar_t displaysoundlist; +EXPORT extern cvar_t displaysoundlist; // multiplayer server rules -extern cvar_t teamplay; -extern cvar_t fraglimit; -extern cvar_t timelimit; -extern cvar_t friendlyfire; -extern cvar_t falldamage; -extern cvar_t weaponstay; -extern cvar_t item_despawn_time; -extern cvar_t item_repick_time; -extern cvar_t max_item_drops; -extern cvar_t forcerespawn; -extern cvar_t flashlight; -extern cvar_t aimcrosshair; -extern cvar_t decalfrequency; -extern cvar_t teamlist; -extern cvar_t teamoverride; -extern cvar_t defaultteam; -extern cvar_t allowmonsters; -extern cvar_t mp_nextmap; // map which will load after the next intermission -extern cvar_t mp_starthealth; -extern cvar_t mp_startarmor; -extern cvar_t mp_bulletsponges; // prevent mappers setting high npc health for no good reason -extern cvar_t mp_bulletspongemax; // max health multiplier for any monster -extern cvar_t mp_maxmonsterrespawns; // limit monster respawns which affect no entity logic -extern cvar_t mp_edictsorting; // sorts edict list by entity index priority -extern cvar_t mp_shitcode; // conditionally enables shitty code that fixes critical problems in specific maps, but subtly breaks many others -extern cvar_t mp_survival_supported; -extern cvar_t mp_survival_starton; -extern cvar_t mp_survival_restart; -extern cvar_t mp_mergemodels; // used merged models to save on model slots -extern cvar_t mp_killfeed; // 0 = off, 1 = player deaths, 2 = player kills/deaths, 3 = player + monster kills/deaths -extern cvar_t pluginlistfile; // name of the plugin list file -extern cvar_t adminlistfile; // name of the admin list file +EXPORT extern cvar_t teamplay; +EXPORT extern cvar_t fraglimit; +EXPORT extern cvar_t timelimit; +EXPORT extern cvar_t friendlyfire; +EXPORT extern cvar_t falldamage; +EXPORT extern cvar_t weaponstay; +EXPORT extern cvar_t item_despawn_time; +EXPORT extern cvar_t item_repick_time; +EXPORT extern cvar_t max_item_drops; +EXPORT extern cvar_t forcerespawn; +EXPORT extern cvar_t flashlight; +EXPORT extern cvar_t aimcrosshair; +EXPORT extern cvar_t decalfrequency; +EXPORT extern cvar_t teamlist; +EXPORT extern cvar_t teamoverride; +EXPORT extern cvar_t defaultteam; +EXPORT extern cvar_t allowmonsters; +EXPORT extern cvar_t mp_nextmap; // map which will load after the next intermission +EXPORT extern cvar_t mp_starthealth; +EXPORT extern cvar_t mp_startarmor; +EXPORT extern cvar_t mp_bulletsponges; // prevent mappers setting high npc health for no good reason +EXPORT extern cvar_t mp_bulletspongemax; // max health multiplier for any monster +EXPORT extern cvar_t mp_maxmonsterrespawns; // limit monster respawns which affect no entity logic +EXPORT extern cvar_t mp_edictsorting; // sorts edict list by entity index priority +EXPORT extern cvar_t mp_shitcode; // conditionally enables shitty code that fixes critical problems in specific maps, but subtly breaks many others +EXPORT extern cvar_t mp_survival_supported; +EXPORT extern cvar_t mp_survival_starton; +EXPORT extern cvar_t mp_survival_restart; +EXPORT extern cvar_t mp_mergemodels; // used merged models to save on model slots +EXPORT extern cvar_t mp_killfeed; // 0 = off, 1 = player deaths, 2 = player kills/deaths, 3 = player + monster kills/deaths +EXPORT extern cvar_t pluginlistfile; // name of the plugin list file +EXPORT extern cvar_t adminlistfile; // name of the admin list file // Enables classic func_pushable physics (which is horribly broken, but fun) // The higher your FPS, the faster you can boost pushables. You also get boosted. -extern cvar_t mp_objectboost; +EXPORT extern cvar_t mp_objectboost; // write network messages to log file for debugging -extern cvar_t mp_debugmsg; +EXPORT extern cvar_t mp_debugmsg; -extern cvar_t mp_respawndelay; +EXPORT extern cvar_t mp_respawndelay; // if a map cfg asks for a default max speed (320 for Half-Life, 270 for Sven Co-op), // then ignore the command and use whatever was set up by the server.cfg -extern cvar_t mp_prefer_server_maxspeed; +EXPORT extern cvar_t mp_prefer_server_maxspeed; // limits monster sound variety to save precache slots. // 0 disables. 1+ = max sounds per action (death/pain/idle/etc.) -extern cvar_t soundvariety; +EXPORT extern cvar_t soundvariety; -extern cvar_t mp_npckill; -extern cvar_t killnpc; // legacy setting. When set to 0, makes scientists and barneys invulnerable +EXPORT extern cvar_t mp_npckill; +EXPORT extern cvar_t killnpc; // legacy setting. When set to 0, makes scientists and barneys invulnerable // Engine Cvars -extern cvar_t *g_psv_gravity; -extern cvar_t *g_psv_aim; -extern cvar_t *g_psv_allow_autoaim; -extern cvar_t *g_footsteps; -extern cvar_t *g_developer; -extern cvar_t *sv_max_client_edicts; -extern cvar_t *sv_stepsize; -extern cvar_t *sv_lowercase; +EXPORT extern cvar_t *g_psv_gravity; +EXPORT extern cvar_t *g_psv_aim; +EXPORT extern cvar_t *g_psv_allow_autoaim; +EXPORT extern cvar_t *g_footsteps; +EXPORT extern cvar_t *g_developer; +EXPORT extern cvar_t *sv_max_client_edicts; +EXPORT extern cvar_t *sv_stepsize; +EXPORT extern cvar_t *sv_lowercase; struct NerfStats { int nerfedMonsterHealth; @@ -111,7 +111,7 @@ struct NerfStats { int totalMonsterHealth; }; -extern NerfStats g_nerfStats; +EXPORT extern NerfStats g_nerfStats; // flags texture types that are present in the current map // used to conditionally precache step/impact sounds @@ -129,34 +129,34 @@ struct TextureTypeStats { bool tex_flesh; }; -extern TextureTypeStats g_textureStats; +EXPORT extern TextureTypeStats g_textureStats; -extern std::unordered_map g_modelReplacementsMap; // model replacements for the current map -extern std::unordered_map g_modelReplacementsMod; // model replacements for this mod -extern std::unordered_map g_modelReplacements; // combined model replacements +EXPORT extern std::unordered_map g_modelReplacementsMap; // model replacements for the current map +EXPORT extern std::unordered_map g_modelReplacementsMod; // model replacements for this mod +EXPORT extern std::unordered_map g_modelReplacements; // combined model replacements -extern std::unordered_map g_soundReplacementsMod; // sound replacements for the current map -extern std::unordered_map g_soundReplacementsMap; // sound replacements for this mod -extern std::unordered_map g_soundReplacements; // combined sound replacements +EXPORT extern std::unordered_map g_soundReplacementsMod; // sound replacements for the current map +EXPORT extern std::unordered_map g_soundReplacementsMap; // sound replacements for this mod +EXPORT extern std::unordered_map g_soundReplacements; // combined sound replacements -extern std::unordered_set g_mapWeapons; // weapons which should be precached (don't use aliases here) -extern std::unordered_map g_itemNameRemap; +EXPORT extern std::unordered_set g_mapWeapons; // weapons which should be precached (don't use aliases here) +EXPORT extern std::unordered_map g_itemNameRemap; // per-monster sound replacement maps // should be a class member, but I'm afraid of the bugs that will come from using non-POD class members -extern std::vector> g_monsterSoundReplacements; +EXPORT extern std::vector> g_monsterSoundReplacements; // map for each entity, containing custom keyvalues // using a global vector instead of a class member because the map is not POD -extern std::vector> g_customKeyValues; +EXPORT extern std::vector> g_customKeyValues; -extern CKeyValue g_emptyKeyValue; // a keyvalue initialized with zeroes +EXPORT extern CKeyValue g_emptyKeyValue; // a keyvalue initialized with zeroes -extern std::unordered_set g_shuffledMonsterSounds; // classes that had their sounds shuffled this map +EXPORT extern std::unordered_set g_shuffledMonsterSounds; // classes that had their sounds shuffled this map -extern bool g_cfgsExecuted; // set to true after server and map cfgs are executed +EXPORT extern bool g_cfgsExecuted; // set to true after server and map cfgs are executed // mark a palyer weapon for precaching (alias names are ok) -void AddPrecacheWeapon(std::string wepName); +EXPORT void AddPrecacheWeapon(std::string wepName); #endif // GAME_H diff --git a/dlls/sound.cpp b/dlls/sound.cpp index a9a9234c..46378cf2 100644 --- a/dlls/sound.cpp +++ b/dlls/sound.cpp @@ -451,15 +451,8 @@ int SENTENCEG_Lookup(const char *sample, char *sentencenum, int bufsz) return -1; } -// rehlds -#define DEFAULT_SOUND_PACKET_VOLUME 255 -#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0f -#define DEFAULT_SOUND_PACKET_PITCH 100 -#define MAX_EDICT_BITS 11 - -void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, float attenuation, - int fFlags, int pitch, const float* origin, uint32_t messageTargets, bool reliable) -{ +mstream* BuildStartSoundMessage(edict_t* entity, int channel, const char* sample, float fvolume, float attenuation, + int fFlags, int pitch, const float* origin) { int sound_num; int field_mask; @@ -472,7 +465,7 @@ void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, if (sound_num >= CVOXFILESENTENCEMAX) { ALERT(at_console, "%s: invalid sentence number: %s\n", __func__, sample + 1); - return; + return NULL; } } else if (*sample == '#') @@ -487,7 +480,7 @@ void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, int ient = ENTINDEX(entity); int volume = clampf(fvolume, 0, 1.0f) * 255; - + if (volume != DEFAULT_SOUND_PACKET_VOLUME) field_mask |= SND_FL_VOLUME; if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) @@ -499,9 +492,10 @@ void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, const int maxStartSoundMessageSz = 16; static uint8_t msgbuffer[maxStartSoundMessageSz]; + static mstream bitbuffer((char*)msgbuffer, 16); memset(msgbuffer, 0, maxStartSoundMessageSz); - mstream bitbuffer((char*)msgbuffer, 16); + bitbuffer.seek(0); bitbuffer.writeBits(field_mask, 9); if (field_mask & SND_FL_VOLUME) @@ -517,9 +511,22 @@ void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, if (bitbuffer.eom()) { ALERT(at_error, "StartSound bit buffer overflow\n"); + return NULL; + } + + return &bitbuffer; +} + +void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, float attenuation, + int fFlags, int pitch, const float* origin, uint32_t messageTargets, bool reliable) +{ + mstream* bitbuffer = BuildStartSoundMessage(entity, channel, sample, fvolume, attenuation, fFlags, pitch, origin); + + if (!bitbuffer) { + return; } - int msgSz = bitbuffer.tell() + 1; + int msgSz = bitbuffer->tell() + 1; //bool anyMessagesWritten = false; for (int i = 1; i <= gpGlobals->maxClients; i++) { @@ -532,7 +539,7 @@ void StartSound(edict_t* entity, int channel, const char* sample, float fvolume, // TODO: should only consider PAS (can hear reload sounds but only distant gunshots) MESSAGE_BEGIN(reliable ? MSG_ONE : MSG_ONE_UNRELIABLE, SVC_SOUND, NULL, plent); - WRITE_BYTES(msgbuffer, msgSz); + WRITE_BYTES((uint8_t*)bitbuffer->getBuffer(), msgSz); MESSAGE_END(); //anyMessagesWritten = true; } diff --git a/dlls/util.h b/dlls/util.h index f5a2a22b..0225eabe 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -54,7 +54,7 @@ extern int g_edictsinit; // 1 if all edicts were allocated so that relocations c extern std::unordered_map g_admins; -extern std::string g_mp3Command; // current global mp3 command +EXPORT extern std::string g_mp3Command; // current global mp3 command extern TYPEDESCRIPTION gEntvarsDescription[]; extern const int ENTVARS_COUNT; @@ -243,7 +243,7 @@ inline uint32_t PLRBIT(const edict_t* pEdict) { return 1 << (ENTINDEX(pEdict) inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { MESSAGE_BEGIN(msg_dest, msg_type, pOrigin, ENT(ent)); } -void WRITE_BYTES(uint8_t* bytes, int count); +EXPORT void WRITE_BYTES(uint8_t* bytes, int count); // Testing the three types of "entity" for nullity #define eoNullEntity 0 @@ -662,6 +662,16 @@ EXPORT float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecE EXPORT void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch); +// rehlds +#define DEFAULT_SOUND_PACKET_VOLUME 255 +#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0f +#define DEFAULT_SOUND_PACKET_PITCH 100 +#define MAX_EDICT_BITS 11 + +// uses a static buffer, returns NULL on failure +EXPORT mstream* BuildStartSoundMessage(edict_t* entity, int channel, const char* sample, float fvolume, + float attenuation, int fFlags, int pitch, const float* origin); + // play the sound for players with bits contained in messageTargets // a player bit = 1 << (ENTINDEX(player_edict) % 31) EXPORT void StartSound(edict_t* entity, int channel, const char* sample, float volume, float attenuation, diff --git a/game_shared/shared_util.h b/game_shared/shared_util.h index 88417e3d..94d0fa32 100644 --- a/game_shared/shared_util.h +++ b/game_shared/shared_util.h @@ -2,10 +2,11 @@ * util functions that both the serverand client need */ #pragma once +#include "Platform.h" #include // same as strncpy except it ensures the destination is null terminated, even if the buffer is too small -char* strcpy_safe(char* dest, const char* src, size_t size); +EXPORT char* strcpy_safe(char* dest, const char* src, size_t size); // same as strncat except it ensures the destination is null terminated, even if the buffer is too small -char* strcat_safe(char* dest, const char* src, size_t size); +EXPORT char* strcat_safe(char* dest, const char* src, size_t size); diff --git a/scripts/hlcoop_plugin.cmake b/scripts/hlcoop_plugin.cmake index e2c1e875..11338461 100644 --- a/scripts/hlcoop_plugin.cmake +++ b/scripts/hlcoop_plugin.cmake @@ -35,12 +35,12 @@ function(hlcoop_setup_plugin OUTPUT_PATH) endif() target_compile_definitions(${PROJECT_NAME} PRIVATE -DQUIVER -DVOXEL -DQUAKE2 -DVALVE_DLL -DCLIENT_WEAPONS -D_CRT_SECURE_NO_DEPRECATE) - target_compile_definitions(${PROJECT_NAME} PRIVATE -DPLUGIN_BUILD PLUGIN_NAME="${PROJECT_NAME}") + target_compile_definitions(${PROJECT_NAME} PRIVATE -DPLUGIN_BUILD -DHLCOOP_BUILD PLUGIN_NAME="${PROJECT_NAME}") target_link_libraries(${PROJECT_NAME} PRIVATE ${SERVER_DLL_NAME}) if (SETUP_IDE) - set(PLUGIN_OUT_PATH "${SERVER_WORK_DIR}/valve_downloads/${OUTPUT_PATH}") + set(PLUGIN_OUT_PATH "${SERVER_WORK_DIR}/valve/${OUTPUT_PATH}") set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_COMMAND "${SERVER_WORK_DIR}/${SERVER_EXE}" VS_DEBUGGER_WORKING_DIRECTORY "${SERVER_WORK_DIR}"