csgo-2018-source/hammer/editgameclass.cpp
2021-07-24 21:11:47 -07:00

762 lines
21 KiB
C++

//========= 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);
}