//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Defines a connection (output-to-input) between two entities. // // The behavior in-game is as follows: // // When the given output in the source entity is triggered, the given // input in the target entity is called after a specified delay, and // the parameter override (if any) is passed to the input handler. If // there is no parameter override, the default parameter is passed. // // This behavior will occur a specified number of times before the // connection between the two entities is removed. // //=============================================================================// #include "stdafx.h" #include "EntityConnection.h" #include "MapEntity.h" #include "hammer.h" #include "MapDoc.h" #include "MapWorld.h" // memdbgon must be the last include file in a .cpp file!!! #include //----------------------------------------------------------------------------- // Purpose: Constructor. //----------------------------------------------------------------------------- CEntityConnection::CEntityConnection(void) { memset(m_szSourceEntity, 0, sizeof(m_szSourceEntity)); memset(m_szTargetEntity, 0, sizeof(m_szTargetEntity)); memset(m_szOutput, 0, sizeof(m_szOutput)); memset(m_szInput, 0, sizeof(m_szInput)); memset(m_szParam, 0, sizeof(m_szParam)); m_pSourceEntityList = new CMapEntityList; m_pTargetEntityList = new CMapEntityList; m_fDelay = 0; m_nTimesToFire = EVENT_FIRE_ALWAYS; } //----------------------------------------------------------------------------- // Purpose: Copy Constructor. //----------------------------------------------------------------------------- CEntityConnection::CEntityConnection( const CEntityConnection &Other ) { m_pSourceEntityList = new CMapEntityList; m_pTargetEntityList = new CMapEntityList; *this = Other; // Invoke the Operator= to complete the construction job } //----------------------------------------------------------------------------- // Purpose: Destructor. //----------------------------------------------------------------------------- CEntityConnection::~CEntityConnection() { if ( m_pSourceEntityList ) { m_pSourceEntityList->RemoveAll(); delete m_pSourceEntityList; m_pSourceEntityList = NULL; } if ( m_pTargetEntityList ) { m_pTargetEntityList->RemoveAll(); delete m_pTargetEntityList; m_pTargetEntityList = NULL; } } //----------------------------------------------------------------------------- // Purpose: Operator= overload. Makes 'this' identical to 'Other'. //----------------------------------------------------------------------------- CEntityConnection &CEntityConnection::operator =(const CEntityConnection &Other) { strcpy(m_szSourceEntity, Other.m_szSourceEntity); strcpy(m_szTargetEntity, Other.m_szTargetEntity); strcpy(m_szOutput, Other.m_szOutput); strcpy(m_szInput, Other.m_szInput); strcpy(m_szParam, Other.m_szParam); m_fDelay = Other.m_fDelay; m_nTimesToFire = Other.m_nTimesToFire; // Invoke EntityList operator= to make copies. *m_pSourceEntityList = *Other.m_pSourceEntityList; *m_pTargetEntityList = *Other.m_pTargetEntityList; return(*this); } //----------------------------------------------------------------------------- // Purpose: Sets a new Input Name and sets links to any matching entities //----------------------------------------------------------------------------- void CEntityConnection::SetSourceName(const char *pszName) { // Save the name of the entity(ies) lstrcpyn(m_szSourceEntity, pszName ? pszName : "<>", sizeof(m_szSourceEntity)); // Update the source entity list // LinkSourceEntities(); // Changing the entity connection source name shouldnt change the source entity linkage, right? } //----------------------------------------------------------------------------- // Purpose: Sets a new Output Name and sets links to any matching entities //----------------------------------------------------------------------------- void CEntityConnection::SetTargetName(const char *pszName) { // Save the name of the entity(ies) lstrcpyn(m_szTargetEntity, pszName ? pszName : "<>", sizeof(m_szTargetEntity)); // Update the target entity list LinkTargetEntities(); } //----------------------------------------------------------------------------- // Purpose: Links to any matching Source entities //----------------------------------------------------------------------------- void CEntityConnection::LinkSourceEntities() { // Empty out the existing entity list m_pSourceEntityList->RemoveAll(); // Get a list of all the entities in the world CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if (pDoc) { CMapWorld *pWorld = pDoc->GetMapWorld(); if (pWorld) { CMapEntityList matches; pWorld->FindEntitiesByName( matches, m_szSourceEntity, false ); for ( int i = 0; i < matches.Count(); i++ ) { CMapEntity *pEntity = matches.Element( i ); m_pSourceEntityList->AddToTail( pEntity ); //pEntity->Connection_Add( this ); // This should already be true on creation, investigate need for this func } } } } //----------------------------------------------------------------------------- // Purpose: Links to any matching Target entities //----------------------------------------------------------------------------- void CEntityConnection::LinkTargetEntities() { // Unlink us from the downstream entities. FOR_EACH_OBJ( *m_pTargetEntityList, pos ) { CMapEntity *pEntity = m_pTargetEntityList->Element( pos ); // If you hit this assert it means that an entity was deleted but not removed // from this entity's list of targets. ASSERT( pEntity != NULL ); if ( pEntity ) { pEntity->Upstream_Remove( this ); } } // Empty out the existing entity list m_pTargetEntityList->RemoveAll(); // Get a list of all the entities in the world CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if (pDoc) { CMapWorld *pWorld = pDoc->GetMapWorld(); if (pWorld) { CMapEntityList matches; pWorld->FindEntitiesByName( matches, m_szTargetEntity, false ); for ( int i = 0; i < matches.Count(); i++ ) { CMapEntity *pEntity = matches.Element( i ); m_pTargetEntityList->AddToTail( pEntity ); // Special -- Add this connection to the target entity connection list pEntity->Upstream_Add( this ); } } } } //------------------------------------------------------------------------------ // Purpose: Tells if any of the target entities are visible. //------------------------------------------------------------------------------ bool CEntityConnection::AreAnyTargetEntitiesVisible() { CMapEntityList *pList = GetTargetEntityList(); for ( int iTarget=0; iTarget < pList->Count(); iTarget++ ) { CMapEntity *pEntity = pList->Element( iTarget ); if ( !pEntity ) continue; if ( pEntity->IsVisible() ) return true; } return false; } //------------------------------------------------------------------------------ // Purpose: Returns true if output string is valid for all this entity //------------------------------------------------------------------------------ bool CEntityConnection::ValidateOutput(const CMapEntity *pEntity, const char* pszOutput) { if (!pEntity) { return false; } GDclass* pClass = pEntity->GetClass(); if (pClass != NULL) { if (pClass->FindOutput(pszOutput) == NULL) { return false; } } return true; } //------------------------------------------------------------------------------ // Purpose : Returns true if output string is valid for all the entities in // the entity list // Input : // Output : //------------------------------------------------------------------------------ bool CEntityConnection::ValidateOutput(const CMapEntityList *pEntityList, const char* pszOutput) { if (!pEntityList) { return false; } FOR_EACH_OBJ( *pEntityList, pos ) { const CMapEntity* pEntity = pEntityList->Element(pos).GetObject(); if ( !pEntity || !ValidateOutput(pEntity,pszOutput) ) { return false; } } return true; } //------------------------------------------------------------------------------ // Purpose: Returns true if the given entity list contains an entity of the // given target name //------------------------------------------------------------------------------ bool CEntityConnection::ValidateTarget( const CMapEntityList *pEntityList, bool bVisibilityCheck, const char *pszTarget) { if (!pEntityList || !pszTarget) return false; // These procedural names are always assumed to exist. if (!stricmp(pszTarget, "!activator") || !stricmp(pszTarget, "!caller") || !stricmp(pszTarget, "!player") || !stricmp(pszTarget, "!self")) return true; FOR_EACH_OBJ( *pEntityList, pos ) { const CMapEntity *pEntity = pEntityList->Element(pos).GetObject(); if ( !pEntity || ( bVisibilityCheck && !pEntity->IsVisible() ) ) continue; if (pEntity->NameMatches(pszTarget)) return true; } return false; } //------------------------------------------------------------------------------ // Purpose: Returns true if all entities with the given target name // have an input of the given input name //------------------------------------------------------------------------------ bool CEntityConnection::ValidateInput( const char *pszTarget, const char *pszInput, bool bVisiblesOnly, CMapDoc *pDoc ) { // Allow any input into !activator and !player. // dvs: TODO: pass in the entity to resolve !self and check input list if (!stricmp(pszTarget, "!activator") || !stricmp(pszTarget, "!caller") || !stricmp(pszTarget, "!player") || !stricmp(pszTarget, "!self")) { return true; } if ( pDoc == NULL ) { pDoc = CMapDoc::GetActiveMapDoc(); } CMapEntityList EntityList; pDoc->FindEntitiesByName(EntityList, pszTarget, bVisiblesOnly); if (EntityList.Count() == 0) { return false; } if (!MapEntityList_HasInput( &EntityList, pszInput)) { return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Finds any output connections from this entity that are bad. // "Bad" is defined as: // // 1) An output that this entity doesn't actually have. // 2) Connecting to a nonexistent entity. // 3) Connecting to a nonexistent input in an entity that exists. // // Input : pEntity - The entity to check for bad connections. //----------------------------------------------------------------------------- void CEntityConnection::FindBadConnections( CMapEntity *pEntity, bool bVisibilityCheck, CUtlVector &BadConnectionList, bool bIgnoreHiddenTargets, bool CheckAllDocuments ) { BadConnectionList.RemoveAll(); if ((!pEntity) || (pEntity->Connections_GetCount() == 0)) { return; } // Get a list of all the entities in the world const CMapEntityList *pAllWorldEntities = NULL; CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if (pDoc) { CMapWorld *pWorld = pDoc->GetMapWorld(); if (pWorld) { pAllWorldEntities = pWorld->EntityList_GetList(); } } // For each connection int nConnCount = pEntity->Connections_GetCount(); for (int i = 0; i < nConnCount; i++) { CEntityConnection *pConnection = pEntity->Connections_Get(i); if (pConnection != NULL) { if ( bIgnoreHiddenTargets ) { if ( pConnection->GetTargetEntityList()->Count() > 0 && !pConnection->AreAnyTargetEntitiesVisible() ) continue; } // Check validity of output for this entity if (!CEntityConnection::ValidateOutput(pEntity, pConnection->GetOutputName())) { BadConnectionList.AddToTail(pConnection); } else { bool bBadConnection = true; if ( CheckAllDocuments == false ) { // Check validity of target entity (is it in the map?) if ( CEntityConnection::ValidateTarget(pAllWorldEntities, bVisibilityCheck, pConnection->GetTargetName() ) == true ) { if ( CEntityConnection::ValidateInput(pConnection->GetTargetName(), pConnection->GetInputName(), true ) == true ) { bBadConnection = false; } } } else { POSITION pos = APP()->pMapDocTemplate->GetFirstDocPosition(); while( pos != NULL ) { CDocument *pDoc = APP()->pMapDocTemplate->GetNextDoc( pos ); CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc ); if ( pMapDoc ) { // Check validity of target entity (is it in the map?) if ( CEntityConnection::ValidateTarget( pMapDoc->GetMapWorld()->EntityList_GetList(), bVisibilityCheck, pConnection->GetTargetName() ) == true ) { // Check validity of input if ( CEntityConnection::ValidateInput( pConnection->GetTargetName(), pConnection->GetInputName(), true, pMapDoc ) == true ) { bBadConnection = false; break; } } } } } if ( bBadConnection == true ) { BadConnectionList.AddToTail(pConnection); } } } } } //------------------------------------------------------------------------------ // Purpose: Check if all the output connections in the given entity are valid. // Output : OUTPUTS_NONE if entity has no outputs // OUTPUTS_GOOD if all entity outputs are good // OUTPUTS_BAD if any entity output is bad //------------------------------------------------------------------------------ int CEntityConnection::ValidateOutputConnections( CMapEntity *pEntity, bool bVisibilityCheck, bool bIgnoreHiddenTargets, bool CheckAllDocuments ) { if (!pEntity) { return CONNECTION_NONE; } if (pEntity->Connections_GetCount() == 0) { return CONNECTION_NONE; } CUtlVector BadConnectionList; FindBadConnections( pEntity, bVisibilityCheck, BadConnectionList, bIgnoreHiddenTargets, CheckAllDocuments ); if (BadConnectionList.Count() > 0) { return CONNECTION_BAD; } return CONNECTION_GOOD; } //----------------------------------------------------------------------------- // Purpose: Fixes any output connections from this entity that are bad. // Input : pEntity - The entity to fix. //----------------------------------------------------------------------------- void CEntityConnection::FixBadConnections(CMapEntity *pEntity, bool bVisibilityCheck ) { CUtlVector BadConnectionList; FindBadConnections(pEntity, bVisibilityCheck, BadConnectionList); // Remove the bad connections. int nBadConnCount = BadConnectionList.Count(); for (int i = 0; i < nBadConnCount; i++) { CEntityConnection *pConnection = BadConnectionList.Element(i); pEntity->Connections_Remove( pConnection ); // // Remove the connection from the upstream list of all entities it targets. // CMapEntityList *pTargetList = pConnection->GetTargetEntityList(); if ( pTargetList ) { FOR_EACH_OBJ( *pTargetList, pos ) { CMapEntity *pEntity = pTargetList->Element( pos ); // If you hit this assert it means that an entity was deleted but not removed // from this entity's list of targets. ASSERT( pEntity != NULL ); if ( pEntity ) { pEntity->Upstream_Remove( pConnection ); } } } delete pConnection; } } //------------------------------------------------------------------------------ // Purpose: Check if all the output connections in the given entity are valid. // Output : INPUTS_NONE, // if entity list has no inputs // INPUTS_GOOD, // if all entity inputs are good // INPUTS_BAD, // if any entity input is bad //------------------------------------------------------------------------------ int CEntityConnection::ValidateInputConnections(CMapEntity *pEntity, bool bVisibilityCheck) { if (!pEntity) { return CONNECTION_NONE; } // No inputs if entity doesn't have a target name const char *pszTargetName = pEntity->GetKeyValue("targetname"); if (!pszTargetName) { return CONNECTION_NONE; } GDclass *pClass = pEntity->GetClass(); if (!pClass) { return CONNECTION_NONE; } // Get a list of all the entities in the world const CMapEntityList *pAllWorldEntities = NULL; CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if (pDoc) { CMapWorld *pWorld = pDoc->GetMapWorld(); if (pWorld) { pAllWorldEntities = pWorld->EntityList_GetList(); } } // Look at outputs from each entity in the world bool bHaveConnection = false; FOR_EACH_OBJ( *pAllWorldEntities, pos ) { const CMapEntity *pTestEntity = pAllWorldEntities->Element(pos).GetObject(); if (pTestEntity == NULL) continue; if ( bVisibilityCheck && !pTestEntity->IsVisible() ) continue; int nConnCount = pTestEntity->Connections_GetCount(); for (int i = 0; i < nConnCount; i++) { // If the connection targets me CEntityConnection *pConnection = pTestEntity->Connections_Get(i); if ( pConnection && pEntity->NameMatches( pConnection->GetTargetName() ) ) { // Validate output if (!ValidateOutput(pTestEntity, pConnection->GetOutputName())) { return CONNECTION_BAD; } // Validate input if (pClass->FindInput(pConnection->GetInputName()) == NULL) { return CONNECTION_BAD; } // FIXME -- Validate the upstream connections the target entities. bHaveConnection = true; } } } if (bHaveConnection) { return CONNECTION_GOOD; } return CONNECTION_NONE; } //----------------------------------------------------------------------------- // Purpose: Compares by delays. Used as a secondary sort by all other columns. //----------------------------------------------------------------------------- int CALLBACK CEntityConnection::CompareDelaysSecondary(CEntityConnection *pConn1, CEntityConnection *pConn2, SortDirection_t eDirection) { float fDelay1; float fDelay2; if (eDirection == Sort_Ascending) { fDelay1 = pConn1->GetDelay(); fDelay2 = pConn2->GetDelay(); } else { fDelay1 = pConn2->GetDelay(); fDelay2 = pConn1->GetDelay(); } if (fDelay1 < fDelay2) { return(-1); } else if (fDelay1 > fDelay2) { return(1); } return(0); } //----------------------------------------------------------------------------- // Purpose: Compares by delays, does a secondary compare by output name. //----------------------------------------------------------------------------- int CALLBACK CEntityConnection::CompareDelays(CEntityConnection *pConn1, CEntityConnection *pConn2, SortDirection_t eDirection) { int nReturn = CompareDelaysSecondary(pConn1, pConn2, eDirection); if (nReturn != 0) { return(nReturn); } // // Always do a secondary sort by output name. // return(CompareOutputNames(pConn1, pConn2, Sort_Ascending)); } //----------------------------------------------------------------------------- // Purpose: Compares by output name, does a secondary compare by delay. //----------------------------------------------------------------------------- int CALLBACK CEntityConnection::CompareOutputNames(CEntityConnection *pConn1, CEntityConnection *pConn2, SortDirection_t eDirection) { int nReturn = 0; if (eDirection == Sort_Ascending) { nReturn = stricmp(pConn1->GetOutputName(), pConn2->GetOutputName()); } else { nReturn = stricmp(pConn2->GetOutputName(), pConn1->GetOutputName()); } // // Always do a secondary sort by delay. // if (nReturn == 0) { nReturn = CompareDelaysSecondary(pConn1, pConn2, Sort_Ascending); } return(nReturn); } //----------------------------------------------------------------------------- // Purpose: Compares by input name, does a secondary compare by delay. //----------------------------------------------------------------------------- int CALLBACK CEntityConnection::CompareInputNames(CEntityConnection *pConn1, CEntityConnection *pConn2, SortDirection_t eDirection) { int nReturn = 0; if (eDirection == Sort_Ascending) { nReturn = stricmp(pConn1->GetInputName(), pConn2->GetInputName()); } else { nReturn = stricmp(pConn2->GetInputName(), pConn1->GetInputName()); } // // Always do a secondary sort by delay. // if (nReturn == 0) { nReturn = CompareDelaysSecondary(pConn1, pConn2, Sort_Ascending); } return(nReturn); } //----------------------------------------------------------------------------- // Purpose: Compares by source name, does a secondary compare by delay. //----------------------------------------------------------------------------- int CALLBACK CEntityConnection::CompareSourceNames(CEntityConnection *pConn1, CEntityConnection *pConn2, SortDirection_t eDirection) { int nReturn = 0; if (eDirection == Sort_Ascending) { nReturn = CompareEntityNames(pConn1->GetSourceName(), pConn2->GetSourceName()); } else { nReturn = CompareEntityNames(pConn2->GetSourceName(), pConn1->GetSourceName()); } // // Always do a secondary sort by delay. // if (nReturn == 0) { nReturn = CompareDelaysSecondary(pConn1, pConn2, Sort_Ascending); } return(nReturn); } //----------------------------------------------------------------------------- // Purpose: Compares by target name, does a secondary compare by delay. //----------------------------------------------------------------------------- int CALLBACK CEntityConnection::CompareTargetNames(CEntityConnection *pConn1, CEntityConnection *pConn2, SortDirection_t eDirection) { int nReturn = 0; if (eDirection == Sort_Ascending) { nReturn = CompareEntityNames(pConn1->GetTargetName(), pConn2->GetTargetName()); } else { nReturn = CompareEntityNames(pConn2->GetTargetName(), pConn1->GetTargetName()); } // // Always do a secondary sort by delay. // if (nReturn == 0) { nReturn = CompareDelaysSecondary(pConn1, pConn2, Sort_Ascending); } return(nReturn); }