517 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			517 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * vim: set ts=4 sw=4 tw=99 noet :
 | |
|  * =============================================================================
 | |
|  * SourceMod
 | |
|  * Copyright (C) 2004-2010 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 <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * 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 <http://www.sourcemod.net/license.php>.
 | |
|  *
 | |
|  * Version: $Id$
 | |
|  */
 | |
| 
 | |
| #include "EventManager.h"
 | |
| #include "ForwardSys.h"
 | |
| #include "HandleSys.h"
 | |
| #include "PluginSys.h"
 | |
| #include "sm_stringutil.h"
 | |
| 
 | |
| EventManager g_EventManager;
 | |
| 
 | |
| SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent *, bool);
 | |
| 
 | |
| const ParamType GAMEEVENT_PARAMS[] = {Param_Cell, Param_String, Param_Cell};
 | |
| typedef List<EventHook *> EventHookList;
 | |
| 
 | |
| class EventForwardFilter : public IForwardFilter
 | |
| {
 | |
| 	EventInfo *pEventInfo;
 | |
| public:
 | |
| 	EventForwardFilter(EventInfo *pEventInfo) : pEventInfo(pEventInfo)
 | |
| 	{
 | |
| 	}
 | |
| 
 | |
| 	void Preprocess(IPluginFunction *fun, FwdParamInfo *params)
 | |
| 	{
 | |
| 		params[2].val = pEventInfo->bDontBroadcast ? 1 : 0;
 | |
| 	}
 | |
| };
 | |
| 
 | |
| EventManager::EventManager() : m_EventType(0)
 | |
| {
 | |
| 	/* Create an event lookup trie */
 | |
| 	m_EventHooks = sm_trie_create();
 | |
| }
 | |
| 
 | |
| EventManager::~EventManager()
 | |
| {
 | |
| 	sm_trie_destroy(m_EventHooks);
 | |
| 
 | |
| 	/* Free memory used by EventInfo structs if any */
 | |
| 	CStack<EventInfo *>::iterator iter;
 | |
| 	for (iter = m_FreeEvents.begin(); iter != m_FreeEvents.end(); iter++)
 | |
| 	{
 | |
| 		delete (*iter);
 | |
| 	}
 | |
| 
 | |
| 	m_FreeEvents.popall();
 | |
| }
 | |
| 
 | |
| void EventManager::OnSourceModAllInitialized()
 | |
| {
 | |
| 	/* Add a hook for IGameEventManager2::FireEvent() */
 | |
| 	SH_ADD_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent, false);
 | |
| 	SH_ADD_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent_Post, true);
 | |
| 
 | |
| 	HandleAccess sec;
 | |
| 
 | |
| 	/* Handle access security for 'GameEvent' handle type */
 | |
| 	sec.access[HandleAccess_Read] = 0;
 | |
| 	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
 | |
| 	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
 | |
| 
 | |
| 	/* Create the 'GameEvent' handle type */
 | |
| 	m_EventType = g_HandleSys.CreateType("GameEvent", this, 0, NULL, &sec, g_pCoreIdent, NULL);
 | |
| }
 | |
| 
 | |
| void EventManager::OnSourceModShutdown()
 | |
| {
 | |
| 	/* Remove hook for IGameEventManager2::FireEvent() */
 | |
| 	SH_REMOVE_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent, false);
 | |
| 	SH_REMOVE_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent_Post, true);
 | |
| 
 | |
| 	/* Remove the 'GameEvent' handle type */
 | |
| 	g_HandleSys.RemoveType(m_EventType, g_pCoreIdent);
 | |
| 
 | |
| 	/* Remove ourselves as listener for events */
 | |
| 	gameevents->RemoveListener(this);
 | |
| }
 | |
| 
 | |
| void EventManager::OnHandleDestroy(HandleType_t type, void *object)
 | |
| {
 | |
| 	EventInfo *pInfo = static_cast<EventInfo *>(object);
 | |
| 
 | |
| 	/* Should only free event when created by a plugin */
 | |
| 	if (pInfo->pOwner)
 | |
| 	{
 | |
| 		/* Free IGameEvent */
 | |
| 		gameevents->FreeEvent(pInfo->pEvent);
 | |
| 
 | |
| 		/* Add EventInfo struct to free event stack */
 | |
| 		m_FreeEvents.push(pInfo);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EventManager::OnPluginUnloaded(IPlugin *plugin)
 | |
| {
 | |
| 	EventHookList *pHookList;
 | |
| 	EventHookList::iterator iter;
 | |
| 	EventHook *pHook;
 | |
| 
 | |
| 	// If plugin has an event hook list...
 | |
| 	if (plugin->GetProperty("EventHooks", reinterpret_cast<void **>(&pHookList), true))
 | |
| 	{
 | |
| 		for (iter = pHookList->begin(); iter != pHookList->end(); iter++)
 | |
| 		{
 | |
| 			pHook = (*iter);
 | |
| 
 | |
| 			if (--pHook->refCount == 0)
 | |
| 			{
 | |
| 				if (pHook->pPreHook)
 | |
| 				{
 | |
| 					g_Forwards.ReleaseForward(pHook->pPreHook);
 | |
| 				}
 | |
| 
 | |
| 				if (pHook->pPostHook)
 | |
| 				{
 | |
| 					g_Forwards.ReleaseForward(pHook->pPostHook);
 | |
| 				}
 | |
| 
 | |
| 				delete pHook;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		delete pHookList;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /* IGameEventListener2::FireGameEvent */
 | |
| void EventManager::FireGameEvent(IGameEvent *pEvent)
 | |
| {
 | |
| 	/* Not going to do anything here.
 | |
| 	   Just need to add ourselves as a listener to make our hook on IGameEventManager2::FireEvent work */
 | |
| }
 | |
| 
 | |
| #if SOURCE_ENGINE >= SE_LEFT4DEAD
 | |
| int EventManager::GetEventDebugID()
 | |
| {
 | |
| 	return EVENT_DEBUG_ID_INIT;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| EventHookError EventManager::HookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
 | |
| {
 | |
| 	EventHook *pHook;
 | |
| 
 | |
| 	/* If we aren't listening to this event... */
 | |
| 	if (!gameevents->FindListener(this, name))
 | |
| 	{
 | |
| 		/* Then add ourselves */
 | |
| 		if (!gameevents->AddListener(this, name, true))
 | |
| 		{
 | |
| 			/* If event doesn't exist... */
 | |
| 			return EventHookErr_InvalidEvent;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* If a hook structure does not exist... */
 | |
| 	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
 | |
| 	{
 | |
| 		EventHookList *pHookList;
 | |
| 		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
 | |
| 
 | |
| 		/* Check plugin for an existing EventHook list */
 | |
| 		if (!plugin->GetProperty("EventHooks", (void **)&pHookList))
 | |
| 		{
 | |
| 			pHookList = new EventHookList();
 | |
| 			plugin->SetProperty("EventHooks", pHookList);
 | |
| 		}
 | |
| 
 | |
| 		/* Create new GameEventHook structure */
 | |
| 		pHook = new EventHook();
 | |
| 
 | |
| 		if (mode == EventHookMode_Pre)
 | |
| 		{
 | |
| 			/* Create forward for a pre hook */
 | |
| 			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Hook, 3, GAMEEVENT_PARAMS);
 | |
| 			/* Add to forward list */
 | |
| 			pHook->pPreHook->AddFunction(pFunction);
 | |
| 		} else {
 | |
| 			/* Create forward for a post hook */
 | |
| 			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
 | |
| 			/* Should we copy data from a pre hook to the post hook? */
 | |
| 			pHook->postCopy = (mode == EventHookMode_Post);
 | |
| 			/* Add to forward list */
 | |
| 			pHook->pPostHook->AddFunction(pFunction);
 | |
| 		}
 | |
| 
 | |
| 		/* Cache the name for post hooks */
 | |
| 		pHook->name = sm_strdup(name);
 | |
| 
 | |
| 		/* Increase reference count */
 | |
| 		pHook->refCount++;
 | |
| 
 | |
| 		/* Add hook structure to hook lists */
 | |
| 		pHookList->push_back(pHook);
 | |
| 		sm_trie_insert(m_EventHooks, name, pHook);
 | |
| 
 | |
| 		return EventHookErr_Okay;
 | |
| 	}
 | |
| 
 | |
| 	/* Hook structure already exists at this point */
 | |
| 
 | |
| 	if (mode == EventHookMode_Pre)
 | |
| 	{
 | |
| 		/* Create pre hook forward if necessary */
 | |
| 		if (!pHook->pPreHook)
 | |
| 		{
 | |
| 			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Event, 3, GAMEEVENT_PARAMS);
 | |
| 		}
 | |
| 
 | |
| 		/* Add plugin function to forward list */
 | |
| 		pHook->pPreHook->AddFunction(pFunction);
 | |
| 	} else {
 | |
| 		/* Create post hook forward if necessary */
 | |
| 		if (!pHook->pPostHook)
 | |
| 		{
 | |
| 			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
 | |
| 		}
 | |
| 
 | |
| 		/* If postCopy is false, then we may want to set it to true */
 | |
| 		if (!pHook->postCopy)
 | |
| 		{
 | |
| 			pHook->postCopy = (mode == EventHookMode_Post);
 | |
| 		}
 | |
| 
 | |
| 		/* Add plugin function to forward list */
 | |
| 		pHook->pPostHook->AddFunction(pFunction);
 | |
| 	}
 | |
| 
 | |
| 	/* Increase reference count */
 | |
| 	pHook->refCount++;
 | |
| 
 | |
| 	return EventHookErr_Okay;
 | |
| }
 | |
| 
 | |
| EventHookError EventManager::UnhookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
 | |
| {
 | |
| 	EventHook *pHook;
 | |
| 	IChangeableForward **pEventForward;
 | |
| 
 | |
| 	/* If hook does not exist at all */
 | |
| 	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
 | |
| 	{
 | |
| 		return EventHookErr_NotActive;
 | |
| 	}
 | |
| 
 | |
| 	/* One forward to rule them all */
 | |
| 	if (mode == EventHookMode_Pre)
 | |
| 	{
 | |
| 		pEventForward = &pHook->pPreHook;
 | |
| 	} else {
 | |
| 		pEventForward = &pHook->pPostHook;
 | |
| 	}
 | |
| 
 | |
| 	/* Remove function from forward's list */
 | |
| 	if (*pEventForward == NULL || !(*pEventForward)->RemoveFunction(pFunction))
 | |
| 	{
 | |
| 		return EventHookErr_InvalidCallback;
 | |
| 	}
 | |
| 
 | |
| 	/* If forward's list contains 0 functions now, free it */
 | |
| 	if ((*pEventForward)->GetFunctionCount() == 0)
 | |
| 	{
 | |
| 		g_Forwards.ReleaseForward(*pEventForward);
 | |
| 		*pEventForward = NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Decrement reference count */
 | |
| 	if (--pHook->refCount == 0)
 | |
| 	{
 | |
| 		/* If reference count is now 0, free hook structure */
 | |
| 
 | |
| 		EventHookList *pHookList;
 | |
| 		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
 | |
| 
 | |
| 		/* Get plugin's event hook list */
 | |
| 		if (!plugin->GetProperty("EventHooks", (void**)&pHookList))
 | |
| 		{
 | |
| 			return EventHookErr_NotActive;
 | |
| 		}
 | |
| 
 | |
| 		/* Make sure the event was actually being hooked */
 | |
| 		if (pHookList->find(pHook) == pHookList->end())
 | |
| 		{
 | |
| 			return EventHookErr_NotActive;
 | |
| 		}
 | |
| 
 | |
| 		/* Remove current structure from plugin's list */
 | |
| 		pHookList->remove(pHook);
 | |
| 
 | |
| 		/* Delete entry in trie */
 | |
| 		sm_trie_delete(m_EventHooks, name);
 | |
| 
 | |
| 		/* Free the cached name */
 | |
| 		delete pHook->name;
 | |
| 
 | |
| 		/* And finally free structure memory */
 | |
| 		delete pHook;
 | |
| 	}
 | |
| 
 | |
| 	return EventHookErr_Okay;
 | |
| }
 | |
| 
 | |
| EventInfo *EventManager::CreateEvent(IPluginContext *pContext, const char *name, bool force)
 | |
| {
 | |
| 	EventInfo *pInfo;
 | |
| 	IGameEvent *pEvent = gameevents->CreateEvent(name, force);
 | |
| 
 | |
| 	if (pEvent)
 | |
| 	{
 | |
| 		if (m_FreeEvents.empty())
 | |
| 		{
 | |
| 			pInfo = new EventInfo();
 | |
| 		} else {
 | |
| 			pInfo = m_FreeEvents.front();
 | |
| 			m_FreeEvents.pop();
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		pInfo->pEvent = pEvent;
 | |
| 		pInfo->pOwner = pContext->GetIdentity();
 | |
| 		pInfo->bDontBroadcast = false;
 | |
| 
 | |
| 		return pInfo;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| void EventManager::FireEvent(EventInfo *pInfo, bool bDontBroadcast)
 | |
| {
 | |
| 	/* Actually fire event now */
 | |
| 	gameevents->FireEvent(pInfo->pEvent, bDontBroadcast);
 | |
| 
 | |
| 	/* IGameEvent is free at this point, so no one owns this */
 | |
| 	pInfo->pOwner = NULL;
 | |
| 
 | |
| 	/* Add EventInfo struct to free event stack */
 | |
| 	m_FreeEvents.push(pInfo);
 | |
| }
 | |
| 
 | |
| void EventManager::CancelCreatedEvent(EventInfo *pInfo)
 | |
| {
 | |
| 	/* Free event from IGameEventManager2 */
 | |
| 	gameevents->FreeEvent(pInfo->pEvent);
 | |
| 
 | |
| 	/* IGameEvent is free at this point, so no one owns this */
 | |
| 	pInfo->pOwner = NULL;
 | |
| 
 | |
| 	/* Add EventInfo struct to free event stack */
 | |
| 	m_FreeEvents.push(pInfo);
 | |
| }
 | |
| 
 | |
| /* IGameEventManager2::FireEvent hook */
 | |
| bool EventManager::OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast)
 | |
| {
 | |
| 	EventHook *pHook;
 | |
| 	IChangeableForward *pForward;
 | |
| 	const char *name;
 | |
| 	cell_t res = Pl_Continue;
 | |
| 	bool broadcast = bDontBroadcast;
 | |
| 
 | |
| 	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
 | |
| 	if (!pEvent)
 | |
| 	{
 | |
| 		RETURN_META_VALUE(MRES_IGNORED, false);
 | |
| 	}
 | |
| 
 | |
| 	name = pEvent->GetName();
 | |
| 	
 | |
| 	if (sm_trie_retrieve(m_EventHooks, name, reinterpret_cast<void **>(&pHook)))
 | |
| 	{
 | |
| 		/* Push the event onto the event stack.  The reference count is increased to make sure 
 | |
| 		 * the structure is not garbage collected in between now and the post hook.
 | |
| 		 */
 | |
| 		pHook->refCount++;
 | |
| 		m_EventStack.push(pHook);
 | |
| 
 | |
| 		pForward = pHook->pPreHook;
 | |
| 
 | |
| 		if (pForward)
 | |
| 		{
 | |
| 			EventInfo info(pEvent, NULL);
 | |
| 			HandleSecurity sec(NULL, g_pCoreIdent);
 | |
| 			Handle_t hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
 | |
| 
 | |
| 			info.bDontBroadcast = bDontBroadcast;
 | |
| 
 | |
| 			EventForwardFilter filter(&info);
 | |
| 
 | |
| 			pForward->PushCell(hndl);
 | |
| 			pForward->PushString(name);
 | |
| 			pForward->PushCell(bDontBroadcast);
 | |
| 			pForward->Execute(&res, &filter);
 | |
| 
 | |
| 			broadcast = info.bDontBroadcast;
 | |
| 
 | |
| 			g_HandleSys.FreeHandle(hndl, &sec);
 | |
| 		}
 | |
| 
 | |
| 		if (pHook->postCopy)
 | |
| 		{
 | |
| 			m_EventCopies.push(gameevents->DuplicateEvent(pEvent));
 | |
| 		}
 | |
| 
 | |
| 		if (res)
 | |
| 		{
 | |
| 			gameevents->FreeEvent(pEvent);
 | |
| 			RETURN_META_VALUE(MRES_SUPERCEDE, false);
 | |
| 		}
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		m_EventStack.push(NULL);
 | |
| 	}
 | |
| 
 | |
| 	if (broadcast != bDontBroadcast)
 | |
| 		RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, true, &IGameEventManager2::FireEvent, (pEvent, broadcast));
 | |
| 
 | |
| 	RETURN_META_VALUE(MRES_IGNORED, true);
 | |
| }
 | |
| 
 | |
| /* IGameEventManager2::FireEvent post hook */
 | |
| bool EventManager::OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast)
 | |
| {
 | |
| 	EventHook *pHook;
 | |
| 	EventInfo info;
 | |
| 	IChangeableForward *pForward;
 | |
| 	Handle_t hndl = 0;
 | |
| 
 | |
| 	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
 | |
| 	if (!pEvent)
 | |
| 	{
 | |
| 		RETURN_META_VALUE(MRES_IGNORED, false);
 | |
| 	}
 | |
| 
 | |
| 	pHook = m_EventStack.front();
 | |
| 
 | |
| 	if (pHook != NULL)
 | |
| 	{
 | |
| 		pForward = pHook->pPostHook;
 | |
| 
 | |
| 		if (pForward)
 | |
| 		{
 | |
| 			if (pHook->postCopy)
 | |
| 			{
 | |
| 				info.bDontBroadcast = bDontBroadcast;
 | |
| 				info.pEvent = m_EventCopies.front();
 | |
| 				info.pOwner = NULL;
 | |
| 				hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
 | |
| 
 | |
| 				pForward->PushCell(hndl);
 | |
| 			} else {
 | |
| 				pForward->PushCell(BAD_HANDLE);
 | |
| 			}
 | |
| 
 | |
| 			pForward->PushString(pHook->name);
 | |
| 			pForward->PushCell(bDontBroadcast);
 | |
| 			pForward->Execute(NULL);
 | |
| 
 | |
| 			if (pHook->postCopy)
 | |
| 			{
 | |
| 				/* Free handle */
 | |
| 				HandleSecurity sec(NULL, g_pCoreIdent);
 | |
| 				g_HandleSys.FreeHandle(hndl, &sec);
 | |
| 
 | |
| 				/* Free event structure */
 | |
| 				gameevents->FreeEvent(info.pEvent);
 | |
| 				m_EventCopies.pop();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/* Decrement reference count, check if a delayed delete is needed */
 | |
| 		if (--pHook->refCount == 0)
 | |
| 		{
 | |
| 			assert(pHook->pPostHook == NULL);
 | |
| 			assert(pHook->pPreHook == NULL);
 | |
| 			sm_trie_delete(m_EventHooks, pHook->name);
 | |
| 			delete pHook->name;
 | |
| 			delete pHook;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	m_EventStack.pop();
 | |
| 
 | |
| 	RETURN_META_VALUE(MRES_IGNORED, true);
 | |
| }
 |