extensions/sdktools/output.cpp
author Kyle Sanderson <kyle.leet@gmail.com>
Mon Aug 12 00:54:21 2013 +0100 (2013-08-12)
changeset 3997 59bcfc51eb79
parent 3515 c14c14e06cc1
permissions -rw-r--r--
Add support for nested datatables. (bug 5446, r=asherkin)
pred@1958
     1
/**
pred@1958
     2
 * vim: set ts=4 :
pred@1958
     3
 * =============================================================================
pred@1958
     4
 * SourceMod SDKTools Extension
fyrenmoo@3089
     5
 * Copyright (C) 2004-2010 AlliedModders LLC.  All rights reserved.
pred@1958
     6
 * =============================================================================
pred@1958
     7
 *
pred@1958
     8
 * This program is free software; you can redistribute it and/or modify it under
pred@1958
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
pred@1958
    10
 * Free Software Foundation.
pred@1958
    11
 * 
pred@1958
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
pred@1958
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
pred@1958
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
pred@1958
    15
 * details.
pred@1958
    16
 *
pred@1958
    17
 * You should have received a copy of the GNU General Public License along with
pred@1958
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
pred@1958
    19
 *
pred@1958
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
pred@1958
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
pred@1958
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
pred@1958
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
pred@1958
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
pred@1958
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
pred@1958
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
pred@1958
    27
 * or <http://www.sourcemod.net/license.php>.
pred@1958
    28
 *
ds@1987
    29
 * Version: $Id$
pred@1958
    30
 */
pred@1958
    31
pred@1958
    32
#include "extension.h"
pred@1958
    33
#include "output.h"
pred@1958
    34
pred@1958
    35
ISourcePawnEngine *spengine = NULL;
pred@1958
    36
EntityOutputManager g_OutputManager;
fyrenmoo@3089
    37
CDetour *fireOutputDetour = NULL;
pred@1958
    38
dvander@1992
    39
EntityOutputManager::EntityOutputManager()
dvander@1992
    40
{
dvander@1992
    41
	info_address = NULL;
dvander@1992
    42
	info_callback = NULL;
dvander@1992
    43
	HookCount = 0;
dvander@1992
    44
	enabled = false;
dvander@1992
    45
}
dvander@1992
    46
dvander@3414
    47
void EntityOutputManager::Shutdown()
dvander@1992
    48
{
dvander@1992
    49
	if (!enabled)
dvander@1992
    50
	{
dvander@1992
    51
		return;
dvander@1992
    52
	}
dvander@1992
    53
dvander@1992
    54
	EntityOutputs->Destroy();
dvander@1992
    55
	ClassNames->Destroy();
fyrenmoo@3089
    56
	fireOutputDetour->Destroy();
dvander@1992
    57
}
dvander@1992
    58
dvander@1992
    59
void EntityOutputManager::Init()
dvander@1992
    60
{
dvander@1992
    61
	enabled = CreateFireEventDetour();
dvander@1992
    62
dvander@1992
    63
	if (!enabled)
dvander@1992
    64
	{
dvander@1992
    65
		return;
dvander@1992
    66
	}
dvander@1992
    67
dvander@1992
    68
	EntityOutputs = adtfactory->CreateBasicTrie();
dvander@1992
    69
	ClassNames = adtfactory->CreateBasicTrie();
dvander@1992
    70
}
dvander@1992
    71
dvander@1992
    72
bool EntityOutputManager::IsEnabled()
dvander@1992
    73
{
dvander@1992
    74
	return enabled;
dvander@1992
    75
}
dvander@1992
    76
fyrenmoo@3141
    77
#ifdef PLATFORM_WINDOWS
fyrenmoo@3141
    78
DETOUR_DECL_MEMBER8(FireOutput, void, int, what, int, the, int, hell, int, msvc, void *, variant_t, CBaseEntity *, pActivator, CBaseEntity *, pCaller, float, fDelay)
fyrenmoo@3141
    79
{
fyrenmoo@3141
    80
	g_OutputManager.FireEventDetour((void *)this, pActivator, pCaller, fDelay);
fyrenmoo@3141
    81
	DETOUR_MEMBER_CALL(FireOutput)(what, the, hell, msvc, variant_t, pActivator, pCaller, fDelay);
fyrenmoo@3141
    82
}
fyrenmoo@3141
    83
#else
fyrenmoo@3089
    84
DETOUR_DECL_MEMBER4(FireOutput, void, void *, variant_t, CBaseEntity *, pActivator, CBaseEntity *, pCaller, float, fDelay)
fyrenmoo@3089
    85
{
fyrenmoo@3089
    86
	g_OutputManager.FireEventDetour((void *)this, pActivator, pCaller, fDelay);
fyrenmoo@3089
    87
	DETOUR_MEMBER_CALL(FireOutput)(variant_t, pActivator, pCaller, fDelay);
fyrenmoo@3089
    88
}
fyrenmoo@3141
    89
#endif
fyrenmoo@3089
    90
pred@1958
    91
bool EntityOutputManager::CreateFireEventDetour()
pred@1958
    92
{
fyrenmoo@3089
    93
	fireOutputDetour = DETOUR_CREATE_MEMBER(FireOutput, "FireOutput");
pred@1958
    94
fyrenmoo@3089
    95
	if (fireOutputDetour) return true;
pred@1958
    96
fyrenmoo@3089
    97
	return false;
pred@1958
    98
}
pred@1958
    99
pred@1958
   100
void EntityOutputManager::FireEventDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay)
pred@1958
   101
{
skamonkey@2940
   102
	if (!pCaller)
skamonkey@2940
   103
	{
skamonkey@2940
   104
		return;
skamonkey@2940
   105
	}
skamonkey@2940
   106
pred@1958
   107
	char sOutput[20];
skamonkey@3515
   108
	Q_snprintf(sOutput, sizeof(sOutput), "%p", pOutput);
pred@1958
   109
pred@1958
   110
	// attempt to directly lookup a hook using the pOutput pointer
pred@1958
   111
	OutputNameStruct *pOutputName = NULL;
pred@1958
   112
pred@1958
   113
	bool fastLookup = false;
pred@1958
   114
	
pred@2834
   115
	// Fast lookup failed - check the slow way for hooks that haven't fired yet
pred@1958
   116
	if ((fastLookup = EntityOutputs->Retrieve(sOutput, (void **)&pOutputName)) == false)
pred@1958
   117
	{
pred@2746
   118
		const char *classname = GetEntityClassname(pCaller);
pred@1958
   119
		const char *outputname = FindOutputName(pOutput, pCaller);
skamonkey@3041
   120
		
skamonkey@3041
   121
		if (!outputname)
skamonkey@3041
   122
		{
skamonkey@3041
   123
			return;
skamonkey@3041
   124
		}
pred@1958
   125
pred@1958
   126
		pOutputName = FindOutputPointer(classname, outputname, false);
pred@1958
   127
pred@1958
   128
		if (!pOutputName)
pred@1958
   129
		{
pred@1958
   130
			return;
pred@1958
   131
		}
pred@1958
   132
	}
pred@1958
   133
pred@1958
   134
	if (!pOutputName->hooks.empty())
pred@1958
   135
	{
pred@1958
   136
		if (!fastLookup)
pred@1958
   137
		{
pred@1958
   138
			// hook exists on this classname and output - map it into our quick find trie
pred@1958
   139
			EntityOutputs->Insert(sOutput, pOutputName);
pred@1958
   140
		}
pred@1958
   141
pred@1958
   142
		SourceHook::List<omg_hooks *>::iterator _iter;
pred@1958
   143
pred@1958
   144
		omg_hooks *hook;
pred@1958
   145
pred@1958
   146
		_iter = pOutputName->hooks.begin();
pred@1958
   147
pred@1958
   148
		while (_iter != pOutputName->hooks.end())
pred@1958
   149
		{
pred@1958
   150
			hook = (omg_hooks *)*_iter;
pred@1958
   151
pred@1958
   152
			hook->in_use = true;
pred@1958
   153
pred@2746
   154
			cell_t ref = gamehelpers->EntityToReference(pCaller);
pred@1958
   155
			
pred@2746
   156
			if (hook->entity_ref != -1 
pred@2746
   157
					&& gamehelpers->ReferenceToIndex(hook->entity_ref) == gamehelpers->ReferenceToIndex(ref)
pred@2746
   158
					&& ref != hook->entity_ref)
pred@1958
   159
			{
pred@2746
   160
				// same entity index but different reference. Entity has changed, kill the hook.
pred@1958
   161
				_iter = pOutputName->hooks.erase(_iter);
pred@1958
   162
				CleanUpHook(hook);
pred@1958
   163
pred@1958
   164
				continue;
pred@1958
   165
			}
pred@1958
   166
pred@2746
   167
			if (hook->entity_ref == -1 || hook->entity_ref == ref) // Global classname hook
pred@1958
   168
			{
pred@1958
   169
				//fire the forward to hook->pf
pred@1958
   170
				hook->pf->PushString(pOutputName->Name);
pred@2746
   171
				hook->pf->PushCell(gamehelpers->ReferenceToBCompatRef(ref));
pred@2746
   172
				hook->pf->PushCell(gamehelpers->EntityToBCompatRef(pActivator));
pred@2746
   173
pred@1958
   174
				//hook->pf->PushCell(handle);
pred@1958
   175
				hook->pf->PushFloat(fDelay);
pred@1958
   176
				hook->pf->Execute(NULL);
pred@1958
   177
pred@2746
   178
				if ((hook->entity_ref != -1) && hook->only_once)
pred@1958
   179
				{
pred@1958
   180
					_iter = pOutputName->hooks.erase(_iter);
pred@1958
   181
					CleanUpHook(hook);
pred@1958
   182
pred@1958
   183
					continue;
pred@1958
   184
				}
pred@1958
   185
pred@1958
   186
				if (hook->delete_me)
pred@1958
   187
				{
pred@1958
   188
					_iter = pOutputName->hooks.erase(_iter);
pred@1958
   189
					CleanUpHook(hook);
pred@1958
   190
					continue;
pred@1958
   191
				}
pred@2062
   192
			}
pred@1958
   193
pred@2062
   194
			hook->in_use = false;
pred@2062
   195
			_iter++;
pred@1958
   196
		}
pred@1958
   197
	}
pred@1958
   198
}
pred@1958
   199
pred@1958
   200
omg_hooks *EntityOutputManager::NewHook()
pred@1958
   201
{
pred@1958
   202
	omg_hooks *hook;
pred@1958
   203
pred@1958
   204
	if (FreeHooks.empty())
pred@1958
   205
	{
pred@1958
   206
		hook = new omg_hooks;
pred@1958
   207
	}
pred@1958
   208
	else
pred@1958
   209
	{
pred@1958
   210
		hook = g_OutputManager.FreeHooks.front();
pred@1958
   211
		g_OutputManager.FreeHooks.pop();
pred@1958
   212
	}
pred@1958
   213
pred@1958
   214
	return hook;
pred@1958
   215
}
pred@1958
   216
pred@1958
   217
void EntityOutputManager::OnHookAdded()
pred@1958
   218
{
pred@1958
   219
	HookCount++;
pred@1958
   220
pred@1958
   221
	if (HookCount == 1)
pred@1958
   222
	{
pred@1958
   223
		// This is the first hook created
fyrenmoo@3089
   224
		fireOutputDetour->EnableDetour();
pred@1958
   225
	}
pred@1958
   226
}
pred@1958
   227
pred@1958
   228
void EntityOutputManager::OnHookRemoved()
pred@1958
   229
{
pred@1958
   230
	HookCount--;
pred@1958
   231
pred@1958
   232
	if (HookCount == 0)
pred@1958
   233
	{
fyrenmoo@3089
   234
		fireOutputDetour->DisableDetour();
pred@1958
   235
	}
pred@1958
   236
}
pred@1958
   237
pred@1958
   238
void EntityOutputManager::CleanUpHook(omg_hooks *hook)
pred@1958
   239
{
pred@1958
   240
	FreeHooks.push(hook);
pred@1958
   241
pred@1958
   242
	OnHookRemoved();
pred@1958
   243
pred@1958
   244
	IPlugin *pPlugin = plsys->FindPluginByContext(hook->pf->GetParentContext()->GetContext());
pred@1958
   245
	SourceHook::List<omg_hooks *> *pList = NULL;
pred@1958
   246
pred@1958
   247
	if (!pPlugin->GetProperty("OutputHookList", (void **)&pList, false) || !pList)
pred@1958
   248
	{
pred@1958
   249
		return;
pred@1958
   250
	}
pred@1958
   251
pred@1958
   252
	SourceHook::List<omg_hooks *>::iterator p_iter = pList->begin();
pred@1958
   253
pred@1958
   254
	omg_hooks *pluginHook;
pred@1958
   255
pred@1958
   256
	while (p_iter != pList->end())
pred@1958
   257
	{
pred@1958
   258
		pluginHook = (omg_hooks *)*p_iter;
pred@1958
   259
		if (pluginHook == hook)
pred@1958
   260
		{
pred@1958
   261
			p_iter = pList->erase(p_iter);
pred@1958
   262
		}
pred@1958
   263
		else
pred@1958
   264
		{
pred@1958
   265
			p_iter++;
pred@1958
   266
		}
pred@1958
   267
	}
pred@1958
   268
}
pred@1958
   269
pred@1958
   270
void EntityOutputManager::OnPluginDestroyed(IPlugin *plugin)
pred@1958
   271
{
pred@1958
   272
	SourceHook::List<omg_hooks *> *pList = NULL;
pred@1958
   273
pred@1958
   274
	if (plugin->GetProperty("OutputHookList", (void **)&pList, true))
pred@1958
   275
	{
pred@1958
   276
		SourceHook::List<omg_hooks *>::iterator p_iter = pList->begin();
pred@1958
   277
		omg_hooks *hook;
pred@1958
   278
pred@1958
   279
		while (p_iter != pList->end())
pred@1958
   280
		{
pred@1958
   281
			hook = (omg_hooks *)*p_iter;
pred@1958
   282
		
pred@1958
   283
			p_iter = pList->erase(p_iter); //remove from this plugins list
pred@1958
   284
			hook->m_parent->hooks.remove(hook); // remove from the y's list
pred@1958
   285
pred@1958
   286
			FreeHooks.push(hook); //save the omg_hook
pred@1958
   287
pred@1958
   288
			OnHookRemoved();
pred@1958
   289
		}		
pred@1958
   290
	}
pred@1958
   291
}
pred@1958
   292
pred@1958
   293
OutputNameStruct *EntityOutputManager::FindOutputPointer(const char *classname, const char *outputname, bool create)
pred@1958
   294
{
pred@1958
   295
	ClassNameStruct *pClassname;
pred@1958
   296
	
pred@1958
   297
	if (!ClassNames->Retrieve(classname, (void **)&pClassname))
pred@1958
   298
	{
pred@1958
   299
		if (create)
pred@1958
   300
		{
pred@1958
   301
			pClassname = new ClassNameStruct;
pred@1958
   302
			ClassNames->Insert(classname, pClassname);
pred@1958
   303
		}
pred@1958
   304
		else
pred@1958
   305
		{
pred@1958
   306
			return NULL;
pred@1958
   307
		}
pred@1958
   308
	}
pred@1958
   309
pred@1958
   310
	OutputNameStruct *pOutputName;
pred@1958
   311
pred@1958
   312
	if (!pClassname->OutputList->Retrieve(outputname, (void **)&pOutputName))
pred@1958
   313
	{
pred@1958
   314
		if (create)
pred@1958
   315
		{
pred@1958
   316
			pOutputName = new OutputNameStruct;
pred@1958
   317
			pClassname->OutputList->Insert(outputname, pOutputName);
pred@1958
   318
			strncpy(pOutputName->Name, outputname, sizeof(pOutputName->Name));
pred@1958
   319
			pOutputName->Name[49] = 0;
pred@1958
   320
		}
pred@1958
   321
		else
pred@1958
   322
		{
pred@1958
   323
			return NULL;
pred@1958
   324
		}
pred@1958
   325
	}
pred@1958
   326
pred@1958
   327
	return pOutputName;
pred@1958
   328
}
pred@1958
   329
pred@1958
   330
// Iterate the datamap of pCaller and look for output pointers with the same address as pOutput
pred@1958
   331
const char *EntityOutputManager::FindOutputName(void *pOutput, CBaseEntity *pCaller)
pred@1958
   332
{
pred@1958
   333
	datamap_t *pMap = gamehelpers->GetDataMap(pCaller);
pred@1958
   334
pred@1958
   335
	while (pMap)
pred@1958
   336
	{
pred@1958
   337
		for (int i=0; i<pMap->dataNumFields; i++)
pred@1958
   338
		{
pred@1958
   339
			if (pMap->dataDesc[i].flags & FTYPEDESC_OUTPUT)
pred@1958
   340
			{
ds@3099
   341
				if ((char *)pCaller + GetTypeDescOffs(&pMap->dataDesc[i]) == pOutput)
pred@1958
   342
				{
pred@1958
   343
					return pMap->dataDesc[i].externalName;
pred@1958
   344
				}
pred@1958
   345
			}
pred@1958
   346
		}
pred@1958
   347
		pMap = pMap->baseMap;
pred@1958
   348
	}
pred@1958
   349
pred@1958
   350
	return NULL;
pred@1958
   351
}
faluco@1986
   352
pred@2746
   353
const char *EntityOutputManager::GetEntityClassname(CBaseEntity *pEntity)
pred@1958
   354
{
pred@2746
   355
	static int offset = -1;
pred@2746
   356
	if (offset == -1)
pred@1958
   357
	{
pred@2746
   358
		datamap_t *pMap = gamehelpers->GetDataMap(pEntity);
kyle@3997
   359
		sm_datatable_info_t info;
kyle@3997
   360
		if (!gamehelpers->FindDataMapInfo(pMap, "m_iClassname", &info))
kyle@3997
   361
		{
kyle@3997
   362
			return NULL;
kyle@3997
   363
		}
kyle@3997
   364
		
kyle@3997
   365
		offset = info.actual_offset;
pred@1958
   366
	}
pred@1958
   367
pred@2834
   368
	return *(const char **)(((unsigned char *)pEntity) + offset);
pred@2751
   369
}