summaryrefslogtreecommitdiff
path: root/hammer/mapworld.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /hammer/mapworld.cpp
downloadarchived-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.cpp1965
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();
+}
+