diff --git a/addons/source-python/data/source-python/entities/bms/CBaseEntity.ini b/addons/source-python/data/source-python/entities/bms/CBaseEntity.ini
index faa2a2b2b..b2750f120 100755
--- a/addons/source-python/data/source-python/entities/bms/CBaseEntity.ini
+++ b/addons/source-python/data/source-python/entities/bms/CBaseEntity.ini
@@ -49,6 +49,12 @@
offset_windows = 109
arguments = POINTER
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ [[get_solid_mask]]
+ offset_linux = 180
+ offset_windows = 179
+ return_type = UINT
+
[input]
diff --git a/addons/source-python/data/source-python/entities/csgo/CBaseEntity.ini b/addons/source-python/data/source-python/entities/csgo/CBaseEntity.ini
index aeab5154e..fd0b52126 100755
--- a/addons/source-python/data/source-python/entities/csgo/CBaseEntity.ini
+++ b/addons/source-python/data/source-python/entities/csgo/CBaseEntity.ini
@@ -51,3 +51,10 @@ srv_check = False
offset_linux = 105
offset_windows = 104
arguments = POINTER
+
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ # https://gist.github.com/jordanbriere/679e6d09ebaa9830979b6f60e61d4b9c
+ [[get_solid_mask]]
+ offset_linux = 169
+ offset_windows = 168
+ return_type = UINT
diff --git a/addons/source-python/data/source-python/entities/gmod/CBaseEntity.ini b/addons/source-python/data/source-python/entities/gmod/CBaseEntity.ini
index d401cf60b..a238dc882 100755
--- a/addons/source-python/data/source-python/entities/gmod/CBaseEntity.ini
+++ b/addons/source-python/data/source-python/entities/gmod/CBaseEntity.ini
@@ -48,3 +48,9 @@
offset_linux = 103
offset_windows = 102
arguments = POINTER
+
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ [[get_solid_mask]]
+ offset_linux = 173
+ offset_windows = 172
+ return_type = UINT
diff --git a/addons/source-python/data/source-python/entities/l4d2/CBaseEntity.ini b/addons/source-python/data/source-python/entities/l4d2/CBaseEntity.ini
index c05ee3d7a..745376892 100755
--- a/addons/source-python/data/source-python/entities/l4d2/CBaseEntity.ini
+++ b/addons/source-python/data/source-python/entities/l4d2/CBaseEntity.ini
@@ -48,3 +48,9 @@
offset_linux = 110
offset_windows = 109
arguments = POINTER
+
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ [[get_solid_mask]]
+ offset_linux = 178
+ offset_windows = 177
+ return_type = UINT
diff --git a/addons/source-python/data/source-python/entities/orangebox/CBaseEntity.ini b/addons/source-python/data/source-python/entities/orangebox/CBaseEntity.ini
index 3e6705de7..fd1691d3d 100755
--- a/addons/source-python/data/source-python/entities/orangebox/CBaseEntity.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/CBaseEntity.ini
@@ -49,6 +49,12 @@
offset_windows = 100
arguments = POINTER
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ [[get_solid_mask]]
+ offset_linux = 168
+ offset_windows = 167
+ return_type = UINT
+
[input]
diff --git a/addons/source-python/docs/source-python/source/developing/module_tutorials/listeners.rst b/addons/source-python/docs/source-python/source/developing/module_tutorials/listeners.rst
index 828cb922a..a1afeffca 100644
--- a/addons/source-python/docs/source-python/source/developing/module_tutorials/listeners.rst
+++ b/addons/source-python/docs/source-python/source/developing/module_tutorials/listeners.rst
@@ -324,6 +324,27 @@ Called when a networked entity has been spawned.
pass
+OnEntityCollision
+-----------------
+
+Called when a non-player entity is about to collide with another entity.
+
+.. note::
+
+ This listener can be extremely noisy. Use :class:`entities.collisions.CollisionHash`,
+ :class:`entities.collisions.CollisionMap`, or :class:`entities.collisions.CollisionSet`
+ if you don't have dynamic conditions to test for.
+
+.. code-block:: python
+
+ from listeners import OnEntityCollision
+
+ @OnEntityCollision
+ def on_entity_collision(entity, other):
+ # Disable weapons/projectiles collisions with everything except players
+ return not (entity.is_weapon() and not other.is_player())
+
+
OnLevelInit
-----------
@@ -406,6 +427,29 @@ Called when the button state of a player changed.
button or button combination has been pressed or released.
+OnPlayerCollision
+-----------------
+
+Called when a player is about to collide with an entity.
+
+.. note::
+
+ This listener can be extremely noisy. Use :class:`entities.collisions.CollisionHash`,
+ :class:`entities.collisions.CollisionMap`, or :class:`entities.collisions.CollisionSet`
+ if you don't have dynamic conditions to test for.
+
+.. code-block:: python
+
+ from listeners import OnPlayerCollision
+
+ @OnPlayerCollision
+ def on_player_collision(player, entity):
+ # Disable teammates collisions
+ if not entity.is_player():
+ return
+ return player.team_index != entity.team_index
+
+
OnPlayerRunCommand
--------------------
diff --git a/addons/source-python/packages/source-python/entities/collisions.py b/addons/source-python/packages/source-python/entities/collisions.py
new file mode 100644
index 000000000..3c2640f00
--- /dev/null
+++ b/addons/source-python/packages/source-python/entities/collisions.py
@@ -0,0 +1,64 @@
+# ../entities/collisions.py
+
+"""Provides entity collisions functionality."""
+
+# =============================================================================
+# >> IMPORTS
+# =============================================================================
+# Source.Python Imports
+# Core
+from core import AutoUnload
+from core import WeakAutoUnload
+
+
+# =============================================================================
+# >> FORWARD IMPORTS
+# =============================================================================
+# Source.Python Imports
+# Entities
+from _entities._collisions import CollisionHash
+from _entities._collisions import CollisionManager
+from _entities._collisions import CollisionMap
+from _entities._collisions import CollisionMode
+from _entities._collisions import CollisionRules
+from _entities._collisions import CollisionSet
+from _entities._collisions import collision_manager
+
+
+# =============================================================================
+# >> ALL DECLARATION
+# =============================================================================
+__all__ = [
+ 'CollisionHash',
+ 'CollisionHook',
+ 'CollisionManager',
+ 'CollisionMap',
+ 'CollisionMode',
+ 'CollisionRules',
+ 'CollisionSet',
+ 'collision_manager',
+]
+
+
+# =============================================================================
+# >> INITIALIZATION
+# =============================================================================
+# Inject WeakAutoUnload into CollisionRules's hierarchy.
+if not issubclass(CollisionRules, WeakAutoUnload):
+ CollisionRules.__bases__ = (WeakAutoUnload,) + CollisionRules.__bases__
+
+
+# =============================================================================
+# >> CLASSES
+# =============================================================================
+class CollisionHook(AutoUnload):
+ """Decorator used to create collision hooks that auto unload."""
+
+ def __init__(self, callback):
+ """Registers the collision hook."""
+ self.callback = callback
+ collision_manager.register_hook(callback)
+
+ def _unload_instance(self):
+ """Unregisters the collision hook."""
+ collision_manager.unregister_hook(self.callback)
diff --git a/addons/source-python/packages/source-python/listeners/__init__.py b/addons/source-python/packages/source-python/listeners/__init__.py
index 1ca2e2307..b66e30910 100755
--- a/addons/source-python/packages/source-python/listeners/__init__.py
+++ b/addons/source-python/packages/source-python/listeners/__init__.py
@@ -65,6 +65,7 @@
from _listeners import on_networked_entity_spawned_listener_manager
from _listeners import on_entity_deleted_listener_manager
from _listeners import on_networked_entity_deleted_listener_manager
+from _listeners import on_entity_collision_listener_manager
from _listeners import on_data_loaded_listener_manager
from _listeners import on_combiner_pre_cache_listener_manager
from _listeners import on_data_unloaded_listener_manager
@@ -72,6 +73,7 @@
from _listeners import on_server_activate_listener_manager
from _listeners import on_tick_listener_manager
from _listeners import on_server_output_listener_manager
+from _listeners import on_player_collision_listener_manager
from _listeners import on_player_run_command_listener_manager
from _listeners import on_player_post_run_command_listener_manager
from _listeners import on_button_state_changed_listener_manager
@@ -99,6 +101,7 @@
'OnNetworkedEntityCreated',
'OnEntityDeleted',
'OnNetworkedEntityDeleted',
+ 'OnEntityCollision',
'OnEntityOutput',
'OnEntityOutputListenerManager',
'OnEntityPreSpawned',
@@ -110,6 +113,7 @@
'OnLevelEnd',
'OnNetworkidValidated',
'OnButtonStateChanged',
+ 'OnPlayerCollision',
'OnPlayerRunCommand',
'OnPlayerPostRunCommand',
'OnPluginLoaded',
@@ -138,6 +142,7 @@
'on_networked_entity_created_listener_manager',
'on_entity_deleted_listener_manager',
'on_networked_entity_deleted_listener_manager',
+ 'on_entity_collision_listener_manager',
'on_entity_output_listener_manager',
'on_entity_pre_spawned_listener_manager',
'on_networked_entity_pre_spawned_listener_manager',
@@ -156,6 +161,7 @@
'on_tick_listener_manager',
'on_version_update_listener_manager',
'on_server_output_listener_manager',
+ 'on_player_collision_listener_manager',
'on_player_run_command_listener_manager',
'on_button_state_changed_listener_manager',
)
@@ -410,6 +416,12 @@ class OnNetworkedEntityDeleted(ListenerManagerDecorator):
manager = on_networked_entity_deleted_listener_manager
+class OnEntityCollision(ListenerManagerDecorator):
+ """Register/unregister a OnEntityCollision listener."""
+
+ manager = on_entity_collision_listener_manager
+
+
class OnDataLoaded(ListenerManagerDecorator):
"""Register/unregister a OnDataLoaded listener."""
@@ -491,6 +503,12 @@ class OnLevelEnd(ListenerManagerDecorator):
_level_initialized = False
+class OnPlayerCollision(ListenerManagerDecorator):
+ """Register/unregister a OnPlayerCollision listener."""
+
+ manager = on_player_collision_listener_manager
+
+
class OnPlayerRunCommand(ListenerManagerDecorator):
"""Register/unregister a run command listener."""
diff --git a/addons/source-python/packages/source-python/plugins/manager.py b/addons/source-python/packages/source-python/plugins/manager.py
index 55cb7357b..d76be19bf 100644
--- a/addons/source-python/packages/source-python/plugins/manager.py
+++ b/addons/source-python/packages/source-python/plugins/manager.py
@@ -11,6 +11,8 @@
# Configobj
from configobj import ConfigObj
from configobj import Section
+# GC
+from gc import collect
# Importlib
from importlib.util import find_spec
from importlib.util import spec_from_file_location
@@ -234,6 +236,7 @@ def unload(self, plugin_name):
self._remove_modules(plugin_name)
del self[plugin_name]
+ collect()
on_plugin_unloaded_manager.notify(plugin)
def reload(self, plugin_name):
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9bb21297a..0aa243d2b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -225,6 +225,7 @@ Set(SOURCEPYTHON_ENTITIES_MODULE_HEADERS
core/modules/entities/${SOURCE_ENGINE}/entities_props_wrap.h
core/modules/entities/${SOURCE_ENGINE}/entities_constants_wrap.h
core/modules/entities/entities_entity.h
+ core/modules/entities/entities_collisions.h
)
Set(SOURCEPYTHON_ENTITIES_MODULE_SOURCES
@@ -241,6 +242,8 @@ Set(SOURCEPYTHON_ENTITIES_MODULE_SOURCES
core/modules/entities/entities_props_wrap.cpp
core/modules/entities/entities_entity.cpp
core/modules/entities/entities_entity_wrap.cpp
+ core/modules/entities/entities_collisions.cpp
+ core/modules/entities/entities_collisions_wrap.cpp
)
# ------------------------------------------------------------------
diff --git a/src/core/modules/entities/entities_collisions.cpp b/src/core/modules/entities/entities_collisions.cpp
new file mode 100644
index 000000000..7b93442b2
--- /dev/null
+++ b/src/core/modules/entities/entities_collisions.cpp
@@ -0,0 +1,881 @@
+/**
+* =============================================================================
+* Source Python
+* Copyright (C) 2012-2021 Source Python Development Team. 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 .
+*
+* As a special exception, the Source Python Team gives you permission
+* to link the code of this program (as well as its derivative works) to
+* "Half-Life 2," the "Source Engine," 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, the Source.Python
+* Development Team grants this exception to all derivative works.
+*/
+
+//-----------------------------------------------------------------------------
+// Includes.
+//-----------------------------------------------------------------------------
+// Source.Python
+#include "modules/entities/entities_collisions.h"
+#include "utilities/conversions.h"
+
+#ifdef __linux__
+ #include "modules/memory/memory_rtti.h"
+#endif
+
+// Boost
+#include "boost/foreach.hpp"
+
+
+//-----------------------------------------------------------------------------
+// Externals.
+//-----------------------------------------------------------------------------
+extern IEngineTrace *enginetrace;
+extern CGlobalVars *gpGlobals;
+
+
+//-----------------------------------------------------------------------------
+// CCollisionManager class.
+//-----------------------------------------------------------------------------
+CCollisionManager::CCollisionManager():
+ m_bInitialized(false),
+ m_uiRefCount(0),
+ m_nTickCount(-1)
+{
+ m_pCollisionHooks = new CListenerManager();
+}
+
+CCollisionManager::~CCollisionManager()
+{
+ delete m_pCollisionHooks;
+}
+
+void CCollisionManager::IncRef()
+{
+ if (!m_uiRefCount) {
+ Initialize();
+ }
+
+ ++m_uiRefCount;
+}
+
+void CCollisionManager::DecRef()
+{
+ if (!m_uiRefCount) {
+ return;
+ }
+
+ --m_uiRefCount;
+
+ if (!m_uiRefCount) {
+ Finalize();
+ }
+}
+
+void CCollisionManager::Initialize()
+{
+ if (m_bInitialized) {
+ return;
+ }
+
+ RegisterHook(&IEngineTrace::TraceRay, 3, 2, "TraceRay");
+ RegisterHook(&IEngineTrace::TraceRayAgainstLeafAndEntityList, 4, 3, "TraceRayAgainstLeafAndEntityList");
+ RegisterHook(&IEngineTrace::SweepCollideable, 6, 5, "SweepCollideable");
+
+ m_bInitialized = true;
+}
+
+void CCollisionManager::Finalize()
+{
+ if (!m_bInitialized) {
+ return;
+ }
+
+ BOOST_FOREACH(CollisionHooksMap_t::value_type it, m_mapHooks) {
+ it.first->RemoveCallback(
+ HOOKTYPE_PRE,
+ (HookHandlerFn *)&CCollisionManager::EnterScope
+ );
+
+ it.first->RemoveCallback(
+ HOOKTYPE_POST,
+ (HookHandlerFn *)&CCollisionManager::ExitScope
+ );
+ }
+
+ m_mapHooks.clear();
+
+ m_bInitialized = false;
+}
+
+void CCollisionManager::RegisterRules(ICollisionRules *pRules)
+{
+ if (m_vecRules.HasElement(pRules)) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError,
+ "The given rules are already registered."
+ );
+ }
+
+ IncRef();
+ m_vecRules.AddToTail(pRules);
+}
+
+void CCollisionManager::UnregisterRules(ICollisionRules *pRules)
+{
+ if (!m_vecRules.FindAndRemove(pRules)) {
+ return;
+ }
+
+ DecRef();
+}
+
+void CCollisionManager::OnNetworkedEntityCreated(object oEntity)
+{
+ unsigned int uiMask;
+
+ BEGIN_BOOST_PY()
+ uiMask = extract(oEntity.attr("get_solid_mask")());
+ END_BOOST_PY()
+
+ if (m_setSolidMasks.find(uiMask) != m_setSolidMasks.end()) {
+ return;
+ }
+
+ m_setSolidMasks.insert(uiMask);
+ m_setSolidMasks.insert(uiMask | CONTENTS_TEAM1);
+ m_setSolidMasks.insert(uiMask | CONTENTS_TEAM2);
+}
+
+void CCollisionManager::OnNetworkedEntityDeleted(CBaseEntityWrapper *pEntity)
+{
+ FOR_EACH_VEC(m_vecRules, i) {
+ m_vecRules[i]->OnEntityDeleted(pEntity);
+ }
+}
+
+void CCollisionManager::RegisterCollisionHook(object oCallback)
+{
+ m_pCollisionHooks->RegisterListener(oCallback.ptr());
+ IncRef();
+}
+
+void CCollisionManager::UnregisterCollisionHook(object oCallback)
+{
+ m_pCollisionHooks->UnregisterListener(oCallback.ptr());
+ DecRef();
+}
+
+list CCollisionManager::GetSolidMasks()
+{
+ list oMasks;
+
+ if (!m_setSolidMasks.size()) {
+ return oMasks;
+ }
+
+ static object ContentFlags = import("engines").attr("trace").attr("ContentFlags");
+ BOOST_FOREACH(unsigned int uiMask, m_setSolidMasks) {
+ oMasks.append(ContentFlags(uiMask));
+ }
+
+ return oMasks;
+}
+
+template
+void CCollisionManager::RegisterHook(T tFunc, unsigned int uiFilterIndex, unsigned int uiMaskIndex, const char *szDebugName)
+{
+ CFunctionInfo *pInfo = GetFunctionInfo(tFunc);
+ if (!pInfo) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError,
+ "Failed to retrieve IEngineTrace::%s's info.",
+ szDebugName
+ )
+ }
+
+ CFunction *pFunc = CPointer((unsigned long)((void *)enginetrace)).MakeVirtualFunction(*pInfo);
+ delete pInfo;
+
+ if (!pFunc || !pFunc->IsHookable()) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError,
+ "IEngineTrace::%s's function is invalid or not hookable.",
+ szDebugName
+ )
+ }
+
+ static CHookManager *pHookManager = GetHookManager();
+ void *pAddr = (void *)pFunc->m_ulAddr;
+ CHook *pHook = pHookManager->FindHook(pAddr);
+ if (!pHook) {
+ pHook = pHookManager->HookFunction(pAddr, pFunc->m_pCallingConvention);
+ if (!pHook) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError,
+ "Failed to hook IEngineTrace::%s.",
+ szDebugName
+ )
+ }
+ }
+
+ delete pFunc;
+
+ CollisionHooksMap_t::const_iterator it = m_mapHooks.find(pHook);
+ if (it != m_mapHooks.end()) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_RuntimeError,
+ "Collision hook \"%s\" is already registered.",
+ szDebugName
+ )
+ }
+
+ CollisionHookData_t hookData;
+ hookData.m_uiFilterIndex = uiFilterIndex;
+ hookData.m_uiMaskIndex = uiMaskIndex;
+ m_mapHooks[pHook] = hookData;
+
+ pHook->AddCallback(
+ HOOKTYPE_PRE,
+ (HookHandlerFn *)&CCollisionManager::EnterScope
+ );
+
+ pHook->AddCallback(
+ HOOKTYPE_POST,
+ (HookHandlerFn *)&CCollisionManager::ExitScope
+ );
+}
+
+bool CCollisionManager::EnterScope(HookType_t eHookType, CHook *pHook)
+{
+ static CCollisionManager *pManager = GetCollisionManager();
+
+ CollisionScope_t scope;
+ scope.m_bSkip = true;
+
+ CollisionHookData_t hookData = pManager->m_mapHooks[pHook];
+
+ int nMask = pHook->GetArgument(hookData.m_uiMaskIndex);
+
+ bool bSolidContents = true;
+ if (pManager->m_setSolidMasks.find(nMask) == pManager->m_setSolidMasks.end()) {
+ if (!pManager->m_pCollisionHooks->GetCount()) {
+ pManager->m_vecScopes.push_back(scope);
+ return false;
+ }
+
+ bSolidContents = false;
+ }
+
+ CTraceFilterSimpleWrapper *pWrapper = NULL;
+ ITraceFilter *pFilter = pHook->GetArgument(hookData.m_uiFilterIndex);
+
+#ifdef _WIN32
+ pWrapper = (CTraceFilterSimpleWrapper *)dynamic_cast(pFilter);
+#elif __linux__
+ static boost::unordered_map s_mapTables;
+ void *pTable = *(void **)pFilter;
+ boost::unordered_map::const_iterator it = s_mapTables.find(pTable);
+ if (it == s_mapTables.end()) {
+ if (GetType(pFilter)->IsDerivedFrom("CTraceFilterSimple")) {
+ s_mapTables[pTable] = true;
+ pWrapper = reinterpret_cast(pFilter);
+ }
+ else {
+ s_mapTables[pTable] = false;
+ }
+ }
+ else if (it->second) {
+ pWrapper = reinterpret_cast(pFilter);
+ }
+#endif
+
+ if (!pWrapper) {
+ pManager->m_vecScopes.push_back(scope);
+ return false;
+ }
+
+ if (!pWrapper->m_pPassEnt) {
+ pManager->m_vecScopes.push_back(scope);
+ return false;
+ }
+
+ const CBaseHandle &pHandle = pWrapper->m_pPassEnt->GetRefEHandle();
+ if (!pHandle.IsValid()) {
+ pManager->m_vecScopes.push_back(scope);
+ return false;
+ }
+
+ unsigned int uiIndex = pHandle.GetEntryIndex();
+ if (uiIndex >= MAX_EDICTS) {
+ pManager->m_vecScopes.push_back(scope);
+ return false;
+ }
+
+ scope.m_pFilter = pWrapper;
+
+ static object ContentFlags = import("engines").attr("trace").attr("ContentFlags");
+ scope.m_oMask = ContentFlags(nMask);
+
+ scope.m_bSolidContents = bSolidContents;
+ scope.m_uiIndex = uiIndex;
+ scope.m_bIsPlayer = ((CBaseEntityWrapper *)pWrapper->m_pPassEnt)->IsPlayer();
+ scope.m_pCache = pManager->GetCache(uiIndex);
+ scope.m_pExtraShouldHitCheckFunction = pWrapper->m_pExtraShouldHitCheckFunction;
+ pWrapper->m_pExtraShouldHitCheckFunction = (ShouldHitFunc_t)CCollisionManager::ShouldHitEntity;
+
+ scope.m_bSkip = false;
+ pManager->m_vecScopes.push_back(scope);
+
+ return false;
+}
+
+bool CCollisionManager::ExitScope(HookType_t eHookType, CHook *pHook)
+{
+ static CCollisionManager *pManager = GetCollisionManager();
+ if (pManager->m_vecScopes.empty()) {
+ return false;
+ }
+
+ CollisionScope_t scope = pManager->m_vecScopes.back();
+ pManager->m_vecScopes.pop_back();
+
+ if (!scope.m_bSkip && scope.m_pExtraShouldHitCheckFunction) {
+ scope.m_pFilter->m_pExtraShouldHitCheckFunction = scope.m_pExtraShouldHitCheckFunction;
+ }
+
+ return false;
+}
+
+bool CCollisionManager::ShouldHitEntity(IHandleEntity *pHandleEntity, int contentsMask)
+{
+ static CCollisionManager *pManager = GetCollisionManager();
+ if (pManager->m_vecScopes.empty()) {
+ return true;
+ }
+
+ CollisionScope_t scope = pManager->m_vecScopes.back();
+ if (scope.m_bSkip) {
+ return true;
+ }
+
+ if (!pHandleEntity) {
+ return true;
+ }
+
+ const CBaseHandle &pHandle = pHandleEntity->GetRefEHandle();
+ if (!pHandle.IsValid()) {
+ return true;
+ }
+
+ unsigned int uiIndex = pHandle.GetEntryIndex();
+ if (uiIndex >= MAX_EDICTS) {
+ return true;
+ }
+
+ if (scope.m_pExtraShouldHitCheckFunction) {
+ if (scope.m_pExtraShouldHitCheckFunction != scope.m_pFilter->m_pExtraShouldHitCheckFunction) {
+ if (!(scope.m_pExtraShouldHitCheckFunction(pHandleEntity, contentsMask))) {
+ return false;
+ }
+ }
+ }
+
+ object oEntity;
+ object oOther;
+
+ if (pManager->m_pCollisionHooks->GetCount()) {
+ oEntity = GetEntityObject(scope.m_uiIndex);
+ oOther = GetEntityObject(uiIndex);
+
+ object oFilter = object(ptr((ITraceFilter *)scope.m_pFilter));
+
+ FOR_EACH_VEC(pManager->m_pCollisionHooks->m_vecCallables, i) {
+ BEGIN_BOOST_PY()
+ object oResult = pManager->m_pCollisionHooks->m_vecCallables[i](oEntity, oOther, oFilter, scope.m_oMask);
+ if (!oResult.is_none() && !extract(oResult)) {
+ scope.m_pCache->SetResult(uiIndex, false);
+ return false;
+ }
+ END_BOOST_PY_NORET()
+ }
+ }
+
+ if (scope.m_pCache->HasResult(uiIndex)) {
+ return scope.m_pCache->GetResult(uiIndex);
+ }
+
+ if (!scope.m_bSolidContents) {
+ return true;
+ }
+
+ FOR_EACH_VEC(pManager->m_vecRules, i) {
+ if (!pManager->m_vecRules[i]->ShouldCollide(
+ (CBaseEntityWrapper *)scope.m_pFilter->m_pPassEnt,
+ (CBaseEntityWrapper *)pHandleEntity)
+ ) {
+ scope.m_pCache->SetResult(uiIndex, false);
+ return false;
+ }
+ }
+
+ if (scope.m_bIsPlayer) {
+ static CCollisionListenerManager *OnPlayerCollision = GetOnPlayerCollisionListenerManager();
+ if (!OnPlayerCollision->GetCount()) {
+ scope.m_pCache->SetResult(uiIndex, true);
+ return true;
+ }
+
+ object oPlayer = GetEntityObject(scope.m_uiIndex);
+
+ if (oOther.is_none()) {
+ oOther = GetEntityObject(uiIndex);
+ }
+
+ if (!OnPlayerCollision->CallCallbacks(oPlayer, oOther)) {
+ scope.m_pCache->SetResult(uiIndex, false);
+ return false;
+ }
+
+ scope.m_pCache->SetResult(uiIndex, true);
+ return true;
+ }
+
+ static CCollisionListenerManager *OnEntityCollision = GetOnEntityCollisionListenerManager();
+ if (!OnEntityCollision->GetCount()) {
+ scope.m_pCache->SetResult(uiIndex, true);
+ return true;
+ }
+
+ if (oEntity.is_none()) {
+ oEntity = GetEntityObject(scope.m_uiIndex);
+ }
+
+ if (oOther.is_none()) {
+ oOther = GetEntityObject(uiIndex);
+ }
+
+ if (!OnEntityCollision->CallCallbacks(oEntity, oOther)) {
+ scope.m_pCache->SetResult(uiIndex, false);
+ return false;
+ }
+
+ scope.m_pCache->SetResult(uiIndex, true);
+ return true;
+}
+
+CCollisionCache *CCollisionManager::GetCache(unsigned int uiIndex)
+{
+ if (gpGlobals->tickcount != m_nTickCount) {
+ BOOST_FOREACH(CollisionCacheMap_t::value_type it, m_mapCache) {
+ delete it.second;
+ }
+
+ m_mapCache.clear();
+ m_nTickCount = gpGlobals->tickcount;
+ }
+ else {
+ CollisionCacheMap_t::const_iterator it = m_mapCache.find(uiIndex);
+ if (it != m_mapCache.end()) {
+ return it->second;
+ }
+ }
+
+ CCollisionCache *pCache = new CCollisionCache();
+ m_mapCache[uiIndex] = pCache;
+
+ return pCache;
+}
+
+
+//-----------------------------------------------------------------------------
+// ICollisionRules class.
+//-----------------------------------------------------------------------------
+ICollisionRules::~ICollisionRules()
+{
+ UnloadInstance();
+}
+
+void ICollisionRules::UnloadInstance()
+{
+ static CCollisionManager *pManager = GetCollisionManager();
+ pManager->UnregisterRules(this);
+}
+
+ECollisionMode ICollisionRules::GetMode()
+{
+ return m_eMode;
+}
+
+void ICollisionRules::SetMode(ECollisionMode eMode)
+{
+ m_eMode = eMode;
+}
+
+
+//-----------------------------------------------------------------------------
+// CCollisionCache class.
+//-----------------------------------------------------------------------------
+bool CCollisionCache::HasResult(unsigned int uiIndex)
+{
+ return IsBitSet(uiIndex);
+}
+
+bool CCollisionCache::GetResult(unsigned int uiIndex)
+{
+ return m_vecCache.IsBitSet(uiIndex);
+}
+
+void CCollisionCache::SetResult(unsigned int uiIndex, bool bResult)
+{
+ Set(uiIndex);
+ m_vecCache.Set((int)uiIndex, bResult);
+}
+
+
+//-----------------------------------------------------------------------------
+// CCollisionHash class.
+//-----------------------------------------------------------------------------
+void CCollisionHash::OnEntityDeleted(CBaseEntityWrapper *pEntity)
+{
+ RemovePairs(pEntity);
+}
+
+bool CCollisionHash::ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther)
+{
+ if (!HasElements()) {
+ return true;
+ }
+
+ bool bResult = !HasPair(pEntity, pOther);
+ return GetMode() == COLLISION_MODE_ALLOW ? !bResult : bResult;
+}
+
+void CCollisionHash::AddPair(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther)
+{
+ if (!pEntity->IsNetworked() || !pOther->IsNetworked()) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError,
+ "Given entity is not networked."
+ )
+ }
+
+ m_setPairs.insert(CollisionPair_t(pEntity, pOther));
+}
+
+void CCollisionHash::RemovePair(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther)
+{
+ m_setPairs.erase(CollisionPair_t(pEntity, pOther));
+}
+
+void CCollisionHash::RemovePairs(CBaseEntityWrapper *pEntity)
+{
+ for (CollisionPairs_t::const_iterator it = m_setPairs.begin(); it != m_setPairs.end(); ) {
+ if (it->first == pEntity || it->second == pEntity) {
+ m_setPairs.erase(it);
+ }
+
+ ++it;
+ }
+}
+
+void CCollisionHash::Clear()
+{
+ m_setPairs.clear();
+}
+
+bool CCollisionHash::Contains(CBaseEntityWrapper *pEntity)
+{
+ BOOST_FOREACH(CollisionPair_t p, m_setPairs) {
+ if (p.first == pEntity || p.second == pEntity) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CCollisionHash::HasPair(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther)
+{
+ return m_setPairs.find(CollisionPair_t(pEntity, pOther)) != m_setPairs.end();
+}
+
+unsigned int CCollisionHash::GetCount(CBaseEntityWrapper *pEntity)
+{
+ unsigned int nCount = 0;
+ BOOST_FOREACH(CollisionPair_t p, m_setPairs) {
+ if (p.first == pEntity || p.second == pEntity) {
+ ++nCount;
+ }
+ }
+
+ return nCount;
+}
+
+list CCollisionHash::GetPairs(CBaseEntityWrapper *pEntity)
+{
+ list oObjects;
+ BOOST_FOREACH(CollisionPair_t p, m_setPairs) {
+ if (p.first == pEntity) {
+ oObjects.append(GetEntityObject(p.second));
+ }
+ else if (p.second == pEntity) {
+ oObjects.append(GetEntityObject(p.first));
+ }
+ }
+
+ return oObjects;
+}
+
+unsigned int CCollisionHash::GetSize()
+{
+ return m_setPairs.size();
+}
+
+bool CCollisionHash::HasElements()
+{
+ return !m_setPairs.empty();
+}
+
+object CCollisionHash::Iterate()
+{
+ list oEntities;
+
+ if (HasElements()) {
+ BOOST_FOREACH(CollisionPair_t it, m_setPairs) {
+ oEntities.append(make_tuple(GetEntityObject(it.first), GetEntityObject(it.second)));
+ }
+ }
+
+ return oEntities.attr("__iter__")();
+}
+
+
+//-----------------------------------------------------------------------------
+// CCollisionSet class.
+//-----------------------------------------------------------------------------
+void CCollisionSet::Add(CBaseEntityWrapper *pEntity)
+{
+ if (Contains(pEntity)) {
+ return;
+ }
+
+ if (!pEntity->IsNetworked()) {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError,
+ "Given entity is not networked."
+ )
+ }
+
+ m_pSet.insert(pEntity);
+}
+
+void CCollisionSet::Remove(CBaseEntityWrapper *pEntity)
+{
+ m_pSet.erase(pEntity);
+}
+
+bool CCollisionSet::Contains(CBaseEntityWrapper *pEntity)
+{
+ return m_pSet.find(pEntity) != m_pSet.end();
+}
+
+void CCollisionSet::Clear()
+{
+ m_pSet.clear();
+}
+
+unsigned int CCollisionSet::GetSize()
+{
+ return m_pSet.size();
+}
+
+bool CCollisionSet::HasElements()
+{
+ return !m_pSet.empty();
+}
+
+object CCollisionSet::Iterate()
+{
+ list oEntities;
+
+ if (HasElements()) {
+ BOOST_FOREACH(CBaseEntityWrapper *pEntity, m_pSet) {
+ oEntities.append(GetEntityObject(pEntity));
+ }
+ }
+
+ return oEntities.attr("__iter__")();
+}
+
+void CCollisionSet::OnEntityDeleted(CBaseEntityWrapper *pEntity)
+{
+ Remove(pEntity);
+};
+
+bool CCollisionSet::ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther)
+{
+ if (!HasElements()) {
+ return true;
+ }
+
+ bool bResult = !(Contains(pEntity) || Contains(pOther));
+ return GetMode() == COLLISION_MODE_ALLOW ? !bResult : bResult;
+};
+
+
+//-----------------------------------------------------------------------------
+// CCollisionMap class.
+//-----------------------------------------------------------------------------
+boost::shared_ptr CCollisionMap::Find(CBaseEntityWrapper *pEntity)
+{
+ CollisionMap_t::const_iterator it = m_mapSets.find(pEntity);
+ if (it != m_mapSets.end()) {
+ return it->second;
+ }
+
+ CCollisionSet *pSet = new CCollisionSet;
+ pSet->SetMode(GetMode());
+
+ boost::shared_ptr spSet = boost::shared_ptr(pSet);
+ m_mapSets[pEntity] = spSet;
+
+ return spSet;
+}
+
+void CCollisionMap::Remove(CBaseEntityWrapper *pEntity)
+{
+ m_mapSets.erase(pEntity);
+}
+
+void CCollisionMap::Clear()
+{
+ m_mapSets.clear();
+}
+
+bool CCollisionMap::Contains(CBaseEntityWrapper *pEntity)
+{
+ return m_mapSets.find(pEntity) != m_mapSets.end();
+}
+
+unsigned int CCollisionMap::GetSize()
+{
+ return m_mapSets.size();
+}
+
+bool CCollisionMap::HasElements()
+{
+ return !m_mapSets.empty();
+}
+
+object CCollisionMap::Iterate()
+{
+ list oEntities;
+
+ if (HasElements()) {
+ BOOST_FOREACH(CollisionMap_t::value_type it, m_mapSets) {
+ oEntities.append(make_tuple(GetEntityObject(it.first), object(it.second)));
+ }
+ }
+
+ return oEntities.attr("__iter__")();
+}
+
+void CCollisionMap::OnEntityDeleted(CBaseEntityWrapper *pEntity)
+{
+ BOOST_FOREACH(CollisionMap_t::value_type it, m_mapSets) {
+ it.second->OnEntityDeleted(pEntity);
+ }
+
+ Remove(pEntity);
+}
+
+bool CCollisionMap::ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther)
+{
+ CollisionMap_t::const_iterator it = m_mapSets.find(pEntity);
+ if (it == m_mapSets.end()) {
+ return true;
+ }
+
+ CCollisionSet *pSet = get_pointer(it->second);
+ if (!pSet || !pSet->HasElements()) {
+ return true;
+ }
+
+ bool bResult = !pSet->Contains(pOther);
+ return GetMode() == COLLISION_MODE_ALLOW ? !bResult : bResult;
+}
+
+
+//-----------------------------------------------------------------------------
+// CCollisionListenerManager class.
+//-----------------------------------------------------------------------------
+CCollisionListenerManager::CCollisionListenerManager():
+ m_bInitialized(false)
+{
+
+}
+
+void CCollisionListenerManager::Initialize()
+{
+ if (m_bInitialized) {
+ return;
+ }
+
+ static CCollisionManager *pManager = GetCollisionManager();
+ pManager->IncRef();
+ m_bInitialized = true;
+}
+
+void CCollisionListenerManager::Finalize()
+{
+ if (!m_bInitialized) {
+ return;
+ }
+
+ static CCollisionManager *pManager = GetCollisionManager();
+ pManager->DecRef();
+ m_bInitialized = false;
+}
+
+bool CCollisionListenerManager::CallCallbacks(object oEntity, object oOther)
+{
+ FOR_EACH_VEC(m_vecCallables, i) {
+ BEGIN_BOOST_PY()
+ object oResult = m_vecCallables[i](oEntity, oOther);
+ if (!oResult.is_none() && !extract(oResult)) {
+ return false;
+ }
+ END_BOOST_PY_NORET()
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Singleton accessors.
+//-----------------------------------------------------------------------------
+static CCollisionListenerManager s_OnEntityCollision;
+CCollisionListenerManager *GetOnEntityCollisionListenerManager()
+{
+ return &s_OnEntityCollision;
+}
+
+static CCollisionListenerManager s_OnPlayerCollision;
+CCollisionListenerManager *GetOnPlayerCollisionListenerManager()
+{
+ return &s_OnPlayerCollision;
+}
diff --git a/src/core/modules/entities/entities_collisions.h b/src/core/modules/entities/entities_collisions.h
new file mode 100644
index 000000000..522b1b2a0
--- /dev/null
+++ b/src/core/modules/entities/entities_collisions.h
@@ -0,0 +1,377 @@
+/**
+* =============================================================================
+* Source Python
+* Copyright (C) 2012-2021 Source Python Development Team. 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 .
+*
+* As a special exception, the Source Python Team gives you permission
+* to link the code of this program (as well as its derivative works) to
+* "Half-Life 2," the "Source Engine," 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, the Source.Python
+* Development Team grants this exception to all derivative works.
+*/
+
+#ifndef _ENTITIES_COLLISIONS_H
+#define _ENTITIES_COLLISIONS_H
+
+//-----------------------------------------------------------------------------
+// Includes.
+//-----------------------------------------------------------------------------
+// Source.Python
+#include "sp_main.h"
+#include "modules/listeners/listeners_manager.h"
+#include "modules/entities/entities_entity.h"
+#include "modules/memory/memory_hooks.h"
+
+// Boost
+#include "boost/unordered_map.hpp"
+#include "boost/unordered_set.hpp"
+
+// SDK
+#include "bitvec.h"
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations.
+//-----------------------------------------------------------------------------
+struct CollisionHookData_t;
+class CCollisionSet;
+class CCollisionCache;
+
+
+//-----------------------------------------------------------------------------
+// Typedefs.
+//-----------------------------------------------------------------------------
+#if !defined(ENGINE_CSGO) && !defined(ENGINE_BLADE) && !defined(ENGINE_BMS)
+ typedef bool (*ShouldHitFunc_t)( IHandleEntity *pHandleEntity, int contentsMask );
+#endif
+
+typedef boost::unordered_map CollisionHooksMap_t;
+
+typedef CBitVec CollisionCache_t;
+typedef boost::unordered_map CollisionCacheMap_t;
+
+typedef std::pair CollisionPair_t;
+typedef boost::unordered_map > CollisionMap_t;
+
+typedef boost::unordered_set CollisionSet_t;
+
+
+//-----------------------------------------------------------------------------
+// CTraceFilterSimpleWrapper class.
+//-----------------------------------------------------------------------------
+class CTraceFilterSimpleWrapper : public CTraceFilter
+{
+public:
+ const IHandleEntity *m_pPassEnt;
+ int m_collisionGroup;
+ ShouldHitFunc_t m_pExtraShouldHitCheckFunction;
+};
+
+
+//-----------------------------------------------------------------------------
+// CollisionScope_t structure.
+//-----------------------------------------------------------------------------
+struct CollisionScope_t
+{
+ bool m_bSkip;
+ unsigned int m_uiIndex;
+ bool m_bIsPlayer;
+ CTraceFilterSimpleWrapper *m_pFilter;
+ ShouldHitFunc_t m_pExtraShouldHitCheckFunction;
+ CCollisionCache *m_pCache;
+ object m_oMask;
+ bool m_bSolidContents;
+};
+
+
+//-----------------------------------------------------------------------------
+// CollisionHookData_t structure.
+//-----------------------------------------------------------------------------
+struct CollisionHookData_t
+{
+ unsigned int m_uiFilterIndex;
+ unsigned int m_uiMaskIndex;
+};
+
+
+//-----------------------------------------------------------------------------
+// ECollisionMode enumeration.
+//-----------------------------------------------------------------------------
+enum ECollisionMode
+{
+ COLLISION_MODE_ALLOW,
+ COLLISION_MODE_PREVENT
+};
+
+
+//-----------------------------------------------------------------------------
+// ICollisionRules class.
+//-----------------------------------------------------------------------------
+class ICollisionRules
+{
+public:
+ virtual ~ICollisionRules();
+
+ virtual void OnEntityDeleted(CBaseEntityWrapper *pEntity) = 0;
+ virtual bool ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther) = 0;
+
+ virtual void UnloadInstance();
+
+ ECollisionMode GetMode();
+ void SetMode(ECollisionMode eMode);
+
+private:
+ ECollisionMode m_eMode;
+};
+
+
+//-----------------------------------------------------------------------------
+// CollisionPairs_t definition and specializations.
+//-----------------------------------------------------------------------------
+namespace boost {
+ inline std::size_t hash_value(CollisionPair_t const &p) {
+ std::size_t s = 0;
+ boost::hash_combine(s, p.first < p.second ? p.first : p.second);
+ boost::hash_combine(s, p.first < p.second ? p.second : p.first);
+ return s;
+ }
+
+ struct hash_equals {
+ inline bool operator()(const CollisionPair_t &a, const CollisionPair_t &b) const {
+ return (a == b) || (a.first == b.second && a.second == b.first);
+ }
+ };
+}
+
+typedef boost::unordered_set, boost::hash_equals> CollisionPairs_t;
+
+
+//-----------------------------------------------------------------------------
+// CCollisionHash class.
+//-----------------------------------------------------------------------------
+class CCollisionHash : public ICollisionRules
+{
+public:
+ void OnEntityDeleted(CBaseEntityWrapper *pEntity);
+ bool ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther);
+
+ void AddPair(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther);
+ void RemovePair(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther);
+ void RemovePairs(CBaseEntityWrapper *pEntity);
+
+ void Clear();
+
+ bool Contains(CBaseEntityWrapper *pEntity);
+ bool HasPair(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther);
+
+ unsigned int GetCount(CBaseEntityWrapper *pEntity);
+ list GetPairs(CBaseEntityWrapper *pEntity);
+
+ unsigned int GetSize();
+ bool HasElements();
+
+ object Iterate();
+
+private:
+ CollisionPairs_t m_setPairs;
+};
+
+
+//-----------------------------------------------------------------------------
+// CCollisionSet class.
+//-----------------------------------------------------------------------------
+class CCollisionSet : public ICollisionRules
+{
+public:
+ void Add(CBaseEntityWrapper *pEntity);
+ void Remove(CBaseEntityWrapper *pEntity);
+ void Clear();
+
+ bool Contains(CBaseEntityWrapper *pEntity);
+ unsigned int GetSize();
+ bool HasElements();
+
+ object Iterate();
+
+ void OnEntityDeleted(CBaseEntityWrapper *pEntity);
+ bool ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther);
+
+private:
+ CollisionSet_t m_pSet;
+};
+
+
+//-----------------------------------------------------------------------------
+// CCollisionMap class.
+//-----------------------------------------------------------------------------
+class CCollisionMap: public ICollisionRules
+{
+public:
+ void OnEntityDeleted(CBaseEntityWrapper *pEntity);
+ bool ShouldCollide(CBaseEntityWrapper *pEntity, CBaseEntityWrapper *pOther);
+
+ boost::shared_ptr Find(CBaseEntityWrapper *pEntity);
+
+ void Remove(CBaseEntityWrapper *pEntity);
+ void Clear();
+
+ bool Contains(CBaseEntityWrapper *pEntity);
+ unsigned int GetSize();
+ bool HasElements();
+
+ object Iterate();
+
+private:
+ CollisionMap_t m_mapSets;
+};
+
+
+//-----------------------------------------------------------------------------
+// CCollisionCache class.
+//-----------------------------------------------------------------------------
+class CCollisionCache : private CollisionCache_t
+{
+public:
+ bool HasResult(unsigned int uiIndex);
+ bool GetResult(unsigned int uiIndex);
+ void SetResult(unsigned int uiIndex, bool bResult);
+
+private:
+ CollisionCache_t m_vecCache;
+};
+
+
+//-----------------------------------------------------------------------------
+// CCollisionManager class.
+//-----------------------------------------------------------------------------
+class CCollisionManager
+{
+public:
+ friend CCollisionManager *GetCollisionManager();
+ friend class CCollisionListenerManager;
+
+private:
+ CCollisionManager();
+ ~CCollisionManager();
+
+public:
+ void Initialize();
+ void Finalize();
+
+ void RegisterRules(ICollisionRules *pRules);
+ void UnregisterRules(ICollisionRules *pRules);
+
+ void OnNetworkedEntityCreated(object oEntity);
+ void OnNetworkedEntityDeleted(CBaseEntityWrapper *pEntity);
+
+ void RegisterCollisionHook(object oCallback);
+ void UnregisterCollisionHook(object oCallback);
+
+ list GetSolidMasks();
+
+protected:
+ void IncRef();
+ void DecRef();
+
+private:
+ template
+ void RegisterHook(T tFunc, unsigned int uiFilterIndex, unsigned int nMaskIndex, const char *szDebugName);
+
+ static bool EnterScope(HookType_t eHookType, CHook *pHook);
+ static bool ExitScope(HookType_t eHookType, CHook *pHook);
+
+ static bool ShouldHitEntity(IHandleEntity *pHandleEntity, int contentsMask);
+
+ CCollisionCache *GetCache(unsigned int uiIndex);
+
+private:
+ bool m_bInitialized;
+ unsigned int m_uiRefCount;
+ CUtlVector m_vecRules;
+ CollisionHooksMap_t m_mapHooks;
+ std::vector m_vecScopes;
+
+ boost::unordered_set m_setSolidMasks;
+
+ int m_nTickCount;
+ CollisionCacheMap_t m_mapCache;
+
+ CListenerManager *m_pCollisionHooks;
+};
+
+// Singleton accessor.
+inline CCollisionManager *GetCollisionManager()
+{
+ static CCollisionManager *s_pEntityCollisionManager = new CCollisionManager;
+ return s_pEntityCollisionManager;
+}
+
+
+//-----------------------------------------------------------------------------
+// ICollisionRules extension class.
+//-----------------------------------------------------------------------------
+class ICollisionRulesExt
+{
+public:
+ template
+ static boost::shared_ptr Construct(ECollisionMode eMode = COLLISION_MODE_PREVENT)
+ {
+ return boost::shared_ptr(new T, &Finalize);
+ }
+
+ template
+ static void Initialize(T *pSelf, object self, ECollisionMode eMode = COLLISION_MODE_PREVENT)
+ {
+ pSelf->SetMode(eMode);
+
+ static CCollisionManager *pManager = GetCollisionManager();
+ pManager->RegisterRules(pSelf);
+ }
+
+ template
+ static void Finalize(T *pSelf)
+ {
+ static CCollisionManager *pManager = GetCollisionManager();
+ pManager->UnregisterRules(pSelf);
+ delete pSelf;
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// CCollisionListenerManager class.
+//-----------------------------------------------------------------------------
+class CCollisionListenerManager : public CListenerManager
+{
+public:
+ CCollisionListenerManager();
+
+ void Initialize();
+ void Finalize();
+
+ bool CallCallbacks(object oEntity, object oOther);
+
+private:
+ bool m_bInitialized;
+};
+
+// Singleton accessors.
+CCollisionListenerManager *GetOnPlayerCollisionListenerManager();
+CCollisionListenerManager *GetOnEntityCollisionListenerManager();
+
+
+#endif // _ENTITIES_COLLISIONS_H
diff --git a/src/core/modules/entities/entities_collisions_wrap.cpp b/src/core/modules/entities/entities_collisions_wrap.cpp
new file mode 100644
index 000000000..c801ad18c
--- /dev/null
+++ b/src/core/modules/entities/entities_collisions_wrap.cpp
@@ -0,0 +1,373 @@
+/**
+* =============================================================================
+* Source Python
+* Copyright (C) 2012-2021 Source Python Development Team. 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 .
+*
+* As a special exception, the Source Python Team gives you permission
+* to link the code of this program (as well as its derivative works) to
+* "Half-Life 2," the "Source Engine," 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, the Source.Python
+* Development Team grants this exception to all derivative works.
+*/
+
+//-----------------------------------------------------------------------------
+// Includes.
+//-----------------------------------------------------------------------------
+// Source.Python
+#include "export_main.h"
+#include "modules/entities/entities_collisions.h"
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations.
+//-----------------------------------------------------------------------------
+static void export_collision_manager(scope);
+static void export_collision_mode(scope);
+static void export_collision_rules(scope);
+static void export_collision_hash(scope);
+static void export_collision_set(scope);
+static void export_collision_map(scope);
+
+
+//-----------------------------------------------------------------------------
+// Declare the _entities._collisions module.
+//-----------------------------------------------------------------------------
+DECLARE_SP_SUBMODULE(_entities, _collisions)
+{
+ export_collision_manager(_collisions);
+ export_collision_mode(_collisions);
+ export_collision_rules(_collisions);
+ export_collision_hash(_collisions);
+ export_collision_set(_collisions);
+ export_collision_map(_collisions);
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports CCollisionManager.
+//-----------------------------------------------------------------------------
+void export_collision_manager(scope _collisions)
+{
+ class_ CollisionManager("CollisionManager", no_init);
+
+ // Methods...
+ CollisionManager.def(
+ "register_hook",
+ &CCollisionManager::RegisterCollisionHook,
+ "Registers a collision hook."
+ );
+
+ CollisionManager.def(
+ "unregister_hook",
+ &CCollisionManager::UnregisterCollisionHook,
+ "Unregisters a collision hook."
+ );
+
+ // Properties...
+ CollisionManager.add_property(
+ "solid_masks",
+ &CCollisionManager::GetSolidMasks,
+ "Returns a list containing the masks that are currently considered solid."
+ );
+
+ // Singleton...
+ _collisions.attr("collision_manager") = object(ptr(GetCollisionManager()));
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports ECollisionMode.
+//-----------------------------------------------------------------------------
+void export_collision_mode(scope _collisions)
+{
+ enum_ CollisionMode("CollisionMode");
+
+ // Values...
+ CollisionMode.value("ALLOW", COLLISION_MODE_ALLOW);
+ CollisionMode.value("PREVENT", COLLISION_MODE_PREVENT);
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports ICollisionRules.
+//-----------------------------------------------------------------------------
+void export_collision_rules(scope _collisions)
+{
+ class_ CollisionRules("CollisionRules", no_init);
+
+ // Properties...
+ CollisionRules.add_property(
+ "mode",
+ &ICollisionRules::GetMode,
+ &ICollisionRules::SetMode,
+ "Returns the collision mode for these rules."
+ );
+
+ // Methods...
+ CollisionRules.def(
+ "should_collide",
+ &ICollisionRules::ShouldCollide,
+ "Returns whether the given entities should collide with each other."
+ );
+
+ // AutoUnload...
+ CollisionRules.def(
+ "_unload_instance",
+ &ICollisionRules::UnloadInstance,
+ "Called when an instance is being unloaded."
+ );
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports CCollisionHash.
+//-----------------------------------------------------------------------------
+void export_collision_hash(scope _collisions)
+{
+ class_, bases > CollisionHash(
+ "CollisionHash",
+ "Collision rules where contained pairs never collide with each other.",
+ no_init
+ );
+
+ // Constructor...
+ CollisionHash.def("__init__",
+ make_constructor(
+ &ICollisionRulesExt::Construct,
+ post_constructor_policies(
+ make_function(
+ &ICollisionRulesExt::Initialize,
+ default_call_policies(),
+ args("self", "mode")
+ )
+ ),
+ (arg("mode")=COLLISION_MODE_PREVENT)
+ )
+ );
+
+ // Methods...
+ CollisionHash.def(
+ "add_pair",
+ &CCollisionHash::AddPair,
+ "Adds the given entity pair to the hash."
+ );
+
+ CollisionHash.def(
+ "remove_pair",
+ &CCollisionHash::RemovePair,
+ "Removes the given pair from the hash."
+ );
+
+ CollisionHash.def(
+ "remove_pairs",
+ &CCollisionHash::RemovePairs,
+ "Removes all pairs associated with the given entity."
+ );
+
+ CollisionHash.def(
+ "has_pair",
+ &CCollisionHash::HasPair,
+ "Returns whether the given pair is in the hash."
+ );
+
+ CollisionHash.def(
+ "get_count",
+ &CCollisionHash::GetCount,
+ "Returns the amount of pairs associated with the given entity."
+ );
+
+ CollisionHash.def(
+ "get_pairs",
+ &CCollisionHash::GetPairs,
+ "Returns a list of all entities associated with the given entity."
+ );
+
+ CollisionHash.def(
+ "clear",
+ &CCollisionHash::Clear,
+ "Removes all entities from the hash."
+ );
+
+ // Special methods...
+ CollisionHash.def(
+ "__bool__",
+ &CCollisionHash::HasElements,
+ "Returns whether the hash is empty or not."
+ );
+
+ CollisionHash.def(
+ "__contains__",
+ &CCollisionHash::Contains,
+ "Returns whether the given entity is in the hash."
+ );
+
+ CollisionHash.def(
+ "__len__",
+ &CCollisionHash::GetSize,
+ "Returns the size of the collision hash."
+ );
+
+ CollisionHash.def(
+ "__iter__",
+ &CCollisionHash::Iterate,
+ "Iterates over all entities contained in the hash."
+ );
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports CCollisionSet.
+//-----------------------------------------------------------------------------
+void export_collision_set(scope _collisions)
+{
+ class_, bases > CollisionSet(
+ "CollisionSet",
+ "Collision rules where contained entities never collide with anything.",
+ no_init
+ );
+
+ // Constructor...
+ CollisionSet.def("__init__",
+ make_constructor(
+ &ICollisionRulesExt::Construct,
+ post_constructor_policies(
+ make_function(
+ &ICollisionRulesExt::Initialize,
+ default_call_policies(),
+ args("self", "mode")
+ )
+ ),
+ (arg("mode")=COLLISION_MODE_PREVENT)
+ )
+ );
+
+ // Methods...
+ CollisionSet.def(
+ "add",
+ &CCollisionSet::Add,
+ "Adds the given entity to the set."
+ );
+
+ CollisionSet.def(
+ "remove",
+ &CCollisionSet::Remove,
+ "Removes the given entity from the set."
+ );
+
+ CollisionSet.def(
+ "clear",
+ &CCollisionSet::Clear,
+ "Removes all entities from the set."
+ );
+
+ // Special methods...
+ CollisionSet.def(
+ "__bool__",
+ &CCollisionSet::HasElements,
+ "Returns whether the set is empty or not."
+ );
+
+ CollisionSet.def(
+ "__contains__",
+ &CCollisionSet::Contains,
+ "Returns whether the given entity is in the set or not."
+ );
+
+ CollisionSet.def(
+ "__iter__",
+ &CCollisionSet::Iterate,
+ "Iterates over all entities contained in the set."
+ );
+
+ CollisionSet.def(
+ "__len__",
+ &CCollisionSet::GetSize,
+ "Returns the amount of entities contained in the set."
+ );
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports CCollisionMap.
+//-----------------------------------------------------------------------------
+void export_collision_map(scope _collisions)
+{
+ class_, bases > CollisionMap(
+ "CollisionMap",
+ "Collision rules that overrides one-way collisions.",
+ no_init
+ );
+
+ // Constructor...
+ CollisionMap.def("__init__",
+ make_constructor(
+ &ICollisionRulesExt::Construct,
+ post_constructor_policies(
+ make_function(
+ &ICollisionRulesExt::Initialize,
+ default_call_policies(),
+ args("self", "mode")
+ )
+ ),
+ (args("mode")=COLLISION_MODE_PREVENT)
+ )
+ );
+
+ // Methods...
+ CollisionMap.def(
+ "clear",
+ &CCollisionMap::Clear,
+ "Removes all entities from the map."
+ );
+
+ // Special methods...
+ CollisionMap.def(
+ "__getitem__",
+ &CCollisionMap::Find,
+ "Returns the collision set associated with the given entity."
+ );
+
+ CollisionMap.def(
+ "__delitem__",
+ &CCollisionMap::Remove,
+ "Removes the collision set associated with the given entity."
+ );
+
+ CollisionMap.def(
+ "__bool__",
+ &CCollisionMap::HasElements,
+ "Returns whether the map is empty or not."
+ );
+
+ CollisionMap.def(
+ "__contains__",
+ &CCollisionMap::Contains,
+ "Returns whether the given entities is in the map or not."
+ );
+
+ CollisionMap.def(
+ "__len__",
+ &CCollisionMap::GetSize,
+ "Returns the amount of entities contained in the map."
+ );
+
+ CollisionMap.def(
+ "__iter__",
+ &CCollisionMap::Iterate,
+ "Iterates over all entities contained in the map."
+ );
+}
diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp
index 22f0fec0a..ede993e2c 100755
--- a/src/core/modules/entities/entities_entity.cpp
+++ b/src/core/modules/entities/entities_entity.cpp
@@ -486,6 +486,11 @@ bool CBaseEntityWrapper::IsWeapon()
return result;
}
+bool CBaseEntityWrapper::IsNetworked()
+{
+ return IServerUnknownExt::IsNetworked((IServerUnknown *)this);
+}
+
IPhysicsObjectWrapper* CBaseEntityWrapper::GetPhysicsObject()
{
return Wrap(GetDatamapProperty("m_pPhysicsObject"));
diff --git a/src/core/modules/entities/entities_entity.h b/src/core/modules/entities/entities_entity.h
index 9801f3052..a17ae8278 100644
--- a/src/core/modules/entities/entities_entity.h
+++ b/src/core/modules/entities/entities_entity.h
@@ -320,6 +320,8 @@ class CBaseEntityWrapper: public IServerEntity
bool IsPlayer();
bool IsWeapon();
+ bool IsNetworked();
+
Vector GetOrigin();
void SetOrigin(Vector& vec);
@@ -447,4 +449,43 @@ class CBaseEntityWrapper: public IServerEntity
};
+//-----------------------------------------------------------------------------
+// Returns an entity pointer as a Python object.
+//-----------------------------------------------------------------------------
+inline object GetEntityObject(CBaseEntityWrapper *pEntity)
+{
+ if (pEntity->IsNetworked()) {
+ if (pEntity->IsPlayer()) {
+ static object Player = import("players").attr("entity").attr("Player");
+ return Player(pEntity->GetIndex());
+ }
+
+ if (pEntity->IsWeapon()) {
+ static object Weapon = import("weapons").attr("entity").attr("Weapon");
+ return Weapon(pEntity->GetIndex());
+ }
+
+ static object Entity = import("entities").attr("entity").attr("Entity");
+ return Entity(pEntity->GetIndex());
+ }
+
+ return object(ptr(pEntity));
+}
+
+inline object GetEntityObject(CBaseEntity *pEntity)
+{
+ return GetEntityObject((CBaseEntityWrapper *)pEntity);
+}
+
+inline object GetEntityObject(unsigned int uiIndex)
+{
+ CBaseEntity *pEntity;
+ if (!BaseEntityFromIndex(uiIndex, pEntity)) {
+ return object();
+ }
+
+ return GetEntityObject((CBaseEntityWrapper *)pEntity);
+}
+
+
#endif // _ENTITIES_ENTITY_H
diff --git a/src/core/modules/listeners/listeners_wrap.cpp b/src/core/modules/listeners/listeners_wrap.cpp
index f1f43160e..a6e6ecd33 100755
--- a/src/core/modules/listeners/listeners_wrap.cpp
+++ b/src/core/modules/listeners/listeners_wrap.cpp
@@ -30,6 +30,7 @@
#include "export_main.h"
#include "utilities/wrap_macros.h"
#include "listeners_manager.h"
+#include "modules/entities/entities_collisions.h"
//-----------------------------------------------------------------------------
@@ -178,6 +179,8 @@ void export_listener_managers(scope _listeners)
_listeners.attr("on_networked_entity_spawned_listener_manager") = object(ptr(GetOnNetworkedEntitySpawnedListenerManager()));
_listeners.attr("on_entity_deleted_listener_manager") = object(ptr(GetOnEntityDeletedListenerManager()));
_listeners.attr("on_networked_entity_deleted_listener_manager") = object(ptr(GetOnNetworkedEntityDeletedListenerManager()));
+ _listeners.attr("on_entity_collision_listener_manager") = object(ptr((CListenerManager *)GetOnEntityCollisionListenerManager()));
+ _listeners.attr("on_player_collision_listener_manager") = object(ptr((CListenerManager *)GetOnPlayerCollisionListenerManager()));
_listeners.attr("on_data_loaded_listener_manager") = object(ptr(GetOnDataLoadedListenerManager()));
_listeners.attr("on_combiner_pre_cache_listener_manager") = object(ptr(GetOnCombinerPreCacheListenerManager()));
diff --git a/src/core/modules/memory/memory_hooks.h b/src/core/modules/memory/memory_hooks.h
index 7cc9ecd50..304157d5f 100644
--- a/src/core/modules/memory/memory_hooks.h
+++ b/src/core/modules/memory/memory_hooks.h
@@ -54,7 +54,7 @@ class CStackData
{ return m_pHook->GetRegisters(); }
str __repr__()
- { return str(tuple(ptr(this))); }
+ { return str(boost::python::tuple(ptr(this))); }
void* GetReturnAddress()
{
diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp
index 19a7b72ec..631bffb8e 100755
--- a/src/core/sp_main.cpp
+++ b/src/core/sp_main.cpp
@@ -60,6 +60,7 @@
#include "modules/listeners/listeners_manager.h"
#include "utilities/conversions.h"
#include "modules/entities/entities_entity.h"
+#include "modules/entities/entities_collisions.h"
#include "modules/core/core.h"
#ifdef _WIN32
@@ -541,16 +542,20 @@ void CSourcePython::OnEntityCreated( CBaseEntity *pEntity )
CALL_LISTENERS(OnEntityCreated, ptr((CBaseEntityWrapper*) pEntity));
- GET_LISTENER_MANAGER(OnNetworkedEntityCreated, on_networked_entity_created_manager);
- if (!on_networked_entity_created_manager->GetCount())
- return;
-
unsigned int uiIndex;
if (!IndexFromBaseEntity(pEntity, uiIndex))
return;
static object Entity = import("entities").attr("entity").attr("Entity");
- CALL_LISTENERS_WITH_MNGR(on_networked_entity_created_manager, Entity(uiIndex));
+ object oEntity = Entity(uiIndex);
+
+ GET_LISTENER_MANAGER(OnNetworkedEntityCreated, on_networked_entity_created_manager);
+ if (on_networked_entity_created_manager->GetCount()) {
+ CALL_LISTENERS_WITH_MNGR(on_networked_entity_created_manager, oEntity);
+ }
+
+ static CCollisionManager *pCollisionManager = GetCollisionManager();
+ pCollisionManager->OnNetworkedEntityCreated(oEntity);
}
void CSourcePython::OnEntitySpawned( CBaseEntity *pEntity )
@@ -587,6 +592,10 @@ void CSourcePython::OnEntityDeleted( CBaseEntity *pEntity )
// Invalidate the internal entity cache once all callbacks have been called.
static object _on_networked_entity_deleted = import("entities").attr("_base").attr("_on_networked_entity_deleted");
_on_networked_entity_deleted(uiIndex);
+
+ // Cleanup active collision hashes.
+ static CCollisionManager *pCollisionManager = GetCollisionManager();
+ pCollisionManager->OnNetworkedEntityDeleted((CBaseEntityWrapper *)pEntity);
}
void CSourcePython::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle )
diff --git a/src/core/sp_python.cpp b/src/core/sp_python.cpp
index 5512e1c63..f5ee8a7ea 100644
--- a/src/core/sp_python.cpp
+++ b/src/core/sp_python.cpp
@@ -310,6 +310,32 @@ struct baseentity_from_python
}
};
+struct baseentity_index_from_python
+{
+ baseentity_index_from_python()
+ {
+ boost::python::converter::registry::insert(
+ &convert,
+ boost::python::type_id()
+ );
+
+ boost::python::converter::registry::insert(
+ &convert,
+ boost::python::type_id()
+ );
+ }
+
+ static void* convert(PyObject* obj)
+ {
+ extract extractor(obj);
+ if (!extractor.check()) {
+ return NULL;
+ }
+
+ return (void *)ExcBaseEntityFromIndex(extractor());
+ }
+};
+
// void*
struct void_ptr_to_python
{
@@ -364,6 +390,7 @@ void InitConverters()
baseentity_to_python();
baseentity_from_python();
+ baseentity_index_from_python();
void_ptr_to_python();
void_ptr_from_python();