//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "vcdblockdoc.h" #include "tier1/KeyValues.h" #include "tier1/utlbuffer.h" #include "toolutils/enginetools_int.h" #include "filesystem.h" #include "vcdblocktool.h" #include "toolframework/ienginetool.h" #include "dmevmfentity.h" #include "datamodel/idatamodel.h" #include "toolutils/attributeelementchoicelist.h" #include "infotargetbrowserpanel.h" #include "vgui_controls/messagebox.h" //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CVcdBlockDoc::CVcdBlockDoc( IVcdBlockDocCallback *pCallback ) : m_pCallback( pCallback ) { m_hVMFRoot = NULL; m_hEditRoot = NULL; m_pBSPFileName[0] = 0; m_pVMFFileName[0] = 0; m_pEditFileName[0] = 0; m_bDirty = false; g_pDataModel->InstallNotificationCallback( this ); } CVcdBlockDoc::~CVcdBlockDoc() { g_pDataModel->RemoveNotificationCallback( this ); } //----------------------------------------------------------------------------- // Inherited from INotifyUI //----------------------------------------------------------------------------- void CVcdBlockDoc::NotifyDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags ) { OnDataChanged( pReason, nNotifySource, nNotifyFlags ); } //----------------------------------------------------------------------------- // Gets the file name //----------------------------------------------------------------------------- const char *CVcdBlockDoc::GetBSPFileName() { return m_pBSPFileName; } const char *CVcdBlockDoc::GetVMFFileName() { return m_pVMFFileName; } void CVcdBlockDoc::SetVMFFileName( const char *pFileName ) { Q_strncpy( m_pVMFFileName, pFileName, sizeof( m_pVMFFileName ) ); Q_FixSlashes( m_pVMFFileName ); SetDirty( true ); } const char *CVcdBlockDoc::GetEditFileName() { return m_pEditFileName; } void CVcdBlockDoc::SetEditFileName( const char *pFileName ) { Q_strncpy( m_pEditFileName, pFileName, sizeof( m_pEditFileName ) ); Q_FixSlashes( m_pEditFileName ); SetDirty( true ); } //----------------------------------------------------------------------------- // Dirty bits //----------------------------------------------------------------------------- void CVcdBlockDoc::SetDirty( bool bDirty ) { m_bDirty = bDirty; } bool CVcdBlockDoc::IsDirty() const { return m_bDirty; } //----------------------------------------------------------------------------- // Saves/loads from file //----------------------------------------------------------------------------- bool CVcdBlockDoc::LoadFromFile( const char *pFileName ) { Assert( !m_hVMFRoot.Get() ); Assert( !m_hEditRoot.Get() ); CAppDisableUndoScopeGuard guard( "CVcdBlockDoc::LoadFromFile", NOTIFY_CHANGE_OTHER ); SetDirty( false ); if ( !pFileName[0] ) return false; // Construct VMF file name from the BSP const char *pGame = Q_stristr( pFileName, "\\game\\" ); if ( !pGame ) { pGame = Q_stristr( pFileName, "\\content\\" ); if ( !pGame ) return false; } // Compute the map name const char *pMaps = Q_stristr( pFileName, "\\maps\\" ); if ( !pMaps ) return false; // Build map name char mapname[ 256 ]; Q_StripExtension( pFileName, mapname, sizeof(mapname) ); char *pszFileName = (char*)Q_UnqualifiedFileName(mapname); int nLen = (int)( (size_t)pGame - (size_t)pFileName ) + 1; Q_strncpy( m_pVMFFileName, pFileName, nLen ); Q_strncat( m_pVMFFileName, "\\content\\", sizeof(m_pVMFFileName) ); Q_strncat( m_pVMFFileName, pGame + 6, sizeof(m_pVMFFileName) ); Q_SetExtension( m_pVMFFileName, ".vmf", sizeof(m_pVMFFileName) ); // Make sure new entities start with ids at 0 CDmeVMFEntity::SetNextEntityId( 0 ); // Build the Edit file name Q_StripExtension( m_pVMFFileName, m_pEditFileName, sizeof(m_pEditFileName) ); Q_strncat( m_pEditFileName, ".vle", sizeof( m_pEditFileName ) ); // Store the BSP file name Q_strncpy( m_pBSPFileName, pFileName, sizeof( m_pBSPFileName ) ); // Set the txt file name. // If we loaded a .bsp, clear out what we're doing // load the Edits file into memory, assign it as our "root" CDmElement *pEdit = NULL; if ( !V_stricmp( Q_GetFileExtension( pFileName ), "vle" ) ) { if ( g_pDataModel->RestoreFromFile( m_pEditFileName, NULL, "vmf", &pEdit ) != DMFILEID_INVALID ) { // If we successfully read the file in, ask it for the max hammer id //int nMaxHammerId = pVMF->GetAttributeValue( "maxHammerId" ); //CDmeVMFEntity::SetNextEntityId( nMaxHammerId + 1 ); m_hEditRoot = pEdit; SetDirty( false ); } } if (pEdit == NULL) { if ( g_pFileSystem->FileExists( m_pEditFileName ) ) { char pBuf[1024]; Q_snprintf( pBuf, sizeof(pBuf), "File %s already exists!\n", m_pEditFileName ); m_pEditFileName[0] = 0; vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Unable to overwrite file!\n", pBuf, g_pVcdBlockTool ); pMessageBox->DoModal( ); return false; } DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( m_pEditFileName ); m_hEditRoot = CreateElement( "root", fileid ); m_hEditRoot->AddAttribute( "entities", AT_ELEMENT_ARRAY ); g_pDataModel->SetFileRoot( fileid, m_hEditRoot ); SetDirty( true ); } guard.Release(); // tell the engine to actually load the map char cmd[ 256 ]; Q_snprintf( cmd, sizeof( cmd ), "disconnect; map %s\n", pszFileName ); enginetools->Command( cmd ); enginetools->Execute( ); return true; } void CVcdBlockDoc::SaveToFile( ) { if ( m_hEditRoot.Get() && m_pEditFileName && m_pEditFileName[0] ) { g_pDataModel->SaveToFile( m_pEditFileName, NULL, "keyvalues", "vmf", m_hEditRoot ); } SetDirty( false ); } //----------------------------------------------------------------------------- // Returns the root object //----------------------------------------------------------------------------- CDmElement *CVcdBlockDoc::GetRootObject() { return m_hEditRoot; } //----------------------------------------------------------------------------- // Returns the entity list //----------------------------------------------------------------------------- CDmAttribute *CVcdBlockDoc::GetEntityList() { return m_hEditRoot ? m_hEditRoot->GetAttribute( "entities", AT_ELEMENT_ARRAY ) : NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CVcdBlockDoc::AddNewInfoTarget( const Vector &vecOrigin, const QAngle &angAngles ) { CDmrElementArray<> entities = GetEntityList(); CDmeVMFEntity *pTarget; { CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Add Info Target", "Add Info Target" ); pTarget = CreateElement( "", entities.GetOwner()->GetFileId() ); pTarget->SetValue( "classname", "info_target" ); pTarget->SetRenderOrigin( vecOrigin ); pTarget->SetRenderAngles( angAngles ); entities.AddToTail( pTarget ); pTarget->MarkDirty(); pTarget->DrawInEngine( true ); } g_pVcdBlockTool->GetInfoTargetBrowser()->SelectNode( pTarget ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CVcdBlockDoc::AddNewInfoTarget( void ) { Vector vecOrigin; QAngle angAngles; float flFov; clienttools->GetLocalPlayerEyePosition( vecOrigin, angAngles, flFov ); AddNewInfoTarget( vecOrigin, vec3_angle ); } //----------------------------------------------------------------------------- // Deletes a commentary node //----------------------------------------------------------------------------- void CVcdBlockDoc::DeleteInfoTarget( CDmeVMFEntity *pNode ) { CDmrElementArray entities = GetEntityList(); int nCount = entities.Count(); for ( int i = 0; i < nCount; ++i ) { if ( pNode == entities[i] ) { CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Delete Info Target", "Delete Info Target" ); CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] ); pNode->DrawInEngine( false ); entities.FastRemove( i ); return; } } } //----------------------------------------------------------------------------- // Purpose: // Input : &vecOrigin - // &angAbsAngles - // Output : CDmeVMFEntity //----------------------------------------------------------------------------- CDmeVMFEntity *CVcdBlockDoc::GetInfoTargetForLocation( Vector &vecOrigin, QAngle &angAbsAngles ) { const CDmrElementArray<> entities = GetEntityList(); int nCount = entities.Count(); for ( int i = 0; i < nCount; ++i ) { CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] ); Vector &vecAngles = *(Vector*)(&pNode->GetRenderAngles()); if ( pNode->GetRenderOrigin().DistTo( vecOrigin ) < 1e-3 && vecAngles.DistTo( *(Vector*)&angAbsAngles ) < 1e-1 ) return pNode; } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : &vecStart - // &vecEnd - // Output : CDmeVMFEntity //----------------------------------------------------------------------------- CDmeVMFEntity *CVcdBlockDoc::GetInfoTargetForLocation( Vector &vecStart, Vector &vecEnd ) { Vector vecDelta; float flEndDist; vecDelta = vecEnd - vecStart; flEndDist = VectorNormalize( vecDelta ); CDmeVMFEntity *pSelectedNode = NULL; float flMinDistFromLine = 1E30; const CDmrElementArray entities = GetEntityList(); int nCount = entities.Count(); for ( int i = 0; i < nCount; ++i ) { CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] ); float flDistAway = DotProduct( pNode->GetRenderOrigin() - vecStart, vecDelta ); if (flDistAway > 0.0 && flDistAway < flEndDist) { float flDistFromLine = (pNode->GetRenderOrigin() - vecStart - vecDelta * flDistAway).Length(); if (flDistFromLine < flMinDistFromLine) { pSelectedNode = pNode; flMinDistFromLine = flDistFromLine; } } } return pSelectedNode; } //----------------------------------------------------------------------------- // Populate string choice lists //----------------------------------------------------------------------------- bool CVcdBlockDoc::GetStringChoiceList( const char *pChoiceListType, CDmElement *pElement, const char *pAttributeName, bool bArrayElement, StringChoiceList_t &list ) { if ( !Q_stricmp( pChoiceListType, "info_targets" ) ) { const CDmrElementArray<> entities = GetEntityList(); StringChoice_t sChoice; sChoice.m_pValue = ""; sChoice.m_pChoiceString = ""; list.AddToTail( sChoice ); int nCount = entities.Count(); for ( int i = 0; i < nCount; ++i ) { CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] ); if ( !V_stricmp( pNode->GetClassName(), "info_target" ) ) { StringChoice_t sChoice; sChoice.m_pValue = pNode->GetTargetName(); sChoice.m_pChoiceString = pNode->GetTargetName(); list.AddToTail( sChoice ); } } return true; } return false; } //----------------------------------------------------------------------------- // Populate element choice lists //----------------------------------------------------------------------------- bool CVcdBlockDoc::GetElementChoiceList( const char *pChoiceListType, CDmElement *pElement, const char *pAttributeName, bool bArrayElement, ElementChoiceList_t &list ) { if ( !Q_stricmp( pChoiceListType, "allelements" ) ) { AddElementsRecursively( m_hEditRoot, list ); return true; } if ( !Q_stricmp( pChoiceListType, "info_targets" ) ) { const CDmrElementArray<> entities = GetEntityList(); bool bFound = false; int nCount = entities.Count(); for ( int i = 0; i < nCount; ++i ) { CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] ); if ( !V_stricmp( pNode->GetClassName(), "info_target" ) ) { bFound = true; ElementChoice_t sChoice; sChoice.m_pValue = pNode; sChoice.m_pChoiceString = pNode->GetTargetName(); list.AddToTail( sChoice ); } } return bFound; } // by default, try to treat the choice list type as a Dme element type AddElementsRecursively( m_hEditRoot, list, pChoiceListType ); return list.Count() > 0; } //----------------------------------------------------------------------------- // Called when data changes //----------------------------------------------------------------------------- void CVcdBlockDoc::OnDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags ) { SetDirty( nNotifyFlags & NOTIFY_SETDIRTYFLAG ? true : false ); m_pCallback->OnDocChanged( pReason, nNotifySource, nNotifyFlags ); } //----------------------------------------------------------------------------- // List of all entity classnames to copy over from the original block //----------------------------------------------------------------------------- static const char *s_pUseOriginalClasses[] = { "worldspawn", "func_occluder", NULL }; //----------------------------------------------------------------------------- // The server just loaded, populate the list with the entities is has //----------------------------------------------------------------------------- void CVcdBlockDoc::ServerLevelInitPostEntity( void ) { CDmrElementArray<> entityList = GetEntityList(); if ( entityList.Count() ) { VerifyAllEdits( entityList ); } else { InitializeFromServer( entityList ); } } //----------------------------------------------------------------------------- // Create a list of entities based on what the server has //----------------------------------------------------------------------------- void CVcdBlockDoc::InitializeFromServer( CDmrElementArray<> &entityList ) { CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Initialize From Server" ); entityList.RemoveAll(); // initialize list with entities on the server CBaseEntity *pServerEnt = servertools->FirstEntity(); while (pServerEnt) { char classname[256]; if (servertools->GetKeyValue( pServerEnt, "classname", classname, sizeof( classname ) ) ) { if ( !Q_stricmp( classname, "info_target" )) { char hammerid[256]; if ( servertools->GetKeyValue( pServerEnt, "hammerid", hammerid, sizeof( hammerid ) ) ) { int nextId = CDmeVMFEntity::GetNextEntityId(); CDmeVMFEntity::SetNextEntityId( atoi( hammerid ) ); CDmeVMFEntity *pTarget = CreateElement( "", entityList.GetOwner()->GetFileId() ); CDmeVMFEntity::SetNextEntityId( nextId ); if ( pTarget->CopyFromServer( pServerEnt ) ) { entityList.AddToTail( pTarget ); } } } } pServerEnt = servertools->NextEntity( pServerEnt ); } } //----------------------------------------------------------------------------- // Check the list of entities on the server against the edits that are already made //----------------------------------------------------------------------------- void CVcdBlockDoc::VerifyAllEdits( const CDmrElementArray<> &entityList ) { // already filled in for (int i = 0; i < entityList.Count(); i++) { CDmeVMFEntity *pEntity = CastElement( entityList[i] ); CBaseEntity *pServerEntity = servertools->FindEntityByHammerID( pEntity->GetEntityId() ); if (pServerEntity != NULL) { if (!pEntity->IsSameOnServer( pServerEntity )) { pEntity->MarkDirty(); } else { pEntity->MarkDirty(false); } } else { pEntity->CreateOnServer(); pEntity->MarkDirty(); } } } //----------------------------------------------------------------------------- // Load the VMF file, merge in all the edits, write it back out //----------------------------------------------------------------------------- bool CVcdBlockDoc::CopyEditsToVMF( ) { const CDmrElementArray entityList = GetEntityList(); CDmElement *pVMF = NULL; DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( m_pVMFFileName ); if ( g_pDataModel->RestoreFromFile( m_pVMFFileName, NULL, "vmf", &pVMF ) == DMFILEID_INVALID ) { // needs some kind of error message return false; } CDmrElementArray vmfEntities( pVMF, "entities" ); int nVMFCount = vmfEntities.Count(); for (int i = 0; i < nVMFCount; i++) { CDmElement *pVMFEntity = vmfEntities[i]; char classname[256]; pVMFEntity->GetValueAsString( "classname", classname, sizeof( classname ) ); if ( Q_stricmp( "info_target", classname ) ) continue; int nHammerID = atoi( pVMFEntity->GetName() ); // find a match. int nCount = entityList.Count(); for (int j = 0; j < nCount; j++) { CDmeVMFEntity *pEntity = CastElement( entityList[j] ); if ( pEntity->IsDirty() && pEntity->GetEntityId() == nHammerID) { char text[256]; pEntity->GetValueAsString( "targetname", text, sizeof( text ) ); pVMFEntity->SetValueFromString( "targetname", text ); pEntity->GetValueAsString( "origin", text, sizeof( text ) ); pVMFEntity->SetValueFromString( "origin", text ); pEntity->GetValueAsString( "angles", text, sizeof( text ) ); pVMFEntity->SetValueFromString( "angles", text ); pEntity->MarkDirty(false); } } } // add the new entities int nCount = entityList.Count(); for (int j = 0; j < nCount; j++) { CDmeVMFEntity *pEntity = CastElement( entityList[j] ); if ( pEntity->IsDirty()) { CDmElement *pVMFEntity = CreateElement( pEntity->GetName(), fileid ); char text[256]; pEntity->GetValueAsString( "classname", text, sizeof( text ) ); pVMFEntity->SetValue( "classname", text ); pEntity->GetValueAsString( "targetname", text, sizeof( text ) ); pVMFEntity->SetValue( "targetname", text ); pEntity->GetValueAsString( "origin", text, sizeof( text ) ); pVMFEntity->SetValue( "origin", text ); pEntity->GetValueAsString( "angles", text, sizeof( text ) ); pVMFEntity->SetValue( "angles", text ); vmfEntities.AddToTail( pVMFEntity ); pEntity->MarkDirty(false); } } // currently, don't overwrite the vmf, not sure if this is serializing correctly yet char tmpname[ 256 ]; Q_StripExtension( m_pVMFFileName, tmpname, sizeof(tmpname) ); Q_SetExtension( tmpname, ".vme", sizeof(tmpname) ); if (!g_pDataModel->SaveToFile( tmpname, NULL, "keyvalues", "vmf", pVMF )) { // needs some kind of error message return false; } /* // If we successfully read the file in, ask it for the max hammer id int nMaxHammerId = pVMF->GetAttributeValue( "maxHammerId" ); CDmeVMFEntity::SetNextEntityId( nMaxHammerId + 1 ); m_hVMFRoot = pVMF; */ return true; } bool CVcdBlockDoc::RememberPlayerPosition() { return true; }