|
|
//========= Copyright � 1996-2005, 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"
#include "..\fow\fow.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); }
//-----------------------------------------------------------------------------
// Called from constructors to initialize data members.
//-----------------------------------------------------------------------------
void CMapWorld::Init() { //
// 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(); }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CMapWorld::CMapWorld( void ) { Init(); }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CMapWorld::CMapWorld( CMapDoc *pOwningDocument ) { Init(); 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 ); }
//-----------------------------------------------------------------------------
// Called by the undo system when this object is restored by undo or redo.
//-----------------------------------------------------------------------------
void CMapWorld::OnUndoRedo() { BaseClass::OnUndoRedo();
// The cull tree doesn't get kept by the undo system so we need to rebuild it.
CullTree_Build(); }
//-----------------------------------------------------------------------------
// 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) { CMapEntity *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) { CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild); if (pEntity != NULL) { m_EntityList.FindAndFastRemove( CUtlReference<CMapEntity>(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) { Assert(pNode != NULL); if ( !pNode ) 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();
const char *pszValue = GetKeyValue( "fow" ); if ( pszValue != NULL ) { CFoW *pFoW = CMapDoc::GetActiveMapDoc()->GetFoW();
if ( pFoW != NULL ) { Vector vWorldMins, vWorldMaxs; int nHorizontalGridSize, nVerticalGridSize;
sscanf( GetKeyValue( "m_vWorldMins" ), "%g %g %g", &vWorldMins.x, &vWorldMins.y, &vWorldMins.z ); sscanf( GetKeyValue( "m_vWorldMaxs" ), "%g %g %g", &vWorldMaxs.x, &vWorldMaxs.y, &vWorldMaxs.z ); nHorizontalGridSize = atoi( GetKeyValue( "m_nHorizontalGridSize" ) ); nVerticalGridSize = atoi( GetKeyValue( "m_nVerticalGridSize" ) );
pFoW->SetSize( vWorldMins, vWorldMaxs, nHorizontalGridSize, nVerticalGridSize );
if ( nVerticalGridSize == -1 ) { int nGridZUnits = atoi( GetKeyValue( "m_nGridZUnits" ) ); float32 *flHeights = ( float * )stackalloc( sizeof( float32 ) * nGridZUnits ); for( int i = 0; i < nGridZUnits; i++ ) { char temp[ 128 ];
sprintf( temp, "m_pVerticalLevels_%d", i ); flHeights[ i ] = atof( GetKeyValue( temp ) ); }
pFoW->SetCustomVerticalLevels( flHeights, nGridZUnits ); } } }
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");
// HAMMER CONSOLE TODO:
bool g_bDebugLoadCordonBrushes = false; if ( (pszValue == NULL) || g_bDebugLoadCordonBrushes ) { 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); }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int SortFuncCompareSaveOrder( const CUtlReference<CMapClass> *pClass1, const CUtlReference<CMapClass> *pClass2 ) { const CMapClass *pObject1 = pClass1->GetObject(); const CMapClass *pObject2 = pClass2->GetObject();
// For all objects that were loaded from the VMF, preserve the load order on save.
int nLoadID1 = pObject1->GetLoadID(); int nLoadID2 = pObject2->GetLoadID();
if ( nLoadID1 > nLoadID2 ) return 1;
if ( nLoadID1 < nLoadID2 ) return -1;
// Load IDs are equal. Sort by unique object ID.
int nID1 = pObject1->GetID(); int nID2 = pObject2->GetID();
if ( nID1 > nID2 ) return 1;
if ( nID1 < nID2 ) return -1;
return 0; }
//-----------------------------------------------------------------------------
// 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);
// IMPORTANT:
// Because UtlReferenceVectors don't preserve order, sort the lists by object ID so that the save
// order is the same every time. This makes it possible to get meaningful diffs between versions!
//
SaveLists.Entities.InPlaceQuickSort( SortFuncCompareSaveOrder ); SaveLists.Groups.InPlaceQuickSort( SortFuncCompareSaveOrder ); SaveLists.Solids.InPlaceQuickSort( SortFuncCompareSaveOrder );
//
// 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 = (CUtlReference< CMapClass >)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 = (CUtlReference<CMapEntity>)pEntities->Element(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 ? pEntity->Connections_GetCount() : 0; 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 you hit this assert it means that an entity was deleted
// but not removed from the world's entity list.
Assert( pEntity != NULL ); if ( !pEntity ) continue; 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 = (CUtlReference<CMapEntity>)pEntities->Element(pos);
if ( pEntity && pEntity->ClassNameMatches( "func_instance" ) ) { 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 && ( 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(); }
|