Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

630 lines
18 KiB

//========= 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<int>( "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<CDmElement>( "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<CDmeVMFEntity>( "", 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<CDmElement> 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<CDmElement> 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<CDmeVMFEntity>( "", 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<CDmeVMFEntity>( 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<CDmElement> 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<CDmElement> 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<CDmeVMFEntity>( 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<CDmeVMFEntity>( entityList[j] );
if ( pEntity->IsDirty())
{
CDmElement *pVMFEntity = CreateElement<CDmElement>( 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<int>( "maxHammerId" );
CDmeVMFEntity::SetNextEntityId( nMaxHammerId + 1 );
m_hVMFRoot = pVMF;
*/
return true;
}
bool CVcdBlockDoc::RememberPlayerPosition()
{
return true;
}