//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Implements a class that encapsulates much of the functionality // of entities. CMapWorld and CMapEntity are both derived from this // class. // // CEditGameClass-derived objects have the following properties: // // Key/value pairs - A list of string pairs that hold data properties // of the object. Each property has a unique name. // // Connections - A list of outputs in this object that are connected to // inputs in another entity. // //=============================================================================// #include "stdafx.h" #include "ChunkFile.h" #include "fgdlib/GameData.h" #include "GameConfig.h" #include "EditGameClass.h" #include "MapEntity.h" #include "mathlib/Mathlib.h" // memdbgon must be the last include file in a .cpp file!!! #include // // An empty string returned by GetComments when we have no comments set. // char *CEditGameClass::g_pszEmpty = ""; //----------------------------------------------------------------------------- // Purpose: Constructor. Initializes data members. //----------------------------------------------------------------------------- CEditGameClass::CEditGameClass(void) { m_pClass = NULL; m_szClass[0] = '\0'; m_pszComments = NULL; } //----------------------------------------------------------------------------- // Purpose: Destructor. Frees memory. //----------------------------------------------------------------------------- CEditGameClass::~CEditGameClass(void) { delete m_pszComments; Connections_RemoveAll(); Upstream_RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pConnection - //----------------------------------------------------------------------------- void CEditGameClass::Connections_Add(CEntityConnection *pConnection) { #if defined(_DEBUG) && 0 LPCTSTR pszTargetName = GetKeyValue("targetname"); if ( pszTargetName && !strcmp(pszTargetName, "zapperpod7_rotator") ) { // Set breakpoint here for debugging this entity's visiblity int foo = 0; } #endif if ( m_Connections.Find(pConnection) == -1 ) m_Connections.AddToTail(pConnection); } //----------------------------------------------------------------------------- // Purpose: // Input : *pConnection - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CEditGameClass::Connections_Remove(CEntityConnection *pConnection) { int nIndex = m_Connections.Find(pConnection); if (nIndex != -1) { m_Connections.Remove(nIndex); return(true); } return(false); } //----------------------------------------------------------------------------- // NOTE: unlike Connections_Remove, this actually frees each connection!! //----------------------------------------------------------------------------- void CEditGameClass::Connections_RemoveAll() { // // Remove all our connections from their targets' upstream lists. // int nConnectionsCount = m_Connections.Count(); for (int nConnection = 0; nConnection < nConnectionsCount; nConnection++) { CEntityConnection *pConnection = m_Connections.Element( nConnection ); #if defined( ENTITY_MAINTAIN_UPSTREAM_LISTS ) 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 ); } } } #endif delete pConnection; } m_Connections.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEditGameClass::Connections_FixBad(bool bRelink) { int nConnectionsCount = m_Connections.Count(); for (int nConnections = 0; nConnections < nConnectionsCount; nConnections++) { CEntityConnection *pConnection = m_Connections.Element(nConnections); CMapEntityList *pTargetEntities = pConnection->GetTargetEntityList(); int nEntityCount = pTargetEntities->Count(); for ( int nEntities = 0; nEntities < nEntityCount; nEntities++ ) { CMapEntity *pEntity = pTargetEntities->Element(nEntities); // 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 ); } } if ( bRelink ) { pConnection->LinkTargetEntities(); } } } //----------------------------------------------------------------------------- // Purpose: // Input : *pConnection - //----------------------------------------------------------------------------- void CEditGameClass::Upstream_Add(CEntityConnection *pConnection) { #if defined(_DEBUG) && 0 LPCTSTR pszTargetName = GetKeyValue("targetname"); if ( pszTargetName && !strcmp(pszTargetName, "zapperpod7_rotator") ) { // Set breakpoint here for debugging this entity's visiblity int foo = 0; } #endif #if defined( ENTITY_MAINTAIN_UPSTREAM_LISTS ) if ( m_Upstream.Find(pConnection) == -1 ) m_Upstream.AddToTail(pConnection); #endif } //----------------------------------------------------------------------------- // Purpose: // Input : *pConnection - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CEditGameClass::Upstream_Remove(CEntityConnection *pConnection) { int nIndex = m_Upstream.Find(pConnection); if (nIndex != -1) { m_Upstream.Remove(nIndex); return(true); } return(false); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEditGameClass::Upstream_RemoveAll(void) { #if defined( ENTITY_MAINTAIN_UPSTREAM_LISTS ) // // Remove all our connections from their targets' upstream lists. // int nUpstreamCount = m_Upstream.Count(); for (int nConnection = 0; nConnection < nUpstreamCount; nConnection++) { CEntityConnection *pConnection = m_Upstream.Element( nConnection ); CMapEntityList *pSourceList = pConnection->GetSourceEntityList(); if ( pSourceList ) { FOR_EACH_OBJ( *pSourceList, pos ) { CMapEntity *pEntity = pSourceList->Element( pos ); pEntity->Connection_Remove( pConnection ); } } } #endif m_Upstream.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEditGameClass::Upstream_FixBad() { #if defined(_DEBUG) && 0 LPCTSTR pszTargetName = GetKeyValue("targetname"); if ( pszTargetName && !strcmp(pszTargetName, "cave_guard_seq1") ) { // Set breakpoint here for debugging this entity int foo = 0; } #endif int nUpstreamCount = m_Upstream.Count(); for (int nUpstream = 0; nUpstream < nUpstreamCount; nUpstream++) { CEntityConnection *pUpstream = m_Upstream.Element(nUpstream); pUpstream->LinkTargetEntities(); } } //----------------------------------------------------------------------------- // Purpose: // Input : pszClass - // bLoading - //----------------------------------------------------------------------------- void CEditGameClass::SetClass(LPCTSTR pszClass, bool bLoading) { extern GameData *pGD; strcpy(m_szClass, pszClass); StripEdgeWhiteSpace(m_szClass); if (pGD) { m_pClass = pGD->ClassForName(m_szClass); } } //----------------------------------------------------------------------------- // Purpose: Copies the data from a given CEditGameClass object into this one. // Input : pFrom - Object to copy. // Output : Returns a pointer to this. //----------------------------------------------------------------------------- CEditGameClass *CEditGameClass::CopyFrom(CEditGameClass *pFrom) { m_pClass = pFrom->m_pClass; strcpy( m_szClass, pFrom->m_szClass ); // // Copy all the keys. // m_KeyValues.RemoveAll(); for ( int i=pFrom->GetFirstKeyValue(); i != pFrom->GetInvalidKeyValue(); i=pFrom->GetNextKeyValue( i ) ) { m_KeyValues.SetValue(pFrom->GetKey(i), pFrom->GetKeyValue(i)); } // // Copy all the connections objects // Connections_RemoveAll(); int nConnCount = pFrom->Connections_GetCount(); for (int i = 0; i < nConnCount; i++) { CEntityConnection *pConn = pFrom->Connections_Get(i); CEntityConnection *pNewConn = new CEntityConnection( *pConn ); Connections_Add(pNewConn); } // // Copy the comments. // SetComments(pFrom->GetComments()); return(this); } //----------------------------------------------------------------------------- // Purpose: Applies the default keys for this object's game class. Called when // the entity's class is changed. //----------------------------------------------------------------------------- void CEditGameClass::GetDefaultKeys( void ) { if ( m_pClass != NULL ) { // // For each variable from the base class... // int nVariableCount = m_pClass->GetVariableCount(); for ( int i = 0; i < nVariableCount; i++ ) { GDinputvariable *pVar = m_pClass->GetVariableAt(i); Assert(pVar != NULL); if (pVar != NULL) { int iIndex; LPCTSTR p = m_KeyValues.GetValue(pVar->GetName(), &iIndex); // // If the variable is not present in this object, set the default value. // if (p == NULL) { MDkeyvalue tmpkv; pVar->ResetDefaults(); pVar->ToKeyValue(&tmpkv); // // Only set the key value if it is non-zero. // if ((tmpkv.szKey[0] != 0) && (tmpkv.szValue[0] != 0) && (stricmp(tmpkv.szValue, "0"))) { SetKeyValue(tmpkv.szKey, tmpkv.szValue); } } } } } } //----------------------------------------------------------------------------- // Purpose: Returns this object's angles as a vector of the form: // [0] PITCH [1] YAW [2] ROLL //----------------------------------------------------------------------------- void CEditGameClass::GetAngles(QAngle &vecAngles) { vecAngles = vec3_angle; const char *pszAngles = GetKeyValue("angles"); if (pszAngles != NULL) { sscanf(pszAngles, "%f %f %f", &vecAngles[PITCH], &vecAngles[YAW], &vecAngles[ROLL]); } } //----------------------------------------------------------------------------- // Purpose: Sets a new yaw, overriding any existing angles. Uses special values // for yaw to indicate pointing straight up or straight down. // // This method of representing orientation is obsolete; this code is // only for importing old RMF or MAP files. // // Input : a - Value for angle. //----------------------------------------------------------------------------- void CEditGameClass::ImportAngle(int nAngle) { QAngle vecAngles; vecAngles.Init(); if (nAngle == -1) // UP { vecAngles[PITCH] = -90; } else if (nAngle == -2) // DOWN { vecAngles[PITCH] = 90; } else { vecAngles[YAW] = nAngle; } SetAngles(vecAngles); } //----------------------------------------------------------------------------- // Purpose: Sets this object's angles as a vector of the form: // [0] PITCH [1] YAW [2] ROLL //----------------------------------------------------------------------------- void CEditGameClass::SetAngles(const QAngle &vecAngles) { char szAngles[80]; sprintf(szAngles, "%g %g %g", (double)vecAngles[PITCH], (double)vecAngles[YAW], (double)vecAngles[ROLL]); SetKeyValue("angles", szAngles); } //----------------------------------------------------------------------------- // Purpose: // Input : *pFile - // Output : ChunkFileResult_t //----------------------------------------------------------------------------- ChunkFileResult_t CEditGameClass::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo) { ChunkFileResult_t eResult = pFile->WriteKeyValue("classname", m_szClass); if (eResult != ChunkFile_Ok) { return(eResult); } // // Determine whether we have a game data class. This will help us decide which keys // to write. // GDclass *pGameDataClass = NULL; if (pGD != NULL) { pGameDataClass = pGD->ClassForName(m_szClass); } // // Consider all the keyvalues in this object for serialization. // for ( int z=m_KeyValues.GetFirst(); z != m_KeyValues.GetInvalidIndex(); z=m_KeyValues.GetNext( z ) ) { MDkeyvalue &KeyValue = m_KeyValues.GetKeyValue(z); // // Don't write keys that were already written above. // bool bAlreadyWritten = false; if (!stricmp(KeyValue.szKey, "classname")) { bAlreadyWritten = true; } // // If the variable wasn't already written above. // if (!bAlreadyWritten) { // // Write it to the MAP file. // eResult = pFile->WriteKeyValue(KeyValue.szKey, KeyValue.szValue); if (eResult != ChunkFile_Ok) { return(eResult); } } } // // If we have a game data class, for each keyvalue in the class definition, write out all keys // that are not present in the object and whose defaults are nonzero in the class definition. // if (pGameDataClass != NULL) { // // For each variable from the base class... // int nVariableCount = pGameDataClass->GetVariableCount(); for (int i = 0; i < nVariableCount; i++) { GDinputvariable *pVar = pGameDataClass->GetVariableAt(i); Assert(pVar != NULL); if (pVar != NULL) { int iIndex; LPCTSTR p = m_KeyValues.GetValue(pVar->GetName(), &iIndex); // // If the variable is not present in this object, write out the default value. // if (p == NULL) { MDkeyvalue TempKey; pVar->ResetDefaults(); pVar->ToKeyValue(&TempKey); // // Only write the key value if it is non-zero. // if ((TempKey.szKey[0] != 0) && (TempKey.szValue[0] != 0) && (stricmp(TempKey.szValue, "0"))) { eResult = pFile->WriteKeyValue(TempKey.szKey, TempKey.szValue); if (eResult != ChunkFile_Ok) { return(eResult); } } } } } } // // Save all the connections. // if ((eResult == ChunkFile_Ok) && (Connections_GetCount() > 0)) { eResult = pFile->BeginChunk("connections"); if (eResult == ChunkFile_Ok) { int nConnCount = Connections_GetCount(); for (int i = 0; i < nConnCount; i++) { CEntityConnection *pConnection = Connections_Get(i); if (pConnection != NULL) { char szTemp[512]; sprintf(szTemp, "%s%c%s%c%s%c%g%c%d", pConnection->GetTargetName(), VMF_IOPARAM_STRING_DELIMITER, pConnection->GetInputName(), VMF_IOPARAM_STRING_DELIMITER, pConnection->GetParam(), VMF_IOPARAM_STRING_DELIMITER, pConnection->GetDelay(), VMF_IOPARAM_STRING_DELIMITER, pConnection->GetTimesToFire()); eResult = pFile->WriteKeyValue(pConnection->GetOutputName(), szTemp); if (eResult != ChunkFile_Ok) { return(eResult); } } } eResult = pFile->EndChunk(); } } return(eResult); } //----------------------------------------------------------------------------- // Purpose: Slightly modified strtok. Does not modify the input string. Does // not skip over more than one separator at a time. This allows parsing // strings where tokens between separators may or may not be present: // // Door01,,,0 would be parsed as "Door01" "" "" "0" // Door01,Open,,0 would be parsed as "Door01" "Open" "" "0" // // Input : token - Returns with a token, or zero length if the token was missing. // str - String to parse. // sep - Character to use as separator. UNDONE: allow multiple separator chars // Output : Returns a pointer to the next token to be parsed. //----------------------------------------------------------------------------- static const char *nexttoken_gameclass(char *token, const char *str, char sep) { if (*str == '\0') { return(NULL); } // // Find the first separator. // const char *ret = str; while ((*str != sep) && (*str != '\0')) { str++; } // // Copy everything up to the first separator into the return buffer. // Do not include separators in the return buffer. // while (ret < str) { *token++ = *ret++; } *token = '\0'; // // Advance the pointer unless we hit the end of the input string. // if (*str == '\0') { return(str); } return(++str); } //----------------------------------------------------------------------------- // Purpose: Builds a connection from a keyvalue pair. // Input : szKey - Contains the name of the output. // szValue - Contains the target, input, delay, and parameter, comma delimited. // pEditGameClass - Entity to receive the connection. // Output : Returns ChunkFile_Ok if the data was well-formed, ChunkFile_Fail if not. //----------------------------------------------------------------------------- ChunkFileResult_t CEditGameClass::LoadKeyCallback(const char *szKey, const char *szValue, CEditGameClass *pEditGameClass) { CEntityConnection *pConnection = new CEntityConnection; // Set the "source" name to be the name of the pEditGameClass' targetname pConnection->SetSourceName( pEditGameClass->GetKeyValue("targetname") ); // Use the classname if no targetname is defined? // Set the "output" from the passed in parameter pConnection->SetOutputName(szKey); // Figure out what delimiter to use. We switched from commas to the nonprintable // character 0x07 when we added the ability to execute vscript code in an input. char chDelim = VMF_IOPARAM_STRING_DELIMITER; if (strchr(szValue, VMF_IOPARAM_STRING_DELIMITER) == NULL) { chDelim = ','; } char szToken[MAX_PATH]; // // Parse the target name. // const char *psz = nexttoken_gameclass(szToken, szValue, chDelim); if (szToken[0] != '\0') { pConnection->SetTargetName(szToken); } // // Parse the input name. // psz = nexttoken_gameclass(szToken, psz, chDelim); if (szToken[0] != '\0') { pConnection->SetInputName(szToken); } // // Parse the parameter override. // psz = nexttoken_gameclass(szToken, psz, chDelim); if (szToken[0] != '\0') { pConnection->SetParam(szToken); } // // Parse the delay. // psz = nexttoken_gameclass(szToken, psz, chDelim); if (szToken[0] != '\0') { pConnection->SetDelay((float)atof(szToken)); } // // Parse the number of times to fire the output. // nexttoken_gameclass(szToken, psz, chDelim); if (szToken[0] != '\0') { pConnection->SetTimesToFire(atoi(szToken)); } pEditGameClass->Connections_Add(pConnection); // Does this belong here or SetSourceName or LinkSourceEntities?? return(ChunkFile_Ok); } //----------------------------------------------------------------------------- // Purpose: // Input : *pFile - // *pEntity - // Output : ChunkFileResult_t //----------------------------------------------------------------------------- ChunkFileResult_t CEditGameClass::LoadConnectionsCallback(CChunkFile *pFile, CEditGameClass *pEditGameClass) { return(pFile->ReadChunk((KeyHandler_t)LoadKeyCallback, pEditGameClass)); } //----------------------------------------------------------------------------- // Purpose: Returns all the spawnflags. //----------------------------------------------------------------------------- unsigned long CEditGameClass::GetSpawnFlags(void) { LPCTSTR pszVal = GetKeyValue("spawnflags"); if (pszVal == NULL) { return(0); } unsigned long val = 0; sscanf( pszVal, "%lu", &val ); return val; } //----------------------------------------------------------------------------- // Purpose: Returns true if a given spawnflag (or flags) is set, false if not. //----------------------------------------------------------------------------- bool CEditGameClass::GetSpawnFlag(unsigned long nFlags) { unsigned long nSpawnFlags = GetSpawnFlags(); return((nSpawnFlags & nFlags) != 0); } //----------------------------------------------------------------------------- // Purpose: Sets the given spawnflag (or flags) to the given state. // Input : nFlag - Flag values to set or clear (ie 1, 2, 4, 8, 16, etc.) // bSet - True to set the flags, false to clear them. //----------------------------------------------------------------------------- void CEditGameClass::SetSpawnFlag(unsigned long nFlags, bool bSet) { unsigned long nSpawnFlags = GetSpawnFlags(); if (bSet) { nSpawnFlags |= nFlags; } else { nSpawnFlags &= ~nFlags; } SetSpawnFlags(nSpawnFlags); } //----------------------------------------------------------------------------- // Purpose: Sets all the spawnflags at once. // Input : nSpawnFlags - New value for spawnflags. //----------------------------------------------------------------------------- void CEditGameClass::SetSpawnFlags(unsigned long nSpawnFlags) { char szValue[80]; V_snprintf( szValue, sizeof( szValue ), "%lu", nSpawnFlags ); SetKeyValue("spawnflags", nSpawnFlags); }