Source code for entities.hooks
# ../entities/hooks.py
"""Provides memory hooking functionality for entities."""
# =============================================================================
# >> IMPORTS
# =============================================================================
# Source.Python Imports
# Core
from core import AutoUnload
# Entities
# Memory
from memory.hooks import HookType
# Filters
from filters.entities import EntityIter
# Listeners
from listeners import OnNetworkedEntityCreated
# Entities
from entities.entity import Entity
# Players
from players.entity import Player
# =============================================================================
# >> ALL DECLARATION
# =============================================================================
__all__ = ('EntityCondition',
'EntityPostHook',
'EntityPreHook',
)
# =============================================================================
# >> CLASSES
# =============================================================================
[docs]class EntityCondition(object):
"""Store some default entity conditions."""
@staticmethod
[docs] def is_player(entity):
"""Return ``True`` if the entity is a player.
:rtype: bool
"""
return entity.is_player()
@staticmethod
[docs] def is_not_player(entity):
"""Return ``True`` if the entity is not a player.
:rtype: bool
"""
return not entity.is_player()
@staticmethod
[docs] def is_human_player(entity):
"""Return ``True`` if the entity is a human player.
:rtype: bool
"""
return entity.is_player() and Player(entity.index).steamid != 'BOT'
@staticmethod
[docs] def is_bot_player(entity):
"""Return ``True`` if the entity is a bot.
:rtype: bool
"""
return entity.is_player() and Player(entity.index).steamid == 'BOT'
@staticmethod
[docs] def equals_entity_classname(*classnames):
"""Return a function that requires an :class:`entities.entity.Entity``
instace. The returned function returns ``True`` if the entity's
classname equals one of the passed classnames.
:rtype: lambda
"""
return lambda entity: entity.classname in classnames
@staticmethod
[docs] def equals_datamap_classname(*classnames):
"""Return a function that requires an :class:`entities.entity.Entity``
instance. The returned function returns ``True`` if the entity's
datamap classname equals one of the passed classnames.
:rtype: lambda
"""
return lambda entity: entity.datamap.class_name in classnames
class _EntityHook(AutoUnload):
"""Create entity pre and post hooks that auto unload."""
def __init__(self, test_function, function):
"""Initialize the hook object.
:param callable test_function:
A callable object that accepts an :class:`entities.entity.Entity`
instance as a parameter. The function should return ``True`` if
the entity matches the required one.
:param str/callable function:
This is the function to hook. It can be either a string that
defines the name of a function of the entity or a callable object
that returns a :class:`memory.Function` instance.
"""
self.test_function = test_function
self.function = function
self.hooked_function = None
self.callback = None
def __call__(self, callback):
"""Store the callback and try initializing the hook.
:param callable callback:
The callback to store.
:return:
The passed callback.
:rtype: callable
"""
# Validate the given callback...
if not callable(callback):
raise TypeError('Given callback is not callable.')
self.callback = callback
# Try initializing the hook...
for entity in EntityIter():
if self.initialize(entity):
# Yay! The entity was the one we were looking for
return self.callback
# Initialization failed. There is currently no entity with the given
# class name. So, we need to wait until such an entity has been
# created.
_waiting_entity_hooks.append(self)
# Return the callback
return self.callback
@property
def hook_type(self):
"""Return the hook type of the decorator.
:rtype: HookType
"""
raise NotImplementedError('No hook_type defined for class.')
def initialize(self, entity):
"""Initialize the hook.
Return ``True`` if the initialization was successful.
:rtype: bool
"""
if not self.test_function(entity):
return False
if callable(self.function):
self.hooked_function = self.function(entity)
else:
self.hooked_function = getattr(entity, self.function)
self.hooked_function.add_hook(self.hook_type, self.callback)
return True
def _unload_instance(self):
"""Unload the hook."""
# Was a function hooked?
if self.hooked_function is not None:
# Was no callback registered?
if self.callback is None:
return
# Unregister the hook...
self.hooked_function.remove_hook(self.hook_type, self.callback)
# Otherwise, make sure the hook is still pending before removing it...
elif self in _waiting_entity_hooks:
_waiting_entity_hooks.remove(self)
[docs]class EntityPreHook(_EntityHook):
"""Decorator class used to create entity pre hooks that auto unload."""
hook_type = HookType.PRE
[docs]class EntityPostHook(_EntityHook):
"""Decorator class used to create entity post hooks that auto unload."""
hook_type = HookType.POST
class _WaitingEntityHooks(list):
"""A dictionary to store hooks waiting for intialization."""
def initialize(self, index):
"""Initialize all hooks waiting for the given entity.
:param int index:
Index of the entity that should be used to initialize the hooks.
"""
# There is nothing to do if no hook is waiting
if not self:
return
entity = Entity(index)
for hook in tuple(self):
# Try initializing the hook
if hook.initialize(entity):
# If it succeeded, remove the hook from the waiting list
self.remove(hook)
_waiting_entity_hooks = _WaitingEntityHooks()
# =============================================================================
# >> LISTENERS
# =============================================================================
@OnNetworkedEntityCreated
def on_networked_entity_created(entity):
"""Called when a new networked entity has been created."""
_waiting_entity_hooks.initialize(entity.index)