diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 5c57c5ef..83381355 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -57,6 +57,9 @@ SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_CSGO SH_DECL_HOOK1_void_vafmt(IVEngineServer, ClientCommand, SH_NOATTRIB, 0, edict_t *); #endif +#if defined CLIENTVOICE_HOOK_SUPPORT +SH_DECL_HOOK1_void(IServerGameClients, ClientVoice, SH_NOATTRIB, 0, edict_t *); +#endif SDKTools g_SdkTools; /**< Global singleton for extension's main interface */ IServerGameEnts *gameents = NULL; @@ -76,6 +79,9 @@ IServer *iserver = NULL; IBaseFileSystem *basefilesystem = NULL; CGlobalVars *gpGlobals; ISoundEmitterSystemBase *soundemitterbase = NULL; +ITimer *g_hTimerSpeaking[SM_MAXPLAYERS+1]; +IForward *m_OnClientSpeaking; +IForward *m_OnClientSpeakingEnd; #if SOURCE_ENGINE >= SE_ORANGEBOX IServerTools *servertools = NULL; @@ -256,6 +262,9 @@ void SDKTools::SDK_OnUnload() g_Hooks.Shutdown(); g_OutputManager.Shutdown(); + forwards->ReleaseForward(m_OnClientSpeaking); + forwards->ReleaseForward(m_OnClientSpeakingEnd); + gameconfs->CloseGameConfigFile(g_pGameConf); playerhelpers->RemoveClientListener(&g_SdkTools); playerhelpers->UnregisterCommandTargetProcessor(this); @@ -314,7 +323,9 @@ bool SDKTools::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_CSGO SH_ADD_HOOK(IVEngineServer, ClientCommand, engine, SH_MEMBER(this, &SDKTools::OnSendClientCommand), false); #endif - +#if defined CLIENTVOICE_HOOK_SUPPORT + SH_ADD_HOOK(IServerGameClients, ClientVoice, serverClients, SH_MEMBER(this, &SDKTools::OnClientVoice), true); +#endif gpGlobals = ismm->GetCGlobals(); enginePatch = SH_GET_CALLCLASS(engine); enginesoundPatch = SH_GET_CALLCLASS(engsound); @@ -326,6 +337,9 @@ bool SDKTools::SDK_OnMetamodUnload(char *error, size_t maxlen) { #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_CSGO SH_REMOVE_HOOK(IVEngineServer, ClientCommand, engine, SH_MEMBER(this, &SDKTools::OnSendClientCommand), false); +#endif +#if defined CLIENTVOICE_HOOK_SUPPORT + SH_REMOVE_HOOK(IServerGameClients, ClientVoice, serverClients, SH_MEMBER(this, &SDKTools::OnClientVoice), true); #endif return true; } @@ -344,6 +358,9 @@ void SDKTools::SDK_OnAllLoaded() s_SoundHooks.Initialize(); g_Hooks.Initialize(); InitializeValveGlobals(); + + m_OnClientSpeaking = forwards->CreateForward("OnClientSpeaking", ET_Ignore, 1, NULL, Param_Cell); + m_OnClientSpeakingEnd = forwards->CreateForward("OnClientSpeakingEnd", ET_Ignore, 1, NULL, Param_Cell); } void SDKTools::OnCoreMapStart(edict_t *pEdictList, int edictCount, int clientMax) @@ -516,6 +533,13 @@ bool SDKTools::InterceptClientConnect(int client, char *error, size_t maxlength) return true; } +#if !defined CLIENTVOICE_HOOK_SUPPORT +void SDKTools::OnClientConnected(int client) +{ + g_Hooks.OnClientConnected(client); +} +#endif + #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_CSGO void SDKTools::OnSendClientCommand(edict_t *pPlayer, const char *szFormat) { @@ -532,6 +556,43 @@ void SDKTools::OnSendClientCommand(edict_t *pPlayer, const char *szFormat) } #endif +SourceMod::ResultType SDKTools::OnTimer(ITimer *pTimer, void *pData) +{ + int client = (int)(intptr_t)pData; + + m_OnClientSpeakingEnd->PushCell(client); + m_OnClientSpeakingEnd->Execute(); + + return Pl_Stop; +} + +void SDKTools::OnTimerEnd(ITimer *pTimer, void *pData) +{ + g_hTimerSpeaking[(int)(intptr_t)pData] = nullptr; +} + +#if defined CLIENTVOICE_HOOK_SUPPORT +void SDKTools::OnClientVoice(edict_t *pPlayer) +{ + if (!pPlayer) + { + return; + } + + int client = IndexOfEdict(pPlayer); + + if (g_hTimerSpeaking[client]) + { + timersys->KillTimer(g_hTimerSpeaking[client]); + } + + g_hTimerSpeaking[client] = timersys->CreateTimer(this, 0.3f, (void *)(intptr_t)client, 0); + + m_OnClientSpeaking->PushCell(client); + m_OnClientSpeaking->Execute(); +} +#endif + void SDKTools::OnClientPutInServer(int client) { g_Hooks.OnClientPutInServer(client); diff --git a/extensions/sdktools/extension.h b/extensions/sdktools/extension.h index caf640e7..365af704 100644 --- a/extensions/sdktools/extension.h +++ b/extensions/sdktools/extension.h @@ -61,6 +61,12 @@ #include #endif +#if SOURCE_ENGINE == SE_ALIENSWARM || SOURCE_ENGINE == SE_PORTAL2 || SOURCE_ENGINE == SE_INSURGENCY || SOURCE_ENGINE == SE_DOI || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_CSGO +#define CLIENTVOICE_HOOK_SUPPORT +#else +#include +#endif + /** * @brief Implementation of the SDK Tools extension. * Note: Uncomment one of the pre-defined virtual functions in order to use it. @@ -70,6 +76,7 @@ class SDKTools : public IHandleTypeDispatch, public IConCommandBaseAccessor, public IClientListener, + public ITimedEvent, public ICommandTargetProcessor { public: //public IHandleTypeDispatch @@ -95,8 +102,15 @@ public: //IConCommandBaseAccessor bool RegisterConCommandBase(ConCommandBase *pVar); public: //IClientListner bool InterceptClientConnect(int client, char *error, size_t maxlength); +#if !defined CLIENTVOICE_HOOK_SUPPORT + void OnClientConnected(int client); +#endif void OnClientPutInServer(int client); void OnClientDisconnecting(int client); +public: +#if defined CLIENTVOICE_HOOK_SUPPORT + void OnClientVoice(edict_t *pPlayer); +#endif public: // IVoiceServer bool OnSetClientListening(int iReceiver, int iSender, bool bListen); void VoiceInit(); @@ -108,6 +122,9 @@ public: // IVoiceServer #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_CSGO void OnSendClientCommand(edict_t *pPlayer, const char *szFormat); #endif +public: //ITimedEvent + ResultType OnTimer(ITimer *pTimer, void *pData); + void OnTimerEnd(ITimer *pTimer, void *pData); public: //ICommandTargetProcessor bool ProcessCommandTarget(cmd_target_info_t *info); @@ -156,6 +173,11 @@ extern HandleType_t g_CallHandle; extern HandleType_t g_TraceHandle; /* Call Wrappers */ extern ICallWrapper *g_pAcceptInput; +/* Timers */ +extern ITimer *g_hTimerSpeaking[SM_MAXPLAYERS+1]; +/* Forwards */ +extern IForward *m_OnClientSpeaking; +extern IForward *m_OnClientSpeakingEnd; /* Call classes */ extern SourceHook::CallClass *enginePatch; extern SourceHook::CallClass *enginesoundPatch; diff --git a/extensions/sdktools/hooks.cpp b/extensions/sdktools/hooks.cpp index 2371489a..565a6903 100644 --- a/extensions/sdktools/hooks.cpp +++ b/extensions/sdktools/hooks.cpp @@ -46,6 +46,9 @@ static bool PRCH_enabled = false; static bool PRCH_used = false; static bool PRCHPost_used = false; static bool FILE_used = false; +#if !defined CLIENTVOICE_HOOK_SUPPORT +static bool PVD_used = false; +#endif SH_DECL_MANUALHOOK2_void(PlayerRunCmdHook, 0, 0, 0, CUserCmd *, IMoveHelper *); SH_DECL_HOOK2(IBaseFileSystem, FileExists, SH_NOATTRIB, 0, bool, const char*, const char *); @@ -54,6 +57,9 @@ SH_DECL_HOOK3(INetChannel, SendFile, SH_NOATTRIB, 0, bool, const char *, unsigne #else SH_DECL_HOOK2(INetChannel, SendFile, SH_NOATTRIB, 0, bool, const char *, unsigned int); #endif +#if !defined CLIENTVOICE_HOOK_SUPPORT +SH_DECL_HOOK1(IClientMessageHandler, ProcessVoiceData, SH_NOATTRIB, 0, bool, CLC_VoiceData *); +#endif SH_DECL_HOOK2_void(INetChannel, ProcessPacket, SH_NOATTRIB, 0, struct netpacket_s *, bool); SourceHook::CallClass *basefilesystemPatch = NULL; @@ -141,6 +147,36 @@ void CHookManager::OnClientConnect(int client) NetChannelHook(client); } +#if !defined CLIENTVOICE_HOOK_SUPPORT +void CHookManager::OnClientConnected(int client) +{ + if (!PVD_used) + { + return; + } + + IClient *pClient = iserver->GetClient(client-1); + if (!pClient) + { + return; + } + + std::vector &netProcessVoiceData = m_netProcessVoiceData; + CVTableHook hook(pClient); + for (size_t i = 0; i < netProcessVoiceData.size(); ++i) + { + if (hook == netProcessVoiceData[i]) + { + return; + } + } + + int hookid = SH_ADD_VPHOOK(IClientMessageHandler, ProcessVoiceData, (IClientMessageHandler *)((intptr_t)(pClient) + 4), SH_MEMBER(this, &CHookManager::ProcessVoiceData), true); + hook.SetHookID(hookid); + netProcessVoiceData.push_back(new CVTableHook(hook)); +} +#endif + void CHookManager::OnClientPutInServer(int client) { if (PRCH_used) @@ -459,6 +495,31 @@ bool CHookManager::SendFile(const char *filename, unsigned int transferID) RETURN_META_VALUE(MRES_IGNORED, false); } +#if !defined CLIENTVOICE_HOOK_SUPPORT +bool CHookManager::ProcessVoiceData(CLC_VoiceData *msg) +{ + IClient *pClient = (IClient *)((intptr_t)(META_IFACEPTR(IClient)) - 4); + if (pClient == NULL) + { + return true; + } + + int client = pClient->GetPlayerSlot() + 1; + + if (g_hTimerSpeaking[client]) + { + timersys->KillTimer(g_hTimerSpeaking[client]); + } + + g_hTimerSpeaking[client] = timersys->CreateTimer(&g_SdkTools, 0.3f, (void *)(intptr_t)client, 0); + + m_OnClientSpeaking->PushCell(client); + m_OnClientSpeaking->Execute(); + + return true; +} +#endif + void CHookManager::OnPluginLoaded(IPlugin *plugin) { if (PRCH_enabled) @@ -502,6 +563,22 @@ void CHookManager::OnPluginLoaded(IPlugin *plugin) } } } + +#if !defined CLIENTVOICE_HOOK_SUPPORT + if (!PVD_used && (m_OnClientSpeaking->GetFunctionCount() || m_OnClientSpeakingEnd->GetFunctionCount())) + { + PVD_used = true; + + int MaxClients = playerhelpers->GetMaxClients(); + for (int i = 1; i <= MaxClients; i++) + { + if (playerhelpers->GetGamePlayer(i)->IsConnected()) + { + OnClientConnected(i); + } + } + } +#endif } void CHookManager::OnPluginUnloaded(IPlugin *plugin) @@ -538,6 +615,19 @@ void CHookManager::OnPluginUnloaded(IPlugin *plugin) m_netChannelHooks.clear(); FILE_used = false; } + +#if !defined CLIENTVOICE_HOOK_SUPPORT + if (PVD_used && !m_OnClientSpeaking->GetFunctionCount() && !m_OnClientSpeakingEnd->GetFunctionCount()) + { + for (size_t i = 0; i < m_netProcessVoiceData.size(); ++i) + { + delete m_netProcessVoiceData[i]; + } + + m_netProcessVoiceData.clear(); + PVD_used = false; + } +#endif } FeatureStatus CHookManager::GetFeatureStatus(FeatureType type, const char *name) diff --git a/extensions/sdktools/hooks.h b/extensions/sdktools/hooks.h index 40011422..3854405d 100644 --- a/extensions/sdktools/hooks.h +++ b/extensions/sdktools/hooks.h @@ -47,6 +47,9 @@ public: void Initialize(); void Shutdown(); void OnClientConnect(int client); +#if !defined CLIENTVOICE_HOOK_SUPPORT + void OnClientConnected(int client); +#endif void OnClientPutInServer(int client); void PlayerRunCmd(CUserCmd *ucmd, IMoveHelper *moveHelper); void PlayerRunCmdPost(CUserCmd *ucmd, IMoveHelper *moveHelper); @@ -57,6 +60,9 @@ public: /* NetChannel/Related Hooks */ bool SendFile(const char *filename, unsigned int transferID, bool isReplayDemo); #else bool SendFile(const char *filename, unsigned int transferID); +#endif +#if !defined CLIENTVOICE_HOOK_SUPPORT + bool ProcessVoiceData(CLC_VoiceData *msg); #endif void ProcessPacket(struct netpacket_s *packet, bool bHasHeader); void ProcessPacket_Post(struct netpacket_s *packet, bool bHasHeader); @@ -78,6 +84,9 @@ private: std::vector m_runUserCmdHooks; std::vector m_runUserCmdPostHooks; std::vector m_netChannelHooks; +#if !defined CLIENTVOICE_HOOK_SUPPORT + std::vector m_netProcessVoiceData; +#endif INetChannel *m_pActiveNetChannel; bool m_bFSTranHookWarned = false; bool m_bReplayEnabled = false; diff --git a/extensions/sdktools/smsdk_config.h b/extensions/sdktools/smsdk_config.h index d845b34b..3be8f8bb 100644 --- a/extensions/sdktools/smsdk_config.h +++ b/extensions/sdktools/smsdk_config.h @@ -66,7 +66,7 @@ #define SMEXT_ENABLE_GAMECONF #define SMEXT_ENABLE_MEMUTILS #define SMEXT_ENABLE_GAMEHELPERS -//#define SMEXT_ENABLE_TIMERSYS +#define SMEXT_ENABLE_TIMERSYS #define SMEXT_ENABLE_ADTFACTORY #define SMEXT_ENABLE_PLUGINSYS diff --git a/extensions/sdktools/voice.cpp b/extensions/sdktools/voice.cpp index 21a4280a..c7bb7c39 100644 --- a/extensions/sdktools/voice.cpp +++ b/extensions/sdktools/voice.cpp @@ -167,6 +167,11 @@ bool SDKTools::OnSetClientListening(int iReceiver, int iSender, bool bListen) void SDKTools::OnClientDisconnecting(int client) { + if (g_hTimerSpeaking[client]) + { + timersys->KillTimer(g_hTimerSpeaking[client]); + } + int max_clients = playerhelpers->GetMaxClients(); if (g_VoiceHookCount == 0) @@ -355,6 +360,33 @@ static cell_t IsClientMuted(IPluginContext *pContext, const cell_t *params) return g_ClientMutes[params[1]][params[2]]; } +/* FIXME: Presently if there's no hook present these natives will result in an invalid state. + * One suggestion could be to look at if the native is bound, and then invoke the hooks in the background. + * However, at that point really we should be enforcing the forward usage to catch new-consumers immediately. + * If you're looking to work on this, you're welcome to ping asherkin or KyleS, or even submit a patch to add this. + * Additional comments can be found here (if GitHub still exists): https://github.com/alliedmodders/sourcemod/pull/1247 + */ +static cell_t IsClientSpeaking(IPluginContext *pContext, const cell_t *params) +{ + IGamePlayer *player; + + player = playerhelpers->GetGamePlayer(params[1]); + if (player == NULL) + { + return pContext->ThrowNativeError("Client index %d is invalid", params[1]); + } + else if (!player->IsConnected()) + { + return pContext->ThrowNativeError("Client %d is not connected", params[1]); + } + else if (!player->IsInGame()) + { + return false; + } + + return g_hTimerSpeaking[params[1]] != nullptr; +} + sp_nativeinfo_t g_VoiceNatives[] = { {"SetClientListeningFlags", SetClientListeningFlags}, diff --git a/plugins/include/sdktools_voice.inc b/plugins/include/sdktools_voice.inc index 2f44879c..e2e76fa3 100644 --- a/plugins/include/sdktools_voice.inc +++ b/plugins/include/sdktools_voice.inc @@ -56,6 +56,20 @@ enum ListenOverride Listen_Yes /**< Can hear */ }; +/** + * Called when a client is speaking. + * + * @param client The client index + */ +forward void OnClientSpeaking(int client); + +/** + * Called once a client speaking end. + * + * @param client The client index + */ +forward void OnClientSpeakingEnd(int client); + /** * Set the client listening flags. *