diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /hammer/mapworld.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'hammer/mapworld.cpp')
| -rw-r--r-- | hammer/mapworld.cpp | 1965 |
1 files changed, 1965 insertions, 0 deletions
diff --git a/hammer/mapworld.cpp b/hammer/mapworld.cpp new file mode 100644 index 0000000..5a33689 --- /dev/null +++ b/hammer/mapworld.cpp @@ -0,0 +1,1965 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "stdafx.h" +#include "generichash.h" +#include "CullTreeNode.h" +#include "GlobalFunctions.h" +#include "MainFrm.h" +#include "MapDefs.h" +#include "MapDoc.h" // dvs: think of a way around the world knowing about the doc +#include "MapEntity.h" +#include "MapGroup.h" +#include "MapSolid.h" +#include "MapWorld.h" +#include "SaveInfo.h" +#include "StatusBarIDs.h" +#include "VisGroup.h" +#include "hammer.h" +#include "Worldsize.h" +#include "MapOverlay.h" +#include "Manifest.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +#pragma warning(disable:4244) + + +class CCullTreeNode; + + +IMPLEMENT_MAPCLASS(CMapWorld) + + +struct SaveLists_t +{ + CMapObjectList Solids; + CMapObjectList Entities; + CMapObjectList Groups; +}; + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSolid - +// *pList - +// Output : static BOOL +//----------------------------------------------------------------------------- +static BOOL AddUsedTextures(CMapSolid *pSolid, CUsedTextureList *pList) +{ + if (!pSolid->IsVisible()) + return TRUE; + + int nFaces = pSolid->GetFaceCount(); + IEditorTexture *pLastTex = NULL; + int nLastElement = 0; + + for (int i = 0; i < nFaces; i++) + { + CMapFace *pFace = pSolid->GetFace(i); + + UsedTexture_t Tex; + Tex.pTex = pFace->GetTexture(); + Tex.nUsageCount = 0; + + if (Tex.pTex != NULL) + { + if (Tex.pTex != pLastTex) + { + int nElement = pList->Find(Tex.pTex); + if (nElement == -1) + { + nElement = pList->AddToTail(Tex); + } + + nLastElement = nElement; + pLastTex = Tex.pTex; + } + + pList->Element(nLastElement).nUsageCount++; + } + } + + return TRUE; +} + + +static BOOL AddOverlayTextures(CMapOverlay *pOverlay, CUsedTextureList *pList) +{ + if (!pOverlay->IsVisible()) + return TRUE; + + UsedTexture_t Tex; + Tex.pTex = pOverlay->GetMaterial(); + Tex.nUsageCount = 0; + + if (Tex.pTex != NULL) + { + int nElement = pList->Find(Tex.pTex); + if (nElement == -1) + nElement = pList->AddToTail(Tex); + + pList->Element(nElement).nUsageCount++; + } + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether the two boxes intersect. +// Input : mins1 - +// maxs1 - +// mins2 - +// maxs2 - +//----------------------------------------------------------------------------- +bool BoxesIntersect(Vector const &mins1, Vector const &maxs1, Vector const &mins2, Vector const &maxs2) +{ + if ((maxs1[0] < mins2[0]) || (mins1[0] > maxs2[0]) || + (maxs1[1] < mins2[1]) || (mins1[1] > maxs2[1]) || + (maxs1[2] < mins2[2]) || (mins1[2] > maxs2[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. Initializes data members. +//----------------------------------------------------------------------------- +CMapWorld::CMapWorld( void ) +{ + +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. Initializes data members. +//----------------------------------------------------------------------------- +CMapWorld::CMapWorld( CMapDoc *pOwningDocument ) +{ + // + // Make sure subsequent UpdateBounds() will be effective. + // + m_Render2DBox.ResetBounds(); + Vector pt( 0, 0, 0 ); + m_Render2DBox.UpdateBounds(pt); + + SetClass("worldspawn"); + m_pCullTree = NULL; + + m_nNextFaceID = 1; // Face IDs start at 1. An ID of 0 means no ID. + + // create the world displacement manager + m_pWorldDispMgr = CreateWorldEditDispMgr(); + + m_pOwningDocument = pOwningDocument; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor. Deletes all paths in the world and the culling tree. +//----------------------------------------------------------------------------- +CMapWorld::~CMapWorld(void) +{ + // Delete paths. + m_Paths.PurgeAndDeleteElements(); + + // + // Delete the culling tree. + // + CullTree_Free(); + + // destroy the world displacement manager + DestroyWorldEditDispMgr( &m_pWorldDispMgr ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Overridden to maintain the culling tree. Root level children of the +// world are kept in the culling tree. +// Input : pChild - object to add as a child. +//----------------------------------------------------------------------------- +void CMapWorld::AddChild(CMapClass *pChild) +{ + CMapClass::AddChild(pChild); + + // + // Add the object to the culling tree. + // + if (m_pCullTree != NULL) + { + m_pCullTree->AddCullTreeObjectRecurse(pChild); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: The sole way to add an object to the world. +// +// NOTE: Do not call this during file load!! Similar (but different) +// bookkeeping is done in PostloadWorld during serialization. +// +// Input : pObject - object to add to the world. +// pParent - object to use as the new object's parent. +//----------------------------------------------------------------------------- +void CMapWorld::AddObjectToWorld(CMapClass *pObject, CMapClass *pParent) +{ + Assert(pObject != NULL); + if (pObject == NULL) + { + return; + } + + // + // Link the object into the tree. + // + if (pParent == NULL) + { + pParent = this; + } + + pParent->AddChild(pObject); + + // + // If this object or any of its children are entities, add the entities + // to our optimized list of entities. + // + EntityList_Add(pObject); + + // + // Notify the object that it has been added to the world. + // + pObject->OnAddToWorld(this); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sorts all the objects in the world into three lists: entities, solids, +// and groups. These lists are then serialized in SaveVMF. +// Input : pSaveLists - Receives lists of objects. +//----------------------------------------------------------------------------- +BOOL CMapWorld::BuildSaveListsCallback(CMapClass *pObject, SaveLists_t *pSaveLists) +{ + CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pObject); + if (pEntity != NULL) + { + pSaveLists->Entities.AddToTail(pEntity); + return(TRUE); + } + + CMapSolid *pSolid = dynamic_cast<CMapSolid *>(pObject); + if (pSolid != NULL) + { + pSaveLists->Solids.AddToTail(pSolid); + return(TRUE); + } + + CMapGroup *pGroup = dynamic_cast<CMapGroup *>(pObject); + if (pGroup != NULL) + { + pSaveLists->Groups.AddToTail(pGroup); + return(TRUE); + } + + return(TRUE); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : CMapClass +//----------------------------------------------------------------------------- +CMapClass *CMapWorld::Copy(bool bUpdateDependencies) +{ + CMapWorld *pWorld = new CMapWorld; + pWorld->CopyFrom(this, bUpdateDependencies); + return pWorld; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pobj - +// Output : CMapClass +//----------------------------------------------------------------------------- +CMapClass *CMapWorld::CopyFrom(CMapClass *pobj, bool bUpdateDependencies) +{ + Assert(pobj->IsMapClass(MAPCLASS_TYPE(CMapWorld))); + CMapWorld *pFrom = (CMapWorld *)pobj; + + 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); + } + } + + return this; +} + + +//----------------------------------------------------------------------------- +// Hash the string to the bucket index where it belongs. +//----------------------------------------------------------------------------- +static inline int EntityBucketForName( const char *pszName ) +{ + if ( !pszName ) + return 0; + + unsigned int nHash = HashStringCaseless( pszName ); + + return nHash % NUM_HASHED_ENTITY_BUCKETS; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CMapWorld::FindEntityBucket( CMapEntity *pEntity, int *pnIndex ) +{ + for ( int i = 0; i < NUM_HASHED_ENTITY_BUCKETS; i++ ) + { + int nIndex = m_EntityListByName[ i ].Find( pEntity ); + if ( nIndex != -1 ) + { + if ( pnIndex ) + { + *pnIndex = nIndex; + } + + return i; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMapWorld::AddEntity( CMapEntity *pEntity ) +{ + if ( m_EntityList.Find( pEntity ) != -1 ) + return; + + // Add it to the flat list. + m_EntityList.AddToTail( pEntity ); + + // If it has a name, add it to the list of entities hashed by name checksum. + const char *pszName = pEntity->GetKeyValue( "targetname" ); + if ( pszName ) + { + int nBucket = EntityBucketForName( pszName ); + m_EntityListByName[ nBucket ].AddToTail( pEntity ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds any entities found in the given object tree to the list of +// entities that are in this world. Called whenever an object is added +// to this world. +// Input : pObject - object (and children) to add to the entity list. +//----------------------------------------------------------------------------- +void CMapWorld::EntityList_Add(CMapClass *pObject) +{ + CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pObject); + if (pEntity != NULL) + { + AddEntity(pEntity); + } + + EnumChildrenPos_t pos; + CMapClass *pChild = pObject->GetFirstDescendent(pos); + while (pChild != NULL) + { + pEntity = dynamic_cast<CMapEntity *>(pChild); + if ((pEntity != NULL) && (m_EntityList.Find(pEntity) == -1)) + { + AddEntity(pEntity); + } + + pChild = pObject->GetNextDescendent(pos); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Removes this object (if it is an entity) or any of its entity +// descendents from this world's entity list. Called when an object is +// removed from this world. +// Input : pObject - Object to remove from the entity list. +//----------------------------------------------------------------------------- +void CMapWorld::EntityList_Remove(CMapClass *pObject, bool bRemoveChildren) +{ + // + // Remove the object itself. + // + CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pObject); + if (pEntity != NULL) + { + // Remove the entity from the flat list. + int nIndex = m_EntityList.Find( pEntity ); + if ( nIndex != -1 ) + { + m_EntityList.FastRemove( nIndex ); + } + + // Remove the entity from the hashed list. + int nOldBucket = FindEntityBucket( pEntity, &nIndex ); + if ( nOldBucket != -1 ) + { + m_EntityListByName[ nOldBucket ].FastRemove( nIndex ); + } + + Assert( m_EntityList.Find( pEntity ) == -1 ); + } + + // + // Remove entity children. + // + if (bRemoveChildren) + { + EnumChildrenPos_t pos; + CMapClass *pChild = pObject->GetFirstDescendent(pos); + while (pChild != NULL) + { + pEntity = dynamic_cast<CMapEntity *>(pChild); + if (pEntity != NULL) + { + m_EntityList.FindAndRemove(pEntity); + } + pChild = pObject->GetNextDescendent(pos); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Overridden to maintain the culling tree. Root level children of the +// world are kept in the culling tree. +// Input : pChild - child to remove. +//----------------------------------------------------------------------------- +void CMapWorld::RemoveChild(CMapClass *pChild, bool bUpdateBounds) +{ + CMapClass::RemoveChild(pChild, bUpdateBounds); + + // + // Remove the object from the culling tree because it is no longer a root-level child. + // + if (m_pCullTree != NULL) + { + m_pCullTree->RemoveCullTreeObjectRecurse(pChild); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will attempt to find a child. If the bool and matrix +// are supplied, the localized matrix will be built. +// Input : key - the key field to lookup +// value - the value to find +// Output : returns the entity found +// bIsInInstance - optional parameter to indicate if the found entity is inside of an instance +// InstanceMatrix - optional parameter to set the localized matrix of the instance stack +//----------------------------------------------------------------------------- +CMapEntity *CMapWorld::FindChildByKeyValue( const char* key, const char* value, bool *bIsInInstance, VMatrix *InstanceMatrix ) +{ + if ( bIsInInstance ) + { + *bIsInInstance = false; + } + + return __super::FindChildByKeyValue( key, value, bIsInInstance, InstanceMatrix ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Removes an object from the world. +// Input : pObject - object to remove from the world. +// bChildren - whether we're removing the object's children as well. +//----------------------------------------------------------------------------- +void CMapWorld::RemoveObjectFromWorld(CMapClass *pObject, bool bRemoveChildren) +{ + Assert(pObject != NULL); + if (pObject == NULL) + { + return; + } + + // + // Unlink the object from the tree. + // + CMapClass *pParent = pObject->GetParent(); + Assert(pParent != NULL); + if (pParent != NULL) + { + pParent->RemoveChild(pObject); + } + + // + // If it (or any of its children) is an entity, remove it from this + // world's list of entities. + // + EntityList_Remove(pObject, bRemoveChildren); + + // + // Notify the object so it can release any pointers it may have to other + // objects in the world. We don't do this in RemoveChild because the object + // may only be changing parents rather than leaving the world. + // + pObject->OnRemoveFromWorld(this, bRemoveChildren); + +} + + +//----------------------------------------------------------------------------- +// Purpose: Special implementation of UpdateChild for the world object. This +// notifies the document that an object's bounding box has changed. +// Input : pChild - +//----------------------------------------------------------------------------- +void CMapWorld::UpdateChild(CMapClass *pChild) +{ + if ( CMapClass::s_bLoadingVMF ) + return; + + // Recalculate the bounds of this child's branch. + pChild->CalcBounds(TRUE); + + // Recalculate own bounds + CalcBounds( FALSE ); + + // + // Relink the child in the culling tree. + // + if (m_pCullTree != NULL) + { + m_pCullTree->UpdateCullTreeObjectRecurse(pChild); + } + + // + // Notify the document that an object in the world has changed. + // + if (!IsTemporary()) // HACK: check to avoid prefab objects ending up in the doc's update list + { + CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); + if (pDoc != NULL) + { + pDoc->UpdateObject(pChild); + } + } + + if ( CMapDoc::GetInLevelLoad() == 0 ) + { + APP()->pMapDocTemplate->UpdateInstanceMap( m_pOwningDocument ); + APP()->pManifestDocTemplate->UpdateInstanceMap( m_pOwningDocument ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pList - +//----------------------------------------------------------------------------- +void CMapWorld::GetUsedTextures(CUsedTextureList &List) +{ + List.RemoveAll(); + EnumChildren((ENUMMAPCHILDRENPROC)AddUsedTextures, (DWORD)&List, MAPCLASS_TYPE(CMapSolid)); + EnumChildren((ENUMMAPCHILDRENPROC)AddOverlayTextures, (DWORD)&List, MAPCLASS_TYPE(CMapOverlay)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pNode - +//----------------------------------------------------------------------------- +void CMapWorld::CullTree_FreeNode(CCullTreeNode *pNode) +{ + if ( pNode == NULL ) + { + Assert(pNode != NULL); + return; + } + + int nChildCount = pNode->GetChildCount(); + if (nChildCount != 0) + { + for (int nChild = 0; nChild < nChildCount; nChild++) + { + CCullTreeNode *pChild = pNode->GetCullTreeChild(nChild); + CullTree_FreeNode(pChild); + } + } + + delete pNode; +} + + +//----------------------------------------------------------------------------- +// Purpose: Recursively deletes the entire culling tree if is it not NULL. +// This does not delete the map objects that the culling tree contains, +// only the leaves and nodes themselves. +//----------------------------------------------------------------------------- +void CMapWorld::CullTree_Free(void) +{ + if (m_pCullTree != NULL) + { + CullTree_FreeNode(m_pCullTree); + m_pCullTree = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Determines if this node is a node or a leaf. If it is a node, it will +// be split into eight children and each child will be populated with +// objects whose bounding boxes intersect them, then split recursively. +// If this node is a leaf, no action is taken and recursion terminates. +// Input : pNode - +//----------------------------------------------------------------------------- +#define MIN_NODE_DIM 1024 // Minimum node size of 170 x 170 x 170 feet +#define MIN_NODE_OBJECT_SPLIT 2 // Don't split nodes with fewer than two objects. + +void CMapWorld::CullTree_SplitNode(CCullTreeNode *pNode) +{ + Vector Mins; + Vector Maxs; + Vector Size; + + pNode->GetBounds(Mins, Maxs); + VectorSubtract(Maxs, Mins, Size); + + if ((Size[0] > MIN_NODE_DIM) && (Size[1] > MIN_NODE_DIM) && (Size[2] > MIN_NODE_DIM)) + { + Vector Mids; + int nChild; + + Mids[0] = (Mins[0] + Maxs[0]) / 2.0; + Mids[1] = (Mins[1] + Maxs[1]) / 2.0; + Mids[2] = (Mins[2] + Maxs[2]) / 2.0; + + for (nChild = 0; nChild < 8; nChild++) + { + Vector ChildMins; + Vector ChildMaxs; + + // + // Create a child and set its bounding box. + // + CCullTreeNode *pChild = new CCullTreeNode; + + if (nChild & 1) + { + ChildMins[0] = Mins[0]; + ChildMaxs[0] = Mids[0]; + } + else + { + ChildMins[0] = Mids[0]; + ChildMaxs[0] = Maxs[0]; + } + + if (nChild & 2) + { + ChildMins[1] = Mins[1]; + ChildMaxs[1] = Mids[1]; + } + else + { + ChildMins[1] = Mids[1]; + ChildMaxs[1] = Maxs[1]; + } + + if (nChild & 4) + { + ChildMins[2] = Mins[2]; + ChildMaxs[2] = Mids[2]; + } + else + { + ChildMins[2] = Mids[2]; + ChildMaxs[2] = Maxs[2]; + } + + pChild->UpdateBounds(ChildMins, ChildMaxs); + + pNode->AddCullTreeChild(pChild); + + Vector mins1; + Vector maxs1; + pChild->GetBounds(mins1, maxs1); + + // + // Check all objects in this node against the child's bounding box, adding the + // objects that intersect to the child's object list. + // + int nObjectCount = pNode->GetObjectCount(); + for (int nObject = 0; nObject < nObjectCount; nObject++) + { + CMapClass *pObject = pNode->GetCullTreeObject(nObject); + Assert(pObject != NULL); + + Vector mins2; + Vector maxs2; + pObject->GetCullBox(mins2, maxs2); + if (BoxesIntersect(mins1, maxs1, mins2, maxs2)) + { + pChild->AddCullTreeObject(pObject); + } + } + } + + // + // Remove all objects from this node's object list (since we are not a leaf). + // + pNode->RemoveAllCullTreeObjects(); + + // + // Recurse into all children with at least two objects, splitting them. + // + int nChildCount = pNode->GetChildCount(); + for (nChild = 0; nChild < nChildCount; nChild++) + { + CCullTreeNode *pChild = pNode->GetCullTreeChild(nChild); + if (pChild->GetObjectCount() >= MIN_NODE_OBJECT_SPLIT) + { + CullTree_SplitNode(pChild); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pNode - +//----------------------------------------------------------------------------- +void CMapWorld::CullTree_DumpNode(CCullTreeNode *pNode, int nDepth) +{ + int nChildCount = pNode->GetChildCount(); + char szText[100]; + + if (nChildCount == 0) + { + // Leaf + OutputDebugString("LEAF:\n"); + int nObjectCount = pNode->GetObjectCount(); + for (int nObject = 0; nObject < nObjectCount; nObject++) + { + CMapClass *pMapClass = pNode->GetCullTreeObject(nObject); + sprintf(szText, "%*c %p %s\n", nDepth, ' ', pMapClass, pMapClass->GetType()); + OutputDebugString(szText); + } + } + else + { + // Node + sprintf(szText, "%*s\n", nDepth, "+"); + OutputDebugString(szText); + + for (int nChild = 0; nChild < nChildCount; nChild++) + { + CCullTreeNode *pChild = pNode->GetCullTreeChild(nChild); + CullTree_DumpNode(pChild, nDepth + 1); + } + + OutputDebugString("\n"); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMapWorld::CullTree_Build(void) +{ + CullTree_Free(); + m_pCullTree = new CCullTreeNode; + + // + // The top level node in the tree uses the largest possible bounding box. + // + Vector BoxMins( g_MIN_MAP_COORD, g_MIN_MAP_COORD, g_MIN_MAP_COORD ); + Vector BoxMaxs( g_MAX_MAP_COORD, g_MAX_MAP_COORD, g_MAX_MAP_COORD ); + m_pCullTree->UpdateBounds(BoxMins, BoxMaxs); + + // + // Populate the top level node with the contents of the world. + // + FOR_EACH_OBJ( m_Children, pos ) + { + CMapClass *pObject = m_Children.Element(pos); + m_pCullTree->AddCullTreeObject(pObject); + } + + // + // Recursively split this node into children and populate them. + // + CullTree_SplitNode(m_pCullTree); + + //DumpCullTreeNode(m_pCullTree, 1); + //OutputDebugString("\n"); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a list of all the groups in the world. +//----------------------------------------------------------------------------- +int CMapWorld::GetGroupList(CUtlVector<CMapGroup *> &GroupList) +{ + GroupList.RemoveAll(); + EnumChildrenPos_t pos; + CMapClass *pChild = GetFirstDescendent(pos); + while (pChild != NULL) + { + if (pChild->IsGroup()) + { + GroupList.AddToTail((CMapGroup *)pChild); + } + + pChild = GetNextDescendent(pos); + } + + return GroupList.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called after all objects in the World have been loaded. Calls the +// PostLoadWorld function for every object in the world, then +// builds the culling tree. +//----------------------------------------------------------------------------- +void CMapWorld::PostloadWorld(void) +{ + // This causes certain calculations to get delayed until the end. + CMapClass::s_bLoadingVMF = true; + + // + // Set the class name from our "classname" key and discard the key. + // + int nIndex; + const char *pszValue = pszValue = m_KeyValues.GetValue("classname", &nIndex); + if (pszValue != NULL) + { + SetClass(pszValue); + RemoveKey(nIndex); + } + + // + // Call PostLoadWorld on all our children and add any entities to the + // entity list. + // + + FOR_EACH_OBJ( m_Children, pos ) + { + CMapClass *pChild = m_Children[pos]; + pChild->PostloadWorld(this); + EntityList_Add(pChild); + } + + // Since s_bLoadingVMF was on before, a bunch of stuff got delayed. Now let's do that stuff. + CMapClass::s_bLoadingVMF = false; + FOR_EACH_OBJ( m_Children, pos ) + { + CMapClass *pChild = m_Children[pos]; + pChild->CalcBounds( TRUE ); + // + // Relink the child in the culling tree. + // + if (m_pCullTree != NULL) + { + m_pCullTree->UpdateCullTreeObjectRecurse(pChild); + } + + pChild->PostUpdate(Notify_Changed); + pChild->SignalChanged(); + } + CalcBounds( FALSE ); // Recalculate the world's bounds now that everyone else's bounds are upadted. +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pData - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::LoadGroupCallback(CChunkFile *pFile, CMapWorld *pWorld) +{ + CMapGroup *pGroup = new CMapGroup; + + ChunkFileResult_t eResult = pGroup->LoadVMF(pFile); + if (eResult == ChunkFile_Ok) + { + pWorld->AddChild(pGroup); + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pLoadInfo - +// *pWorld - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::LoadHiddenCallback(CChunkFile *pFile, CMapWorld *pWorld) +{ + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("solid", (ChunkHandler_t)LoadSolidCallback, pWorld); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk(); + pFile->PopHandlers(); + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles keyvalues when loading the world chunk of MAP files. +// Input : szKey - Key to handle. +// szValue - Value of key. +// pWorld - World being loaded. +// Output : Returns ChunkFile_Ok if all is well. +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::LoadKeyCallback(const char *szKey, const char *szValue, CMapWorld *pWorld) +{ + if (!stricmp(szKey, "id")) + { + pWorld->SetID(atoi(szValue)); + } + else if (stricmp(szKey, "mapversion") != 0) + { + pWorld->SetKeyValue(szKey, szValue); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pLoadInfo - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::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("group", (ChunkHandler_t)LoadGroupCallback, 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 : *pLoadInfo - +// *pWorld - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::LoadSolidCallback(CChunkFile *pFile, CMapWorld *pWorld) +{ + CMapSolid *pSolid = new CMapSolid; + + bool bValid; + ChunkFileResult_t eResult = pSolid->LoadVMF(pFile, bValid); + + if ((eResult == ChunkFile_Ok) && (bValid)) + { + const char *pszValue = pSolid->GetEditorKeyValue("cordonsolid"); + if (pszValue == NULL) + { + pWorld->AddChild(pSolid); + } + } + else + { + delete pSolid; + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calls PresaveWorld in all of the world's descendents. +//----------------------------------------------------------------------------- +void CMapWorld::PresaveWorld(void) +{ + EnumChildrenPos_t pos; + CMapClass *pChild = GetFirstDescendent(pos); + while (pChild != NULL) + { + pChild->PresaveWorld(); + pChild = GetNextDescendent(pos); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::SaveSolids(CChunkFile *pFile, CSaveInfo *pSaveInfo, int saveFlags) +{ + PresaveWorld(); + + SaveLists_t SaveLists; + EnumChildrenRecurseGroupsOnly((ENUMMAPCHILDRENPROC)BuildSaveListsCallback, (DWORD)&SaveLists); + + return SaveObjectListVMF(pFile, pSaveInfo, &SaveLists.Solids, saveFlags); +} + + +//----------------------------------------------------------------------------- +// Purpose: Saves all solids, entities, and groups in the world to a VMF file. +// Input : pFile - File object to use for saving. +// pSaveInfo - Holds rules for which objects to save. +// Output : Returns ChunkFile_Ok if the save was successful, or an error code. +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo, int saveFlags) +{ + PresaveWorld(); + + // + // Sort the world objects into lists for saving into different chunks. + // + SaveLists_t SaveLists; + EnumChildrenRecurseGroupsOnly((ENUMMAPCHILDRENPROC)BuildSaveListsCallback, (DWORD)&SaveLists); + + // + // Begin the world chunk. + // + ChunkFileResult_t eResult = ChunkFile_Ok; + + if( !(saveFlags & SAVEFLAGS_LIGHTSONLY) ) + { + eResult = pFile->BeginChunk("world"); + + // + // Save world ID - it's always zero. + // + if (eResult == ChunkFile_Ok) + { + eResult = pFile->WriteKeyValueInt("id", GetID()); + } + + // + // HACK: Save map version. This is already being saved in the version info block by the doc. + // + if (eResult == ChunkFile_Ok) + { + eResult = pFile->WriteKeyValueInt("mapversion", CMapDoc::GetActiveMapDoc()->GetDocVersion()); + } + + // + // Save world keys. + // + if (eResult == ChunkFile_Ok) + { + CEditGameClass::SaveVMF(pFile, pSaveInfo); + } + + // + // Save world solids. + // + if (eResult == ChunkFile_Ok) + { + eResult = SaveObjectListVMF(pFile, pSaveInfo, &SaveLists.Solids, saveFlags); + } + + // + // Save groups. + // + if (eResult == ChunkFile_Ok) + { + eResult = SaveObjectListVMF(pFile, pSaveInfo, &SaveLists.Groups, saveFlags); + } + + // + // End the world chunk. + // + if (eResult == ChunkFile_Ok) + { + pFile->EndChunk(); + } + } + + // + // Save entities and their solid children. + // + if (eResult == ChunkFile_Ok) + { + eResult = SaveObjectListVMF(pFile, pSaveInfo, &SaveLists.Entities, saveFlags); + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pList - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapWorld::SaveObjectListVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo, const CMapObjectList *pList, int saveFlags) +{ + FOR_EACH_OBJ( *pList, pos ) + { + CMapClass *pObject = pList->Element(pos); + + // Only save lights if that's what they want. + if( saveFlags & SAVEFLAGS_LIGHTSONLY ) + { + CMapEntity *pMapEnt = dynamic_cast<CMapEntity*>( pObject ); + bool bIsLight = pMapEnt && strncmp( pMapEnt->GetClassName(), "light", 5 ) == 0; + if( !bIsLight ) + continue; + } + + + if (pObject != NULL) + { + ChunkFileResult_t eResult = pObject->SaveVMF(pFile, pSaveInfo); + if (eResult != ChunkFile_Ok) + { + return(eResult); + } + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a given character to the end of a string if there isn't one already. +// Input : psz - String to add the backslash to. +// ch - Character to check for (and add if not found). +// nSize - Size of buffer pointer to by psz. +// Output : Returns true if there was enough space in the dest buffer, false if not. +//----------------------------------------------------------------------------- +static bool EnsureTrailingChar(char *psz, char ch, int nSize) +{ + int nLen = strlen(psz); + if ((psz[0] != '\0') && (psz[nLen - 1] != ch)) + { + if (nLen < (nSize - 1)) + { + psz[nLen++] = ch; + psz[nLen] = '\0'; + } + else + { + // No room to add the character. + return(false); + } + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Finds the face with the corresponding face ID. +// FIXME: AAARGH, slow!! Need to build a table or something. +// Input : nFaceID - +//----------------------------------------------------------------------------- +CMapFace *CMapWorld::FaceID_FaceForID(int nFaceID) +{ + EnumChildrenPos_t pos; + CMapClass *pChild = GetFirstDescendent(pos); + while (pChild != NULL) + { + CMapSolid *pSolid = dynamic_cast <CMapSolid *>(pChild); + if (pSolid != NULL) + { + int nFaceCount = pSolid->GetFaceCount(); + for (int nFace = 0; nFace < nFaceCount; nFace++) + { + CMapFace *pFace = pSolid->GetFace(nFace); + if (pFace->GetFaceID() == nFaceID) + { + return(pFace); + } + } + } + + pChild = GetNextDescendent(pos); + } + + return(NULL); +} + + +//----------------------------------------------------------------------------- +// Purpose: Concatenates strings without overrunning the dest buffer. +// Input : szDest - +// szSrc - +// nDestSize - +// Output : Returns true if all chars were copied, false if we ran out of room. +//----------------------------------------------------------------------------- +static bool AppendString(char *szDest, char const *szSrc, int nDestSize) +{ + int nDestLen = strlen(szDest); + int nDestAvail = nDestSize - nDestLen - 1; + + char *pszStart = szDest + nDestLen; + char *psz = pszStart; + + while ((nDestAvail > 0) && (*szSrc != '\0')) + { + *psz++ = *szSrc++; + nDestAvail--; + } + + *psz = '\0'; + + if (*szSrc != '\0') + { + // If we ran out of room, don't append anything. We don't want partial strings. + *pszStart = '\0'; + } + + return(*szSrc == '\0'); +} + + +//----------------------------------------------------------------------------- +// Purpose: Encode the list of fully selected and partially selected faces in +// a string of the form: "4 5 12 (1 8)" +// +// This is used for multiselect editing of sidelist keyvalues. +// +// Input : pszValue - The buffer to receive the face lists encoded as a string. +// pFullFaceList - the list of faces that are considered fully in the list +// pPartialFaceList - the list of faces that are partially in the list +//----------------------------------------------------------------------------- +bool CMapWorld::FaceID_FaceIDListsToString(char *pszList, int nSize, CMapFaceIDList *pFullFaceIDList, CMapFaceIDList *pPartialFaceIDList) +{ + if (pszList == NULL) + { + return(false); + } + + pszList[0] = '\0'; + + // + // Add the fully selected faces, space delimited. + // + if (pFullFaceIDList != NULL) + { + for (int i = 0; i < pFullFaceIDList->Count(); i++) + { + int nFace = pFullFaceIDList->Element(i); + + char szID[64]; + itoa(nFace, szID, 10); + if (!EnsureTrailingChar(pszList, ' ', nSize) || !AppendString(pszList, szID, nSize)) + { + return(false); + } + } + } + + // + // Add the partially selected faces inside of parentheses. + // + if (pPartialFaceIDList != NULL) + { + if (pPartialFaceIDList->Count() > 0) + { + if (!EnsureTrailingChar(pszList, ' ', nSize) || !AppendString(pszList, "(", nSize)) + { + return(false); + } + + bool bFirst = true; + + for (int i = 0; i < pPartialFaceIDList->Count(); i++) + { + int nFace = pPartialFaceIDList->Element(i); + + char szID[64]; + itoa(nFace, szID, 10); + if (!bFirst) + { + if (!EnsureTrailingChar(pszList, ' ', nSize)) + { + return(false); + } + } + bFirst = false; + if (!AppendString(pszList, szID, nSize)) + { + return(false); + } + } + + AppendString(pszList, ")", nSize); + } + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Encode the list of fully selected and partially selected faces in +// a string of the form: "4 5 12 (1 8)" +// +// This is used for multiselect editing of sidelist keyvalues. +// +// Input : pszValue - The buffer to receive the face lists encoded as a string. +// pFullFaceList - the list of faces that are considered fully in the list +// pPartialFaceList - the list of faces that are partially in the list +//----------------------------------------------------------------------------- +bool CMapWorld::FaceID_FaceListsToString(char *pszList, int nSize, CMapFaceList *pFullFaceList, CMapFaceList *pPartialFaceList) +{ + if (pszList == NULL) + { + return(false); + } + + pszList[0] = '\0'; + + // + // Add the fully selected faces, space delimited. + // + if (pFullFaceList != NULL) + { + for (int i = 0; i < pFullFaceList->Count(); i++) + { + CMapFace *pFace = pFullFaceList->Element(i); + + char szID[64]; + itoa(pFace->GetFaceID(), szID, 10); + if (!EnsureTrailingChar(pszList, ' ', nSize) || !AppendString(pszList, szID, nSize)) + { + return(false); + } + } + } + + // + // Add the partially selected faces inside of parentheses. + // + if (pPartialFaceList != NULL) + { + if (pPartialFaceList->Count() > 0) + { + if (!EnsureTrailingChar(pszList, ' ', nSize) || !AppendString(pszList, "(", nSize)) + { + return(false); + } + + bool bFirst = true; + + for (int i = 0; i < pPartialFaceList->Count(); i++) + { + CMapFace *pFace = pPartialFaceList->Element(i); + + char szID[64]; + itoa(pFace->GetFaceID(), szID, 10); + if (!bFirst) + { + if (!EnsureTrailingChar(pszList, ' ', nSize)) + { + return(false); + } + } + bFirst = false; + if (!AppendString(pszList, szID, nSize)) + { + return(false); + } + } + + AppendString(pszList, ")", nSize); + } + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Decode a string of the form: "4 5 12 (1 8)" into a list of fully +// selected and a list of partially selected face IDs. +// +// This is used for multiselect editing of sidelist keyvalues. +// +// Input : pszValue - The buffer to receive the face lists encoded as a string. +// pFullFaceList - the list of faces that are considered fully in the list +// pPartialFaceList - the list of faces that are partially in the list +//----------------------------------------------------------------------------- +void CMapWorld::FaceID_StringToFaceIDLists(CMapFaceIDList *pFullFaceList, CMapFaceIDList *pPartialFaceList, const char *pszValue) +{ + if (pFullFaceList != NULL) + { + pFullFaceList->RemoveAll(); + } + + if (pPartialFaceList != NULL) + { + pPartialFaceList->RemoveAll(); + } + + if (pszValue != NULL) + { + char szVal[KEYVALUE_MAX_VALUE_LENGTH]; + strcpy(szVal, pszValue); + + int nParens = 0; + bool bInParens = false; + + char *psz = strtok(szVal, " "); + while (psz != NULL) + { + // + // Strip leading or trailing parentheses from the substring. + // + bool bFirstValid = true; + char *pszRemoveParens = psz; + while (*pszRemoveParens != '\0') + { + if (*pszRemoveParens == '(') + { + nParens++; + *pszRemoveParens = '\0'; + } + else if (*pszRemoveParens == ')') + { + nParens--; + *pszRemoveParens = '\0'; + } + else if (bFirstValid) + { + // + // Note the parentheses depth at the start of this number. + // + if (nParens > 0) + { + bInParens = true; + } + else + { + bInParens = false; + } + + psz = pszRemoveParens; + bFirstValid = false; + } + pszRemoveParens++; + } + + // + // The substring should now be a single face ID. Get the corresponding + // face and add it to the list. + // + int nFaceID = atoi(psz); + if (bInParens) + { + if (pPartialFaceList != NULL) + { + pPartialFaceList->AddToTail(nFaceID); + } + } + else + { + if (pFullFaceList != NULL) + { + pFullFaceList->AddToTail(nFaceID); + } + } + + // + // Get the next substring. + // + psz = strtok(NULL, " "); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Decode a string of the form: "4 5 12 (1 8)" into a list of fully +// selected and a list of partially selected faces. +// +// This is used for multiselect editing of sidelist keyvalues. +// +// Input : pszValue - The buffer to receive the face lists encoded as a string. +// pFullFaceList - the list of faces that are considered fully in the list +// pPartialFaceList - the list of faces that are partially in the list +//----------------------------------------------------------------------------- +void CMapWorld::FaceID_StringToFaceLists(CMapFaceList *pFullFaceList, CMapFaceList *pPartialFaceList, const char *pszValue) +{ + CMapFaceIDList FullFaceIDList; + CMapFaceIDList PartialFaceIDList; + + FaceID_StringToFaceIDLists(&FullFaceIDList, &PartialFaceIDList, pszValue); + + if (pFullFaceList != NULL) + { + pFullFaceList->RemoveAll(); + + for (int i = 0; i < FullFaceIDList.Count(); i++) + { + // + // Get the corresponding face and add it to the list. + // + // FACEID TODO: fix so we only interate the world objects once + CMapFace *pFace = FaceID_FaceForID(FullFaceIDList.Element(i)); + if (pFace != NULL) + { + pFullFaceList->AddToTail(pFace); + } + } + } + + if (pPartialFaceList != NULL) + { + pPartialFaceList->RemoveAll(); + + for (int i = 0; i < PartialFaceIDList.Count(); i++) + { + // + // Get the corresponding face and add it to the list. + // + // FACEID TODO: fix so we only interate the world objects once + CMapFace *pFace = FaceID_FaceForID(PartialFaceIDList.Element(i)); + if (pFace != NULL) + { + pPartialFaceList->AddToTail(pFace); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: increments the numerals at the end of a string +// appends 0 if no numerals exist +// Input : newName - +//----------------------------------------------------------------------------- +static void IncrementStringName( char *str, int nMaxLength ) +{ + // walk backwards through the string looking for where the digits stop + int orgLen = Q_strlen(str); + int pos = orgLen; + while ( (pos > 0) && V_isdigit(str[pos-1]) ) + { + pos--; + } + + // if no digits found, append a "1" + if ( pos == orgLen ) + { + Q_strncat( str, "1", nMaxLength ); + } + else + { + // get the number + int iNum = Q_atoi( str+pos ); + + // increment the number + iNum++; + + // cut off old number + str[pos]=0; + + // add the new number to the string + Q_snprintf( str, nMaxLength, "%s%d", str, iNum ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Generates a new, unique targetname for the given entity based on an +// existing entity name. +// a static function +// Input : pObject - the entity +// startName - the name of the original entity - assumed to already exist in the map +// outputName - the new name based on the original name, guaranteed to be unique +// szPrefix - a string to prepend to the new name +// newNameBufferSize - +// bMakeUnique - if true, the generated name will be unique in this world and pRoot +// szPrefix - prefix to prepend to the new name +// pRoot - an optional tree of objects to look in for uniqueness +//----------------------------------------------------------------------------- +bool CMapWorld::GenerateNewTargetname( const char *startName, char *outputName, int newNameBufferSize, bool bMakeUnique, const char *szPrefix, CMapClass *pRoot ) +{ + outputName[0] = 0; + + if ( szPrefix ) + { + // add prefix if any give + Q_strncpy( outputName, szPrefix, newNameBufferSize ); + } + + // add start name + Q_strncat( outputName, startName, newNameBufferSize ); + + // if new name is still empty, set entity as default + if ( Q_strlen( outputName ) == 0 ) + { + Q_strncpy( outputName, "entity", newNameBufferSize ); + } + + // Only append numbers to the name if we need to. It's possible that adding + // the prefix was sufficient to make the name unique. + if ( bMakeUnique && FindEntityByName( outputName, false, true ) ) + { + // try to find entities that match the name + CMapEntity *pEnt = NULL; + do + { + // increment the entity name + IncrementStringName( outputName, newNameBufferSize ); + + pEnt = FindEntityByName( outputName, false, true ); + + if ( !pEnt && pRoot ) + { + pEnt = pRoot->FindChildByKeyValue( "targetname", outputName ); + } + + } while ( pEnt ); + } + + return true; +} + +void CMapWorld::PostloadVisGroups() +{ + bool bFoundOrphans = false; + CMapObjectList orphans; + CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); + + FOR_EACH_OBJ( m_Children, pos ) + { + CMapClass *pChild = m_Children[pos]; + + if ( pChild->PostloadVisGroups( true ) == true ) + { + orphans.AddToTail( pChild ); + bFoundOrphans = true; + } + } + if ( bFoundOrphans == true ) + { + pDoc->VisGroups_CreateNamedVisGroup( orphans, "_orphaned hidden", true, false ); + GetMainWnd()->MessageBox( "Orphaned objects were found and placed into the \"_orphaned hidden\" visgroup.", "Orphaned Objects Found", MB_OK | MB_ICONEXCLAMATION); + } + + // Link up all the connections to the entities + const CMapEntityList *pEntities = EntityList_GetList(); + FOR_EACH_OBJ( *pEntities, pos ) + { + CMapEntity *pEntity = dynamic_cast< CMapEntity *>( (*pEntities)[pos] ); +#if defined(_DEBUG) && 0 + LPCTSTR pszTargetName = pEntity->GetKeyValue("targetname"); + if ( pszTargetName && !strcmp(pszTargetName, "relay_cancelVCDs") ) + { + // Set breakpoint here for debugging this entity's visiblity + int foo = 0; + } +#endif + int nConnections = pEntity->Connections_GetCount(); + for ( int pos2 = 0; pos2 < nConnections; pos2++ ) + { + CEntityConnection *pEntityConnection = pEntity->Connections_Get(pos2); + + // Link this connection back to the entity + pEntityConnection->GetSourceEntityList()->AddToTail( pEntity ); + pEntityConnection->LinkTargetEntities(); + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CMapEntity *CMapWorld::FindEntityByName( const char *pszName, bool bVisiblesOnly, bool bSearchInstanceParms ) +{ + if ( !pszName ) + return NULL; + + CMapEntityList *pList = &m_EntityList; + + if ( !strchr( pszName, '*' ) ) + { + int nBucket = EntityBucketForName( pszName ); + pList = &m_EntityListByName[nBucket]; + } + + int nCount = pList->Count(); + for ( int i = 0; i < nCount; i++ ) + { + CMapEntity *pEntity = pList->Element( i ); + + if ( pEntity->IsVisible() || !bVisiblesOnly ) + { + if ( pEntity->NameMatches( pszName ) ) + { + return pEntity; + } + } + } + + if ( bSearchInstanceParms == true ) + { + const CMapEntityList *pEntities = EntityList_GetList(); + FOR_EACH_OBJ( *pEntities, pos ) + { + CMapEntity *pEntity = dynamic_cast< CMapEntity *>( (*pEntities)[pos] ); + if ( pEntity->ClassNameMatches( "func_instance" ) == true ) + { + for ( int j = pEntity->GetFirstKeyValue(); j != pEntity->GetInvalidKeyValue(); j = pEntity->GetNextKeyValue( j ) ) + { + LPCTSTR pInstanceKey = pEntity->GetKey( j ); + LPCTSTR pInstanceValue = pEntity->GetKeyValue( j ); + if ( strnicmp( pInstanceKey, "replace", strlen( "replace" ) ) == 0 ) + { + const char *InstancePos = strchr( pInstanceValue, ' ' ); + if ( InstancePos == NULL ) + { + continue; + } + + if ( strcmpi( pszName, InstancePos + 1 ) == 0 ) + { + return pEntity; + } + } + } + } + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Finds all entities in the map with a given class name. +// Input : pFound - List of entities with the class name. +// pszClassName - Class name to match, case insensitive. +// Output : Returns true if any matches were found, false if not. +//----------------------------------------------------------------------------- +bool CMapWorld::FindEntitiesByClassName(CMapEntityList &Found, const char *pszClassName, bool bVisiblesOnly) +{ + Found.RemoveAll(); + + int nCount = EntityList_GetCount(); + for ( int i = 0; i < nCount; i++ ) + { + CMapEntity *pEntity = EntityList_GetEntity( i ); + + if ( pEntity->IsVisible() || !bVisiblesOnly ) + { + if ( pEntity->ClassNameMatches( pszClassName ) ) + { + Found.AddToTail( pEntity ); + } + } + } + + return( Found.Count() != 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFound - +// pszTargetName - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMapWorld::FindEntitiesByKeyValue(CMapEntityList &Found, const char *pszKey, const char *pszValue, bool bVisiblesOnly) +{ + Found.RemoveAll(); + + int nCount = EntityList_GetCount(); + for ( int i = 0; i < nCount; i++ ) + { + CMapEntity *pEntity = EntityList_GetEntity( i ); + + if ( pEntity->IsVisible() || !bVisiblesOnly ) + { + const char *pszThisValue = pEntity->GetKeyValue( pszKey ); + + if ( pszThisValue != NULL ) + { + if (( pszValue != NULL ) && ( !stricmp( pszValue, pszThisValue ))) + { + Found.AddToTail( pEntity ); + } + } + else if (pszValue == NULL) + { + Found.AddToTail( pEntity ); + } + } + } + + return( Found.Count() != 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMapWorld::FindEntitiesByName( CMapEntityList &Found, const char *pszName, bool bVisiblesOnly ) +{ + Found.RemoveAll(); + + if ( !pszName ) + return false; + + CMapEntityList *pList = &m_EntityList; + + if ( !strchr( pszName, '*' ) ) + { + int nBucket = EntityBucketForName( pszName ); + pList = &m_EntityListByName[nBucket]; + } + + int nCount = pList->Count(); + for ( int i = 0; i < nCount; i++ ) + { + CMapEntity *pEntity = pList->Element( i ); + + if ( pEntity->IsVisible() || !bVisiblesOnly ) + { + if ( pEntity->NameMatches( pszName ) ) + { + Found.AddToTail( pEntity ); + } + } + } + + return( Found.Count() != 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMapWorld::FindEntitiesByNameOrClassName(CMapEntityList &Found, const char *pszName, bool bVisiblesOnly) +{ + Found.RemoveAll(); + + int nCount = EntityList_GetCount(); + for ( int i = 0; i < nCount; i++ ) + { + CMapEntity *pEntity = EntityList_GetEntity( i ); + + if ( pEntity->IsVisible() || !bVisiblesOnly ) + { + if ( pEntity->NameMatches( pszName ) || pEntity->ClassNameMatches( pszName ) ) + { + Found.AddToTail( pEntity ); + } + } + } + + return( Found.Count() != 0 ); +} + + +//----------------------------------------------------------------------------- +// Tell all our children to update their dependencies because of the given object. +//----------------------------------------------------------------------------- +void CMapWorld::UpdateAllDependencies( CMapClass *pObject ) +{ + // + // Entities need to be put in their proper hash bucket if the name changed. + // + CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pObject); + if ( pEntity ) + { + int nNewBucket = -1; + const char *pszName = pEntity->GetKeyValue( "targetname" ); + if ( pszName ) + { + nNewBucket = EntityBucketForName( pszName ); + } + + int nIndex; + int nOldBucket = FindEntityBucket( pEntity, &nIndex ); + + if ( nOldBucket != nNewBucket ) + { + // Remove the entity from the hashed list. + if ( nOldBucket != -1 ) + { + m_EntityListByName[ nOldBucket ].FastRemove( nIndex ); + } + + // Add the entity back to the hashed list in the proper bucket. + if ( nNewBucket != -1 ) + { + m_EntityListByName[ nNewBucket ].AddToTail( pEntity ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns if this map world is editable. If it is not part of an instance +// or manifest, then it is editable. Otherwise, it lets the owning document +// determine the editing state. +//----------------------------------------------------------------------------- +bool CMapWorld::IsEditable( void ) +{ + if ( !m_pOwningDocument ) + { + return true; + } + + return m_pOwningDocument->IsEditable(); +} + |