csgo-2018-source/hammer/mapentity.cpp

2434 lines
68 KiB
C++
Raw Normal View History

2021-07-25 12:11:47 +08:00
//===== Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "stdafx.h"
#include "collisionutils.h"
#include "fgdlib/gdclass.h"
#include "IEditorTexture.h"
#include "GlobalFunctions.h"
#include "hammer_mathlib.h"
#include "HelperFactory.h"
#include "MapAlignedBox.h"
#include "MapSweptPlayerHull.h"
#include "MapDefs.h"
#include "MapDoc.h"
#include "MapEntity.h"
#include "MapAnimator.h"
#include "MapSolid.h"
#include "MapView2D.h" // dvs FIXME: For HitTest2D implementation
#include "MapViewLogical.h"
#include "MapWorld.h"
#include "Options.h"
#include "Render2D.h"
#include "SaveInfo.h"
#include "VisGroup.h"
#include "MapSprite.h"
#include "camera.h"
#include "hammer.h"
#include "vmfentitysupport.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
IMPLEMENT_MAPCLASS(CMapEntity)
#define LOGICAL_BOX_WIDTH 300
#define LOGICAL_BOX_HEIGHT 300
#define LOGICAL_BOX_INNER_OFFSET 10
#define LOGICAL_BOX_CONNECTOR_INPUT_WIDTH 50
#define LOGICAL_BOX_CONNECTOR_OUTPUT_WIDTH 50
#define LOGICAL_BOX_CONNECTOR_RADIUS 10
#define LOGICAL_BOX_ARROW_LENGTH 25
#define LOGICAL_BOX_ARROW_HEIGHT 10
class CMapAnimator;
class CMapKeyFrame;
bool CMapEntity::s_bShowDotACamera = false;
bool CMapEntity::s_bShowEntityNames = true;
bool CMapEntity::s_bShowEntityConnections = false;
bool CMapEntity::s_bShowUnconnectedEntities = true;
static CMapObjectList FoundEntities;
//-----------------------------------------------------------------------------
// Purpose:
// Input : pEntity -
// pKV -
// Output :
//-----------------------------------------------------------------------------
static BOOL FindKeyValue(CMapEntity *pEntity, MDkeyvalue *pKV)
{
LPCTSTR pszValue = pEntity->GetKeyValue(pKV->szKey);
if (!pszValue || strcmpi(pszValue, pKV->szValue))
{
return TRUE;
}
FoundEntities.AddToTail(pEntity);
return TRUE;
}
//-----------------------------------------------------------------------------
// Purpose: Compares two entity names, allowing wildcards in EITHER string.
// Assumes that the wildcard character '*' marks the end of comparison,
// in other words, these are identical:
//
// test*
// test*stuff
//
// Input : szName1 -
// szName2 -
// Output : int
//-----------------------------------------------------------------------------
int CompareEntityNames(const char *szName1, const char *szName2)
{
int nCompareLen = -1;
const char *pszWildcard1 = strchr(szName1, '*');
if (pszWildcard1)
{
nCompareLen = pszWildcard1 - szName1;
}
const char *pszWildcard2 = strchr(szName2, '*');
if (pszWildcard2)
{
if (nCompareLen == -1)
{
nCompareLen = pszWildcard2 - szName2;
}
else
{
// Wildcards in both strings -- use the shorter pattern.
nCompareLen = min(nCompareLen, pszWildcard2 - szName2);
}
}
if (nCompareLen != -1)
{
if (nCompareLen > 0)
{
return strnicmp(szName1, szName2, nCompareLen);
}
// One of the strings had a wildcard as the first character.
return 0;
}
return stricmp(szName1, szName2);
}
//-----------------------------------------------------------------------------
// Replaces references to the old node ID with references to the new node ID.
//-----------------------------------------------------------------------------
static void ReplaceNodeIDRecursive(CMapClass *pRoot, int nOldNodeID, int nNewNodeID)
{
CMapEntity *pEntity = dynamic_cast <CMapEntity *>(pRoot);
if (pEntity)
{
GDclass *pClass = pEntity->GetClass();
if (!pClass)
return;
int nVarCount = pClass->GetVariableCount();
for (int i = 0; i < nVarCount; i++)
{
GDinputvariable *pVar = pClass->GetVariableAt(i);
if (pVar->GetType() == ivNodeDest)
{
const char *pszValue = pEntity->GetKeyValue(pVar->GetName());
if (pszValue && (atoi(pszValue) == nOldNodeID))
{
char szValue[100];
itoa(nNewNodeID, szValue, 10);
pEntity->SetKeyValue(pVar->GetName(), szValue);
}
}
}
}
else
{
const CMapObjectList *pChildren = pRoot->GetChildren();
FOR_EACH_OBJ( *pChildren, pos )
{
ReplaceNodeIDRecursive((CUtlReference< CMapClass >)pChildren->Element(pos), nOldNodeID, nNewNodeID);
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
static void ReplaceNodeIDRefs(CMapObjectList &newList, int nOldNodeID, int nNewNodeID)
{
// If they are the same, do nothing. This can happen when pasting from one
// map to another map.
if (nOldNodeID == nNewNodeID)
return;
FOR_EACH_OBJ( newList, pos )
{
CMapClass *pNew = newList.Element(pos);
ReplaceNodeIDRecursive(pNew, nOldNodeID, nNewNodeID);
}
}
//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CMapEntity::CMapEntity(void) : flags(0)
{
m_pMoveParent = NULL;
m_pAnimatorChild = NULL;
m_vecLogicalPosition.Init( COORD_NOTINIT, COORD_NOTINIT );
CalculateTypeFlags();
}
//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CMapEntity::~CMapEntity(void)
{
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose: Adds a bounding box helper to this entity. If this entity's class
// specifies a bounding box, it will be the correct size.
// Input : pClass -
//-----------------------------------------------------------------------------
void CMapEntity::AddBoundBoxForClass(GDclass *pClass, bool bLoading)
{
Vector Mins;
Vector Maxs;
//
// If we have a class and it specifies a class, use that bounding box.
//
if ((pClass != NULL) && (pClass->HasBoundBox()))
{
pClass->GetBoundBox(Mins, Maxs);
}
//
// Otherwise, use a default bounding box.
//
else
{
VectorFill(Mins, -8);
VectorFill(Maxs, 8);
}
//
// Create the box and add it as one of our children.
//
CMapAlignedBox *pBox = new CMapAlignedBox(Mins, Maxs);
pBox->SetOrigin(m_Origin);
pBox->SetSelectionState(GetSelectionState());
//
// HACK: Make sure that the new child gets properly linked into the world.
// This is not correct because it bypasses the doc's AddObjectToWorld code.
//
// Don't call AddObjectToWorld during VMF load because we don't want to call
// OnAddToWorld during VMF load. We update our helpers during PostloadWorld.
//
CMapWorld *pWorld = (CMapWorld *)GetWorldObject(this);
if ((!bLoading) && (pWorld != NULL))
{
pWorld->AddObjectToWorld(pBox, this);
}
else
{
AddChild(pBox);
}
}
//-----------------------------------------------------------------------------
// Purpose: Sets our child's render color to our render color.
// Input : pChild - Child object being added.
//-----------------------------------------------------------------------------
void CMapEntity::AddChild(CMapClass *pChild)
{
CMapClass::AddChild(pChild);
//
// Notify the new child of all our keys. Don't bother for solids.
//
if (dynamic_cast<CMapSolid*>(pChild) == NULL)
{
for ( int i=GetFirstKeyValue(); i != GetInvalidKeyValue(); i=GetNextKeyValue( i ) )
{
MDkeyvalue KeyValue = m_KeyValues.GetKeyValue(i);
pChild->OnParentKeyChanged( KeyValue.szKey, KeyValue.szValue );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Adds a helper object as a child of this entity.
// Input : pHelper - The helper object.
// bLoading - True if this is being called from Postload, false otherwise.
//-----------------------------------------------------------------------------
void CMapEntity::AddHelper(CMapClass *pHelper, bool bLoading)
{
if (!IsPlaceholder())
{
//
// Solid entities have no origin, so place the helper at our center.
//
Vector vecCenter;
m_Render2DBox.GetBoundsCenter(vecCenter);
pHelper->SetOrigin(vecCenter);
}
else
{
pHelper->SetOrigin(m_Origin);
}
pHelper->SetSelectionState(GetSelectionState());
//
// If we have a game data class, set the child's render color to the color
// dictated by the game data class.
//
// Note: in AddChild, where it calls OnParentKeyChanged for everything, the color in the helper can
// get set to something else based on the entity's properties (CMapLightCone does this, for example).
//
GDclass *pClass = GetClass();
if ( pClass )
{
color32 rgbColor = pClass->GetColor();
pHelper->SetRenderColor(rgbColor);
}
//
// HACK: Make sure that the new child gets properly linked into the world.
// This is not correct because it bypasses the doc's AddObjectToWorld code.
//
// Don't call AddObjectToWorld during VMF load because we don't want to call
// OnAddToWorld during VMF load. We update our helpers during PostloadWorld.
//
CMapWorld *pWorld = (CMapWorld *)GetWorldObject(this);
if ((!bLoading) && (pWorld != NULL))
{
pWorld->AddObjectToWorld(pHelper, this);
}
else
{
AddChild(pHelper);
}
//
// dvs: HACK for animator children. Better for CMapEntity to have a SetAnimatorChild
// function that the CMapAnimator could call. Better still, eliminate the knowledge
// that CMapEntity has about its animator child.
//
CMapAnimator *pAnim = dynamic_cast<CMapAnimator *>(pHelper);
if (pAnim != NULL)
{
m_pAnimatorChild = pAnim;
}
}
//-----------------------------------------------------------------------------
// Purpose: Creates all helper objects defined by the FGD and adds them as
// children of this entity. Helper objects perform rendering, UI, and
// bookkeeping functions for their parent entities. If the class
// definition does not specify any helpers, or none of the helpers
// could be added, a box helper is added so that the entity has some
// visual representation.
// Inputs : pClass -
// bLoading - True if this is being called from Postload, false otherwise.
//-----------------------------------------------------------------------------
void CMapEntity::AddHelpersForClass(GDclass *pClass, bool bLoading)
{
bool bAddedOneVisual = false;
if (((pClass != NULL) && (pClass->HasBoundBox())))
{
AddBoundBoxForClass(pClass, bLoading);
bAddedOneVisual = true;
}
//
// If we have a game class from the FGD, add whatever helpers are declared in that
// class definition.
//
if (pClass != NULL)
{
//
// Add all the helpers that this class declares in the FGD.
//
GDclass *pClass = GetClass();
//
// For every helper in the class definition...
//
int nHelperCount = pClass->GetHelperCount();
for (int i = 0; i < nHelperCount; i++)
{
CHelperInfo *pHelperInfo = pClass->GetHelper(i);
//
// Create the helper and attach it to this entity.
//
CMapClass *pHelper = CHelperFactory::CreateHelper(pHelperInfo, this);
if (pHelper != NULL)
{
AddHelper(pHelper, bLoading);
if (pHelper->IsVisualElement())
{
bAddedOneVisual = true;
}
}
}
//
// Look for keys that define helpers.
//
// FIXME: make this totally data driven like the helper factory, or better
// yet, like the LINK_ENTITY_TO_CLASS stuff in the game DLL
int nVarCount = pClass->GetVariableCount();
for (int i = 0; i < nVarCount; i++)
{
GDinputvariable *pVar = pClass->GetVariableAt(i);
GDIV_TYPE eType = pVar->GetType();
CHelperInfo HelperInfo;
bool bCreate = false;
switch (eType)
{
case ivOrigin:
{
const char *pszKey = pVar->GetName();
HelperInfo.SetName("origin");
HelperInfo.AddParameter(pszKey);
bCreate = true;
break;
}
case ivVecLine:
{
const char *pszKey = pVar->GetName();
HelperInfo.SetName("vecline");
HelperInfo.AddParameter(pszKey);
bCreate = true;
break;
}
case ivAxis:
{
const char *pszKey = pVar->GetName();
HelperInfo.SetName("axis");
HelperInfo.AddParameter(pszKey);
bCreate = true;
break;
}
}
//
// Create the helper and attach it to this entity.
//
if (bCreate)
{
CMapClass *pHelper = CHelperFactory::CreateHelper(&HelperInfo, this);
if (pHelper != NULL)
{
AddHelper(pHelper, bLoading);
if (pHelper->IsVisualElement())
{
bAddedOneVisual = true;
}
}
}
}
}
//
// Any solid children we have will also work as visual elements.
//
if (!IsPlaceholder())
{
bAddedOneVisual = true;
}
//
// If we have no game class and we are a point entity, add an "obsolete" sprite helper
// so level designers know to update the entity.
//
else if (pClass == NULL)
{
CHelperInfo HelperInfo;
HelperInfo.SetName("iconsprite");
HelperInfo.AddParameter("sprites/obsolete.vmt");
CMapClass *pSprite = CHelperFactory::CreateHelper(&HelperInfo, this);
if (pSprite != NULL)
{
AddHelper(pSprite, bLoading);
bAddedOneVisual = true;
}
}
//
// If we still haven't added any visible helpers, we need to add a bounding box so that there
// is some visual representation for this entity. We also add the bounding box if the
// entity's class specifies a bounding box.
//
if (!bAddedOneVisual)
{
AddBoundBoxForClass(pClass, bLoading);
}
if ( !CMapClass::s_bLoadingVMF )
{
CalcBounds(TRUE);
PostUpdate(Notify_Changed);
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns a deep copy of this object.
// Output : Returns a pointer to the new allocated object.
//-----------------------------------------------------------------------------
CMapClass *CMapEntity::Copy(bool bUpdateDependencies)
{
CMapEntity *pNew = new CMapEntity;
pNew->CopyFrom(this, bUpdateDependencies);
return pNew;
}
//-----------------------------------------------------------------------------
// Purpose: Performs a deep copy of a given object into this object.
// Input : pobj - Object to copy from.
// Output : Returns a pointer to this object.
//-----------------------------------------------------------------------------
CMapClass *CMapEntity::CopyFrom(CMapClass *pobj, bool bUpdateDependencies)
{
Assert(pobj->IsMapClass(MAPCLASS_TYPE(CMapEntity)));
CMapEntity *pFrom = (CMapEntity*) pobj;
flags = pFrom->flags;
m_Origin = pFrom->m_Origin;
m_vecLogicalPosition = pFrom->m_vecLogicalPosition;
CMapClass::CopyFrom(pobj, bUpdateDependencies);
//
// Copy our keys. If our targetname changed we must relink all targetname pointers.
//
const char *pszOldTargetName = CEditGameClass::GetKeyValue("targetname");
char szOldTargetName[MAX_IO_NAME_LEN];
if (pszOldTargetName != NULL)
{
strcpy(szOldTargetName, pszOldTargetName);
}
CEditGameClass::CopyFrom(pFrom);
const char *pszNewTargetName = CEditGameClass::GetKeyValue("targetname");
if ((bUpdateDependencies) && (pszNewTargetName != NULL))
{
if (stricmp(szOldTargetName, pszNewTargetName) != 0)
{
UpdateAllDependencies(this);
}
}
CalculateTypeFlags();
SignalChanged();
return(this);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bFullUpdate -
//-----------------------------------------------------------------------------
void CMapEntity::CalcBounds(BOOL bFullUpdate)
{
CMapClass::CalcBounds(bFullUpdate);
//
// If we are a solid entity, set our origin to our bounds center.
//
if (IsSolidClass())
{
m_Render2DBox.GetBoundsCenter(m_Origin);
}
}
//-----------------------------------------------------------------------------
// Purpose: Debugging hook.
//-----------------------------------------------------------------------------
#pragma warning (disable:4189)
void CMapEntity::Debug(void)
{
int i = m_KeyValues.GetFirst();
MDkeyvalue &KeyValue = m_KeyValues.GetKeyValue(i);
}
#pragma warning (default:4189)
//-----------------------------------------------------------------------------
// Purpose: If this entity has a name key, returns a string with "<name> <classname>"
// in it. Otherwise returns a buffer with "<classname>" in it.
// Output : String description of the entity.
//-----------------------------------------------------------------------------
const char* CMapEntity::GetDescription(void)
{
static char szBuf[128];
const char *pszName = GetKeyValue("targetname");
if (pszName != NULL)
{
sprintf(szBuf, "%s - %s", pszName, GetClassName());
}
else
{
strcpy(szBuf, GetClassName());
}
return(szBuf);
}
//-----------------------------------------------------------------------------
// Purpose: Returns the color that this entity should use for rendering.
//-----------------------------------------------------------------------------
void CMapEntity::GetRenderColor( CRender2D *pRender, unsigned char &red, unsigned char &green, unsigned char &blue )
{
if ( IsSelected() )
{
red = GetRValue(Options.colors.clrSelection);
green = GetGValue(Options.colors.clrSelection);
blue = GetBValue(Options.colors.clrSelection);
}
else
{
GDclass *pClass = GetClass();
if (pClass)
{
color32 rgbColor = pClass->GetColor();
red = rgbColor.r;
green = rgbColor.g;
blue = rgbColor.b;
}
else
{
red = GetRValue(Options.colors.clrEntity);
green = GetGValue(Options.colors.clrEntity);
blue = GetBValue(Options.colors.clrEntity);
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns the color that this entity should use for rendering.
//-----------------------------------------------------------------------------
color32 CMapEntity::GetRenderColor( CRender2D *pRender )
{
color32 clr;
GetRenderColor( pRender, clr.r, clr.g, clr.b );
return clr;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the size of this object.
// Output : Size, in bytes, of this object, not including any dynamically
// allocated data members.
//-----------------------------------------------------------------------------
size_t CMapEntity::GetSize(void)
{
return(sizeof(*this));
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pFile -
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::LoadVMF(CChunkFile *pFile)
{
//
// Set up handlers for the subchunks that we are interested in.
//
CChunkHandlerMap Handlers;
Handlers.AddHandler("solid", (ChunkHandler_t)LoadSolidCallback, this);
Handlers.AddHandler("hidden", (ChunkHandler_t)LoadHiddenCallback, this);
Handlers.AddHandler("editor", (ChunkHandler_t)LoadEditorCallback, this);
Handlers.AddHandler("connections", (ChunkHandler_t)LoadConnectionsCallback, (CEditGameClass *)this);
VmfAddMapEntityHandlers( &Handlers, static_cast< IMapEntity_Type_t * >( this ) );
pFile->PushHandlers(&Handlers);
ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadKeyCallback, this);
pFile->PopHandlers();
return(eResult);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *szKey -
// *szValue -
// *pEntity -
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::LoadKeyCallback(const char *szKey, const char *szValue, CMapEntity *pEntity)
{
if (!stricmp(szKey, "id"))
{
pEntity->SetID(atoi(szValue));
// PORTAL2 SHIP: keep track of load order to preserve it on save so that maps can be diffed.
pEntity->m_nLoadID = CMapDoc::GetActiveMapDoc()->GetNextLoadID();
}
else
{
//
// While loading, set key values directly rather than via SetKeyValue. This avoids
// all the unnecessary bookkeeping that goes on in SetKeyValue.
//
pEntity->m_KeyValues.SetValue(szKey, szValue);
}
pEntity->CalculateTypeFlags();
pEntity->SignalChanged();
return(ChunkFile_Ok);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bVisible -
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::LoadHiddenCallback(CChunkFile *pFile, CMapEntity *pEntity)
{
//
// Set up handlers for the subchunks that we are interested in.
//
CChunkHandlerMap Handlers;
Handlers.AddHandler("solid", (ChunkHandler_t)LoadSolidCallback, pEntity);
Handlers.AddHandler("editor", (ChunkHandler_t)LoadEditorCallback, pEntity);
pFile->PushHandlers(&Handlers);
ChunkFileResult_t eResult = pFile->ReadChunk();
pFile->PopHandlers();
return(eResult);
}
ChunkFileResult_t CMapEntity::LoadEditorKeyCallback( const char *szKey, const char *szValue, CMapEntity *pMapEntity )
{
if ( !stricmp( szKey, "logicalpos" ) )
{
CChunkFile::ReadKeyValueVector2(szValue, pMapEntity->m_vecLogicalPosition );
return ChunkFile_Ok;
}
return CMapClass::LoadEditorKeyCallback( szKey, szValue, pMapEntity );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::LoadEditorCallback(CChunkFile *pFile, CMapEntity *pObject)
{
return pFile->ReadChunk( (KeyHandler_t)LoadEditorKeyCallback, pObject );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pFile -
// *pEntity -
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::LoadSolidCallback(CChunkFile *pFile, CMapEntity *pEntity)
{
CMapSolid *pSolid = new CMapSolid;
bool bValid;
ChunkFileResult_t eResult = pSolid->LoadVMF(pFile, bValid);
if ((eResult == ChunkFile_Ok) && (bValid))
{
pEntity->AddChild(pSolid);
}
else
{
delete pSolid;
}
return(eResult);
}
//-----------------------------------------------------------------------------
// Purpose: Sets this entity's origin and updates the bounding box.
// Input : o - Origin to set.
//-----------------------------------------------------------------------------
void CMapEntity::SetOrigin(Vector& o)
{
Vector vecOrigin;
GetOrigin(vecOrigin);
if (vecOrigin == o)
return;
CMapClass::SetOrigin(o);
// dvs: is this still necessary?
if (!(flags & flagPlaceholder))
{
// not a placeholder.. no origin.
return;
}
if ( !CMapClass::s_bLoadingVMF )
{
CalcBounds( TRUE );
PostUpdate(Notify_Changed);
SignalChanged();
}
}
//-----------------------------------------------------------------------------
// Purpose: Removes all of this entity's helpers.
// Input : bRemoveSolidChildren - Whether to also remove any solid children. This
// is true when changing from a solid entity to a point entity.
//-----------------------------------------------------------------------------
void CMapEntity::RemoveHelpers(bool bRemoveSolids)
{
for( int pos=m_Children.Count()-1; pos>=0; pos-- )
{
CMapClass *pChild = m_Children[pos];
if (bRemoveSolids || ((dynamic_cast <CMapSolid *> (pChild)) == NULL))
{
m_Children.FastRemove(pos);
}
// LEAKLEAK: need to KeepForDestruction to avoid undo crashes, but how? where?
//delete pChild;
}
}
//-----------------------------------------------------------------------------
// Building targetnames which deal with *
//-----------------------------------------------------------------------------
static inline void BuildNewTargetName( const char *pOldName, const char *pNewName, char *pBuffer )
{
strcpy(pBuffer, pNewName);
// If we matched a key value that contains wildcards, preserve the
// wildcards when we replace the name.
//
// For example, "oldname*" would become "newname*" instead of just "newname"
// FIXME: ??? handle different-length names with wildcards, eg. "old_vort*" => "new_weasel*"
const char *pszWildcard = strchr(pOldName, '*');
if (pszWildcard)
{
strcpy(&pBuffer[pszWildcard - pOldName], "*");
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CMapEntity::ReplaceTargetname(const char *szOldName, const char *szNewName)
{
// NOTE: Case-sensitive compare because people might want to replace one case with another.
if ( !Q_strcmp( szOldName, szNewName ) )
{
// The names already match. There is nothing to do!
return;
}
char szTempName[MAX_KEYVALUE_LEN];
//
// Replace any keys whose value matches the old name.
//
for ( int i=GetFirstKeyValue(); i != GetInvalidKeyValue(); i=GetNextKeyValue( i ) )
{
MDkeyvalue KeyValue = m_KeyValues.GetKeyValue(i);
if (!CompareEntityNames(KeyValue.szValue, szOldName))
{
BuildNewTargetName( KeyValue.szValue, szNewName, szTempName );
SetKeyValue( KeyValue.szKey, szTempName );
}
}
//
// Replace any connections that target the old name.
//
int nConnCount = Connections_GetCount();
for (int i = 0; i < nConnCount; i++)
{
CEntityConnection *pConn = Connections_Get(i);
if (!CompareEntityNames( pConn->GetTargetName(), szOldName ))
{
BuildNewTargetName( pConn->GetTargetName(), szNewName, szTempName );
pConn->SetTargetName(szTempName);
}
if (!CompareEntityNames( pConn->GetSourceName(), szOldName ))
{
BuildNewTargetName( pConn->GetSourceName(), szNewName, szTempName );
pConn->SetSourceName(szTempName);
}
if ( !CompareEntityNames( pConn->GetParam(), szOldName ))
{
BuildNewTargetName( pConn->GetParam(), szNewName, szTempName );
pConn->SetParam(szTempName);
}
}
CMapClass::ReplaceTargetname(szOldName, szNewName);
}
//-----------------------------------------------------------------------------
// Purpose:
// Inputs : pszClass -
// bLoading - True if this is being called from Postload, false otherwise.
//-----------------------------------------------------------------------------
void CMapEntity::SetClass(LPCTSTR pszClass, bool bLoading)
{
Assert(pszClass);
//
// If we are just setting to the same class, don't do anything.
//
if (IsClass(pszClass))
{
return;
}
//
// Copy class name & resolve GDclass pointer.
//
CEditGameClass::SetClass(pszClass, bLoading);
UpdateObjectColor();
//
// If our new class is defined in the FGD, set our color and our default keys
// from the class.
//
if (IsClass())
{
SetPlaceholder(!IsSolidClass());
GetDefaultKeys();
if (IsNodeClass() && (GetNodeID() == 0))
{
AssignNodeID();
}
}
//
// If not, use whether or not we have solid children to determine whether
// we are a point entity or a solid entity.
//
else
{
SetPlaceholder(HasSolidChildren() ? FALSE : TRUE);
}
//
// Add whatever helpers our class requires, or a default bounding box if
// our class is unknown and we are a point entity.
//
UpdateHelpers(bLoading);
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
if ( !pDoc->IsLoading() )
{
pDoc->RemoveFromAutoVisGroups( this );
pDoc->AddToAutoVisGroup( this );
}
//
// HACK: If we are now a decal, make sure we have a valid texture.
//
if (!strcmp(pszClass, "infodecal"))
{
if (!GetKeyValue("texture"))
{
SetKeyValue("texture", "clip");
}
}
CalculateTypeFlags();
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose: Assigns the next unique node ID to this entity.
//-----------------------------------------------------------------------------
void CMapEntity::AssignNodeID(void)
{
char szID[80];
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
itoa(pDoc->GetNextNodeID(), szID, 10);
SetKeyValue("nodeid", szID);
}
struct CClassNameFlagsMatcher
{
char const *m_pClassname;
int m_nFlagsToOR;
};
static CClassNameFlagsMatcher s_ClassFlagsTable[]={
{ "light_environment", ENTITY_FLAG_IS_LIGHT },
{ "light", ENTITY_FLAG_IS_LIGHT },
{ "light_spot", ENTITY_FLAG_IS_LIGHT },
{ "prop_static", ENTITY_FLAG_SHOW_IN_LPREVIEW2 },
{ "func_instance", ENTITY_FLAG_IS_INSTANCE },
};
void CMapEntity::CalculateTypeFlags( void )
{
m_EntityTypeFlags = 0;
const char *pszClassName = GetClassName();
if (pszClassName != NULL)
for(int i=0; i<NELEMS( s_ClassFlagsTable ); i++)
if ( ! stricmp( pszClassName, s_ClassFlagsTable[i].m_pClassname ) )
m_EntityTypeFlags |= s_ClassFlagsTable[i].m_nFlagsToOR;
}
void CMapEntity::SignalChanged( void )
{
if ( m_EntityTypeFlags & ENTITY_FLAG_IS_LIGHT )
SignalUpdate( EVTYPE_LIGHTING_CHANGED );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapEntity::EnsureUniqueNodeID(CMapWorld *pWorld)
{
bool bBuildNewNodeID = true;
int nOurNodeID = GetNodeID();
if (nOurNodeID != 0)
{
//
// We already have a node ID. Make sure that it is unique. If not,
// we need to generate a new one.
//
bBuildNewNodeID = false;
EnumChildrenPos_t pos;
CMapClass *pChild = pWorld->GetFirstDescendent(pos);
while (pChild != NULL)
{
CMapEntity *pEntity = dynamic_cast <CMapEntity *> (pChild);
if ((pEntity != NULL) && (pEntity != this))
{
int nThisNodeID = pEntity->GetNodeID();
if (nThisNodeID)
{
if (nThisNodeID == nOurNodeID)
{
bBuildNewNodeID = true;
break;
}
}
}
pChild = pWorld->GetNextDescendent(pos);
}
}
if (bBuildNewNodeID)
{
AssignNodeID();
}
}
//-----------------------------------------------------------------------------
// Purpose: Called after the entire map has been loaded. This allows the object
// to perform any linking with other map objects or to do other operations
// that require all world objects to be present.
//-----------------------------------------------------------------------------
void CMapEntity::PostloadWorld(CMapWorld *pWorld)
{
int nIndex;
//
// Set our origin from our "origin" key and discard the key.
//
const char *pszValue = m_KeyValues.GetValue("origin", &nIndex);
if (pszValue != NULL)
{
Vector Origin;
sscanf(pszValue, "%f %f %f", &Origin[0], &Origin[1], &Origin[2]);
SetOrigin(Origin);
}
//
// Set our angle from our "angle" key and discard the key.
//
pszValue = m_KeyValues.GetValue("angle", &nIndex);
if (pszValue != NULL)
{
ImportAngle(atoi(pszValue));
RemoveKey(nIndex);
}
//
// Set the class name from our "classname" key and discard the key.
// This also adds the helpers appropriate for the class.
//
pszValue = m_KeyValues.GetValue("classname", &nIndex);
if (pszValue != NULL)
{
//
// Copy the classname to a temp buffer because SetClass mucks with the
// keyvalues and our pointer might become bad.
//
char szClassName[MAX_CLASS_NAME_LEN];
strcpy(szClassName, pszValue);
SetClass(szClassName, true);
//
// Need to re-get the index of the classname key since it may have changed
// as a result of the above SetClass call.
//
pszValue = m_KeyValues.GetValue("classname", &nIndex);
if (pszValue != NULL)
{
RemoveKey(nIndex);
}
}
//
// Now that we have set the class, remove the origin key if this entity isn't
// supposed to expose it in the keyvalues list.
//
if (IsPlaceholder() && (!IsClass() || GetClass()->VarForName("origin") == NULL))
{
const char *pszValue = m_KeyValues.GetValue("origin", &nIndex);
if (pszValue != NULL)
{
RemoveKey(nIndex);
}
}
//
// Must do this after assigning the class.
//
if (IsNodeClass() && (GetKeyValue("nodeid") == NULL))
{
AssignNodeID();
}
// Set a reasonable default
Vector2D vecLogicalPos = GetLogicalPosition();
if ( vecLogicalPos.x == COORD_NOTINIT )
{
CMapDoc::GetActiveMapDoc()->GetDefaultNewLogicalPosition( vecLogicalPos );
SetLogicalPosition( vecLogicalPos );
}
//
// Call in all our children (some of which were created above).
//
CMapClass::PostloadWorld(pWorld);
CalculateTypeFlags();
}
//-----------------------------------------------------------------------------
// Purpose: Insures that the entity has all the helpers that it needs (and no more
// than it should) given its class.
//-----------------------------------------------------------------------------
void CMapEntity::UpdateHelpers(bool bLoading)
{
//
// If we have any helpers, delete them. Delete any solid children if we are
// a point class.
//
RemoveHelpers(IsPlaceholder() == TRUE);
//
// Add the helpers appropriate for our current class.
//
AddHelpersForClass(GetClass(), bLoading);
}
//-----------------------------------------------------------------------------
// Safely sets the move parent. Will assert and not set it if pEnt is equal to this ent,
// or if this ent is already a parent of pEnt.
//-----------------------------------------------------------------------------
void CMapEntity::SetMoveParent( CMapEntity *pEnt )
{
// Make sure pEnt is not already parented to (or identical to) me.
CMapEntity *pCur = pEnt;
for ( int i=0; i < 300; i++ )
{
if ( pCur == NULL )
{
break;
}
else if ( pCur == this )
{
Assert( !"SetMoveParent: recursive parenting!" );
m_pMoveParent = NULL;
return;
}
pCur = pCur->m_pMoveParent;
}
m_pMoveParent = pEnt;
}
//-----------------------------------------------------------------------------
// Purpose: Allows the entity to update its key values based on a change in one
// of its children. The child exposes the property as a key value pair.
// Input : pChild - The child whose property changed.
// szKey - The name of the property that changed.
// szValue - The new value of the property.
//-----------------------------------------------------------------------------
void CMapEntity::NotifyChildKeyChanged(CMapClass *pChild, const char *szKey, const char *szValue)
{
m_KeyValues.SetValue(szKey, szValue);
//
// Notify all our other non-solid children that a key has changed.
//
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pObject = m_Children.Element(pos);
if ((pObject != pChild) && (pChild != NULL) && (dynamic_cast<CMapSolid *>(pObject) == NULL))
{
pObject->OnParentKeyChanged(szKey, szValue);
}
}
CalcBounds();
CalculateTypeFlags();
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapEntity::DeleteKeyValue(LPCSTR pszKey)
{
char szOldValue[KEYVALUE_MAX_VALUE_LENGTH];
const char *pszOld = GetKeyValue(pszKey);
if (pszOld != NULL)
{
strcpy(szOldValue, pszOld);
}
else
{
szOldValue[0] = '\0';
}
CEditGameClass::DeleteKeyValue(pszKey);
OnKeyValueChanged(pszKey, szOldValue, "");
CalculateTypeFlags();
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapEntity::SetKeyValue(LPCSTR pszKey, LPCSTR pszValue)
{
//
// Get the current value so we can tell if it is changing.
//
char szOldValue[KEYVALUE_MAX_VALUE_LENGTH];
const char *pszOld = GetKeyValue(pszKey);
if (pszOld != NULL)
{
strcpy(szOldValue, pszOld);
}
else
{
szOldValue[0] = '\0';
}
CEditGameClass::SetKeyValue(pszKey, pszValue);
OnKeyValueChanged(pszKey, szOldValue, pszValue);
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose: Notifies the entity that it has been cloned.
// Input : pClone -
//-----------------------------------------------------------------------------
void CMapEntity::OnPreClone(CMapClass *pClone, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList)
{
CMapClass::OnPreClone(pClone, pWorld, OriginalList, NewList);
if (OriginalList.Count() == 1)
{
// dvs: TODO: make this FGD-driven instead of hardcoded, see also MapKeyFrame.cpp
// dvs: TODO: use letters of the alphabet between adjacent numbers, ie path2a path2b, etc.
if (!stricmp(GetClassName(), "path_corner") || !stricmp(GetClassName(), "path_track") || !stricmp(GetClassName(), "info_blob_spit_path") )
{
//
// Generate a new name for the clone.
//
CMapEntity *pNewEntity = dynamic_cast<CMapEntity*>(pClone);
Assert(pNewEntity != NULL);
if (!pNewEntity)
return;
// create a new targetname for the clone
char newName[128];
const char *oldName = GetKeyValue("targetname");
if (!oldName || oldName[0] == 0)
oldName = "path";
pWorld->GenerateNewTargetname(oldName, newName, sizeof(newName), true, NULL);
pNewEntity->SetKeyValue("targetname", newName);
}
}
if (IsNodeClass())
{
((CMapEntity *)pClone)->AssignNodeID();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pClone -
// pWorld -
// OriginalList -
// NewList -
//-----------------------------------------------------------------------------
void CMapEntity::OnClone(CMapClass *pClone, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList)
{
CMapClass::OnClone(pClone, pWorld, OriginalList, NewList);
if (OriginalList.Count() == 1)
{
if (!stricmp(GetClassName(), "path_corner") || !stricmp(GetClassName(), "path_track") || !stricmp(GetClassName(), "info_blob_spit_path") )
{
// dvs: TODO: make this FGD-driven instead of hardcoded, see also MapKeyFrame.cpp
// dvs: TODO: use letters of the alphabet between adjacent numbers, ie path2a path2b, etc.
CMapEntity *pNewEntity = dynamic_cast<CMapEntity*>(pClone);
Assert(pNewEntity != NULL);
if (!pNewEntity)
return;
const char *pFieldName = !stricmp(GetClassName(), "info_blob_spit_path") ? "NextPath" : "target";
// Point the clone at what we were pointing at.
const char *pszNext = GetKeyValue( pFieldName );
if (pszNext)
{
pNewEntity->SetKeyValue( pFieldName, pszNext );
}
// Point this path corner at the clone.
SetKeyValue( pFieldName, pNewEntity->GetKeyValue("targetname"));
}
}
if (IsNodeClass())
{
ReplaceNodeIDRefs(NewList, GetNodeID(), ((CMapEntity *)pClone)->GetNodeID());
}
}
//-----------------------------------------------------------------------------
// Purpose: Notifies the object that a copy of it is being pasted from the
// clipboard before the copy is added to the world.
// Input : pCopy - The copy of this object that is being added to the world.
// pSourceWorld - The world that the originals were in.
// pDestWorld - The world that the copies are being added to.
// OriginalList - The list of original objects that were copied.
// NewList - The list of copied.
//-----------------------------------------------------------------------------
void CMapEntity::OnPrePaste( CMapClass *pCopy, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList )
{
if (IsNodeClass())
{
// Generate a new node ID.
((CMapEntity *)pCopy)->AssignNodeID();
}
CMapClass::OnPrePaste(pCopy, pSourceWorld, pDestWorld, OriginalList, NewList);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pCopy -
// pSourceWorld -
// pDestWorld -
// OriginalList -
// NewList -
//-----------------------------------------------------------------------------
void CMapEntity::OnPaste(CMapClass *pCopy, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList)
{
if (IsNodeClass())
{
ReplaceNodeIDRefs(NewList, GetNodeID(), ((CMapEntity *)pCopy)->GetNodeID());
}
CMapClass::OnPaste(pCopy, pSourceWorld, pDestWorld, OriginalList, NewList);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pszKey -
// pszOldValue -
// pszValue -
//-----------------------------------------------------------------------------
void CMapEntity::OnKeyValueChanged(const char *pszKey, const char *pszOldValue, const char *pszValue)
{
// notify all our children that a key has changed
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pChild = m_Children.Element( pos );
if ( pChild != NULL )
{
pChild->OnParentKeyChanged( pszKey, pszValue );
}
}
//
// Changing our movement parent. Store a pointer to the movement parent
// for when we're playing animations.
//
if ( !stricmp(pszKey, "parentname") )
{
CMapWorld *pWorld = (CMapWorld *)GetWorldObject( this );
if (pWorld != NULL)
{
CMapEntity *pMoveParent = (CMapEntity *)UpdateDependency(m_pMoveParent, pWorld->FindEntityByName( pszValue));
SetMoveParent( pMoveParent );
}
}
//
// Changing our model - rebuild the helpers from scratch.
// dvs: this could probably go away - move support into the helper code.
//
else if (!stricmp(pszKey, "model"))
{
if (stricmp(pszOldValue, pszValue) != 0)
{
// We don't call SetKeyValue during VMF load.
UpdateHelpers(false);
}
}
//
// If our targetname has changed, we have to relink EVERYTHING, not
// just our dependents, because someone else may point to our new targetname.
//
else if (!stricmp(pszKey, "targetname") && (stricmp(pszOldValue, pszValue) != 0))
{
UpdateAllDependencies(this);
}
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this entity has any solid children. Entities of
// classes that are not in the FGD are considered solid entities if
// they have at least one solid child, point entities if not.
//-----------------------------------------------------------------------------
bool CMapEntity::HasSolidChildren(void)
{
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pChild = m_Children.Element(pos);
if ((dynamic_cast <CMapSolid *> (pChild)) != NULL)
{
return(true);
}
}
return(false);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CMapEntity::OnApply( void )
{
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pChild = m_Children.Element(pos);
if ( pChild )
{
pChild->OnApply();
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Called after this object is added to the world.
//
// NOTE: This function is NOT called during serialization. Use PostloadWorld
// to do similar bookkeeping after map load.
//
// Input : pWorld - The world that we have been added to.
//-----------------------------------------------------------------------------
void CMapEntity::OnAddToWorld(CMapWorld *pWorld)
{
CMapClass::OnAddToWorld(pWorld);
//
// If we are a node class, we must insure that we have a valid unique ID.
//
if (IsNodeClass())
{
EnsureUniqueNodeID(pWorld);
}
//
// If we have a targetname, relink all the targetname pointers in the world
// because someone might be looking for our targetname.
//
if (GetKeyValue("targetname") != NULL)
{
UpdateAllDependencies(this);
}
}
//-----------------------------------------------------------------------------
// Purpose: Called before this object is deleted from the world.
//
// Input : pWorld - The world that we have been added to.
// b
//-----------------------------------------------------------------------------
void CMapEntity::OnRemoveFromWorld(CMapWorld *pWorld, bool bNotifyChildren)
{
// Disconnect this now removed entity from the rest of the world
Connections_FixBad(false);
Upstream_FixBad();
CMapClass::OnRemoveFromWorld(pWorld, bNotifyChildren);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pObject - The object that changed.
//-----------------------------------------------------------------------------
void CMapEntity::OnNotifyDependent(CMapClass *pObject, Notify_Dependent_t eNotifyType)
{
CMapClass::OnNotifyDependent(pObject, eNotifyType);
if (eNotifyType == Notify_Removed)
{
//
// Check for our move parent going away.
//
if (pObject == m_pMoveParent)
{
CMapWorld *pWorld = (CMapWorld *)GetWorldObject(this);
const char *pszParentName = CEditGameClass::GetKeyValue("parentname");
if ((pWorld != NULL) && (pszParentName != NULL))
{
CMapEntity *pMoveParent = (CMapEntity *)UpdateDependency(m_pMoveParent, pWorld->FindEntityByName( pszParentName));
SetMoveParent( pMoveParent );
}
else
{
CMapEntity *pMoveParent = (CMapEntity *)UpdateDependency(m_pMoveParent, NULL);
SetMoveParent( pMoveParent );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Iterates through an object, and all it's children, looking for an
// entity with a matching key and value
// Input : key -
// value -
// Output : Returns a pointer to the entity found.
//-----------------------------------------------------------------------------
CMapEntity *CMapEntity::FindChildByKeyValue( LPCSTR key, LPCSTR value, bool *bIsInInstance, VMatrix *InstanceMatrix )
{
if ((key == NULL) || (value == NULL))
{
return(NULL);
}
int index;
LPCSTR val = CEditGameClass::GetKeyValue(key, &index);
if ( val && value && !stricmp(value, val) )
{
return this;
}
return CMapClass::FindChildByKeyValue( key, value, bIsInInstance, InstanceMatrix );
}
//-----------------------------------------------------------------------------
// Purpose: Returns a coordinate frame to render in, if the entity is animating
// Input : matrix -
// Output : returns true if a new matrix is returned, false if it is just the identity
//-----------------------------------------------------------------------------
bool CMapEntity::GetTransformMatrix( VMatrix& matrix )
{
bool gotMatrix = false;
// if we have a move parent, get its transformation matrix
if ( m_pMoveParent )
{
if ( m_pMoveParent == this )
{
Assert( !"Recursive parenting." );
}
else
{
gotMatrix = m_pMoveParent->GetTransformMatrix( matrix );
}
}
if ( m_pAnimatorChild )
{
// return a matrix that will transform any vector into our (animated) space
if ( gotMatrix )
{
// return ParentMatrix * OurMatrix
VMatrix tmpMat, animatorMat;
bool gotAnimMatrix = m_pAnimatorChild->GetTransformMatrix( animatorMat );
if ( !gotAnimMatrix )
{
// since we didn't get a new matrix from our child just return our parent's
return true;
}
matrix = matrix * animatorMat;
}
else
{
// no parent, we're at the top of the game
gotMatrix = m_pAnimatorChild->GetTransformMatrix( matrix );
}
}
return gotMatrix;
}
//-----------------------------------------------------------------------------
// Saves editor data
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::SaveEditorData(CChunkFile *pFile)
{
#ifndef SDK_BUILD
ChunkFileResult_t eResult = pFile->WriteKeyValueVector2("logicalpos", m_vecLogicalPosition);
if (eResult != ChunkFile_Ok)
return eResult;
#endif // SDK_BUILD
return BaseClass::SaveEditorData( pFile );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapEntity::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo)
{
//
// Check rules before saving this object.
//
if (!pSaveInfo->ShouldSaveObject(this))
{
return(ChunkFile_Ok);
}
ChunkFileResult_t eResult = ChunkFile_Ok;
//
// If it's a solidentity but it doesn't have any solids,
// don't save it.
//
if (!IsPlaceholder() && !m_Children.Count())
{
return(ChunkFile_Ok);
}
//
// If we are hidden, place this object inside of a hidden chunk.
//
if (!IsVisible())
{
eResult = pFile->BeginChunk("hidden");
}
//
// Begin this entity's scope.
//
eResult = pFile->BeginChunk("entity");
//
// Save the entity's ID.
//
if (eResult == ChunkFile_Ok)
{
eResult = pFile->WriteKeyValueInt("id", GetID());
}
//
// Save our keys.
//
if (eResult == ChunkFile_Ok)
{
eResult = CEditGameClass::SaveVMF(pFile, pSaveInfo);
}
//
// If this is a point entity of an unknown type or a point entity that doesn't
// declare an origin key, save our origin.
//
if (IsPlaceholder() && (!IsClass() || GetClass()->VarForName("origin") == NULL))
{
char szOrigin[80];
sprintf(szOrigin, "%g %g %g", (double)m_Origin[0], (double)m_Origin[1], (double)m_Origin[2]);
pFile->WriteKeyValue("origin", szOrigin);
}
//
// Save all our descendents.
//
eResult = ChunkFile_Ok;
EnumChildrenPos_t pos;
CMapClass *pChild = GetFirstDescendent(pos);
while ((pChild != NULL) && (eResult == ChunkFile_Ok))
{
if ( pChild->ShouldSerialize() )
{
eResult = pChild->SaveVMF(pFile, pSaveInfo);
}
pChild = GetNextDescendent(pos);
}
//
// Save our base class' information within our chunk.
//
if (eResult == ChunkFile_Ok)
{
eResult = CMapClass::SaveVMF(pFile, pSaveInfo);
}
//
// Save custom model information
//
eResult = VmfSaveVmfEntityHandlers( pFile, static_cast< IMapEntity_Type_t * >( this ),
static_cast< IMapEntity_SaveInfo_t * >( pSaveInfo ) );
//
// End this entity's scope.
//
if (eResult == ChunkFile_Ok)
{
pFile->EndChunk();
}
//
// End the hidden chunk if we began it.
//
if (!IsVisible())
{
eResult = pFile->EndChunk();
}
return(eResult);
}
//-----------------------------------------------------------------------------
// Purpose: Overloaded to use the color from our FGD definition.
// Output : Returns true if the color was specified by this call, false if not.
//-----------------------------------------------------------------------------
bool CMapEntity::UpdateObjectColor()
{
if (!BaseClass::UpdateObjectColor())
{
if (IsClass())
{
color32 rgbColor = m_pClass->GetColor();
SetRenderColor(rgbColor);
return true;
}
}
else
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pWorld -
// pObject -
//-----------------------------------------------------------------------------
void CMapEntity::UpdateDependencies(CMapWorld *pWorld, CMapClass *pObject)
{
CMapClass::UpdateDependencies(pWorld, pObject);
//
// If we have a movement parent, relink to our movement parent.
//
const char *pszParentName = CEditGameClass::GetKeyValue("parentname");
if (pszParentName != NULL)
{
CMapEntity *pMoveParent = (CMapEntity *)UpdateDependency(m_pMoveParent, pWorld->FindEntityByName( pszParentName));
SetMoveParent( pMoveParent );
}
else
{
CMapEntity *pMoveParent = (CMapEntity *)UpdateDependency(m_pMoveParent, NULL);
SetMoveParent( pMoveParent );
}
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
if ( pDoc && !pDoc->IsLoading() )
{
// Update any downstream/upstream connections objects associated with this entity
Connections_FixBad();
Upstream_FixBad();
}
}
//-----------------------------------------------------------------------------
// Purpose: Places the entity properly on a plane surface, at a given location
// Input: pos - position on the plane
// plane - surface plane to align to
// align - alignment type (top, bottom)
// Output:
//-----------------------------------------------------------------------------
#define ALIGN_EPSILON 1 // World units
void CMapEntity::AlignOnPlane( Vector& pos, PLANE *plane, alignType_e align )
{
float fOffset = 0.0f;
Vector vecNewPos;
//Depending on the alignment type, get the offset from the surface
switch ( align )
{
case ALIGN_BOTTOM:
fOffset = m_Origin[2] - m_Render2DBox.bmins[2];
break;
case ALIGN_TOP:
fOffset = m_Render2DBox.bmaxs[2] - m_Origin[2];
break;
}
//Push our point out and away from this surface
VectorMA( pos, fOffset + ALIGN_EPSILON, plane->normal, vecNewPos );
//Update the entity and children
SetOrigin( vecNewPos );
SignalChanged();
}
//-----------------------------------------------------------------------------
// Purpose: Looks for an input with a given name in the entity list. ALL entities
// in the list must have the given input for a match to be found.
// Input : szInput - Name of the input.
// Output : Returns true if the input name was found in all entities, false if not.
//-----------------------------------------------------------------------------
bool MapEntityList_HasInput(const CMapEntityList *pList, const char *szInput, InputOutputType_t eType)
{
GDclass *pLastClass = NULL;
FOR_EACH_OBJ( *pList, pos )
{
const CMapEntity *pEntity = pList->Element(pos).GetObject();
GDclass *pClass = pEntity ? pEntity->GetClass() : NULL;
if ((pClass != pLastClass) && (pClass != NULL))
{
CClassInput *pInput = pClass->FindInput(szInput);
if (!pInput)
{
return false;
}
if ((eType != iotInvalid) && (pInput->GetType() != eType))
{
return false;
}
//
// Cheap optimization to help minimize redundant checks.
//
pLastClass = pClass;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a pointer to the object that should be added to the selection
// list because this object was clicked on with a given selection mode.
// Input : eSelectMode -
//-----------------------------------------------------------------------------
CMapClass *CMapEntity::PrepareSelection(SelectMode_t eSelectMode)
{
//
// Select up the hierarchy when in Groups selection mode if we belong to a group.
//
if ((eSelectMode == selectGroups) && (m_pParent != NULL) && !IsWorldObject(m_pParent))
{
return GetParent()->PrepareSelection(eSelectMode);
}
//
// Don't select solid entities when in Solids selection mode. We'll select
// their solid children.
//
if ((eSelectMode == selectSolids) && !IsPlaceholder())
{
return NULL;
}
return this;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pRender -
//-----------------------------------------------------------------------------
void CMapEntity::Render2D(CRender2D *pRender)
{
// Render all our children (helpers & solids)
BaseClass::Render2D(pRender);
CMapView2D *pView = (CMapView2D*)pRender->GetView();
Vector vecMins, vecMaxs;
GetRender2DBox(vecMins, vecMaxs);
if ( pRender->GetInstanceRendering() )
{
Vector vecExpandedMins, vecExpandedMaxs;
pRender->TransformInstanceAABB( vecMins, vecMaxs, vecExpandedMins, vecExpandedMaxs );
vecMins = vecExpandedMins;
vecMaxs = vecExpandedMaxs;
}
Vector2D pt, pt2;
pView->WorldToClient(pt, vecMins);
pView->WorldToClient(pt2, vecMaxs);
color32 rgbColor = GetRenderColor( pRender );
pRender->SetDrawColor( rgbColor.r, rgbColor.g, rgbColor.b );
// Render the entity's name and class name if enabled.
if (s_bShowEntityNames && pView->GetZoom() >= 1)
{
pRender->SetTextColor( rgbColor.r, rgbColor.g, rgbColor.b );
const char *pszTargetName = GetKeyValue("targetname");
if (pszTargetName != NULL)
{
pRender->DrawText(pszTargetName, pt.x, pt.y + 2, CRender2D::TEXT_JUSTIFY_BOTTOM );
}
const char *pszClassName = GetClassName();
if (pszClassName != NULL)
{
pRender->DrawText(pszClassName, pt.x, pt2.y - 2, CRender2D::TEXT_JUSTIFY_TOP );
}
}
//
// Draw the connections between entities and their targets if enabled.
//
if (s_bShowEntityConnections)
{
LPCTSTR pszTarget = GetKeyValue("target");
if (pszTarget != NULL)
{
CMapWorld *pWorld = GetWorldObject(this);
MDkeyvalue kv("targetname", pszTarget);
CMapObjectList FoundEntities;
FoundEntities.RemoveAll();
pWorld->EnumChildren((ENUMMAPCHILDRENPROC)FindKeyValue, (DWORD)&kv, MAPCLASS_TYPE(CMapEntity));
Vector vCenter1,vCenter2;
GetBoundsCenter( vCenter1 );
FOR_EACH_OBJ( FoundEntities, p )
{
CMapClass *pMapClass = (CUtlReference< CMapClass >)FoundEntities.Element(p);
CMapClass *pEntity = (CMapEntity *)pMapClass;
pEntity->GetBoundsCenter(vCenter2);
pRender->DrawLine( vCenter1, vCenter2 );
}
}
}
// Draw the forward vector if we have an "angles" key and we're selected.
// HACK: don't draw the forward vector for lights, they negate pitch. The model helper will handle it.
if ((GetSelectionState() != SELECT_NONE) &&
(!GetClassName() || (strnicmp(GetClassName(), "light_", 6) != 0)) &&
(GetKeyValue("angles") != NULL))
{
Vector vecOrigin;
GetOrigin(vecOrigin);
QAngle vecAngles;
GetAngles(vecAngles);
Vector vecForward;
AngleVectors(vecAngles, &vecForward);
pRender->SetDrawColor( 255, 255, 0 );
pRender->DrawLine(vecOrigin, vecOrigin + vecForward * 24);
}
}
//-----------------------------------------------------------------------------
// Gets the 2D logical view bounding box
//-----------------------------------------------------------------------------
void CMapEntity::GetRenderLogicalBox( Vector2D &mins, Vector2D &maxs )
{
mins.x = m_vecLogicalPosition.x;
maxs.x = m_vecLogicalPosition.x + LOGICAL_BOX_WIDTH + LOGICAL_BOX_CONNECTOR_INPUT_WIDTH + LOGICAL_BOX_CONNECTOR_OUTPUT_WIDTH;
mins.y = m_vecLogicalPosition.y;
maxs.y = m_vecLogicalPosition.y + LOGICAL_BOX_HEIGHT;
}
//-----------------------------------------------------------------------------
// Logical position accessor
//-----------------------------------------------------------------------------
const Vector2D& CMapEntity::GetLogicalPosition( )
{
return m_vecLogicalPosition;
}
void CMapEntity::SetLogicalPosition( const Vector2D &vecPosition )
{
m_vecLogicalPosition = vecPosition;
}
//-----------------------------------------------------------------------------
// Returns a logical position
//-----------------------------------------------------------------------------
void CMapEntity::GetLogicalConnectionPosition( LogicalConnection_t i, Vector2D &vecPosition )
{
Vector2D vecMins, vecMaxs;
GetRenderLogicalBox( vecMins, vecMaxs );
vecPosition.y = ( vecMins.y + vecMaxs.y ) * 0.5f;
if ( i == LOGICAL_CONNECTION_INPUT )
{
vecPosition.x = vecMins.x;
}
else
{
vecPosition.x = vecMaxs.x;
}
}
//-----------------------------------------------------------------------------
// Renders into the logical view
//-----------------------------------------------------------------------------
void CMapEntity::RenderLogical( CRender2D *pRender )
{
// Render all our children (helpers & solids)
BaseClass::RenderLogical(pRender);
Vector2D vecMins, vecMaxs;
GetRenderLogicalBox( vecMins, vecMaxs );
Vector2D vecBoxMins = vecMins;
Vector2D vecBoxMaxs = vecMaxs;
vecBoxMins.x += LOGICAL_BOX_CONNECTOR_INPUT_WIDTH;
vecBoxMaxs.x -= LOGICAL_BOX_CONNECTOR_OUTPUT_WIDTH;
// Define the entity highlight/lowlight edges
Vector2D vecInnerMins = vecBoxMins, vecInnerMaxs = vecBoxMaxs;
vecInnerMins.x += LOGICAL_BOX_INNER_OFFSET;
vecInnerMins.y += LOGICAL_BOX_INNER_OFFSET;
vecInnerMaxs.x -= LOGICAL_BOX_INNER_OFFSET;
vecInnerMaxs.y -= LOGICAL_BOX_INNER_OFFSET;
// Get the entity render color
color32 rgbColor = GetRenderColor( pRender );
color32 rgbHighlight = {7*rgbColor.r/8, 7*rgbColor.g/8, 7*rgbColor.b/8, 255 };
color32 rgbLowlight = {5*rgbColor.r/8, 5*rgbColor.g/8, 5*rgbColor.b/8, 255 };
color32 rgbEdgeColor = {3*rgbColor.r/8, 3*rgbColor.g/8, 3*rgbColor.b/8, 255 };
color32 rgbInterior = {2*rgbColor.r/8, 2*rgbColor.g/8, 2*rgbColor.b/8, 255 };
// Draw an inside UpperLeft highlight rect (leading edge highlight)
pRender->SetDrawColor( rgbHighlight.r, rgbHighlight.g, rgbHighlight.b );
pRender->DrawRectangle( Vector( vecBoxMins.x, vecBoxMins.y, 0.0f ), Vector( vecBoxMaxs.x, vecBoxMaxs.y, 0.0f ), true, 0 );
// Draw an inside LowerRight lowlight rect (trailing edge lowlight)
pRender->SetDrawColor( rgbLowlight.r, rgbLowlight.g, rgbLowlight.b );
pRender->DrawRectangle( Vector( vecInnerMins.x, vecBoxMins.y, 0.0f ), Vector( vecBoxMaxs.x, vecInnerMaxs.y, 0.0f ), true, 0 );
// Draw an outside border rect in the entities render color
pRender->SetDrawColor( rgbEdgeColor.r, rgbEdgeColor.g, rgbEdgeColor.b );
pRender->DrawRectangle( Vector( vecBoxMins.x, vecBoxMins.y, 0.0f ), Vector( vecBoxMaxs.x, vecBoxMaxs.y, 0.0f ), false, 0 );
// Draw the small diagonals connecting the outer and inner corners
pRender->DrawLine( Vector( vecBoxMins.x, vecBoxMins.y, 0.0f ), Vector( vecBoxMaxs.x, vecBoxMaxs.y, 0.0f ) );
pRender->DrawLine( Vector( vecBoxMins.x, vecBoxMaxs.y, 0.0f ), Vector( vecBoxMaxs.x, vecBoxMins.y, 0.0f ) );
// Draw interior background first
pRender->SetDrawColor( rgbInterior.r, rgbInterior.g, rgbInterior.b );
pRender->DrawRectangle( Vector( vecInnerMins.x, vecInnerMins.y, 0.0f ), Vector( vecInnerMaxs.x, vecInnerMaxs.y, 0.0f ), true, 0 );
// Draws the sprite helper(s) (if it has them)
bool bFoundSpriteHelper = false;
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pMapClass = (CUtlReference< CMapClass >)m_Children[pos];
CMapSprite *pSprite = dynamic_cast<CMapSprite*>( pMapClass );
if ( pSprite )
{
// Render the sprite on top of the background
pSprite->RenderLogicalAt( pRender, vecInnerMins, vecInnerMaxs );
bFoundSpriteHelper = true;
}
}
// Fill in the interior with entity color if no sprite was found
if ( !bFoundSpriteHelper )
{
// Redraw the interior with the entity's render color
pRender->SetDrawColor( rgbColor.r, rgbColor.g, rgbColor.b );
pRender->DrawRectangle( Vector( vecInnerMins.x, vecInnerMins.y, 0.0f ), Vector( vecInnerMaxs.x, vecInnerMaxs.y, 0.0f ), true, 0 );
// Put an inner border around the entity color block
pRender->SetDrawColor( rgbEdgeColor.r, rgbEdgeColor.g, rgbEdgeColor.b );
pRender->DrawRectangle( Vector( vecInnerMins.x, vecInnerMins.y, 0.0f ), Vector( vecInnerMaxs.x, vecInnerMaxs.y, 0.0f ), false, 0 );
}
// Draw the rest of the entity in the entity color
pRender->SetDrawColor( rgbColor.r, rgbColor.g, rgbColor.b );
// Draws the connectors
float flConnectorY = ( vecMins.y + vecMaxs.y ) * 0.5f;
pRender->DrawCircle( Vector( vecMins.x + LOGICAL_BOX_CONNECTOR_RADIUS, flConnectorY, 0.0f ), LOGICAL_BOX_CONNECTOR_RADIUS );
pRender->MoveTo( Vector( vecMins.x + 2 * LOGICAL_BOX_CONNECTOR_RADIUS, flConnectorY, 0.0f ) );
pRender->DrawLineTo( Vector( vecBoxMins.x, flConnectorY, 0.0f ) );
pRender->MoveTo( Vector( vecBoxMaxs.x, flConnectorY, 0.0f ) );
pRender->DrawLineTo( Vector( vecMaxs.x - LOGICAL_BOX_ARROW_LENGTH, flConnectorY, 0.0f ) );
pRender->DrawLineTo( Vector( vecMaxs.x - LOGICAL_BOX_ARROW_LENGTH, flConnectorY + LOGICAL_BOX_ARROW_HEIGHT, 0.0f ) );
pRender->DrawLineTo( Vector( vecMaxs.x, flConnectorY, 0.0f ) );
pRender->DrawLineTo( Vector( vecMaxs.x - LOGICAL_BOX_ARROW_LENGTH, flConnectorY - LOGICAL_BOX_ARROW_HEIGHT, 0.0f ) );
pRender->DrawLineTo( Vector( vecMaxs.x - LOGICAL_BOX_ARROW_LENGTH, flConnectorY, 0.0f ) );
// Stop drawing the text once the entity itself gets too small.
Vector2D pt, pt2;
pRender->GetView()->WorldToClient( pt, Vector( vecBoxMins.x, vecBoxMins.y, 0.0f ) );
pRender->GetView()->WorldToClient( pt2, Vector( vecBoxMaxs.x, vecBoxMaxs.y, 0.0f ) );
if ( fabs( pt.y - pt2.y ) < 32 )
return;
// Render the entity's name and class name if enabled.
pRender->SetTextColor( rgbColor.r, rgbColor.g, rgbColor.b );
// Draw the inputs and outputs
const char *pszTargetName = GetKeyValue("targetname");
if (pszTargetName != NULL)
{
pRender->DrawText( pszTargetName, Vector2D( (vecMins.x+vecMaxs.x)/2, vecMaxs.y ), 0, -1, CRender2D::TEXT_JUSTIFY_TOP | CRender2D::TEXT_JUSTIFY_HORZ_CENTER );
}
if ( fabs( pt.y - pt2.y ) < 50 )
return;
const char *pszClassName = GetClassName();
if (pszClassName != NULL)
{
pRender->DrawText( pszClassName, Vector2D( (vecMins.x+vecMaxs.x)/2, vecMins.y ), 0, 1, CRender2D::TEXT_JUSTIFY_BOTTOM | CRender2D::TEXT_JUSTIFY_HORZ_CENTER );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether this entity snaps to half grid or not. Some entities,
// such as hinges, need to snap to a 0.5 grid to center on geometry.
//-----------------------------------------------------------------------------
bool CMapEntity::ShouldSnapToHalfGrid()
{
return (GetClass() && GetClass()->ShouldSnapToHalfGrid());
}
//-----------------------------------------------------------------------------
// Purpose: Returns the integer value of the nodeid key of this entity.
//-----------------------------------------------------------------------------
int CMapEntity::GetNodeID(void)
{
int nNodeID = 0;
const char *pszNodeID = GetKeyValue("nodeid");
if (pszNodeID)
{
nNodeID = atoi(pszNodeID);
}
return nNodeID;
}
//-----------------------------------------------------------------------------
// Returns whether this object intersects the given cordon bounds.
// Return true to keep the object, false to cull it.
//-----------------------------------------------------------------------------
bool CMapEntity::IsIntersectingCordon(const Vector &vecMins, const Vector &vecMaxs)
{
// Point entities are culled by their origin, not by their bounding box.
// An exception to that is swept hulls, such as ladders, that are more like solid ents.
if ( IsPointClass() && !IsSweptHullClass( this ) )
{
Vector vecOrigin;
GetOrigin(vecOrigin);
return IsPointInBox(vecOrigin, vecMins, vecMaxs);
}
return IsIntersectingBox(vecMins, vecMaxs);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pView -
// vecPoint -
// nHitData -
// Output :
//-----------------------------------------------------------------------------
bool CMapEntity::HitTest2D(CMapView2D *pView, const Vector2D &point, HitInfo_t &HitData)
{
if ( !IsVisible() )
return false;
if ( BaseClass::HitTest2D(pView, point, HitData) )
return true;
//
// Only check point entities; brush entities are selected via their brushes.
//
if ( !IsPointClass() )
return false;
// First check center X.
Vector vecCenter, vecViewPoint;
GetBoundsCenter(vecCenter);
Vector2D vecClientCenter;
pView->WorldToClient(vecClientCenter, vecCenter);
pView->GetCamera()->GetViewPoint( vecViewPoint );
HitData.pObject = this;
HitData.nDepth = vecViewPoint[pView->axThird]-vecCenter[pView->axThird];
if ( pView->CheckDistance( point, vecClientCenter, HANDLE_RADIUS) )
{
HitData.uData = 0;
return true;
}
else if (!Options.view2d.bSelectbyhandles)
{
//
// See if any edges of the bbox are within a certain distance from the the point.
//
int iSelUnits = 2;
int x1 = point.x - iSelUnits;
int x2 = point.x + iSelUnits;
int y1 = point.y - iSelUnits;
int y2 = point.y + iSelUnits;
Vector vecMins;
Vector vecMaxs;
GetRender2DBox(vecMins, vecMaxs);
Vector2D vecClientMins;
Vector2D vecClientMaxs;
pView->WorldToClient(vecClientMins, vecMins);
pView->WorldToClient(vecClientMaxs, vecMaxs);
Vector2D vecEdges[4] =
{
Vector2D(vecClientMins.x, vecClientMins.y),
Vector2D(vecClientMaxs.x, vecClientMins.y),
Vector2D(vecClientMaxs.x, vecClientMaxs.y),
Vector2D(vecClientMins.x, vecClientMaxs.y),
};
for (int i = 0; i < 4; i++)
{
if (IsLineInside(vecEdges[i], vecEdges[(i + 1) % 4], x1, y1, x2, y2))
{
HitData.uData = i+1;
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Hit test for the logical view
//-----------------------------------------------------------------------------
bool CMapEntity::HitTestLogical( CMapViewLogical *pView, const Vector2D &vecPoint, HitInfo_t &hitData )
{
if ( !IsVisible() || !IsLogical() || !IsVisibleLogical() )
return false;
if ( BaseClass::HitTestLogical( pView, vecPoint, hitData ) )
return true;
// Is the point inside the box?
Vector2D vecMins;
Vector2D vecMaxs;
GetRenderLogicalBox( vecMins, vecMaxs );
Vector2D vecClientMins;
Vector2D vecClientMaxs;
pView->WorldToClient(vecClientMins, vecMins);
pView->WorldToClient(vecClientMaxs, vecMaxs);
NormalizeBox( vecClientMins, vecClientMaxs );
if ( IsPointInside( vecPoint, vecClientMins, vecClientMaxs ) )
{
hitData.pObject = this;
hitData.uData = 0;
hitData.nDepth = 0.0f;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Is this logical?
//-----------------------------------------------------------------------------
bool CMapEntity::IsLogical(void)
{
GDclass *pClass = GetClass();
return pClass && (( pClass->GetInputCount() > 0 ) || ( pClass->GetOutputCount() > 0 )) || (m_Connections.Count() || m_Upstream.Count());
}
//-----------------------------------------------------------------------------
// Is it visible in the logical view?
//-----------------------------------------------------------------------------
bool CMapEntity::IsVisibleLogical(void)
{
return IsVisible();
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this entity's name matches the given name, considering
// wildcards.
// Input : szName -
//-----------------------------------------------------------------------------
bool CMapEntity::NameMatches(const char *szName) const
{
const char *pszTargetName = GetKeyValue( "targetname" );
if (pszTargetName)
{
return !CompareEntityNames(pszTargetName, szName);
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this entity's classname matches the given name, considering
// wildcards.
// Input : szName -
//-----------------------------------------------------------------------------
bool CMapEntity::ClassNameMatches(const char *szName) const
{
const char *pszClassName = GetClassName();
if (pszClassName)
{
return !CompareEntityNames(pszClassName, szName);
}
return false;
}