//========= 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 #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(pObject); if (pEntity != NULL) { pSaveLists->Entities.AddToTail(pEntity); return(TRUE); } CMapSolid *pSolid = dynamic_cast(pObject); if (pSolid != NULL) { pSaveLists->Solids.AddToTail(pSolid); return(TRUE); } CMapGroup *pGroup = dynamic_cast(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(pObject); if (pEntity != NULL) { AddEntity(pEntity); } EnumChildrenPos_t pos; CMapClass *pChild = pObject->GetFirstDescendent(pos); while (pChild != NULL) { pEntity = dynamic_cast(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(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(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 &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( 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 (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(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(); }