ds@576: /**
dvander@2883: * vim: set ts=4 sw=4 tw=99 noet :
ds@1330: * =============================================================================
dvander@1231: * SourceMod
ds@3099: * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
ds@1330: * =============================================================================
ds@653: *
ds@1330: * This program is free software; you can redistribute it and/or modify it under
ds@1330: * the terms of the GNU General Public License, version 3.0, as published by the
ds@1330: * Free Software Foundation.
dvander@1231: *
ds@1330: * This program is distributed in the hope that it will be useful, but WITHOUT
ds@1330: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
ds@1330: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
ds@1330: * details.
dvander@1231: *
ds@1330: * You should have received a copy of the GNU General Public License along with
ds@1330: * this program. If not, see .
dvander@1231: *
ds@1330: * As a special exception, AlliedModders LLC gives you permission to link the
ds@1330: * code of this program (as well as its derivative works) to "Half-Life 2," the
ds@1330: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
ds@1330: * by the Valve Corporation. You must obey the GNU General Public License in
ds@1330: * all respects for all other code used. Additionally, AlliedModders LLC grants
ds@1330: * this exception to all derivative works. AlliedModders LLC defines further
ds@1330: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
ds@1330: * or .
ds@653: *
ds@653: * Version: $Id$
ds@653: */
ds@576:
ds@576: #include "EventManager.h"
ds@576: #include "sm_stringutil.h"
dvander@3872: #include "logic_bridge.h"
ds@576:
ds@576: EventManager g_EventManager;
ds@576:
ds@576: SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent *, bool);
ds@576:
ds@582: const ParamType GAMEEVENT_PARAMS[] = {Param_Cell, Param_String, Param_Cell};
ds@576: typedef List EventHookList;
ds@576:
dvander@2883: class EventForwardFilter : public IForwardFilter
dvander@2883: {
dvander@2883: EventInfo *pEventInfo;
dvander@2883: public:
dvander@2883: EventForwardFilter(EventInfo *pEventInfo) : pEventInfo(pEventInfo)
dvander@2883: {
dvander@2883: }
dvander@2883:
dvander@2883: void Preprocess(IPluginFunction *fun, FwdParamInfo *params)
dvander@2883: {
dvander@2883: params[2].val = pEventInfo->bDontBroadcast ? 1 : 0;
dvander@2883: }
dvander@2883: };
dvander@2883:
ds@712: EventManager::EventManager() : m_EventType(0)
ds@576: {
ds@576: }
ds@576:
ds@576: EventManager::~EventManager()
ds@576: {
ds@577: /* Free memory used by EventInfo structs if any */
ds@577: CStack::iterator iter;
ds@577: for (iter = m_FreeEvents.begin(); iter != m_FreeEvents.end(); iter++)
ds@577: {
ds@577: delete (*iter);
ds@577: }
ds@577:
ds@577: m_FreeEvents.popall();
ds@576: }
ds@576:
ds@576: void EventManager::OnSourceModAllInitialized()
ds@576: {
ds@576: /* Add a hook for IGameEventManager2::FireEvent() */
kyle@3819: SH_ADD_HOOK(IGameEventManager2, FireEvent, gameevents, SH_MEMBER(this, &EventManager::OnFireEvent), false);
kyle@3819: SH_ADD_HOOK(IGameEventManager2, FireEvent, gameevents, SH_MEMBER(this, &EventManager::OnFireEvent_Post), true);
ds@576:
ds@576: HandleAccess sec;
ds@576:
ds@576: /* Handle access security for 'GameEvent' handle type */
ds@576: sec.access[HandleAccess_Read] = 0;
ds@576: sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
ds@576: sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
ds@576:
ds@576: /* Create the 'GameEvent' handle type */
dvander@3872: m_EventType = handlesys->CreateType("GameEvent", this, 0, NULL, &sec, g_pCoreIdent, NULL);
ds@576: }
ds@576:
ds@576: void EventManager::OnSourceModShutdown()
ds@576: {
ds@576: /* Remove hook for IGameEventManager2::FireEvent() */
kyle@3819: SH_REMOVE_HOOK(IGameEventManager2, FireEvent, gameevents, SH_MEMBER(this, &EventManager::OnFireEvent), false);
kyle@3819: SH_REMOVE_HOOK(IGameEventManager2, FireEvent, gameevents, SH_MEMBER(this, &EventManager::OnFireEvent_Post), true);
ds@576:
ds@576: /* Remove the 'GameEvent' handle type */
dvander@3872: handlesys->RemoveType(m_EventType, g_pCoreIdent);
ds@576:
ds@576: /* Remove ourselves as listener for events */
ds@576: gameevents->RemoveListener(this);
ds@576: }
ds@576:
ds@576: void EventManager::OnHandleDestroy(HandleType_t type, void *object)
ds@576: {
ds@577: EventInfo *pInfo = static_cast(object);
ds@576:
ds@577: /* Should only free event when created by a plugin */
ds@577: if (pInfo->pOwner)
ds@577: {
ds@577: /* Free IGameEvent */
ds@577: gameevents->FreeEvent(pInfo->pEvent);
ds@577:
ds@577: /* Add EventInfo struct to free event stack */
ds@577: m_FreeEvents.push(pInfo);
ds@576: }
ds@576: }
ds@576:
ds@576: void EventManager::OnPluginUnloaded(IPlugin *plugin)
ds@576: {
ds@576: EventHookList *pHookList;
ds@582: EventHookList::iterator iter;
ds@576: EventHook *pHook;
ds@576:
ds@576: // If plugin has an event hook list...
ds@576: if (plugin->GetProperty("EventHooks", reinterpret_cast(&pHookList), true))
ds@576: {
ds@582: for (iter = pHookList->begin(); iter != pHookList->end(); iter++)
ds@576: {
ds@576: pHook = (*iter);
ds@576:
ds@576: if (--pHook->refCount == 0)
ds@576: {
ds@576: if (pHook->pPreHook)
ds@576: {
skamonkey@4139: forwardsys->ReleaseForward(pHook->pPreHook);
ds@576: }
ds@576:
ds@576: if (pHook->pPostHook)
ds@576: {
skamonkey@4139: forwardsys->ReleaseForward(pHook->pPostHook);
ds@576: }
ds@576:
ds@576: delete pHook;
ds@576: }
ds@576: }
ds@576:
ds@576: delete pHookList;
ds@576: }
ds@576: }
ds@576:
ds@576: /* IGameEventListener2::FireGameEvent */
ds@576: void EventManager::FireGameEvent(IGameEvent *pEvent)
ds@576: {
ds@576: /* Not going to do anything here.
ds@576: Just need to add ourselves as a listener to make our hook on IGameEventManager2::FireEvent work */
ds@576: }
ds@576:
ds@3099: #if SOURCE_ENGINE >= SE_LEFT4DEAD
ds@3099: int EventManager::GetEventDebugID()
ds@3099: {
ds@3099: return EVENT_DEBUG_ID_INIT;
ds@3099: }
ds@3099: #endif
ds@3099:
ds@576: EventHookError EventManager::HookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
ds@576: {
ds@576: EventHook *pHook;
ds@576:
ds@576: /* If we aren't listening to this event... */
ds@576: if (!gameevents->FindListener(this, name))
ds@576: {
ds@576: /* Then add ourselves */
ds@576: if (!gameevents->AddListener(this, name, true))
ds@576: {
ds@576: /* If event doesn't exist... */
ds@576: return EventHookErr_InvalidEvent;
ds@576: }
ds@576: }
ds@576:
ds@576: /* If a hook structure does not exist... */
dvander@4075: if (!m_EventHooks.retrieve(name, &pHook))
ds@576: {
ds@576: EventHookList *pHookList;
dvander@3872: IPlugin *plugin = scripts->FindPluginByContext(pFunction->GetParentContext()->GetContext());
ds@576:
ds@576: /* Check plugin for an existing EventHook list */
ds@576: if (!plugin->GetProperty("EventHooks", (void **)&pHookList))
ds@576: {
ds@576: pHookList = new EventHookList();
ds@576: plugin->SetProperty("EventHooks", pHookList);
ds@576: }
ds@576:
ds@576: /* Create new GameEventHook structure */
ds@576: pHook = new EventHook();
ds@576:
ds@576: if (mode == EventHookMode_Pre)
ds@576: {
ds@576: /* Create forward for a pre hook */
skamonkey@4139: pHook->pPreHook = forwardsys->CreateForwardEx(NULL, ET_Hook, 3, GAMEEVENT_PARAMS);
ds@576: /* Add to forward list */
ds@576: pHook->pPreHook->AddFunction(pFunction);
ds@576: } else {
ds@576: /* Create forward for a post hook */
skamonkey@4139: pHook->pPostHook = forwardsys->CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
ds@576: /* Should we copy data from a pre hook to the post hook? */
ds@576: pHook->postCopy = (mode == EventHookMode_Post);
ds@576: /* Add to forward list */
ds@576: pHook->pPostHook->AddFunction(pFunction);
ds@576: }
ds@576:
dvander@2567: /* Cache the name for post hooks */
pred@2708: pHook->name = sm_strdup(name);
dvander@2567:
ds@576: /* Increase reference count */
ds@576: pHook->refCount++;
ds@576:
ds@576: /* Add hook structure to hook lists */
ds@576: pHookList->push_back(pHook);
dvander@4075: m_EventHooks.insert(name, pHook);
ds@576:
ds@576: return EventHookErr_Okay;
ds@576: }
ds@576:
ds@576: /* Hook structure already exists at this point */
ds@576:
ds@576: if (mode == EventHookMode_Pre)
ds@576: {
ds@576: /* Create pre hook forward if necessary */
ds@576: if (!pHook->pPreHook)
ds@576: {
skamonkey@4139: pHook->pPreHook = forwardsys->CreateForwardEx(NULL, ET_Event, 3, GAMEEVENT_PARAMS);
ds@576: }
ds@576:
ds@576: /* Add plugin function to forward list */
ds@576: pHook->pPreHook->AddFunction(pFunction);
ds@576: } else {
ds@576: /* Create post hook forward if necessary */
ds@576: if (!pHook->pPostHook)
ds@576: {
skamonkey@4139: pHook->pPostHook = forwardsys->CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
ds@576: }
ds@576:
ds@576: /* If postCopy is false, then we may want to set it to true */
ds@576: if (!pHook->postCopy)
ds@576: {
ds@576: pHook->postCopy = (mode == EventHookMode_Post);
ds@576: }
ds@576:
ds@576: /* Add plugin function to forward list */
ds@576: pHook->pPostHook->AddFunction(pFunction);
ds@576: }
ds@576:
ds@576: /* Increase reference count */
ds@576: pHook->refCount++;
ds@576:
ds@576: return EventHookErr_Okay;
ds@576: }
ds@576:
ds@576: EventHookError EventManager::UnhookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
ds@576: {
ds@576: EventHook *pHook;
ds@576: IChangeableForward **pEventForward;
ds@576:
ds@576: /* If hook does not exist at all */
dvander@4075: if (!m_EventHooks.retrieve(name, &pHook))
ds@576: {
ds@576: return EventHookErr_NotActive;
ds@576: }
ds@576:
ds@576: /* One forward to rule them all */
ds@576: if (mode == EventHookMode_Pre)
ds@576: {
ds@576: pEventForward = &pHook->pPreHook;
ds@576: } else {
ds@576: pEventForward = &pHook->pPostHook;
ds@576: }
ds@576:
ds@576: /* Remove function from forward's list */
ds@576: if (*pEventForward == NULL || !(*pEventForward)->RemoveFunction(pFunction))
ds@576: {
ds@576: return EventHookErr_InvalidCallback;
ds@576: }
ds@576:
ds@576: /* If forward's list contains 0 functions now, free it */
ds@576: if ((*pEventForward)->GetFunctionCount() == 0)
ds@576: {
skamonkey@4139: forwardsys->ReleaseForward(*pEventForward);
ds@576: *pEventForward = NULL;
ds@576: }
ds@576:
ds@576: /* Decrement reference count */
ds@576: if (--pHook->refCount == 0)
ds@576: {
ds@576: /* If reference count is now 0, free hook structure */
ds@576:
ds@576: EventHookList *pHookList;
dvander@3872: IPlugin *plugin = scripts->FindPluginByContext(pFunction->GetParentContext()->GetContext());
ds@576:
ds@576: /* Get plugin's event hook list */
dvander@1022: if (!plugin->GetProperty("EventHooks", (void**)&pHookList))
dvander@1022: {
dvander@1022: return EventHookErr_NotActive;
dvander@1022: }
dvander@1022:
dvander@1022: /* Make sure the event was actually being hooked */
dvander@1022: if (pHookList->find(pHook) == pHookList->end())
dvander@1022: {
dvander@1022: return EventHookErr_NotActive;
dvander@1022: }
ds@576:
ds@576: /* Remove current structure from plugin's list */
ds@576: pHookList->remove(pHook);
ds@576:
ds@576: /* Delete entry in trie */
dvander@4075: m_EventHooks.remove(name);
ds@576:
dvander@2567: /* Free the cached name */
pred@2708: delete pHook->name;
dvander@2567:
ds@576: /* And finally free structure memory */
ds@576: delete pHook;
ds@576: }
ds@576:
ds@576: return EventHookErr_Okay;
ds@576: }
ds@576:
ds@713: EventInfo *EventManager::CreateEvent(IPluginContext *pContext, const char *name, bool force)
ds@577: {
ds@577: EventInfo *pInfo;
ds@713: IGameEvent *pEvent = gameevents->CreateEvent(name, force);
ds@577:
ds@577: if (pEvent)
ds@577: {
ds@577: if (m_FreeEvents.empty())
ds@577: {
ds@577: pInfo = new EventInfo();
ds@577: } else {
ds@577: pInfo = m_FreeEvents.front();
ds@577: m_FreeEvents.pop();
ds@577: }
ds@577:
ds@577:
ds@577: pInfo->pEvent = pEvent;
ds@577: pInfo->pOwner = pContext->GetIdentity();
dvander@2883: pInfo->bDontBroadcast = false;
ds@577:
ds@577: return pInfo;
ds@577: }
ds@577:
ds@577: return NULL;
ds@577: }
ds@577:
ds@712: void EventManager::FireEvent(EventInfo *pInfo, bool bDontBroadcast)
ds@577: {
ds@577: /* Actually fire event now */
ds@577: gameevents->FireEvent(pInfo->pEvent, bDontBroadcast);
ds@577:
ds@577: /* IGameEvent is free at this point, so no one owns this */
ds@577: pInfo->pOwner = NULL;
ds@577:
ds@577: /* Add EventInfo struct to free event stack */
ds@577: m_FreeEvents.push(pInfo);
ds@577: }
ds@577:
ds@687: void EventManager::CancelCreatedEvent(EventInfo *pInfo)
ds@687: {
ds@687: /* Free event from IGameEventManager2 */
ds@687: gameevents->FreeEvent(pInfo->pEvent);
ds@687:
ds@687: /* IGameEvent is free at this point, so no one owns this */
ds@687: pInfo->pOwner = NULL;
ds@687:
ds@687: /* Add EventInfo struct to free event stack */
ds@687: m_FreeEvents.push(pInfo);
ds@687: }
ds@687:
ds@576: /* IGameEventManager2::FireEvent hook */
ds@576: bool EventManager::OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast)
ds@576: {
ds@576: EventHook *pHook;
ds@576: IChangeableForward *pForward;
ds@684: const char *name;
ds@576: cell_t res = Pl_Continue;
dvander@2883: bool broadcast = bDontBroadcast;
ds@2106:
ds@2106: /* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
ds@2106: if (!pEvent)
ds@2106: {
ds@2106: RETURN_META_VALUE(MRES_IGNORED, false);
ds@2106: }
dvander@2567:
ds@684: name = pEvent->GetName();
dvander@2567:
dvander@4075: if (m_EventHooks.retrieve(name, &pHook))
ds@576: {
dvander@2567: /* Push the event onto the event stack. The reference count is increased to make sure
dvander@2567: * the structure is not garbage collected in between now and the post hook.
dvander@2567: */
dvander@2567: pHook->refCount++;
dvander@2567: m_EventStack.push(pHook);
dvander@2567:
ds@576: pForward = pHook->pPreHook;
ds@576:
ds@576: if (pForward)
ds@576: {
ds@2538: EventInfo info(pEvent, NULL);
ds@2538: HandleSecurity sec(NULL, g_pCoreIdent);
dvander@3872: Handle_t hndl = handlesys->CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
ds@576:
dvander@2883: info.bDontBroadcast = bDontBroadcast;
dvander@2883:
dvander@2883: EventForwardFilter filter(&info);
dvander@2883:
ds@576: pForward->PushCell(hndl);
ds@684: pForward->PushString(name);
ds@576: pForward->PushCell(bDontBroadcast);
dvander@2883: pForward->Execute(&res, &filter);
dvander@2883:
dvander@2883: broadcast = info.bDontBroadcast;
ds@576:
dvander@3872: handlesys->FreeHandle(hndl, &sec);
ds@576: }
ds@576:
ds@576: if (pHook->postCopy)
ds@576: {
ds@2538: m_EventCopies.push(gameevents->DuplicateEvent(pEvent));
ds@576: }
ds@576:
ds@576: if (res)
ds@576: {
ds@576: gameevents->FreeEvent(pEvent);
ds@576: RETURN_META_VALUE(MRES_SUPERCEDE, false);
ds@576: }
ds@576: }
dvander@2567: else
dvander@2567: {
dvander@2567: m_EventStack.push(NULL);
dvander@2567: }
ds@576:
dvander@2883: if (broadcast != bDontBroadcast)
dvander@2883: RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, true, &IGameEventManager2::FireEvent, (pEvent, broadcast));
dvander@2883:
ds@576: RETURN_META_VALUE(MRES_IGNORED, true);
ds@576: }
ds@576:
ds@576: /* IGameEventManager2::FireEvent post hook */
ds@576: bool EventManager::OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast)
ds@576: {
ds@576: EventHook *pHook;
ds@2538: EventInfo info;
ds@576: IChangeableForward *pForward;
dvander@603: Handle_t hndl = 0;
ds@576:
ds@2106: /* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
ds@2106: if (!pEvent)
ds@2106: {
ds@2106: RETURN_META_VALUE(MRES_IGNORED, false);
ds@2106: }
ds@2106:
dvander@2567: pHook = m_EventStack.front();
ds@684:
dvander@2567: if (pHook != NULL)
ds@576: {
ds@576: pForward = pHook->pPostHook;
ds@576:
ds@576: if (pForward)
ds@576: {
ds@576: if (pHook->postCopy)
ds@576: {
dvander@2883: info.bDontBroadcast = bDontBroadcast;
ds@2538: info.pEvent = m_EventCopies.front();
ds@2538: info.pOwner = NULL;
dvander@3872: hndl = handlesys->CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
ds@684:
ds@576: pForward->PushCell(hndl);
ds@576: } else {
ds@576: pForward->PushCell(BAD_HANDLE);
ds@576: }
ds@576:
dvander@2567: pForward->PushString(pHook->name);
ds@576: pForward->PushCell(bDontBroadcast);
ds@576: pForward->Execute(NULL);
ds@576:
ds@576: if (pHook->postCopy)
ds@576: {
ds@576: /* Free handle */
dvander@857: HandleSecurity sec(NULL, g_pCoreIdent);
dvander@3872: handlesys->FreeHandle(hndl, &sec);
ds@576:
ds@576: /* Free event structure */
ds@2538: gameevents->FreeEvent(info.pEvent);
ds@2538: m_EventCopies.pop();
ds@576: }
ds@576: }
dvander@2567:
dvander@2567: /* Decrement reference count, check if a delayed delete is needed */
dvander@2567: if (--pHook->refCount == 0)
dvander@2567: {
dvander@2567: assert(pHook->pPostHook == NULL);
dvander@2567: assert(pHook->pPreHook == NULL);
dvander@4075: m_EventHooks.remove(pHook->name);
pred@2708: delete pHook->name;
dvander@2567: delete pHook;
dvander@2567: }
ds@576: }
ds@576:
dvander@2567: m_EventStack.pop();
ds@684:
ds@576: RETURN_META_VALUE(MRES_IGNORED, true);
ds@576: }