summaryrefslogtreecommitdiff
path: root/hammer/mapentity.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /hammer/mapentity.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'hammer/mapentity.cpp')
-rw-r--r--hammer/mapentity.cpp2418
1 files changed, 2418 insertions, 0 deletions
diff --git a/hammer/mapentity.cpp b/hammer/mapentity.cpp
new file mode 100644
index 0000000..536856f
--- /dev/null
+++ b/hammer/mapentity.cpp
@@ -0,0 +1,2418 @@
+//========= Copyright 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"
+
+// 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_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(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 *pClassLocal = GetClass();
+
+ //
+ // For every helper in the class definition...
+ //
+ int nHelperCount = pClassLocal->GetHelperCount();
+ for (int i = 0; i < nHelperCount; i++)
+ {
+ CHelperInfo *pHelperInfo = pClassLocal->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 = pClassLocal->GetVariableCount();
+ for (int i = 0; i < nVarCount; i++)
+ {
+ GDinputvariable *pVar = pClassLocal->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.spr");
+
+ 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
+ {
+ V_strcpy_safe( 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);
+
+ 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));
+ }
+ 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))
+ {
+ 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)
+ {
+ V_strcpy_safe(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"))
+ {
+ //
+ // 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"))
+ {
+ // 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;
+
+ // Point the clone at what we were pointing at.
+ const char *pszNext = GetKeyValue("target");
+ if (pszNext)
+ {
+ pNewEntity->SetKeyValue("target", pszNext);
+ }
+
+ // Point this path corner at the clone.
+ SetKeyValue("target", 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);
+ }
+
+ //
+ // 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 )
+ {
+ CMapEntity *pEntity = pList->Element(pos);
+ GDclass *pClass = pEntity->GetClass();
+ 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 FoundEntitiesTarget;
+ FoundEntitiesTarget.RemoveAll();
+ pWorld->EnumChildren((ENUMMAPCHILDRENPROC)FindKeyValue, (DWORD)&kv, MAPCLASS_TYPE(CMapEntity));
+
+ Vector vCenter1,vCenter2;
+ GetBoundsCenter( vCenter1 );
+
+ FOR_EACH_OBJ( FoundEntitiesTarget, p )
+ {
+ CMapClass *pEntity = (CMapEntity *)FoundEntitiesTarget.Element(p);
+ 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 = {(byte)(7*rgbColor.r/8), (byte)(7*rgbColor.g/8), (byte)(7*rgbColor.b/8), (byte)255 };
+ color32 rgbLowlight = { (byte)(5*rgbColor.r/8), (byte)(5*rgbColor.g/8), (byte)(5*rgbColor.b/8), (byte)255 };
+ color32 rgbEdgeColor = { (byte)(3*rgbColor.r/8), (byte)(3*rgbColor.g/8), (byte)(3*rgbColor.b/8), (byte)255 };
+ color32 rgbInterior = { (byte)(2*rgbColor.r/8), (byte)(2*rgbColor.g/8), (byte)(2*rgbColor.b/8), (byte)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 )
+ {
+ CMapSprite *pSprite = dynamic_cast<CMapSprite*>( m_Children[pos] );
+ 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;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether this object should be hidden based on the given
+// cordon bounds.
+// Output : Returns true to cull the object, false to keep it.
+//-----------------------------------------------------------------------------
+bool CMapEntity::IsCulledByCordon(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 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 char *pszClassName = GetClassName();
+ if (pszClassName)
+ {
+ return !CompareEntityNames(pszClassName, szName);
+ }
+
+ return false;
+}