X Tutup
/** * ============================================================================= * Source Python * Copyright (C) 2012-2015 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 // -------------------------------------------------------- #include "sp_python.h" #include "sp_main.h" #include "interface.h" #include "filesystem.h" #include "eiface.h" #include "igameevents.h" #include "convar.h" #include "Color.h" #include "vstdlib/random.h" #include "engine/IEngineTrace.h" #include "tier2/tier2.h" #include "game/server/iplayerinfo.h" #include "shake.h" // Linux compile fails without this. #include "game/shared/IEffects.h" #include "utilities/wrap_macros.h" #include "engine/IEngineSound.h" #include "engine/IEngineTrace.h" #include "public/toolframework/itoolentity.h" #include "dyncall.h" #include "networkstringtabledefs.h" #include "edict.h" #include "convar.h" #include "utilities/call_python.h" #include "vphysics_interface.h" #include "datacache/imdlcache.h" #include "ivoiceserver.h" #include "tier0/threadtools.h" #include "manager.h" #include "modules/listeners/listeners_manager.h" #include "utilities/conversions.h" #include "modules/entities/entities.h" #include "modules/entities/entities_entity.h" #include "modules/entities/entities_collisions.h" #include "modules/entities/entities_transmit.h" #include "modules/core/core.h" #ifdef _WIN32 #include "Windows.h" #endif #include "sp_hooks.h" //----------------------------------------------------------------------------- // Disable warnings. //----------------------------------------------------------------------------- #if defined(_WIN32) # pragma warning( disable : 4005 ) #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Interfaces from the engine //----------------------------------------------------------------------------- IVEngineServer* engine = NULL; // helper functions (messaging clients, loading content, making entities, running commands, etc) IGameEventManager2* gameeventmanager = NULL; // game events interface IPlayerInfoManager* playerinfomanager = NULL; // game dll interface to interact with players IBotManager* botmanager = NULL; // game dll interface to interact with bots IServerPluginHelpers* helpers = NULL; // special 3rd party plugin helpers from the engine IUniformRandomStream* randomStr = NULL; IEngineTrace* enginetrace = NULL; IEngineSound* enginesound = NULL; CGlobalVars* gpGlobals = NULL; IFileSystem* filesystem = NULL; IServerGameDLL* servergamedll = NULL; IServerGameClients* servergameclients = NULL; IServerTools* servertools = NULL; IServerGameEnts* gameents = NULL; // Interface to get at server entities IPhysics* physics = NULL; IPhysicsCollision* physcollision = NULL; IPhysicsSurfaceProps* physprops = NULL; IMDLCache* modelcache = NULL; IVoiceServer* voiceserver = NULL; INetworkStringTableContainer* networkstringtable = NULL; //----------------------------------------------------------------------------- // External globals //----------------------------------------------------------------------------- extern ICvar* g_pCVar; //----------------------------------------------------------------------------- // Extern functions //----------------------------------------------------------------------------- extern void InitCommands(); extern void ClearAllCommands(); extern PLUGIN_RESULT DispatchClientCommand(edict_t *pEntity, const CCommand &command); extern CConVarChangedListenerManager* GetOnConVarChangedListenerManager(); extern CServerOutputListenerManager* GetOnServerOutputListenerManager(); //----------------------------------------------------------------------------- // The plugin is a static singleton that is exported as an interface //----------------------------------------------------------------------------- CSourcePython g_SourcePythonPlugin; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CSourcePython, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_SourcePythonPlugin ); //----------------------------------------------------------------------------- // Interface helper class. //----------------------------------------------------------------------------- struct InterfaceHelper_t { const char* szInterface; void** pGlobal; }; //----------------------------------------------------------------------------- // We need all of these interfaces in order to function. //----------------------------------------------------------------------------- InterfaceHelper_t gEngineInterfaces[] = { {INTERFACEVERSION_VENGINESERVER, (void **)&engine}, {INTERFACEVERSION_GAMEEVENTSMANAGER2, (void **)&gameeventmanager}, {INTERFACEVERSION_ISERVERPLUGINHELPERS, (void **)&helpers}, {INTERFACEVERSION_ENGINETRACE_SERVER, (void **)&enginetrace}, {IENGINESOUND_SERVER_INTERFACE_VERSION, (void **)&enginesound}, {VENGINE_SERVER_RANDOM_INTERFACE_VERSION, (void **)&randomStr}, {FILESYSTEM_INTERFACE_VERSION, (void **)&filesystem}, {INTERFACENAME_NETWORKSTRINGTABLESERVER, (void **)&networkstringtable}, {VPHYSICS_INTERFACE_VERSION, (void **)&physics}, {VPHYSICS_COLLISION_INTERFACE_VERSION, (void **)&physcollision}, {VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, (void **)&physprops}, {MDLCACHE_INTERFACE_VERSION, (void **)&modelcache}, {INTERFACEVERSION_VOICESERVER, (void **)&voiceserver}, {NULL, NULL} }; InterfaceHelper_t gGameInterfaces[] = { {INTERFACEVERSION_PLAYERINFOMANAGER, (void **)&playerinfomanager}, {INTERFACEVERSION_PLAYERBOTMANAGER, (void **)&botmanager}, {INTERFACEVERSION_SERVERGAMEDLL, (void **)&servergamedll}, {INTERFACEVERSION_SERVERGAMECLIENTS, (void **)&servergameclients}, {VSERVERTOOLS_INTERFACE_VERSION, (void **)&servertools}, {INTERFACEVERSION_SERVERGAMEENTS, (void **)&gameents}, {NULL, NULL} }; //----------------------------------------------------------------------------- // Get all engine interfaces. //----------------------------------------------------------------------------- bool GetInterfaces( InterfaceHelper_t* pInterfaceList, CreateInterfaceFn factory ) { InterfaceHelper_t* pInterface = pInterfaceList; while( pInterface->szInterface ) { void** pGlobal = pInterface->pGlobal; // Get the interface from the given factory. *pGlobal = factory(pInterface->szInterface, NULL); // If it's not valid, bail out. if( *pGlobal ) { DevMsg(1, MSG_PREFIX "Interface %s at %x\n", pInterface->szInterface, *pGlobal); } else { Warning(MSG_PREFIX "Could not retrieve interface %s\n", pInterface->szInterface); return false; } pInterface++; } return true; } //----------------------------------------------------------------------------- // Purpose: constructor/destructor //----------------------------------------------------------------------------- CSourcePython::CSourcePython() { m_iClientCommandIndex = 0; m_pOldMDLCacheNotifier = NULL; } CSourcePython::~CSourcePython() { } //----------------------------------------------------------------------------- // Purpose: called when the plugin is loaded, load the interface we need from the engine //----------------------------------------------------------------------------- bool CSourcePython::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ) { #if defined(ENGINE_CSGO) || defined(ENGINE_BLADE) DevMsg(1, MSG_PREFIX "Connecting interfaces...\n"); ConnectInterfaces(&interfaceFactory, 1); #else DevMsg(1, MSG_PREFIX "Connecting tier1 libraries...\n"); ConnectTier1Libraries( &interfaceFactory, 1 ); //DevMsg(1, MSG_PREFIX "Connecting tier2 libraries...\n"); //ConnectTier2Libraries( &interfaceFactory, 2 ); #endif // Get all engine interfaces. DevMsg(1, MSG_PREFIX "Retrieving engine interfaces...\n"); if( !GetInterfaces(gEngineInterfaces, interfaceFactory) ) { return false; } // Get all game interfaces. DevMsg(1, MSG_PREFIX "Retrieving game interfaces...\n"); if( !GetInterfaces(gGameInterfaces, gameServerFactory) ) { return false; } DevMsg(1, MSG_PREFIX "Retrieving global variables...\n"); gpGlobals = playerinfomanager->GetGlobalVars(); if (!gpGlobals) { Msg(MSG_PREFIX "Could retrieve global variables.\n"); return false; } DevMsg(1, MSG_PREFIX "Initializing mathlib...\n"); MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); DevMsg(1, MSG_PREFIX "Initializing server and say commands...\n"); InitCommands(); // Initialize python DevMsg(1, MSG_PREFIX "Initializing python...\n"); if( !g_PythonManager.Initialize() ) { Msg(MSG_PREFIX "Could not initialize python.\n"); return false; } // TODO: Don't hardcode the 64 bytes offset #ifdef ENGINE_LEFT4DEAD2 #define CACHE_NOTIFY_OFFSET 68 #else #define CACHE_NOTIFY_OFFSET 64 #endif DevMsg(1, MSG_PREFIX "Retrieving the current cache notifier...\n"); m_pOldMDLCacheNotifier = *(IMDLCacheNotify **)(((unsigned long) modelcache) + CACHE_NOTIFY_OFFSET); DevMsg(1, MSG_PREFIX "Setting the new cache notifier...\n"); modelcache->SetCacheNotify(this); g_EntityHooks.push_back(new PlayerHook( "run_command", (HookHandlerFn*) (void*) &PrePlayerRunCommand, HOOKTYPE_PRE)); g_EntityHooks.push_back(new PlayerHook( "run_command", (HookHandlerFn*) (void*) &PrePlayerRunCommand, HOOKTYPE_POST)); InitHooks(); Msg(MSG_PREFIX "Loaded successfully.\n"); return true; } //----------------------------------------------------------------------------- // Purpose: called when the plugin is unloaded (turned off) //----------------------------------------------------------------------------- void CSourcePython::Unload( void ) { Msg(MSG_PREFIX "Unloading...\n"); DevMsg(1, MSG_PREFIX "Resetting cache notifier...\n"); modelcache->SetCacheNotify(m_pOldMDLCacheNotifier); DevMsg(1, MSG_PREFIX "Shutting down python...\n"); g_PythonManager.Shutdown(); DevMsg(1, MSG_PREFIX "Clearing convar changed listener...\n"); GetOnConVarChangedListenerManager()->clear(); DevMsg(1, MSG_PREFIX "Clearing server output listeners...\n"); GetOnServerOutputListenerManager()->clear(); DevMsg(1, MSG_PREFIX "Unhooking all functions...\n"); GetHookManager()->UnhookAllFunctions(); DevMsg(1, MSG_PREFIX "Clearing all commands...\n"); ClearAllCommands(); DevMsg(1, MSG_PREFIX "Unregistering ConVar...\n"); ConVar_Unregister( ); // New in CSGO... #if defined(ENGINE_CSGO) || defined(ENGINE_BLADE) DevMsg(1, MSG_PREFIX "Disconnecting interfaces...\n"); DisconnectInterfaces(); #else //DevMsg(1, MSG_PREFIX "Disconnecting tier2 libraries...\n"); //DisconnectTier2Libraries( ); DevMsg(1, MSG_PREFIX "Disconnecting tier1 libraries...\n"); DisconnectTier1Libraries( ); #endif Msg(MSG_PREFIX "Unloaded successfully.\n"); #ifdef _WIN32 // This "fixes" a crash after SP has been unloaded. Sleep(1000); #endif } //----------------------------------------------------------------------------- // Purpose: called when the plugin is paused (i.e should stop running but isn't unloaded) //----------------------------------------------------------------------------- void CSourcePython::Pause( void ) { } //----------------------------------------------------------------------------- // Purpose: called when the plugin is unpaused (i.e should start executing again) //----------------------------------------------------------------------------- void CSourcePython::UnPause( void ) { } //----------------------------------------------------------------------------- // Purpose: the name of this plugin, returned in "plugin_print" command //----------------------------------------------------------------------------- const char *CSourcePython::GetPluginDescription( void ) { return "Source.Python, (C) 2012-2025, Source.Python Team."; } //----------------------------------------------------------------------------- // Purpose: called on level start //----------------------------------------------------------------------------- void CSourcePython::LevelInit( char const *pMapName ) { CALL_LISTENERS(OnLevelInit, pMapName); } //----------------------------------------------------------------------------- // Purpose: called on level start, when the server is ready to accept client connections // edictCount is the number of entities in the level, clientMax is the max client count //----------------------------------------------------------------------------- void CSourcePython::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) { list edicts; for(int i=0; i < edictCount; i++) edicts.append(pEdictList[i]); CALL_LISTENERS(OnServerActivate, edicts, edictCount, clientMax); } //----------------------------------------------------------------------------- // Purpose: called once per server frame, do recurring work here (like checking for timeouts) //----------------------------------------------------------------------------- void CSourcePython::GameFrame( bool simulating ) { CALL_LISTENERS(OnTick); } //----------------------------------------------------------------------------- // Purpose: called on level end (as the server is shutting down or going to a new map) //----------------------------------------------------------------------------- void CSourcePython::LevelShutdown( void ) // !!!!this can get called multiple times per map change { CALL_LISTENERS(OnLevelShutdown); // Cleanup active collision rules. static CCollisionManager *pCollisionManager = GetCollisionManager(); pCollisionManager->OnLevelShutdown(); // Cleanup active transmission rules. static CTransmitManager *pTransmitManager = GetTransmitManager(); pTransmitManager->OnLevelShutdown(); } //----------------------------------------------------------------------------- // Purpose: called when a client spawns into a server (i.e as they begin to play) //----------------------------------------------------------------------------- void CSourcePython::ClientActive( edict_t *pEntity ) { unsigned int iEntityIndex; if (!IndexFromEdict(pEntity, iEntityIndex)) return; CALL_LISTENERS(OnClientActive, iEntityIndex); } //----------------------------------------------------------------------------- // Purpose: called when a client leaves a server (or is timed out) //----------------------------------------------------------------------------- void CSourcePython::ClientDisconnect( edict_t *pEntity ) { unsigned int iEntityIndex; if (!IndexFromEdict(pEntity, iEntityIndex)) return; CALL_LISTENERS(OnClientDisconnect, iEntityIndex); } //----------------------------------------------------------------------------- // Purpose: called on //----------------------------------------------------------------------------- void CSourcePython::ClientPutInServer( edict_t *pEntity, char const *playername ) { CALL_LISTENERS(OnClientPutInServer, ptr(pEntity), playername); } //----------------------------------------------------------------------------- // Purpose: called on level start //----------------------------------------------------------------------------- void CSourcePython::SetCommandClient( int index ) { m_iClientCommandIndex = index; } //----------------------------------------------------------------------------- // Purpose: called on level start //----------------------------------------------------------------------------- void CSourcePython::ClientSettingsChanged( edict_t *pEdict ) { unsigned int iEntityIndex; if (!IndexFromEdict(pEdict, iEntityIndex)) return; CALL_LISTENERS(OnClientSettingsChanged, iEntityIndex); } //----------------------------------------------------------------------------- // Purpose: called when a client joins a server //----------------------------------------------------------------------------- PLUGIN_RESULT CSourcePython::ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) { CPointer allowConnect = CPointer((unsigned long) bAllowConnect); CPointer rejectMessage = CPointer((unsigned long) reject); CALL_LISTENERS(OnClientConnect, allowConnect, ptr(pEntity), pszName, pszAddress, rejectMessage, maxrejectlen); return PLUGIN_OVERRIDE; } //----------------------------------------------------------------------------- // Purpose: called when a client is authenticated //----------------------------------------------------------------------------- PLUGIN_RESULT CSourcePython::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) { CALL_LISTENERS(OnNetworkidValidated, pszUserName, pszNetworkID); return PLUGIN_CONTINUE; } //----------------------------------------------------------------------------- // Purpose: called when a cvar value query is finished //----------------------------------------------------------------------------- void CSourcePython::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) { PythonLog(4, "Cvar query (cookie: %d, status: %d) - name: %s, value: %s", iCookie, eStatus, pCvarName, pCvarValue ); unsigned int iEntityIndex; if (!IndexFromEdict(pPlayerEntity, iEntityIndex)) return; CALL_LISTENERS(OnQueryCvarValueFinished, (int) iCookie, iEntityIndex, eStatus, pCvarName, pCvarValue); } //----------------------------------------------------------------------------- // Orangebox. //----------------------------------------------------------------------------- PLUGIN_RESULT CSourcePython::ClientCommand( edict_t *pEntity, const CCommand &args ) { return DispatchClientCommand(pEntity, args); } //----------------------------------------------------------------------------- // Alien Swarm. //----------------------------------------------------------------------------- #if defined(ENGINE_CSGO) || defined(ENGINE_BLADE) void CSourcePython::ClientFullyConnect( edict_t *pEntity ) { unsigned int iEntityIndex; if (!IndexFromEdict(pEntity, iEntityIndex)) return; CALL_LISTENERS(OnClientFullyConnect, iEntityIndex); } #endif #if defined(ENGINE_CSGO) || defined(ENGINE_BMS) || defined(ENGINE_BLADE) || defined(ENGINE_ORANGEBOX) void CSourcePython::OnEdictAllocated( edict_t *edict ) { unsigned int iEntityIndex; if (!IndexFromEdict(edict, iEntityIndex)) return; CALL_LISTENERS(OnEdictAllocated, iEntityIndex); } void CSourcePython::OnEdictFreed( const edict_t *edict ) { CALL_LISTENERS(OnEdictFreed, ptr(edict)); } #endif #ifdef ENGINE_BMS void CSourcePython::OnEntityPreSpawned( CBaseEntity *pEntity ) { CALL_LISTENERS(OnEntityPreSpawned, ptr((CBaseEntityWrapper*) pEntity)); GET_LISTENER_MANAGER(OnNetworkedEntityPreSpawned, on_networked_entity_pre_spawned_manager); if (!on_networked_entity_pre_spawned_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_pre_spawned_manager, Entity(uiIndex)); } #endif void CSourcePython::OnEntityCreated( CBaseEntity *pEntity ) { edict_t *pEdict; if (EdictFromBaseEntity(pEntity, pEdict)) { IServerUnknown* pServerUnknown = pEdict->GetUnknown(); if (pServerUnknown) pEdict->m_pNetworkable = pServerUnknown->GetNetworkable(); } InitHooks(pEntity); CALL_LISTENERS(OnEntityCreated, ptr((CBaseEntityWrapper*) pEntity)); unsigned int uiIndex; if (!IndexFromBaseEntity(pEntity, uiIndex)) return; static object Entity = import("entities").attr("entity").attr("Entity"); 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 ) { CALL_LISTENERS(OnEntitySpawned, ptr((CBaseEntityWrapper*) pEntity)); GET_LISTENER_MANAGER(OnNetworkedEntitySpawned, on_networked_entity_spawned_manager); if (!on_networked_entity_spawned_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_spawned_manager, Entity(uiIndex)); } void CSourcePython::OnEntityDeleted( CBaseEntity *pEntity ) { // #455 - Temporarily rebind ourself to our edict if needed. bool bRebound = false; edict_t *pEdict; if (EdictFromBaseEntity(pEntity, pEdict) && !pEdict->GetUnknown()) { reinterpret_cast(pEdict)->SetUnknown((IServerUnknown *)pEntity); bRebound = true; } CALL_LISTENERS(OnEntityDeleted, ptr((CBaseEntityWrapper*) pEntity)); unsigned int uiIndex; if (!IndexFromBaseEntity(pEntity, uiIndex)) return; GET_LISTENER_MANAGER(OnNetworkedEntityDeleted, on_networked_entity_deleted_manager); if (on_networked_entity_deleted_manager->GetCount()) { static object Entity = import("entities").attr("entity").attr("Entity"); CALL_LISTENERS_WITH_MNGR(on_networked_entity_deleted_manager, Entity(uiIndex)); } // 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 rules. static CCollisionManager *pCollisionManager = GetCollisionManager(); pCollisionManager->OnNetworkedEntityDeleted((CBaseEntityWrapper *)pEntity); // Cleanup active transmission rules. static CTransmitManager *pTransmitManager = GetTransmitManager(); pTransmitManager->OnNetworkedEntityDeleted((CBaseEntityWrapper *)pEntity, uiIndex); if (bRebound) { reinterpret_cast(pEdict)->SetUnknown(NULL); } } void CSourcePython::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) { if (m_pOldMDLCacheNotifier) m_pOldMDLCacheNotifier->OnDataLoaded(type, handle); CALL_LISTENERS(OnDataLoaded, type, handle); } void CSourcePython::OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ) { if (m_pOldMDLCacheNotifier) m_pOldMDLCacheNotifier->OnDataUnloaded(type, handle); CALL_LISTENERS(OnDataUnloaded, type, handle); } #if defined(ENGINE_CSGO) || defined(ENGINE_BLADE) void CSourcePython::OnCombinerPreCache(MDLCacheDataType_t type, MDLHandle_t handle ) { if (m_pOldMDLCacheNotifier) m_pOldMDLCacheNotifier->OnCombinerPreCache(type, handle); CALL_LISTENERS(OnCombinerPreCache, type, handle); } bool CSourcePython::ShouldSupressLoadWarning(MDLHandle_t handle) { bool result = false; if (m_pOldMDLCacheNotifier) result = m_pOldMDLCacheNotifier->ShouldSupressLoadWarning(handle); return result; } bool CSourcePython::BNetworkCryptKeyCheckRequired( uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, bool bClientWantsToUseCryptKey ) { return false; } bool CSourcePython::BNetworkCryptKeyValidate( uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, int nEncryptionKeyIndexFromClient, int numEncryptedBytesFromClient, byte *pbEncryptedBufferFromClient, byte *pbPlainTextKeyForNetchan ) { return false; } #endif
X Tutup