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();