|
|
//========= 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 <tier0/memdbgon.h>
//
// 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); }
|