|
|
//========= 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 <tier0/memdbgon.h>
//-----------------------------------------------------------------------------
// 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 : "<<null>>", 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 : "<<null>>", 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<CEntityConnection *> &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<CEntityConnection *> 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<CEntityConnection *> 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); }
|