//===== Copyright © 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 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 (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(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(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 " " // in it. Otherwise returns a buffer with "" 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 (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; iGetFirstDescendent(pos); while (pChild != NULL) { CMapEntity *pEntity = dynamic_cast (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(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(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(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 (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( 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; }