From df1a8671ee798f9fc8c28f1214ad8ae0cebff404 Mon Sep 17 00:00:00 2001 From: wootguy Date: Sun, 27 Oct 2024 23:40:55 -0700 Subject: [PATCH] update plugin api and add helpers copied Scheduler, CommandArgs, admin logic from mmlib. Added a few more hooks. Plugins send the game a name so they can be identified when calling game functions (needed for clearing timers when a plugin is unloaded). Added a "removeplugin" command and "adminlistfile" cvar. "pluginlist" cvar renamed to "pluginlistfile". --- dlls/CMakeLists.txt | 4 ++ dlls/CommandArgs.cpp | 74 +++++++++++++++++++ dlls/CommandArgs.h | 26 +++++++ dlls/PluginHooks.h | 29 ++++++-- dlls/PluginManager.cpp | 131 +++++++++++++++++++++++++++------- dlls/PluginManager.h | 10 ++- dlls/Scheduler.cpp | 68 ++++++++++++++++++ dlls/Scheduler.h | 81 +++++++++++++++++++++ dlls/client.cpp | 23 ++++-- dlls/client_commands.cpp | 47 ++++++------ dlls/game.cpp | 15 +++- dlls/game.h | 3 +- dlls/util.cpp | 91 +++++++++++++++++++++++ dlls/util.h | 29 ++++++-- game_shared/voice_gamemgr.cpp | 4 +- 15 files changed, 563 insertions(+), 72 deletions(-) create mode 100644 dlls/CommandArgs.cpp create mode 100644 dlls/CommandArgs.h create mode 100644 dlls/Scheduler.cpp create mode 100644 dlls/Scheduler.h diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index c28f35ee..0b320aca 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -415,11 +415,13 @@ set(UTIL_HDR Bsp.h bsptypes.h bsplimits.h + CommandArgs.h plane.h util.h mstream.h lagcomp.h vector.h + Scheduler.h TextMenu.h ../game_shared/shared_util.h ) @@ -427,11 +429,13 @@ set(UTIL_SRC animation.cpp Bsp.cpp bsptypes.cpp + CommandArgs.cpp plane.cpp sound.cpp util.cpp mstream.cpp lagcomp.cpp + Scheduler.cpp TextMenu.cpp ../pm_shared/pm_debug.cpp ../pm_shared/pm_math.cpp diff --git a/dlls/CommandArgs.cpp b/dlls/CommandArgs.cpp new file mode 100644 index 00000000..e6ccfede --- /dev/null +++ b/dlls/CommandArgs.cpp @@ -0,0 +1,74 @@ +#include "CommandArgs.h" +#include "util.h" + +CommandArgs::CommandArgs() { + +} + +void CommandArgs::loadArgs() { + std::string firstArgLower = toLowerCase(CMD_ARGV(0)); + isConsoleCmd = firstArgLower != "say" && firstArgLower != "say_team"; + + std::string argStr = CMD_ARGC() > 1 ? CMD_ARGS() : ""; + + if (isConsoleCmd) { + argStr = CMD_ARGV(0) + std::string(" ") + argStr; + } + + if (!isConsoleCmd && argStr.length() > 2 && argStr[0] == '\"' && argStr[argStr.length() - 1] == '\"') { + argStr = argStr.substr(1, argStr.length() - 2); // strip surrounding quotes + } + + while (!argStr.empty()) { + // strip spaces + argStr = trimSpaces(argStr); + + + if (argStr[0] == '\"') { // quoted argument (include the spaces between quotes) + argStr = argStr.substr(1); + int endQuote = argStr.find("\""); + + if (endQuote == -1) { + args.push_back(argStr); + break; + } + + args.push_back(argStr.substr(0, endQuote)); + argStr = argStr.substr(endQuote + 1); + } + else { + // normal argument, separate by space + int nextSpace = argStr.find(" "); + + if (nextSpace == -1) { + args.push_back(argStr); + break; + } + + args.push_back(argStr.substr(0, nextSpace)); + argStr = argStr.substr(nextSpace + 1); + } + } +} + +std::string CommandArgs::ArgV(int idx) { + if (idx >= 0 && idx < (int)args.size()) { + return args[idx]; + } + + return ""; +} + +int CommandArgs::ArgC() { + return args.size(); +} + +std::string CommandArgs::getFullCommand() { + std::string str = ArgV(0); + + for (int i = 1; i < (int)args.size(); i++) { + str += " " + args[i]; + } + + return str; +} diff --git a/dlls/CommandArgs.h b/dlls/CommandArgs.h new file mode 100644 index 00000000..e6e69add --- /dev/null +++ b/dlls/CommandArgs.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#include + +struct CommandArgs { + bool isConsoleCmd; + + // gets current globally defined args + EXPORT CommandArgs(); + + EXPORT void loadArgs(); + + // returns empty string if idx is out of bounds + EXPORT std::string ArgV(int idx); + + // return number of args + EXPORT int ArgC(); + + // return entire command string + EXPORT std::string getFullCommand(); + +private: + std::vector args; +}; diff --git a/dlls/PluginHooks.h b/dlls/PluginHooks.h index d54e7d55..41731b1d 100644 --- a/dlls/PluginHooks.h +++ b/dlls/PluginHooks.h @@ -1,3 +1,4 @@ +#pragma once #include "mod_api.h" struct HOOK_RETURN_DATA { @@ -21,24 +22,40 @@ struct HLCOOP_PLUGIN_HOOKS { HOOK_RETURN_DATA (*pfnMapActivate)(); // called before the player PreThink function - HOOK_RETURN_DATA (*pfnPlayerPreThink)(CBasePlayer*); + HOOK_RETURN_DATA (*pfnPlayerPreThink)(CBasePlayer* pPlayer); // called before the player PostThink function - HOOK_RETURN_DATA (*pfnPlayerPostThink)(CBasePlayer*); + HOOK_RETURN_DATA (*pfnPlayerPostThink)(CBasePlayer* pPlayer); // called before the player Use function is called - HOOK_RETURN_DATA (*pfnPlayerUse)(CBasePlayer*); + HOOK_RETURN_DATA (*pfnPlayerUse)(CBasePlayer* pPlayer); + + // called before a client command is processed + HOOK_RETURN_DATA (*pfnClientCommand)(CBasePlayer* pPlayer); + + // called when a client connects to the server. Return 0 to reject the connection with the given reason. + HOOK_RETURN_DATA (*pfnClientConnect)(edict_t* pEntity, const char* pszName, const char* pszAddress, char szRejectReason[128]); + + // called when a player is fully connected to the server and is about to spawn + HOOK_RETURN_DATA (*pfnClientPutInServer)(CBasePlayer* pPlayer); }; +EXPORT cvar_t* RegisterPluginCVar(void* plugin, char* name, char* strDefaultValue, int intDefaultValue, int flags); + +EXPORT void RegisterPlugin(void* plugin, HLCOOP_PLUGIN_HOOKS* hooks, const char* name); + // boilerplate for PluginInit functions // must be inline so that plugins don't reference the game definition of HLCOOP_API_VERSION -inline int InitPluginApi(HLCOOP_PLUGIN_HOOKS* destApi, HLCOOP_PLUGIN_HOOKS* srcApi, int interfaceVersion) { +inline int InitPluginApi(void* plugin, HLCOOP_PLUGIN_HOOKS* srcApi, int interfaceVersion) { if (interfaceVersion != HLCOOP_API_VERSION) { ALERT(at_error, "Plugin API version mismatch. Game wanted: %d, Plugin has: %d\n", interfaceVersion, HLCOOP_API_VERSION); return 0; } - memcpy(destApi, srcApi, sizeof(HLCOOP_PLUGIN_HOOKS)); - +#ifdef PLUGIN_NAME + RegisterPlugin(plugin, srcApi, PLUGIN_NAME); +#else + ALERT(at_error, "Plugin was not compiled correctly. PLUGIN_NAME is undefined.\n"); +#endif return 1; } \ No newline at end of file diff --git a/dlls/PluginManager.cpp b/dlls/PluginManager.cpp index 257d8a1b..8007a5ff 100644 --- a/dlls/PluginManager.cpp +++ b/dlls/PluginManager.cpp @@ -3,9 +3,22 @@ #include "PluginManager.h" #include "cbase.h" #include +#include "Scheduler.h" PluginManager g_pluginManager; +#define MAX_PLUGIN_CVARS 256 + +struct ExternalCvar { + int pluginId; + cvar_t cvar; +}; + +ExternalCvar g_plugin_cvars[MAX_PLUGIN_CVARS]; +int g_plugin_cvar_count = 0; + +int g_plugin_id = 0; + #ifdef _WIN32 #include "windows.h" #define LOADLIB_FUNC_NAME "LoadLibrary" @@ -20,11 +33,10 @@ PluginManager g_pluginManager; #define HMODULE void* #endif -typedef int(*PLUGIN_INIT_FUNCTION)(HLCOOP_PLUGIN_HOOKS* pFunctionTable, int interfaceVersion); +typedef int(*PLUGIN_INIT_FUNCTION)(void* plugin, int interfaceVersion); typedef void(*PLUGIN_EXIT_FUNCTION)(void); bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { - std::string gamePath = fpath; if (isMapPlugin) { @@ -44,6 +56,7 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { plugin.isMapPlugin = isMapPlugin; plugin.fpath = fpath; + plugin.id = g_plugin_id++; #ifdef _WIN32 plugin.h_module = LoadLibraryA(plugin.fpath.c_str()); @@ -62,7 +75,7 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { if (apiFunc) { int apiVersion = HLCOOP_API_VERSION; - if (apiFunc(&plugin.hooks, apiVersion)) { + if (apiFunc(&plugin, apiVersion)) { ALERT(at_console, "Loaded plugin '%s'\n", plugin.fpath.c_str()); } else { ALERT(at_error, "PluginInit call failed in plugin '%s'.\n", plugin.fpath.c_str()); @@ -91,6 +104,8 @@ void PluginManager::UnloadPlugin(const Plugin& plugin) { ALERT(at_console, "PluginExit not found in plugin '%s'\n", plugin.fpath.c_str()); } + g_Scheduler.RemoveTimers(plugin.name); + FreeLibrary((HMODULE)plugin.h_module); ALERT(at_console, "Removed plugin: '%s'\n", plugin.fpath.c_str()); } @@ -106,6 +121,30 @@ void PluginManager::RemovePlugin(const Plugin& plugin) { } } +void PluginManager::RemovePlugin(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) { + RemovePlugin(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; @@ -123,12 +162,12 @@ void PluginManager::RemovePlugins(bool mapPluginsOnly) { void PluginManager::UpdateServerPlugins(bool forceUpdate) { static uint64_t lastEditTime = 0; - const char* configPath = CVAR_GET_STRING("pluginlist"); + const char* configPath = CVAR_GET_STRING("pluginlistfile"); std::string path = getGameFilePath(configPath); if (path.empty()) { - ALERT(at_warning, "Missing plugin config: '%s'\n", configPath); + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Missing plugin config: '%s'\n", configPath)); } uint64_t editTime = getFileModifiedTime(path.c_str()); @@ -137,15 +176,15 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) { return; // no changes made } - lastEditTime = editTime; - std::ifstream infile(path); if (!infile.is_open()) { - ALERT(at_console, "Failed to open plugin config: '%s'\n", path.c_str()); + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Failed to open plugin config: '%s'\n", path.c_str())); return; } + lastEditTime = editTime; + std::vector pluginPaths; int lineNum = 0; @@ -163,8 +202,8 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) { std::string gamePath = getGameFilePath(pluginPath.c_str()); if (gamePath.empty()) { - ALERT(at_console, "Error on line %d of '%s'. Plugin not found: '%s'\n", - lineNum, path.c_str(), pluginPath.c_str()); + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Error on line %d of '%s'. Plugin not found: '%s'\n", + lineNum, path.c_str(), pluginPath.c_str())); continue; } @@ -236,10 +275,11 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) { }); if (numFailed) { - ALERT(at_error, "Loaded %d plugins. %d plugins failed to load.\n", numLoaded, numFailed); + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Loaded %d plugins. %d plugins failed to load.\n", + numLoaded, numFailed)); } else { - ALERT(at_console, "Loaded %d plugins\n", numLoaded); + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Loaded %d plugins\n", numLoaded)); } } @@ -270,24 +310,14 @@ void PluginManager::ListPlugins(edict_t* plr) { for (int i = 0; i < (int)plugins.size(); i++) { const Plugin& plugin = plugins[i]; - std::string name = plugin.fpath; - int lastSlash = name.find_last_of("/"); - if (lastSlash != -1) { - name = name.substr(lastSlash + 1); - } - int lastDot = name.find_last_of("."); - if (lastDot != -1) { - name = name.substr(0, lastDot); - } - const char* type = plugin.isMapPlugin ? "MAP" : "SERVER"; if (isAdmin) { - lines.push_back(UTIL_VarArgs("%2d) %-20s %-8s %-44s\n", i + 1, name.c_str(), type, plugin.fpath.c_str())); + lines.push_back(UTIL_VarArgs("%2d) %-20s %-8s %-44s\n", i + 1, plugin.name, type, plugin.fpath.c_str())); } else { // player shouldn't know how the server files are laid out - lines.push_back(UTIL_VarArgs("%2d) %-20s %-8s\n", i + 1, name.c_str(), type)); + lines.push_back(UTIL_VarArgs("%2d) %-20s %-8s\n", i + 1, plugin.name, type)); } } lines.push_back("--------------------------------------------------------------------------------\n"); @@ -314,6 +344,59 @@ ENTITYINIT PluginManager::GetCustomEntityInitFunc(const char* pname) { return NULL; } +cvar_t* RegisterPluginCVar(void* pluginptr, char* name, char* strDefaultValue, int intDefaultValue, int flags) { + if (!pluginptr) { + return NULL; + } + + Plugin* plugin = (Plugin*)pluginptr; + + if (g_plugin_cvar_count >= MAX_PLUGIN_CVARS) { + ALERT(at_error, "Plugin cvar limit exceeded! Failed to register: %s\n", name); + return NULL; + } + + cvar_t* existing = CVAR_GET_POINTER(name); + if (existing) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Plugin cvar already registered: %s\n", name)); + + // update the owner of the cvar + for (int i = 0; i < MAX_PLUGIN_CVARS; i++) { + if (existing == &g_plugin_cvars[i].cvar) { + g_plugin_cvars[i].pluginId = plugin->id; + } + } + + return existing; + } + + ExternalCvar& extvar = g_plugin_cvars[g_plugin_cvar_count]; + + extvar.cvar.name = STRING(ALLOC_STRING(name)); + extvar.cvar.string = STRING(ALLOC_STRING(strDefaultValue)); + extvar.cvar.flags = flags | FCVAR_EXTDLL; + extvar.cvar.value = intDefaultValue; + extvar.cvar.next = NULL; + extvar.pluginId = plugin->id; + + g_plugin_cvar_count++; + + CVAR_REGISTER(&extvar.cvar); + + return CVAR_GET_POINTER(name); +} + +void RegisterPlugin(void* pluginptr, HLCOOP_PLUGIN_HOOKS* hooks, const char* name) { + if (!pluginptr) { + return; + } + + Plugin* plugin = (Plugin*)pluginptr; + + plugin->name = name; + memcpy(&plugin->hooks, hooks, sizeof(HLCOOP_PLUGIN_HOOKS)); +} + // custom entity loader called by the engine during map load extern "C" DLLEXPORT void custom(entvars_t * pev) { ENTITYINIT initFunc = g_pluginManager.GetCustomEntityInitFunc(STRING(pev->classname)); diff --git a/dlls/PluginManager.h b/dlls/PluginManager.h index 716af710..c745a0cb 100644 --- a/dlls/PluginManager.h +++ b/dlls/PluginManager.h @@ -5,11 +5,13 @@ #include "const.h" struct Plugin { - std::string fpath; - void* h_module; // handle to the library HLCOOP_PLUGIN_HOOKS hooks; + const char* name; // must be unique. Each plugin defines this + void* h_module; // handle to the library bool isMapPlugin; + int id; // unique per server instance int sortId; // not unique + std::string fpath; }; typedef void(*ENTITYINIT)(struct entvars_s*); @@ -36,6 +38,8 @@ class PluginManager { void RemovePlugin(const Plugin& plugin); + void RemovePlugin(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 @@ -77,4 +81,4 @@ class PluginManager { ENTITYINIT GetCustomEntityInitFunc(const char* pname); }; -extern PluginManager g_pluginManager; \ No newline at end of file +extern PluginManager g_pluginManager; diff --git a/dlls/Scheduler.cpp b/dlls/Scheduler.cpp new file mode 100644 index 00000000..4d1199ae --- /dev/null +++ b/dlls/Scheduler.cpp @@ -0,0 +1,68 @@ +#include "Scheduler.h" +#include "util.h" +#include + +using namespace std; + +Scheduler g_Scheduler; +unsigned int g_schedule_id = 1; + +void Scheduler::Think() { + float now = g_engfuncs.pfnTime(); + + vector> funcsToCall; + + for (int i = 0; i < (int)functions.size(); i++) { + ScheduledFunction_internal& func = functions[i]; + + if (now - func.lastCall < func.delay) { + continue; + } + + // wait to call function in case it adds/removes schedules and messes up this loop + funcsToCall.push_back(func.func); + + func.lastCall = now; + func.callCount++; + + if (func.maxCalls >= 0 && func.callCount >= func.maxCalls) { + functions.erase(functions.begin() + i); + i--; + } + } + + for (int i = 0; i < (int)funcsToCall.size(); i++) { + funcsToCall[i](); + } +} + +void Scheduler::RemoveTimer(ScheduledFunction sched) { + for (int i = 0; i < (int)functions.size(); i++) { + if (functions[i].scheduleId == sched.scheduleId) { + functions.erase(functions.begin() + i); + return; + } + } +} + +void Scheduler::RemoveTimers(const char* owner) { + std::vector newFuncs; + + for (int i = 0; i < (int)functions.size(); i++) { + if (strcmp(functions[i].owner, owner)) { + newFuncs.push_back(functions[i]); + return; + } + } + + functions = newFuncs; +} + +bool ScheduledFunction::HasBeenRemoved() { + for (int i = 0; i < (int)g_Scheduler.functions.size(); i++) { + if (g_Scheduler.functions[i].scheduleId == scheduleId) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/dlls/Scheduler.h b/dlls/Scheduler.h new file mode 100644 index 00000000..270204e2 --- /dev/null +++ b/dlls/Scheduler.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include "enginecallback.h" +#include +#include + +struct ScheduledFunction_internal { + std::function func; + const char* owner; + float delay; + int callCount; + int maxCalls; // infinite if < 0 + float lastCall; + unsigned int scheduleId; +}; + +// for ease of angelscript porting +class ScheduledFunction { +public: + unsigned int scheduleId = 0; + + ScheduledFunction() {} + ScheduledFunction(int scheduleId) : scheduleId(scheduleId) {} + + bool HasBeenRemoved(); +}; + +EXPORT extern unsigned int g_schedule_id; // don't touch this + + +// there should only be one instance of this in the game (g_Scheduler) +class Scheduler { +public: + std::vector functions; + + template + ScheduledFunction SetTimeout(F&& func, float delay, Args&&... args) { + ScheduledFunction_internal f = { + std::bind(std::forward(func), std::forward(args)...), +#ifdef PLUGIN_NAME + PLUGIN_NAME, +#else + NULL, +#endif + delay, + 0, + 1, + g_engfuncs.pfnTime(), + g_schedule_id + }; + functions.push_back(f); + return ScheduledFunction(g_schedule_id++); + } + + template + ScheduledFunction SetInterval(F&& func, float delay, int maxCalls, Args&&... args) { + ScheduledFunction_internal f = { + std::bind(std::forward(func), std::forward(args)...), +#ifdef PLUGIN_NAME + PLUGIN_NAME, +#else + NULL, +#endif + delay, + 0, + maxCalls, + g_engfuncs.pfnTime(), + g_schedule_id + }; + functions.push_back(f); + return ScheduledFunction(g_schedule_id++); + } + + void Think(); + + void RemoveTimer(ScheduledFunction schedule); + + void RemoveTimers(const char* owner); +}; + +EXPORT extern Scheduler g_Scheduler; \ No newline at end of file diff --git a/dlls/client.cpp b/dlls/client.cpp index bd778c83..9664aae4 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -45,6 +45,7 @@ #include "skill.h" #include "CGamePlayerEquip.h" #include "PluginManager.h" +#include "Scheduler.h" #if !defined ( _WIN32 ) #include @@ -89,7 +90,9 @@ called when a player connects to a server ============ */ BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) -{ +{ + CALL_HOOKS(BOOL, &HLCOOP_PLUGIN_HOOKS::pfnClientConnect, pEntity, pszName, pszAddress, szRejectReason); + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); // a client connecting during an intermission can cause problems @@ -272,18 +275,20 @@ void ClientPutInServer( edict_t *pEntity ) UTIL_StopGlobalMp3(pEntity); } - // Allocate a CBasePlayer for pev, and call spawn - pPlayer->Spawn(); pPlayer->m_initSoundTime = gpGlobals->time + 1.0f; + pPlayer->pev->iuser1 = 0; // disable any spec modes + pPlayer->pev->iuser2 = 0; + // Reset interpolation during first frame pPlayer->pev->effects |= EF_NOINTERP; - pPlayer->pev->iuser1 = 0; // disable any spec modes - pPlayer->pev->iuser2 = 0; + CALL_HOOKS_VOID(&HLCOOP_PLUGIN_HOOKS::pfnClientPutInServer, pPlayer); - if (g_pGameRules->IsMultiplayer()) - { + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn(); + + if (g_pGameRules->IsMultiplayer()) { FireTargets("game_playerjoin", pPlayer, pPlayer, USE_TOGGLE, 0); } } @@ -682,6 +687,8 @@ void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) } } + LoadAdminList(); + CALL_HOOKS_VOID(&HLCOOP_PLUGIN_HOOKS::pfnMapActivate); } @@ -800,6 +807,8 @@ void StartFrame( void ) } lagcomp_update(); + + g_Scheduler.Think(); } diff --git a/dlls/client_commands.cpp b/dlls/client_commands.cpp index e5c53b2e..38302906 100644 --- a/dlls/client_commands.cpp +++ b/dlls/client_commands.cpp @@ -372,17 +372,25 @@ bool CheatCommand(edict_t* pEntity) { // Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. void ClientCommand(edict_t* pEntity) { - TextMenuClientCommandHook(pEntity); - - const char* pcmd = CMD_ARGV(0); - const char* pstr; - // Is the client spawned yet? if (!pEntity->pvPrivateData) return; entvars_t* pev = &pEntity->v; + CBasePlayer* pPlayer = GetClassPtr((CBasePlayer*)pev); + + if (!pPlayer) { + return; + } + + CALL_HOOKS_VOID(&HLCOOP_PLUGIN_HOOKS::pfnClientCommand, pPlayer); + + TextMenuClientCommandHook(pEntity); + + const char* pcmd = CMD_ARGV(0); + const char* pstr; + if (CheatCommand(pEntity)) { return; } @@ -396,37 +404,36 @@ void ClientCommand(edict_t* pEntity) } else if (FStrEq(pcmd, "fullupdate")) { - GetClassPtr((CBasePlayer*)pev)->ForceClientDllUpdate(); + pPlayer->ForceClientDllUpdate(); } else if (FStrEq(pcmd, "give")) { if (g_flWeaponCheat != 0.0) { int iszItem = ALLOC_STRING(CMD_ARGV(1)); // Make a copy of the classname - GetClassPtr((CBasePlayer*)pev)->GiveNamedItem(STRING(iszItem)); + pPlayer->GiveNamedItem(STRING(iszItem)); } } - else if (FStrEq(pcmd, "drop")) { // player is dropping an item. - GetClassPtr((CBasePlayer*)pev)->DropPlayerItem((char*)CMD_ARGV(1)); + pPlayer->DropPlayerItem((char*)CMD_ARGV(1)); } else if (FStrEq(pcmd, "dropammo")) { // player is dropping an item. - GetClassPtr((CBasePlayer*)pev)->DropAmmo(false); + pPlayer->DropAmmo(false); } else if (FStrEq(pcmd, "dropammo2")) { // player is dropping an item. - GetClassPtr((CBasePlayer*)pev)->DropAmmo(true); + pPlayer->DropAmmo(true); } else if (FStrEq(pcmd, "fov")) { if (g_flWeaponCheat && CMD_ARGC() > 1) { - GetClassPtr((CBasePlayer*)pev)->m_iFOV = atoi(CMD_ARGV(1)); + pPlayer->m_iFOV = atoi(CMD_ARGV(1)); } else { @@ -435,7 +442,7 @@ void ClientCommand(edict_t* pEntity) } else if (FStrEq(pcmd, "use")) { - GetClassPtr((CBasePlayer*)pev)->SelectItem((char*)CMD_ARGV(1)); + pPlayer->SelectItem((char*)CMD_ARGV(1)); } else if (g_weaponClassnames.count(pcmd)) { @@ -446,23 +453,21 @@ void ClientCommand(edict_t* pEntity) wepCname = pcmd + dirEnd + 1; } - GetClassPtr((CBasePlayer*)pev)->SelectItem(wepCname); + pPlayer->SelectItem(wepCname); } else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd)) { - GetClassPtr((CBasePlayer*)pev)->SelectItem(pcmd); + pPlayer->SelectItem(pcmd); } else if (FStrEq(pcmd, "lastinv")) { - GetClassPtr((CBasePlayer*)pev)->SelectLastItem(); + pPlayer->SelectLastItem(); } else if (FStrEq(pcmd, "spectate")) // clients wants to become a spectator { // always allow proxies to become a spectator if ((pev->flags & FL_PROXY) || allow_spectators.value) { - CBasePlayer* pPlayer = GetClassPtr((CBasePlayer*)pev); - if (gpGlobals->time - pPlayer->m_lastObserverSwitch < 3.0f) { float timeleft = 3.0f - (gpGlobals->time - pPlayer->m_lastObserverSwitch); CLIENT_PRINTF(pPlayer->edict(), print_center, UTIL_VarArgs("Wait %.1f seconds", timeleft)); @@ -501,8 +506,6 @@ void ClientCommand(edict_t* pEntity) } else if (FStrEq(pcmd, "specmode")) // new spectator mode { - CBasePlayer* pPlayer = GetClassPtr((CBasePlayer*)pev); - if (pPlayer->IsObserver()) pPlayer->Observer_SetMode(atoi(CMD_ARGV(1))); } @@ -512,8 +515,6 @@ void ClientCommand(edict_t* pEntity) } else if (FStrEq(pcmd, "follownext")) // follow next player { - CBasePlayer* pPlayer = GetClassPtr((CBasePlayer*)pev); - if (pPlayer->IsObserver()) pPlayer->Observer_FindNextPlayer(atoi(CMD_ARGV(1)) ? true : false); } @@ -521,7 +522,7 @@ void ClientCommand(edict_t* pEntity) { g_pluginManager.ListPlugins(pEntity); } - else if (g_pGameRules->ClientCommand(GetClassPtr((CBasePlayer*)pev), pcmd)) + else if (g_pGameRules->ClientCommand(pPlayer, pcmd)) { // MenuSelect returns true only if the command is properly handled, so don't print a warning } diff --git a/dlls/game.cpp b/dlls/game.cpp index eb9dc8f0..932143f9 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -59,7 +59,8 @@ cvar_t mp_edictsorting ={"mp_edictsorting","1", FCVAR_SERVER, 0, 0 }; cvar_t mp_shitcode ={"mp_shitcode","0", FCVAR_SERVER, 0, 0 }; cvar_t mp_mergemodels ={"mp_mergemodels","0", FCVAR_SERVER, 0, 0 }; cvar_t mp_killfeed ={"mp_killfeed","1", FCVAR_SERVER, 0, 0 }; -cvar_t pluginlist ={"pluginlist","plugins.txt", FCVAR_SERVER, 0, 0 }; +cvar_t pluginlistfile ={"pluginlistfile","plugins.txt", FCVAR_SERVER, 0, 0 }; +cvar_t adminlistfile ={"adminlistfile","admins.txt", FCVAR_SERVER, 0, 0 }; cvar_t soundvariety={"mp_soundvariety","0", FCVAR_SERVER, 0, 0 }; @@ -211,6 +212,14 @@ void list_plugins() { g_pluginManager.ListPlugins(NULL); } +void remove_plugin() { + if (CMD_ARGC() < 2) { + return; + } + + g_pluginManager.RemovePlugin(CMD_ARGV(1)); +} + void test_command() { } @@ -229,6 +238,7 @@ void GameDLLInit( void ) g_engfuncs.pfnAddServerCommand("edicts", PrintEntindexStats); g_engfuncs.pfnAddServerCommand("reloadplugins", reload_plugins); g_engfuncs.pfnAddServerCommand("listplugins", list_plugins); + g_engfuncs.pfnAddServerCommand("removeplugin", remove_plugin); // Register cvars here: g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); @@ -281,7 +291,8 @@ void GameDLLInit( void ) CVAR_REGISTER (&mp_shitcode); CVAR_REGISTER (&mp_mergemodels); CVAR_REGISTER (&mp_killfeed); - CVAR_REGISTER (&pluginlist); + CVAR_REGISTER (&pluginlistfile); + CVAR_REGISTER (&adminlistfile); CVAR_REGISTER (&mp_chattime); diff --git a/dlls/game.h b/dlls/game.h index 4bc9e958..19a0cd2d 100644 --- a/dlls/game.h +++ b/dlls/game.h @@ -67,7 +67,8 @@ 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 pluginlist; // name of the plugin list file +extern cvar_t pluginlistfile; // name of the plugin list file +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. diff --git a/dlls/util.cpp b/dlls/util.cpp index 4f9f222c..753603dd 100644 --- a/dlls/util.cpp +++ b/dlls/util.cpp @@ -41,6 +41,10 @@ #include #include #include +#include + +using namespace std::chrono; + #ifndef WIN32 #include #endif @@ -94,6 +98,8 @@ Bsp g_bsp; std::string g_mp3Command; +std::map g_admins; + unsigned int U_Random( void ) { glSeed *= 69069; @@ -4778,4 +4784,89 @@ const char* getActiveWeapon(entvars_t* pev) { CBasePlayer* plr = (CBasePlayer*)ent; return plr->m_pActiveItem ? STRING(plr->m_pActiveItem->pev->classname) : ""; +} + +uint64_t getEpochMillis() { + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +double TimeDifference(uint64_t start, uint64_t end) { + if (end > start) { + return (end - start) / 1000.0; + } + else { + return -((start - end) / 1000.0); + } +} + +void LoadAdminList(bool forceUpdate) { + const char* ADMIN_LIST_FILE = CVAR_GET_STRING("adminlistfile"); + + g_admins.clear(); + + static uint64_t lastEditTime = 0; + + std::string fpath = getGameFilePath(ADMIN_LIST_FILE); + + if (fpath.empty()) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Missing admin list: '%s'\n", ADMIN_LIST_FILE)); + return; + } + + uint64_t editTime = getFileModifiedTime(fpath.c_str()); + + if (!forceUpdate && lastEditTime == editTime) { + return; // no changes made + } + + std::ifstream infile(fpath); + + if (!infile.is_open()) { + ALERT(at_console, "Failed to open admins file: %s\n", ADMIN_LIST_FILE); + return; + } + + lastEditTime = editTime; + + std::string line; + while (std::getline(infile, line)) { + if (line.empty()) { + continue; + } + + // strip comments + int endPos = line.find_first_of(" \t#/\n"); + std::string steamId = trimSpaces(line.substr(0, endPos)); + + if (steamId.length() < 1) { + continue; + } + + int adminLevel = ADMIN_YES; + + if (steamId[0] == '*') { + adminLevel = ADMIN_OWNER; + steamId = steamId.substr(1); + } + + g_admins[steamId] = adminLevel; + } + + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Loaded %d admin(s) from file", g_admins.size())); +} + +int AdminLevel(edict_t* plr) { + std::string steamId = (*g_engfuncs.pfnGetPlayerAuthId)(plr); + + if (!IS_DEDICATED_SERVER()) { + if (ENTINDEX(plr) == 1) { + return ADMIN_OWNER; // listen server owner is always the first player to join (I hope) + } + } + + if (g_admins.find(steamId) != g_admins.end()) { + return g_admins[steamId]; + } + + return ADMIN_NO; } \ No newline at end of file diff --git a/dlls/util.h b/dlls/util.h index eeae498b..7653770f 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -29,7 +29,6 @@ #include #include #include "game.h" -#include #include #include #include @@ -79,6 +78,14 @@ extern int g_edictsinit; // 1 if all edicts were allocated so that relocations c #define MAX_PRECACHE_MODEL 510 #define MAX_PRECACHE_EVENT 256 +enum AdminLevel { + ADMIN_NO, + ADMIN_YES, + ADMIN_OWNER +}; + +extern std::map g_admins; + enum merged_item_bodies { MERGE_MDL_W_9MMAR, MERGE_MDL_W_9MMARCLIP, @@ -861,9 +868,23 @@ EXPORT void PlayCDTrack(int iTrack); const char* cstr(string_t s); // strips unsafe chars from value to prevent sneaky stuff like "sv_gravity 800;rcon_password lololol" -std::string sanitize_cvar_value(std::string val); +EXPORT std::string sanitize_cvar_value(std::string val); -const char* getActiveWeapon(entvars_t* pev); +EXPORT const char* getActiveWeapon(entvars_t* pev); // for debugging -bool ModelIsValid(entvars_t* edict, studiohdr_t* header); \ No newline at end of file +bool ModelIsValid(entvars_t* edict, studiohdr_t* header); + +EXPORT const char* getPlayerUniqueId(edict_t* plr); + +EXPORT uint64_t steamid_to_steamid64(const char* steamid); + +EXPORT uint64_t getPlayerCommunityId(edict_t* plr); + +EXPORT void LoadAdminList(bool forceUpdate=false); // call on each map change, so AdminLevel can work + +EXPORT int AdminLevel(edict_t* player); + +EXPORT uint64_t getEpochMillis(); + +EXPORT double TimeDifference(uint64_t start, uint64_t end); \ No newline at end of file diff --git a/game_shared/voice_gamemgr.cpp b/game_shared/voice_gamemgr.cpp index abc003cd..99035c44 100644 --- a/game_shared/voice_gamemgr.cpp +++ b/game_shared/voice_gamemgr.cpp @@ -241,8 +241,8 @@ void CVoiceGameMgr::UpdateMasks() // Build a mask of who they can hear based on the game rules. for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) { - CBaseEntity *pEnt = UTIL_PlayerByIndex(iOtherClient+1); - if(pEnt && (bAllTalk || m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt)) ) + CBaseEntity *pEnt2 = UTIL_PlayerByIndex(iOtherClient+1); + if(pEnt2 && (bAllTalk || m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt2)) ) { gameRulesMask[iOtherClient] = true; }