diff --git a/extensions/sdktools/Makefile b/extensions/sdktools/Makefile index 66260818..71b4e669 100644 --- a/extensions/sdktools/Makefile +++ b/extensions/sdktools/Makefile @@ -16,7 +16,8 @@ PROJECT = sdktools OBJECTS = sdk/smsdk_ext.cpp extension.cpp vdecoder.cpp vcallbuilder.cpp vcaller.cpp \ vnatives.cpp vsound.cpp tenatives.cpp trnatives.cpp tempents.cpp vstringtable.cpp \ - vhelpers.cpp vglobals.cpp voice.cpp inputnatives.cpp teamnatives.cpp + vhelpers.cpp vglobals.cpp voice.cpp inputnatives.cpp teamnatives.cpp output.cpp \ + outputnatives.cpp detours.cpp ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### diff --git a/extensions/sdktools/detours.cpp b/extensions/sdktools/detours.cpp new file mode 100644 index 00000000..be18add3 --- /dev/null +++ b/extensions/sdktools/detours.cpp @@ -0,0 +1,39 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: tenatives.cpp 1566 2007-10-14 22:12:46Z faluco $ + */ + +#include "extension.h" +#include "output.h" + + + + + + diff --git a/extensions/sdktools/detours.h b/extensions/sdktools/detours.h new file mode 100644 index 00000000..707b2cfb --- /dev/null +++ b/extensions/sdktools/detours.h @@ -0,0 +1,98 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: extension.h 1775 2007-12-06 02:25:35Z faluco $ + */ + +#ifndef _INCLUDE_SOURCEMOD_DETOURS_H_ +#define _INCLUDE_SOURCEMOD_DETOURS_H_ + +#if defined PLATFORM_LINUX +#include +#define PAGE_SIZE 4096 +#define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1)) +#define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC +#endif + +struct patch_t +{ + patch_t() + { + patch[0] = 0; + bytes = 0; + } + unsigned char patch[20]; + size_t bytes; +}; + +inline void ProtectMemory(void *addr, int length, int prot) +{ +#if defined PLATFORM_LINUX + void *addr2 = (void *)ALIGN(addr); + mprotect(addr2, sysconf(_SC_PAGESIZE), prot); +#elif defined PLATFORM_WINDOWS + DWORD old_prot; + VirtualProtect(addr, length, prot, &old_prot); +#endif +} + +inline void SetMemPatchable(void *address, size_t size) +{ + ProtectMemory(address, (int)size, PAGE_EXECUTE_READWRITE); +} + +inline void DoGatePatch(unsigned char *target, void *callback) +{ + SetMemPatchable(target, 20); + + target[0] = 0xFF; /* JMP */ + target[1] = 0x25; /* MEM32 */ + *(void **)(&target[2]) = callback; +} + +inline void ApplyPatch(void *address, int offset, const patch_t *patch, patch_t *restore) +{ + ProtectMemory(address, 20, PAGE_EXECUTE_READWRITE); + + unsigned char *addr = (unsigned char *)address + offset; + if (restore) + { + for (size_t i=0; ibytes; i++) + { + restore->patch[i] = addr[i]; + } + restore->bytes = patch->bytes; + } + + for (size_t i=0; ibytes; i++) + { + addr[i] = patch->patch[i]; + } +} + +#endif //_INCLUDE_SOURCEMOD_DETOURS_H_ diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 865cd8ae..ca87044e 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -36,6 +36,7 @@ #include "vglobals.h" #include "tempents.h" #include "vsound.h" +#include "output.h" #if defined ORANGEBOX_BUILD #define SDKTOOLS_GAME_FILE "sdktools.games.ep2" @@ -92,6 +93,7 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->AddNatives(myself, g_VoiceNatives); sharesys->AddNatives(myself, g_EntInputNatives); sharesys->AddNatives(myself, g_TeamNatives); + sharesys->AddNatives(myself, g_EntOutputNatives); SM_GET_IFACE(GAMEHELPERS, g_pGameHelpers); @@ -116,6 +118,14 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) MathLib_Init(2.2f, 2.2f, 0.0f, 2); + spengine = g_pSM->GetScriptingEngine(); + + //g_OutputManager.VariantHandle = handlesys->CreateType("Variant", &g_OutputManager, 0, NULL, NULL, myself->GetIdentity(), NULL); + + plsys->AddPluginsListener(&g_OutputManager); + + g_OutputManager.Init(); + return true; } diff --git a/extensions/sdktools/msvc8/sdktools.vcproj b/extensions/sdktools/msvc8/sdktools.vcproj index d1c2c415..cd0c00ea 100644 --- a/extensions/sdktools/msvc8/sdktools.vcproj +++ b/extensions/sdktools/msvc8/sdktools.vcproj @@ -40,7 +40,7 @@ + + + + @@ -583,10 +591,18 @@ RelativePath="..\CellRecipientFilter.h" > + + + + diff --git a/extensions/sdktools/output.cpp b/extensions/sdktools/output.cpp new file mode 100644 index 00000000..6cb4cdce --- /dev/null +++ b/extensions/sdktools/output.cpp @@ -0,0 +1,559 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: tenatives.cpp 1566 2007-10-14 22:12:46Z faluco $ + */ + +#include "extension.h" +#include "output.h" + +ISourcePawnEngine *spengine = NULL; +EntityOutputManager g_OutputManager; + +bool EntityOutputManager::CreateFireEventDetour() +{ + g_pGameConf->GetMemSig("FireOutput", &info_address); + g_pGameConf->GetOffset("FireOutputBackup", (int *)&(info_restore.bytes)); + + if (!info_address) + { + g_pSM->LogError(myself, "Could not locate FireOutput - Disabling Entity Outputs"); + return false; + } + + if (!info_restore.bytes) + { + g_pSM->LogError(myself, "Could not locate FireOutputBackup - Disabling Entity Outputs"); + return false; + } + + /* First, save restore bits */ + for (size_t i=0; iLogMessage(myself, "Backing up: %x", info_restore.patch[i]); + } + + info_callback = spengine->ExecAlloc(100); + JitWriter wr; + JitWriter *jit = ≀ + wr.outbase = (jitcode_t)info_callback; + wr.outptr = wr.outbase; + + g_pSM->LogMessage(myself, "info_callback : %x", info_callback); + g_pSM->LogMessage(myself, "info_address : %x", info_address); + + /* Function we are detouring into is + * + * void FireEventDetour(CBaseEntityOutput(void *) *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay = 0 ) + */ + + /* push fDelay [esp+20h] + * push pCaller [esp+1Ch] + * push pActivator [esp+18h] + * push pOutput [ecx] + */ + + +#if defined PLATFORM_WINDOWS + + IA32_Push_Rm_Disp8_ESP(jit, 32); + IA32_Push_Rm_Disp8_ESP(jit, 32); + IA32_Push_Rm_Disp8_ESP(jit, 32); + + /* variant_t doesnt do anything so i'll disable this bit */ + //IA32_Push_Rm_Disp8_ESP(jit, 32); + //IA32_Push_Rm_Disp8_ESP(jit, 32); + //IA32_Push_Rm_Disp8_ESP(jit, 32); + //IA32_Push_Rm_Disp8_ESP(jit, 32); + //IA32_Push_Rm_Disp8_ESP(jit, 32); + + IA32_Push_Reg(jit, REG_ECX); + +#elif defined PLATFORM_LINUX + IA32_Push_Rm_Disp8_ESP(jit, 20); + IA32_Push_Rm_Disp8_ESP(jit, 20); + IA32_Push_Rm_Disp8_ESP(jit, 20); + // We miss the variant_t pointer + IA32_Push_Rm_Disp8_ESP(jit, 16); +#endif + + jitoffs_t call = IA32_Call_Imm32(jit, 0); + IA32_Write_Jump32_Abs(jit, call, (void *)TempDetour); + + +#if defined PLATFORM_LINUX + IA32_Add_Rm_Imm8(jit, REG_ESP, 4, MOD_REG); //add esp, 4 +#elif defined PLATFORM_WINDOWS + IA32_Pop_Reg(jit, REG_ECX); +#endif + + IA32_Add_Rm_Imm8(jit, REG_ESP, 12, MOD_REG); //add esp, 12 (0Ch) + + + /* Patch old bytes in */ + for (size_t i=0; iwrite_ubyte(info_restore.patch[i]); + g_pSM->LogMessage(myself, "Writing: %x", info_restore.patch[i]); + } + + /* Return to the original function */ + call = IA32_Jump_Imm32(jit, 0); + IA32_Write_Jump32_Abs(jit, call, (unsigned char *)info_address + info_restore.bytes); + + return true; +} + +void EntityOutputManager::InitFireEventDetour() +{ + if (!is_detoured) + { + DoGatePatch((unsigned char *)info_address, &info_callback); + is_detoured = true; + } +} + +void EntityOutputManager::DeleteFireEventDetour() +{ + if (is_detoured) + { + ShutdownFireEventDetour(); + } + + if (info_callback) + { + /* Free the gate */ + spengine->ExecFree(info_callback); + info_callback = NULL; + } +} + +void TempDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) +{ + g_OutputManager.FireEventDetour(pOutput, pActivator, pCaller, fDelay); +} + +void EntityOutputManager::ShutdownFireEventDetour() +{ + if (info_callback) + { + /* Remove the patch */ + ApplyPatch(info_address, 0, &info_restore, NULL); + is_detoured = false; + } +} + +void EntityOutputManager::FireEventDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) +{ + char sOutput[20]; + Q_snprintf(sOutput, sizeof(sOutput), "%x", pOutput); + + // attempt to directly lookup a hook using the pOutput pointer + OutputNameStruct *pOutputName = NULL; + + edict_t *pEdict = gameents->BaseEntityToEdict(pCaller); + + /* DEBUG START */ + + const char *classname = pEdict->GetClassName(); + const char *outputname = FindOutputName(pOutput, pCaller); + + g_pSM->LogMessage(myself, "Output \"%s\" fired from entity \"%s\"", outputname, classname); + + /* DEBUG END */ + + + bool fastLookup = false; + + // Fast lookup failed - check the slow way for hooks that havn't fired yet + if ((fastLookup = EntityOutputs->Retrieve(sOutput, (void **)&pOutputName)) == false) + { + const char *classname = pEdict->GetClassName(); + const char *outputname = FindOutputName(pOutput, pCaller); + + pOutputName = FindOutputPointer(classname, outputname, false); + + if (!pOutputName) + { + return; + } + } + + if (!pOutputName->hooks.empty()) + { + if (!fastLookup) + { + // hook exists on this classname and output - map it into our quick find trie + EntityOutputs->Insert(sOutput, pOutputName); + } + + SourceHook::List::iterator _iter; + + omg_hooks *hook; + + _iter = pOutputName->hooks.begin(); + + while (_iter != pOutputName->hooks.end()) + { + hook = (omg_hooks *)*_iter; + + hook->in_use = true; + + int serial = pEdict->m_NetworkSerialNumber; + + if (serial != hook->entity_filter && hook->entity_index == engine->IndexOfEdict(pEdict)) + { + // same entity index but different serial number. Entity has changed, kill the hook. + _iter = pOutputName->hooks.erase(_iter); + CleanUpHook(hook); + + continue; + } + + if (hook->entity_filter == -1 || hook->entity_filter == serial) // Global classname hook + { + /* + Handle_t handle; + + if (Value.FieldType() == FIELD_VOID) + { + handle = 0; + } + else + { + varhandle_t *varhandle = new varhandle_t; + memmove ((void *)&varhandle->crab, (void *)&Value, sizeof(variant_t)); + + handle = handlesys->CreateHandle(VariantHandle, (void *)varhandle, hook->pf->GetParentContext()->GetIdentity(), myself->GetIdentity(), NULL); + } + */ + + //fire the forward to hook->pf + hook->pf->PushString(pOutputName->Name); + hook->pf->PushCell(engine->IndexOfEdict(pEdict)); + hook->pf->PushCell(engine->IndexOfEdict(gameents->BaseEntityToEdict(pActivator))); + //hook->pf->PushCell(handle); + hook->pf->PushFloat(fDelay); + hook->pf->Execute(NULL); + + if ((hook->entity_filter != -1) && hook->only_once) + { + _iter = pOutputName->hooks.erase(_iter); + CleanUpHook(hook); + + continue; + } + + if (hook->delete_me) + { + _iter = pOutputName->hooks.erase(_iter); + CleanUpHook(hook); + continue; + } + + hook->in_use = false; + _iter++; + + continue; + } + } + } +} + +omg_hooks *EntityOutputManager::NewHook() +{ + omg_hooks *hook; + + if (FreeHooks.empty()) + { + hook = new omg_hooks; + } + else + { + hook = g_OutputManager.FreeHooks.front(); + g_OutputManager.FreeHooks.pop(); + } + + return hook; +} + +void EntityOutputManager::OnHookAdded() +{ + HookCount++; + + if (HookCount == 1) + { + // This is the first hook created + InitFireEventDetour(); + } +} + +void EntityOutputManager::OnHookRemoved() +{ + HookCount--; + + if (HookCount == 0) + { + //we need to check if we are inside a detour. easy. + //if we are how do we + ShutdownFireEventDetour(); + } +} + +void EntityOutputManager::CleanUpHook(omg_hooks *hook) +{ + FreeHooks.push(hook); + + OnHookRemoved(); + + IPlugin *pPlugin = plsys->FindPluginByContext(hook->pf->GetParentContext()->GetContext()); + SourceHook::List *pList = NULL; + + if (!pPlugin->GetProperty("OutputHookList", (void **)&pList, false) || !pList) + { + return; + } + + SourceHook::List::iterator p_iter = pList->begin(); + + omg_hooks *pluginHook; + + while (p_iter != pList->end()) + { + pluginHook = (omg_hooks *)*p_iter; + if (pluginHook == hook) + { + p_iter = pList->erase(p_iter); + } + else + { + p_iter++; + } + } +} + +void EntityOutputManager::OnPluginDestroyed(IPlugin *plugin) +{ + SourceHook::List *pList = NULL; + + if (plugin->GetProperty("OutputHookList", (void **)&pList, true)) + { + SourceHook::List::iterator p_iter = pList->begin(); + omg_hooks *hook; + + while (p_iter != pList->end()) + { + hook = (omg_hooks *)*p_iter; + + p_iter = pList->erase(p_iter); //remove from this plugins list + hook->m_parent->hooks.remove(hook); // remove from the y's list + + FreeHooks.push(hook); //save the omg_hook + + OnHookRemoved(); + } + } +} + +OutputNameStruct *EntityOutputManager::FindOutputPointer(const char *classname, const char *outputname, bool create) +{ + ClassNameStruct *pClassname; + + if (!ClassNames->Retrieve(classname, (void **)&pClassname)) + { + if (create) + { + pClassname = new ClassNameStruct; + ClassNames->Insert(classname, pClassname); + } + else + { + return NULL; + } + } + + OutputNameStruct *pOutputName; + + if (!pClassname->OutputList->Retrieve(outputname, (void **)&pOutputName)) + { + if (create) + { + pOutputName = new OutputNameStruct; + pClassname->OutputList->Insert(outputname, pOutputName); + strncpy(pOutputName->Name, outputname, sizeof(pOutputName->Name)); + pOutputName->Name[49] = 0; + } + else + { + return NULL; + } + } + + return pOutputName; +} + +// Iterate the datamap of pCaller and look for output pointers with the same address as pOutput +const char *EntityOutputManager::FindOutputName(void *pOutput, CBaseEntity *pCaller) +{ + datamap_t *pMap = gamehelpers->GetDataMap(pCaller); + + while (pMap) + { + for (int i=0; idataNumFields; i++) + { + if (pMap->dataDesc[i].flags & FTYPEDESC_OUTPUT) + { + if ((char *)pCaller + pMap->dataDesc[i].fieldOffset[0] == pOutput) + { + return pMap->dataDesc[i].externalName; + } + } + } + pMap = pMap->baseMap; + } + + return NULL; +} +#if 0 +// Almost identical copy of this function from cbase.cpp - FIELD_EHANDLE changed to remove dependencies +const char *variant_t::ToString( void ) const +{ + COMPILE_TIME_ASSERT( sizeof(string_t) == sizeof(int) ); + + static char szBuf[512]; + + switch (fieldType) + { + case FIELD_STRING: + { + return(STRING(iszVal)); + } + + case FIELD_BOOLEAN: + { + if (bVal == 0) + { + Q_strncpy(szBuf, "false",sizeof(szBuf)); + } + else + { + Q_strncpy(szBuf, "true",sizeof(szBuf)); + } + return(szBuf); + } + + case FIELD_INTEGER: + { + Q_snprintf( szBuf, sizeof( szBuf ), "%i", iVal ); + return(szBuf); + } + + case FIELD_FLOAT: + { + Q_snprintf(szBuf,sizeof(szBuf), "%g", flVal); + return(szBuf); + } + + case FIELD_COLOR32: + { + Q_snprintf(szBuf,sizeof(szBuf), "%d %d %d %d", (int)rgbaVal.r, (int)rgbaVal.g, (int)rgbaVal.b, (int)rgbaVal.a); + return(szBuf); + } + + case FIELD_VECTOR: + { + Q_snprintf(szBuf,sizeof(szBuf), "[%g %g %g]", (double)vecVal[0], (double)vecVal[1], (double)vecVal[2]); + return(szBuf); + } + + case FIELD_VOID: + { + szBuf[0] = '\0'; + return(szBuf); + } + + case FIELD_EHANDLE: + { + CBaseHandle temp = eVal; + + const char *pszName = g_OutputManager.BaseHandleToEdict(temp)->GetClassName(); + + if (pszName == NULL) + { + Q_strncpy( szBuf, "<>", 512 ); + return (szBuf); + } + + Q_strncpy( szBuf, pszName, 512 ); + return (szBuf); + } + + default: + break; + } + + return("No conversion to string"); +} +#endif +// Thanks SM core +edict_t *EntityOutputManager::BaseHandleToEdict(CBaseHandle &hndl) +{ + if (!hndl.IsValid()) + { + return NULL; + } + + int index = hndl.GetEntryIndex(); + + edict_t *pStoredEdict; + + pStoredEdict = engine->PEntityOfEntIndex(index); + + if (pStoredEdict == NULL) + { + return NULL; + } + + IServerEntity *pSE = pStoredEdict->GetIServerEntity(); + + if (pSE == NULL) + { + return NULL; + } + + if (pSE->GetRefEHandle() != hndl) + { + return NULL; + } + + return pStoredEdict; +} + + + diff --git a/extensions/sdktools/output.h b/extensions/sdktools/output.h new file mode 100644 index 00000000..ac38eb9b --- /dev/null +++ b/extensions/sdktools/output.h @@ -0,0 +1,176 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: output.h 1775 2007-12-06 02:25:35Z faluco $ + */ + +#ifndef _INCLUDE_SOURCEMOD_OUTPUT_H_ +#define _INCLUDE_SOURCEMOD_OUTPUT_H_ + +#include +#include +//#include "variant_t.h" +#include "sh_list.h" +#include "sh_stack.h" +#include "sm_trie_tpl.h" +#include "detours.h" + +extern ISourcePawnEngine *spengine; + +struct OutputNameStruct; + +/** + * This is a function specific hook that corresponds to an entity classname + * and outputname. There can be many of these for each classname/output combo + */ +struct omg_hooks +{ + int entity_filter; // if not -1 is an entity signature + int entity_index; + bool only_once; + IPluginFunction *pf; + OutputNameStruct *m_parent; + bool in_use; + bool delete_me; +}; + +/** + * This represents an output belonging to a specific classname + */ +struct OutputNameStruct +{ + SourceHook::List hooks; + char Name[50]; +}; + +/** + * This represents an entity classname + */ +struct ClassNameStruct +{ + //Trie mapping outputname to a OutputNameStruct + //KTrie OutputList; + IBasicTrie *OutputList; + + ClassNameStruct() + { + OutputList = adtfactory->CreateBasicTrie(); + } + + ~ClassNameStruct() + { + OutputList->Destroy(); + } +}; + +class EntityOutputManager : public IPluginsListener +{ +public: + EntityOutputManager() + { + info_address = NULL; + info_callback = NULL; + HookCount = 0; + is_detoured = false; + enabled = false; + } + + ~EntityOutputManager() + { + EntityOutputs->Destroy(); + ClassNames->Destroy(); + ShutdownFireEventDetour(); + } + + void Init() + { + enabled = CreateFireEventDetour(); + + if (!enabled) + { + return; + } + + EntityOutputs = adtfactory->CreateBasicTrie(); + ClassNames = adtfactory->CreateBasicTrie(); + } + + bool IsEnabled() + { + return enabled; + } + + void FireEventDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay); + + void OnPluginDestroyed(IPlugin *plugin); + + OutputNameStruct *FindOutputPointer(const char *classname, const char *outputname, bool create); + + void CleanUpHook(omg_hooks *hook); + + omg_hooks *NewHook(); + + void OnHookAdded(); + void OnHookRemoved(); + +private: + bool enabled; + + // Patch/unpatch the server dll + void InitFireEventDetour(); + void ShutdownFireEventDetour(); + bool is_detoured; + + //These create/delete the allocated memory and write into it + bool CreateFireEventDetour(); + void DeleteFireEventDetour(); + + const char *FindOutputName(void *pOutput, CBaseEntity *pCaller); + edict_t *BaseHandleToEdict(CBaseHandle &hndl); + + //Maps CEntityOutput * to a OutputNameStruct + IBasicTrie *EntityOutputs; + // Maps classname to a ClassNameStruct + IBasicTrie *ClassNames; + + SourceHook::CStack FreeHooks; //Stores hook pointers to avoid calls to new + + int HookCount; + + patch_t info_restore; + void *info_address; + void *info_callback; +}; + +void TempDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay); + +extern EntityOutputManager g_OutputManager; + +extern sp_nativeinfo_t g_EntOutputNatives[]; + +#endif //_INCLUDE_SOURCEMOD_OUTPUT_H_ diff --git a/extensions/sdktools/outputnatives.cpp b/extensions/sdktools/outputnatives.cpp new file mode 100644 index 00000000..4fc0796b --- /dev/null +++ b/extensions/sdktools/outputnatives.cpp @@ -0,0 +1,611 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: outputnatives.cpp 1521 2007-10-01 21:20:30Z faluco $ + */ + +#include "extension.h" +#include "output.h" + + +// HookSingleEntityOutput(ent, const String:output[], function, bool:once); +cell_t HookSingleEntityOutput(IPluginContext *pContext, const cell_t *params) +{ + if (!g_OutputManager.IsEnabled()) + { + return pContext->ThrowNativeError("Entity Outputs are disabled - See error logs for details"); + } + + edict_t *pEdict = engine->PEntityOfEntIndex(params[1]); + if (!pEdict) + { + return pContext->ThrowNativeError("Invalid Entity index %i", params[1]); + } + const char *classname = pEdict->GetClassName(); + + char *outputname; + pContext->LocalToString(params[2], &outputname); + + OutputNameStruct *pOutputName = g_OutputManager.FindOutputPointer((const char *)classname, outputname, true); + + //Check for an existing identical hook + SourceHook::List::iterator _iter; + + omg_hooks *hook; + + IPluginFunction *pFunction; + pFunction = pContext->GetFunctionById(params[3]); + + for (_iter=pOutputName->hooks.begin(); _iter!=pOutputName->hooks.end(); _iter++) + { + hook = (omg_hooks *)*_iter; + if (hook->pf == pFunction && hook->entity_filter == pEdict->m_NetworkSerialNumber) + { + return 0; + } + } + + hook = g_OutputManager.NewHook(); + + hook->entity_filter = pEdict->m_NetworkSerialNumber; + hook->entity_index = engine->IndexOfEdict(pEdict); + hook->only_once= !!params[4]; + hook->pf = pFunction; + hook->m_parent = pOutputName; + hook->in_use = false; + hook->delete_me = false; + + pOutputName->hooks.push_back(hook); + + g_OutputManager.OnHookAdded(); + + IPlugin *pPlugin = plsys->FindPluginByContext(pContext->GetContext()); + SourceHook::List *pList = NULL; + + if (!pPlugin->GetProperty("OutputHookList", (void **)&pList, false) || !pList) + { + pList = new SourceHook::List; + pPlugin->SetProperty("OutputHookList", pList); + } + + pList->push_back(hook); + + return 1; +} + + +// HookEntityOutput(const String:classname[], const String:output[], function); +cell_t HookEntityOutput(IPluginContext *pContext, const cell_t *params) +{ + if (!g_OutputManager.IsEnabled()) + { + return pContext->ThrowNativeError("Entity Outputs are disabled - See error logs for details"); + } + + //Find or create the base structures for this classname and the output + char *classname; + pContext->LocalToString(params[1], &classname); + + char *outputname; + pContext->LocalToString(params[2], &outputname); + + OutputNameStruct *pOutputName = g_OutputManager.FindOutputPointer((const char *)classname, outputname, true); + + //Check for an existing identical hook + SourceHook::List::iterator _iter; + + omg_hooks *hook; + + IPluginFunction *pFunction; + pFunction = pContext->GetFunctionById(params[3]); + + for (_iter=pOutputName->hooks.begin(); _iter!=pOutputName->hooks.end(); _iter++) + { + hook = (omg_hooks *)*_iter; + if (hook->pf == pFunction && hook->entity_filter == -1) + { + //already hooked to this function... + //throw an error or just let them get away with stupidity? + // seems like poor coding if they dont know if something is hooked or not + return 0; + } + } + + hook = g_OutputManager.NewHook(); + + hook->entity_filter = -1; + hook->pf = pFunction; + hook->m_parent = pOutputName; + hook->in_use = false; + hook->delete_me = false; + + pOutputName->hooks.push_back(hook); + + g_OutputManager.OnHookAdded(); + + IPlugin *pPlugin = plsys->FindPluginByContext(pContext->GetContext()); + SourceHook::List *pList = NULL; + + if (!pPlugin->GetProperty("OutputHookList", (void **)&pList, false) || !pList) + { + pList = new SourceHook::List; + pPlugin->SetProperty("OutputHookList", pList); + } + + pList->push_back(hook); + + return 1; +} + +// UnHookEntityOutput(const String:classname[], const String:output[], EntityOutput:callback); +cell_t UnHookEntityOutput(IPluginContext *pContext, const cell_t *params) +{ + if (!g_OutputManager.IsEnabled()) + { + return pContext->ThrowNativeError("Entity Outputs are disabled - See error logs for details"); + } + + char *classname; + pContext->LocalToString(params[1], &classname); + + char *outputname; + pContext->LocalToString(params[2], &outputname); + + OutputNameStruct *pOutputName = g_OutputManager.FindOutputPointer((const char *)classname, outputname, false); + + if (!pOutputName) + { + return 0; + } + + //Check for an existing identical hook + SourceHook::List::iterator _iter; + + omg_hooks *hook; + + IPluginFunction *pFunction; + pFunction = pContext->GetFunctionById(params[3]); + + for (_iter=pOutputName->hooks.begin(); _iter!=pOutputName->hooks.end(); _iter++) + { + hook = (omg_hooks *)*_iter; + if (hook->pf == pFunction && hook->entity_filter == -1) + { + // remove this hook. + if (hook->in_use) + { + hook->delete_me = true; + return 1; + } + + pOutputName->hooks.erase(_iter); + g_OutputManager.CleanUpHook(hook); + + return 1; + } + } + + return 0; +} + +// UnHookSingleEntityOutput(entity, const String:output[], EntityOutput:callback); +cell_t UnHookSingleEntityOutput(IPluginContext *pContext, const cell_t *params) +{ + if (!g_OutputManager.IsEnabled()) + { + return pContext->ThrowNativeError("Entity Outputs are disabled - See error logs for details"); + } + + // Find the classname of the entity and lookup the classname and output structures + edict_t *pEdict = engine->PEntityOfEntIndex(params[1]); + if (!pEdict) + { + return pContext->ThrowNativeError("Invalid Entity index %i", params[1]); + } + + const char *classname = pEdict->GetClassName(); + + char *outputname; + pContext->LocalToString(params[2], &outputname); + + OutputNameStruct *pOutputName = g_OutputManager.FindOutputPointer((const char *)classname, outputname, false); + + if (!pOutputName) + { + return 0; + } + + //Check for an existing identical hook + SourceHook::List::iterator _iter; + + omg_hooks *hook; + + IPluginFunction *pFunction; + pFunction = pContext->GetFunctionById(params[3]); + + for (_iter=pOutputName->hooks.begin(); _iter!=pOutputName->hooks.end(); _iter++) + { + hook = (omg_hooks *)*_iter; + if (hook->pf == pFunction && hook->entity_index == engine->IndexOfEdict(pEdict)) + { + // remove this hook. + if (hook->in_use) + { + hook->delete_me = true; + return 1; + } + + pOutputName->hooks.erase(_iter); + g_OutputManager.CleanUpHook(hook); + + return 1; + } + } + + return 0; +} + +#if 0 +static cell_t GetVariantType(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + return pInfo->crab.FieldType(); +} + +static cell_t GetVariantInt(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + if (pInfo->crab.FieldType() != FIELD_INTEGER) + { + return pContext->ThrowNativeError("Variant is not an integer"); + } + + return pInfo->crab.Int(); +} + +static cell_t GetVariantBool(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + if (pInfo->crab.FieldType() != FIELD_BOOLEAN) + { + return pContext->ThrowNativeError("Variant is not a boolean"); + } + + return pInfo->crab.Bool(); +} + +static cell_t GetVariantEntity(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + if (pInfo->crab.FieldType() != FIELD_EHANDLE) + { + return pContext->ThrowNativeError("Variant is not an entity"); + } + + edict_t *pEdict = g_OutputManager.BaseHandleToEdict((CBaseHandle)pInfo->crab.Entity()); + + return engine->IndexOfEdict(pEdict); +} + +static cell_t GetVariantFloat(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + if (pInfo->crab.FieldType() != FIELD_FLOAT) + { + return pContext->ThrowNativeError("Variant is not a float"); + } + + return sp_ftoc(pInfo->crab.Float()); +} + +static cell_t GetVariantVector(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + if (pInfo->crab.FieldType() != FIELD_VECTOR) + { + return pContext->ThrowNativeError("Variant is not a vector"); + } + + cell_t *r; + pContext->LocalToPhysAddr(params[2], &r); + + Vector temp; + pInfo->crab.Vector3D(temp); + + r[0] = sp_ftoc(temp[0]); + r[1] = sp_ftoc(temp[1]); + r[2] = sp_ftoc(temp[2]); + + return 0; +} + +static cell_t GetVariantColour(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + if (pInfo->crab.FieldType() != FIELD_COLOR32) + { + return pContext->ThrowNativeError("Variant is not a colour"); + } + + cell_t *r; + pContext->LocalToPhysAddr(params[2], &r); + + color32 temp = pInfo->crab.Color32(); + + r[0] = temp.r; + r[1] = temp.g; + r[2] = temp.b; + r[4] = temp.a; + + return 0; +} + +static cell_t GetVariantString(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + varhandle_t *pInfo; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_OutputManager.VariantHandle, &sec, (void **)&pInfo)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid variant handle %x (error %d)", hndl, err); + } + + char *dest; + const char *src = pInfo->crab.String(); + size_t len; + + pContext->LocalToString(params[2], &dest); + + /* Perform bounds checking */ + len = strlen(src); + if (len >= (unsigned)params[3]) + { + len = params[3] - 1; + } else { + len = params[3]; + } + + /* Copy */ + memmove(dest, src, len); + + dest[len] = '\0'; + + return 0; +} +#endif + +sp_nativeinfo_t g_EntOutputNatives[] = +{ + {"HookEntityOutput", HookEntityOutput}, + {"UnHookEntityOutput", UnHookEntityOutput}, + {"HookSingleEntityOutput", HookSingleEntityOutput}, + {"UnHookSingleEntityOutput", UnHookSingleEntityOutput}, +#if 0 //Removed because we don't need them + {"GetVariantType", GetVariantType}, + {"GetVariantInt", GetVariantInt}, + {"GetVariantFloat", GetVariantFloat}, + {"GetVariantBool", GetVariantBool}, + {"GetVariantString", GetVariantString}, + {"GetVariantEntity", GetVariantEntity}, + {"GetVariantVector", GetVariantVector}, + {"GetVariantColour", GetVariantColour}, +#endif + {NULL, NULL}, +}; + +#if 0 +//Include file stuff that wasn't needed: + +enum FieldType +{ + FieldType_Float = 1, + FieldType_String = 2, + FieldType_Vector = 3, + FieldType_Int = 5, + FieldType_Bool = 6, + FieldType_Colour = 9, + FieldType_Entity = 13 +} + +/** + * Gets the current type of a variant handle + * + * @param variant A variant handle. + * @return Current type of the variant. + */ +native FieldType:GetVariantType(Handle:variant); + +/** + * Retreives a Float from a variant handle. + * + * @param variant A variant handle. + * @return Float value. + * @error Variant handle is not type FieldType_Float + */ +native Float:GetVariantFloat(Handle:variant); + +/** + * Retreives an Integer from a variant handle. + * + * @param variant A variant handle. + * @return Int value. + * @error Variant handle is not type FieldType_Int + */ +native GetVariantInt(Handle:variant); + +/** + * Retreives a bool from a variant handle. + * + * @param variant A variant handle. + * @return bool value. + * @error Variant handle is not type FieldType_Bool + */ +native bool:GetVariantBool(Handle:variant); + +/** + * Retreives an entity from a variant handle. + * + * @param variant A variant handle. + * @return Entity Index. + * @error Variant handle is not type FieldType_Entity + */ +native GetVariantEntity(Handle:variant); + +/** + * Retreives a Vector from a variant handle. + * + * @param variant A variant handle. + * @param vec buffer to store the Vector. + * @noreturn + * @error Variant handle is not type FieldType_Vector + */ +native GetVariantVector(Handle:variant, Float:vec[3]); + +/** + * Retreives a String from a variant handle. + * + * @note This native does not require the variant to be FieldType_String, + * It can convert any type into a string representation. + * + * @param variant A variant handle. + * @param buffer buffer to store the string in + * @param maxlen Maximum length of buffer. + * @noreturn + */ +native GetVariantString(Handle:variant, String:buffer[], maxlen); + +/** + * Retreives a Colour from a variant handle. + * + * @note Colour is in format r,g,b,a + * + * @param variant A variant handle. + * @param colour buffer array to store the colour in. + * @noreturn + * @error Variant handle is not type FieldType_Colour + */ +native GetVariantColour(Handle:variant, colour[4]); +#endif diff --git a/gamedata/sdktools.games.ep2.txt b/gamedata/sdktools.games.ep2.txt index 5f3a8d5f..fdf04d04 100644 --- a/gamedata/sdktools.games.ep2.txt +++ b/gamedata/sdktools.games.ep2.txt @@ -305,12 +305,20 @@ } "Signatures" { - "FireEvent" + "FireOutput" { "library" "server" "windows" "\x81\xEC\x1C\x01\x00\x00\x53\x55\x56\x8B\x71\x14\x85\xF6" "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } + "Offsets" + { + "FireOutputBackup" + { + "windows" "6" + "linux" "10" + } + } } } diff --git a/gamedata/sdktools.games.txt b/gamedata/sdktools.games.txt index d5d9c74a..cc5f454b 100644 --- a/gamedata/sdktools.games.txt +++ b/gamedata/sdktools.games.txt @@ -1707,13 +1707,21 @@ } "Signatures" { - "FireEvent" + "FireOutput" { "library" "server" "windows" "\x81\xEC\x1C\x03\x00\x00\x53\x55\x56\x8B\x71\x14" "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } + "Offsets" + { + "FireOutputBackup" + { + "windows" "6" + "linux" "6" + } + } } } diff --git a/plugins/include/sdktools.inc b/plugins/include/sdktools.inc index e3e8a38a..f0f67720 100644 --- a/plugins/include/sdktools.inc +++ b/plugins/include/sdktools.inc @@ -47,6 +47,7 @@ #include #include #include +#include enum SDKCallType { diff --git a/plugins/include/sdktools_entoutput.inc b/plugins/include/sdktools_entoutput.inc new file mode 100644 index 00000000..a506609f --- /dev/null +++ b/plugins/include/sdktools_entoutput.inc @@ -0,0 +1,91 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: sdktools_entoutput.inc 1504 2007-09-28 19:56:19Z faluco $ + */ + +#if defined _sdktools_entoutput_included + #endinput +#endif +#define _sdktools_entoutput_included + +/** + * Called when an entity output is fired. + * + * @param output Name of the output that fired. + * @param caller Entity index of the caller. + * @param activator Entity index of the activator. + * @param delay Delay in seconds? before the event gets fired. + */ +functag EntityOutput public(const String:output[], caller, activator, Float:delay); + +/** + * Add an entity output hook on a entity classname + * + * @param classname The classname to hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @noreturn + * @error Entity Outputs disabled. + */ +native HookEntityOutput(const String:classname[], const String:output[], EntityOutput:callback); + +/** + * Remove an entity output hook. + * @param classname The classname to hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @return True on success, false if no valid hook was found. + * @error Entity Outputs disabled. + */ +native bool:UnHookEntityOutput(const String:classname[], const String:output[], EntityOutput:callback); + +/** + * Add an entity output hook on a single entity instance + * + * @param entity The entity on which to add a hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @param once Only fire this hook once and then remove itself. + * @noreturn + * @error Entity Outputs disabled or Invalid Entity index. + */ +native HookSingleEntityOutput(entity, const String:output[], EntityOutput:callback , bool:once=false); + +/** + * Remove a single entity output hook. + * + * @param entity The entity on which to remove the hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @return True on success, false if no valid hook was found. + * @error Entity Outputs disabled or Invalid Entity index. + */ +native bool:UnHookSingleEntityOutput(entity, const String:output[], EntityOutput:callback); + diff --git a/plugins/testsuite/outputtest.sp b/plugins/testsuite/outputtest.sp new file mode 100644 index 00000000..605501f5 --- /dev/null +++ b/plugins/testsuite/outputtest.sp @@ -0,0 +1,59 @@ +#include +#include + +public Plugin:myinfo = +{ + name = "Entity Output Hook Testing", + author = "AlliedModders LLC", + description = "Test suite for Entity Output Hooks", + version = "1.0.0.0", + url = "http://www.sourcemod.net/" +}; + +public OnPluginStart() +{ + HookEntityOutput("point_spotlight", "OnLightOn", OutputHook); + + HookEntityOutput("func_door", "OnOpen", OutputHook); + HookEntityOutput("func_door_rotating", "OnOpen", OutputHook); + HookEntityOutput("func_door", "OnClose", OutputHook); + HookEntityOutput("func_door_rotating", "OnClose", OutputHook); +} + +public OutputHook(const String:name[], caller, activator, Float:delay) +{ + LogMessage("[ENTOUTPUT] %s", name); +} + +public OnMapStart() +{ + new ent = FindEntityByClassname(-1, "point_spotlight"); + + if (ent == -1) + { + LogError("Could not find a point_spotlight"); + ent = CreateEntityByName("point_spotlight"); + DispatchSpawn(ent); + } + + HookSingleEntityOutput(ent, "OnLightOn", OutputHook, true); + HookSingleEntityOutput(ent, "OnLightOff", OutputHook, true); + + AcceptEntityInput(ent, "LightOff", ent, ent); + AcceptEntityInput(ent, "LightOn", ent, ent); + + AcceptEntityInput(ent, "LightOff", ent, ent); + AcceptEntityInput(ent, "LightOn", ent, ent); + + HookSingleEntityOutput(ent, "OnLightOn", OutputHook, false); + HookSingleEntityOutput(ent, "OnLightOff", OutputHook, false); + + AcceptEntityInput(ent, "LightOff", ent, ent); + AcceptEntityInput(ent, "LightOn", ent, ent); + AcceptEntityInput(ent, "LightOff", ent, ent); + AcceptEntityInput(ent, "LightOn", ent, ent); + + //Comment these out (and reload the plugin heaps) to test for leaks on plugin unload + UnHookSingleEntityOutput(ent, "OnLightOn", OutputHook); + UnHookSingleEntityOutput(ent, "OnLightOff", OutputHook); +} \ No newline at end of file