diff --git a/addons/source-python/packages/source-python/memory/hooks.py b/addons/source-python/packages/source-python/memory/hooks.py index 29b9d8204..1c45bc113 100644 --- a/addons/source-python/packages/source-python/memory/hooks.py +++ b/addons/source-python/packages/source-python/memory/hooks.py @@ -5,11 +5,16 @@ # ============================================================================= # >> IMPORTS # ============================================================================= +# Python +from contextlib import contextmanager + # Source.Python Imports # Core from core import AutoUnload # Memory from _memory import HookType +from _memory import set_hooks_disabled +from _memory import get_hooks_disabled from memory import Function @@ -19,6 +24,9 @@ __all__ = ('HookType', 'PostHook', 'PreHook', + 'set_hooks_disabled', + 'get_hooks_disabled', + 'hooks_disabled' ) @@ -73,3 +81,44 @@ class PostHook(_Hook): """Decorator class used to create post hooks that auto unload.""" hook_type = HookType.POST + + +# ============================================================================= +# >> FUNCTIONS +# ============================================================================= +@contextmanager +def hooks_disabled(disabled=True): + """Temporarily disable or enable all hook callbacks. By default hooks are + enabled. Thus, this context manager is mainly used to temporarily disable + hook callbacks. If the context ends, the original value is restored. This + can be used e. g. to avoid recursive calls when damaging a player in a + ``on_take_damage`` hook or ``player_hurt`` event. + + .. note:: + + This would only disable hooks created with Source.Python. Hooks that + have been created by other server plugins will still be called. + + Example: + + .. code:: python + + from players.entity import Player + from memory.hooks import hooks_disabled + + # Get a Player instance of the player with index 1 + player = Player(1) + + # Damage the player. This would call e. g. on_take_damage hooks + player.take_damage(5) + + # To avoid calling the on_take_damage hooks, use the following: + with hooks_disabled() + player.take_damage(5) + """ + old = get_hooks_disabled() + set_hooks_disabled(disabled) + try: + yield + finally: + set_hooks_disabled(old) diff --git a/src/core/modules/memory/memory_hooks.cpp b/src/core/modules/memory/memory_hooks.cpp index 0b2709f32..2f17074f5 100644 --- a/src/core/modules/memory/memory_hooks.cpp +++ b/src/core/modules/memory/memory_hooks.cpp @@ -44,6 +44,8 @@ using namespace boost::python; // g_mapCallbacks[][] -> [, , ...] std::map > > g_mapCallbacks; +bool g_HooksDisabled; + // ============================================================================ // >> HELPER FUNCTIONS @@ -80,6 +82,9 @@ object GetArgument(CHook* pHook, int iIndex) // ============================================================================ bool SP_HookHandler(HookType_t eHookType, CHook* pHook) { + if (g_HooksDisabled) + return false; + std::list callbacks = g_mapCallbacks[pHook][eHookType]; // No need to do all this stuff, if there is no callback registered diff --git a/src/core/modules/memory/memory_hooks.h b/src/core/modules/memory/memory_hooks.h index d96ba897a..59da969f2 100644 --- a/src/core/modules/memory/memory_hooks.h +++ b/src/core/modules/memory/memory_hooks.h @@ -76,4 +76,16 @@ class CStackData //--------------------------------------------------------------------------------- bool SP_HookHandler(HookType_t eHookType, CHook* pHook); +extern bool g_HooksDisabled; + +inline void SetHooksDisabled(bool value) +{ + g_HooksDisabled = value; +} + +inline bool GetHooksDisabled() +{ + return g_HooksDisabled; +} + #endif // MEMORY_HOOKS_H diff --git a/src/core/modules/memory/memory_wrap.cpp b/src/core/modules/memory/memory_wrap.cpp index 6b69c11c0..516755a35 100644 --- a/src/core/modules/memory/memory_wrap.cpp +++ b/src/core/modules/memory/memory_wrap.cpp @@ -68,6 +68,8 @@ void export_protection(scope); // ============================================================================ DECLARE_SP_MODULE(_memory) { + SetHooksDisabled(false); + export_function_info(_memory); export_binary_file(_memory); export_pointer(_memory); @@ -956,6 +958,18 @@ void export_functions(scope _memory) ":param DataType data_type: The data type you would like to get the size of.\n" ":param int alignment: The alignment that should be used." ); + + def("get_hooks_disabled", + &GetHooksDisabled, + "Return whether or not hook callbacks are disabled.\n" + "\n" + ":rtype: bool"); + + def("set_hooks_disabled", + &SetHooksDisabled, + "Set whether or not hook callbacks are disabled.\n" + "\n" + ":param bool disabled: If ``True``, hook callbacks are disabled."); }