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.
540 lines
11 KiB
540 lines
11 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
// vmf_tweak.cpp : Defines the entry point for the console application. |
|
// |
|
|
|
#include "stdafx.h" |
|
#include "chunkfile.h" |
|
#include "utllinkedlist.h" |
|
#include <stdlib.h> |
|
#include "tier1/strtools.h" |
|
#include "tier0/icommandline.h" |
|
#include "tier1/utlrbtree.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "tier1/KeyValues.h" |
|
#include "filesystem.h" |
|
#include "FileSystem_Tools.h" |
|
#include "cmdlib.h" |
|
|
|
#include <windows.h> |
|
|
|
char const *g_pInFilename = NULL; |
|
|
|
char *CopyString( char const *pStr ); |
|
|
|
unsigned long g_CurLoadOrder = 0; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// default spec function |
|
//----------------------------------------------------------------------------- |
|
SpewRetval_t VMFTweakSpewFunc( SpewType_t spewType, char const *pMsg ) |
|
{ |
|
OutputDebugString( pMsg ); |
|
printf( pMsg ); |
|
switch( spewType ) |
|
{ |
|
case SPEW_MESSAGE: |
|
case SPEW_WARNING: |
|
case SPEW_LOG: |
|
return SPEW_CONTINUE; |
|
|
|
case SPEW_ASSERT: |
|
case SPEW_ERROR: |
|
default: |
|
return SPEW_DEBUGGER; |
|
} |
|
} |
|
|
|
void logprint( char const *logfile, const char *fmt, ... ) |
|
{ |
|
char string[ 8192 ]; |
|
va_list va; |
|
va_start( va, fmt ); |
|
vsprintf( string, fmt, va ); |
|
va_end( va ); |
|
|
|
FILE *fp = NULL; |
|
static bool first = false; |
|
if ( first ) |
|
{ |
|
first = false; |
|
fp = fopen( logfile, "wb" ); |
|
} |
|
else |
|
{ |
|
fp = fopen( logfile, "ab" ); |
|
} |
|
if ( fp ) |
|
{ |
|
char *p = string; |
|
while ( *p ) |
|
{ |
|
if ( *p == '\n' ) |
|
{ |
|
fputc( '\r', fp ); |
|
} |
|
fputc( *p, fp ); |
|
p++; |
|
} |
|
fclose( fp ); |
|
} |
|
} |
|
|
|
|
|
class CChunkKeyBase |
|
{ |
|
public: |
|
unsigned long m_LoadOrder; // Which order it appeared in the file. |
|
}; |
|
|
|
|
|
class CKeyValue : public CChunkKeyBase |
|
{ |
|
public: |
|
void SetKey( const char *pStr ) |
|
{ |
|
delete [] m_pKey; |
|
m_pKey = CopyString( pStr ); |
|
} |
|
|
|
void SetValue( const char *pStr ) |
|
{ |
|
delete [] m_pValue; |
|
m_pValue = CopyString( pStr ); |
|
} |
|
|
|
|
|
public: |
|
char *m_pKey; |
|
char *m_pValue; |
|
}; |
|
|
|
|
|
class CChunk : public CChunkKeyBase |
|
{ |
|
public: |
|
// Returns true if the chunk has the specified key and if its value |
|
// is equal to pValueStr (case-insensitive). |
|
bool CompareKey( char const *pKeyName, char const *pValueStr ); |
|
|
|
// Look for a key by name. |
|
CKeyValue* FindKey( char const *pKeyName ); |
|
CKeyValue* FindKey( char const *pKeyName, const char *pValue ); |
|
|
|
CChunk* FindChunk( char const *pKeyName ); |
|
|
|
// Find a key by name, and replace any occurences with the new name. |
|
void RenameKey( const char *szOldName, const char *szNewName ); |
|
|
|
public: |
|
|
|
char *m_pChunkName; |
|
CUtlLinkedList<CKeyValue*, unsigned short> m_Keys; |
|
CUtlLinkedList<CChunk*, unsigned short> m_Chunks; |
|
}; |
|
|
|
|
|
CChunk* ParseChunk( char const *pChunkName, bool bOnlyOne ); |
|
|
|
|
|
CChunk *g_pCurChunk = 0; |
|
CChunkFile *g_pChunkFile = 0; |
|
int g_DotCounter = 0; |
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------- // |
|
// CChunk implementation. |
|
// --------------------------------------------------------------------------------- // |
|
|
|
bool CChunk::CompareKey( char const *pKeyName, char const *pValueStr ) |
|
{ |
|
CKeyValue *pKey = FindKey( pKeyName ); |
|
|
|
if( pKey && stricmp( pKey->m_pValue, pValueStr ) == 0 ) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
|
|
CKeyValue* CChunk::FindKey( char const *pKeyName ) |
|
{ |
|
for( unsigned short i=m_Keys.Head(); i != m_Keys.InvalidIndex(); i=m_Keys.Next(i) ) |
|
{ |
|
CKeyValue *pKey = m_Keys[i]; |
|
|
|
if( stricmp( pKey->m_pKey, pKeyName ) == 0 ) |
|
return pKey; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
CChunk* CChunk::FindChunk( char const *pChunkName ) |
|
{ |
|
for( unsigned short i=m_Chunks.Head(); i != m_Chunks.InvalidIndex(); i=m_Chunks.Next(i) ) |
|
{ |
|
CChunk *pChunk = m_Chunks[i]; |
|
|
|
if( stricmp( pChunk->m_pChunkName, pChunkName ) == 0 ) |
|
return pChunk; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
CKeyValue* CChunk::FindKey( char const *pKeyName, const char *pValue ) |
|
{ |
|
for( unsigned short i=m_Keys.Head(); i != m_Keys.InvalidIndex(); i=m_Keys.Next(i) ) |
|
{ |
|
CKeyValue *pKey = m_Keys[i]; |
|
|
|
if( stricmp( pKey->m_pKey, pKeyName ) == 0 && stricmp( pKey->m_pValue, pValue ) == 0 ) |
|
return pKey; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
void CChunk::RenameKey( const char *szOldName, const char *szNewName ) |
|
{ |
|
if ((!szOldName) || (!szNewName)) |
|
return; |
|
|
|
CKeyValue *pKey = FindKey( szOldName ); |
|
if ( pKey ) |
|
{ |
|
delete pKey->m_pKey; |
|
pKey->m_pKey = CopyString( szNewName ); |
|
} |
|
} |
|
|
|
|
|
// --------------------------------------------------------------------------------- // |
|
// Util functions. |
|
// --------------------------------------------------------------------------------- // |
|
char *CopyString( char const *pStr ) |
|
{ |
|
char *pRet = new char[ strlen(pStr) + 1 ]; |
|
strcpy( pRet, pStr ); |
|
return pRet; |
|
} |
|
|
|
ChunkFileResult_t MyDefaultHandler( CChunkFile *pFile, void *pData, char const *pChunkName ) |
|
{ |
|
CChunk *pChunk = ParseChunk( pChunkName, true ); |
|
g_pCurChunk->m_Chunks.AddToTail( pChunk ); |
|
return ChunkFile_Ok; |
|
} |
|
|
|
|
|
ChunkFileResult_t MyKeyHandler( const char *szKey, const char *szValue, void *pData ) |
|
{ |
|
// Add the key to the current chunk. |
|
CKeyValue *pKey = new CKeyValue; |
|
pKey->m_LoadOrder = g_CurLoadOrder++; |
|
|
|
pKey->m_pKey = CopyString( szKey ); |
|
pKey->m_pValue = CopyString( szValue ); |
|
|
|
g_pCurChunk->m_Keys.AddToTail( pKey ); |
|
return ChunkFile_Ok; |
|
} |
|
|
|
|
|
CChunk* ParseChunk( char const *pChunkName, bool bOnlyOne ) |
|
{ |
|
// Add the new chunk. |
|
CChunk *pChunk = new CChunk; |
|
pChunk->m_pChunkName = CopyString( pChunkName ); |
|
pChunk->m_LoadOrder = g_CurLoadOrder++; |
|
|
|
// Parse it out.. |
|
CChunk *pOldChunk = g_pCurChunk; |
|
g_pCurChunk = pChunk; |
|
|
|
//if( ++g_DotCounter % 16 == 0 ) |
|
// printf( "." ); |
|
|
|
while( 1 ) |
|
{ |
|
if( g_pChunkFile->ReadChunk( MyKeyHandler ) != ChunkFile_Ok ) |
|
break; |
|
|
|
if( bOnlyOne ) |
|
break; |
|
} |
|
|
|
g_pCurChunk = pOldChunk; |
|
return pChunk; |
|
} |
|
|
|
|
|
CChunk* ReadChunkFile( char const *pInFilename ) |
|
{ |
|
CChunkFile chunkFile; |
|
|
|
if( chunkFile.Open( pInFilename, ChunkFile_Read ) != ChunkFile_Ok ) |
|
{ |
|
printf( "Error opening chunk file %s for reading.\n", pInFilename ); |
|
return NULL; |
|
} |
|
|
|
printf( "Reading.." ); |
|
chunkFile.SetDefaultChunkHandler( MyDefaultHandler, 0 ); |
|
g_pChunkFile = &chunkFile; |
|
|
|
CChunk *pRet = ParseChunk( "***ROOT***", false ); |
|
printf( "\n\n" ); |
|
|
|
return pRet; |
|
} |
|
|
|
class CChunkHolder |
|
{ |
|
public: |
|
static bool SortChunkFn( const CChunkHolder &a, const CChunkHolder &b ) |
|
{ |
|
return a.m_LoadOrder < b.m_LoadOrder; |
|
} |
|
|
|
unsigned long m_LoadOrder; |
|
CKeyValue *m_pKey; |
|
CChunk *m_pChunk; |
|
}; |
|
|
|
|
|
void WriteChunks_R( CChunkFile *pFile, CChunk *pChunk, bool bRoot ) |
|
{ |
|
if( !bRoot ) |
|
{ |
|
pFile->BeginChunk( pChunk->m_pChunkName ); |
|
} |
|
|
|
// Sort them.. |
|
CUtlRBTree<CChunkHolder,int> sortedStuff( 0, 0, &CChunkHolder::SortChunkFn ); |
|
|
|
// Write keys. |
|
for( unsigned short i=pChunk->m_Keys.Head(); i != pChunk->m_Keys.InvalidIndex(); i = pChunk->m_Keys.Next( i ) ) |
|
{ |
|
CChunkHolder holder; |
|
holder.m_pKey = pChunk->m_Keys[i]; |
|
holder.m_LoadOrder = holder.m_pKey->m_LoadOrder; |
|
holder.m_pChunk = NULL; |
|
sortedStuff.Insert( holder ); |
|
} |
|
|
|
// Write subchunks. |
|
for( int i=pChunk->m_Chunks.Head(); i != pChunk->m_Chunks.InvalidIndex(); i = pChunk->m_Chunks.Next( i ) ) |
|
{ |
|
CChunkHolder holder; |
|
holder.m_pChunk = pChunk->m_Chunks[i]; |
|
holder.m_LoadOrder = holder.m_pChunk->m_LoadOrder; |
|
holder.m_pKey = NULL; |
|
sortedStuff.Insert( holder ); |
|
} |
|
|
|
// Write stuff in sorted order. |
|
int i = sortedStuff.FirstInorder(); |
|
if ( i != sortedStuff.InvalidIndex() ) |
|
{ |
|
while ( 1 ) |
|
{ |
|
CChunkHolder &h = sortedStuff[i]; |
|
if ( h.m_pKey ) |
|
{ |
|
pFile->WriteKeyValue( h.m_pKey->m_pKey, h.m_pKey->m_pValue ); |
|
} |
|
else |
|
{ |
|
WriteChunks_R( pFile, h.m_pChunk, false ); |
|
} |
|
if ( i == sortedStuff.LastInorder() ) |
|
break; |
|
i = sortedStuff.NextInorder( i ); |
|
} |
|
} |
|
|
|
if( !bRoot ) |
|
{ |
|
pFile->EndChunk(); |
|
} |
|
} |
|
|
|
|
|
bool WriteChunkFile( char const *pOutFilename, CChunk *pRoot ) |
|
{ |
|
CChunkFile chunkFile; |
|
|
|
if( chunkFile.Open( pOutFilename, ChunkFile_Write ) != ChunkFile_Ok ) |
|
{ |
|
printf( "Error opening chunk file %s for writing.\n", pOutFilename ); |
|
return false; |
|
} |
|
|
|
printf( "Writing.." ); |
|
WriteChunks_R( &chunkFile, pRoot, true ); |
|
printf( "\n\n" ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
// |
|
// |
|
// EXAMPLE |
|
// |
|
// |
|
void ScanRopeSlack( CChunk *pChunk ) |
|
{ |
|
if( stricmp( pChunk->m_pChunkName, "entity" ) == 0 ) |
|
{ |
|
if( pChunk->CompareKey( "classname", "keyframe_rope" ) || |
|
pChunk->CompareKey( "classname", "move_rope" ) ) |
|
{ |
|
CKeyValue *pKey = pChunk->FindKey( "slack" ); |
|
if( pKey ) |
|
{ |
|
// Subtract 100 from all the Slack properties. |
|
float flCur = (float)atof( pKey->m_pValue ); |
|
char str[256]; |
|
sprintf( str, "%f", flCur + 100 ); |
|
pKey->m_pValue = CopyString( str ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
int g_nLogicAutoReplacementsMade = 0; |
|
void LogicAuto( CChunk *pChunk ) |
|
{ |
|
if ( pChunk->CompareKey( "classname", "logic_auto" ) ) |
|
{ |
|
CChunk *pConnections = pChunk->FindChunk( "connections" ); |
|
if ( !pConnections ) |
|
return; |
|
|
|
bool bFound = false; |
|
for ( int i=0; i < pConnections->m_Keys.Count(); i++ ) |
|
{ |
|
CKeyValue *pTestKV = pConnections->m_Keys[i]; |
|
if ( V_stristr( pTestKV->m_pValue, "tonemap" ) == pTestKV->m_pValue ) |
|
{ |
|
bFound = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( !bFound ) |
|
return; |
|
|
|
++g_nLogicAutoReplacementsMade; |
|
|
|
// Ok, this is a logic_auto with OnMapSpawn outputs in its connections. |
|
CUtlLinkedList<CKeyValue*,int> newKeys; |
|
FOR_EACH_LL( pConnections->m_Keys, i ) |
|
{ |
|
CKeyValue *pTestKV = pConnections->m_Keys[i]; |
|
if ( V_stricmp( pTestKV->m_pKey, "OnMapSpawn" ) == 0 ) |
|
{ |
|
if ( V_stristr( pTestKV->m_pValue, "tonemap" ) == pTestKV->m_pValue ) |
|
{ |
|
CKeyValue *pNewKV = new CKeyValue; |
|
pNewKV->SetKey( "OnMapTransition" ); |
|
pNewKV->SetValue( pTestKV->m_pValue ); |
|
newKeys.AddToTail( pNewKV ); |
|
} |
|
} |
|
} |
|
|
|
FOR_EACH_LL( newKeys, i ) |
|
{ |
|
if ( !pConnections->FindKey( "OnMapTransition", newKeys[i]->m_pValue ) ) |
|
{ |
|
pConnections->m_Keys.AddToTail( newKeys[i] ); |
|
} |
|
} |
|
|
|
// Fix spawnflags. |
|
CKeyValue *pFlags = pChunk->FindKey("spawnflags"); |
|
if ( pFlags ) |
|
{ |
|
unsigned long curVal; |
|
sscanf( pFlags->m_pValue, "%lu", &curVal ); |
|
if ( curVal & 1 ) |
|
{ |
|
--curVal; |
|
char str[512]; |
|
sprintf( str, "%lu", curVal ); |
|
pFlags->SetValue( str ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
void ScanChunks( CChunk *pChunk, void (*fn)(CChunk *) ) |
|
{ |
|
fn( pChunk ); |
|
|
|
// Recurse into the children. |
|
for( unsigned short i=pChunk->m_Chunks.Head(); i != pChunk->m_Chunks.InvalidIndex(); i = pChunk->m_Chunks.Next( i ) ) |
|
{ |
|
ScanChunks( pChunk->m_Chunks[i], fn ); |
|
} |
|
} |
|
|
|
int main(int argc, char* argv[]) |
|
{ |
|
CommandLine()->CreateCmdLine( argc, argv ); |
|
SpewOutputFunc( VMFTweakSpewFunc ); |
|
|
|
if( argc < 2 ) |
|
{ |
|
printf( "vmf_tweak <input file> [output file]\n" ); |
|
return 1; |
|
} |
|
|
|
g_pInFilename = argv[1]; |
|
char const *pOutFilename = argc >= 3 ? argv[2] : ""; |
|
|
|
CChunk *pRoot = ReadChunkFile( g_pInFilename ); |
|
if( !pRoot ) |
|
return 2; |
|
|
|
// |
|
// |
|
// |
|
// This is where you can put code to modify the VMF. |
|
// |
|
// |
|
// |
|
|
|
// If they didn't specify -game on the command line, use VPROJECT. |
|
char workingdir[ 256 ]; |
|
workingdir[0] = 0; |
|
Q_getwd( workingdir, sizeof(workingdir) ); |
|
CmdLib_InitFileSystem( workingdir ); |
|
ScanChunks( pRoot, LogicAuto ); |
|
FileSystem_Term(); |
|
|
|
Msg( "%s: %d logic_auto replacements made.\n", g_pInFilename, g_nLogicAutoReplacementsMade ); |
|
|
|
if ( argc >= 3 ) |
|
{ |
|
if( !WriteChunkFile( pOutFilename, pRoot ) ) |
|
return 3; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|