sqwarmed/sdk_src/game/missionchooser/asw_map_builder.cpp

745 lines
24 KiB
C++
Raw Normal View History

2024-08-29 19:18:30 -04:00
#include "convar.h"
#include "asw_map_builder.h"
#include "missionchooser/iasw_mission_chooser_source.h"
#include "missionchooser/iasw_random_missions.h"
#include "filesystem.h"
#include "threadtools.h"
#include "KeyValues.h"
#include "asw_mission_chooser.h"
#ifdef SUPPORT_VBSP_2
#include "vbsp2lib/serializesimplebspfile.h"
#include "vbsp2lib/simplemapfile.h"
#include "vbsp2lib/simplebspfile.h"
#endif
#include "vstdlib/random.h"
#include "tilegen_core.h"
#include "MapLayout.h"
#include "layout_system/tilegen_layout_system.h"
#include "LevelTheme.h"
#include "VMFExporter.h"
#include "Room.h"
#include "cdll_int.h"
// includes needed for the creating of a new process and handling its output
// ASW TODO: Handle Linux/Xbox way of doing this
#pragma warning( disable : 4005 )
#include <windows.h>
#include <iostream>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// @TODO: Nuke all of the switches for VBSP1 vs VBSP2 and old mission system vs new mission system. Move level generation to the background thread.
static ConVar asw_vbsp2( "asw_vbsp2", "0", FCVAR_REPLICATED ); // 0 = Use default map builder (VBSP.EXE), 1 = Use new, experimental level builder (VBSP2LIB.LIB)
static ConVar tilegen_retry_count( "tilegen_retry_count", "20", FCVAR_CHEAT, "The number of level generation retries to attempt after which tilegen will give up." );
ConVar asw_regular_floor_texture( "asw_regular_floor_texture", "REGULAR_FLOOR", FCVAR_NONE, "Regular floor texture to replace" );
ConVar asw_alien_floor_texture( "asw_alien_floor_texture", "ALIEN_FLOOR", FCVAR_NONE, "Alien floor texture used for replacement" );
DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_TilegenGeneral, "TilegenGeneral" );
extern IVEngineClient *engine;
static const int g_ProgressAmounts[] =
{
0, // Not yet started
1, // Initialized
15, // Map loaded
30, // Instances resolved
70, // BSP created
100, // BSP saved, all done.
};
static const char *g_ProgressLabels[] =
{
"Initializing...",
"Loading map...",
"Resolving Instances...",
"Creating BSP...",
"Saving BSP...",
"Done!"
};
enum MapBuilderCommand_t
{
MBC_PROCESS_MAP = 0,
MBC_SHUTDOWN = 1
};
#ifdef SUPPORT_VBSP_2
// Callback passed in to CSimpleMapFile::ResolveInstances to perform fix-up on instanced maps before the BSP process.
static void FixupInstance( void *pContext, CSimpleMapFile *pInstanceMapFile, MapEntityKeyValuePair_t *pFuncInstanceKeyValuePairs, int nNumKeyValuePairs );
#endif
//-----------------------------------------------------------------------------
// Worker thread implementation to handle building maps.
//-----------------------------------------------------------------------------
class CMapBuilderWorkerThread : public CWorkerThread
{
public:
CMapBuilderWorkerThread( CASW_Map_Builder *pMapBuilder ) :
m_pMapBuilder( pMapBuilder )
{
}
virtual int Run()
{
while ( true )
{
WaitForCall();
if ( GetCallParam() == MBC_SHUTDOWN )
{
// exit cleanly
return 0;
}
else
{
#ifdef SUPPORT_VBSP_2
char filename[MAX_PATH];
// Safe to access m_szVBSP2MapName because it will not be changed by the main thread while this is happening.
Q_snprintf( filename, sizeof( filename ), "maps\\%s", m_pMapBuilder->m_szVBSP2MapName );
CSimpleMapFile *pSimpleMapFile;
m_pMapBuilder->m_nVBSP2Progress = g_ProgressAmounts[1];
CSimpleMapFile::LoadFromFile( g_pFullFileSystem, filename, &pSimpleMapFile );
m_pMapBuilder->m_nVBSP2Progress = g_ProgressAmounts[2];
pSimpleMapFile->ResolveInstances( CSimpleMapFile::CONVERT_STRUCTURAL_TO_DETAIL, FixupInstance, m_pMapBuilder );
m_pMapBuilder->m_nVBSP2Progress = g_ProgressAmounts[3];
// mess with the pSimpleMapFile here
/*
CMapLayout *pLayout = m_pMapBuilder->GetCurrentlyBuildingMapLayout();
// find the alien floor texture
int iBrushes = pSimpleMapFile->GetBrushCount();
const MapBrush_t *pBrushes = pSimpleMapFile->GetBrushes();
const MapBrushSide_t *pBrushSides = pSimpleMapFile->GetBrushSides();
MapTextureInfo_t *pTextureInfos = pSimpleMapFile->GetTextureInfos();
MapTextureData_t *pTextureDatas = pSimpleMapFile->GetTextureData();
const MapTextureInfo_t *pAlienFloorTextureInfo = NULL; // a texture info that's using the alien floor material
for ( int i = 0; i < iBrushes; i++ )
{
for ( int side = pBrushes[i].m_nFirstSideIndex; side < pBrushes[i].m_nFirstSideIndex + pBrushes[i].m_nNumSides; side++ )
{
const MapTextureInfo_t *pTextureInfo = &pTextureInfos[ pBrushSides[ side ].m_nTextureInfoIndex ];
const MapTextureData_t *pTextureData = &pTextureDatas[ pTextureInfo->m_nTextureDataIndex ];
if ( !Q_stricmp( pTextureData->m_MaterialName, asw_alien_floor_texture.GetString() ) )
{
pAlienFloorTextureInfo = pTextureInfo;
break;
}
}
}
if ( pAlienFloorTextureInfo )
{
// now find all sides using the regular floor texture
for ( int i = 0; i < iBrushes; i++ )
{
// is this brush in an encounter room? - just check the center for now
Vector vecCenter = ( pBrushes[i].m_vMinBounds + pBrushes[i].m_vMaxBounds ) * 0.5f;
CRoom *pRoom = pLayout->GetRoom( vecCenter );
if ( !pRoom || !pRoom->HasAlienEncounter() )
continue;
for ( int side = pBrushes[i].m_nFirstSideIndex; side < pBrushes[i].m_nFirstSideIndex + pBrushes[i].m_nNumSides; side++ )
{
MapTextureInfo_t *pTextureInfo = &pTextureInfos[ pBrushSides[ side ].m_nTextureInfoIndex ];
MapTextureData_t *pTextureData = &pTextureDatas[ pTextureInfo->m_nTextureDataIndex ];
if ( !Q_stricmp( pTextureData->m_MaterialName, asw_regular_floor_texture.GetString() ) )
{
// switch regular floor over to using the new texture index
pTextureInfo->m_nTextureDataIndex = pAlienFloorTextureInfo->m_nTextureDataIndex;
}
}
}
}
else
{
Warning( "Couldn't find alien floor texture in map\n" );
}
*/
// re-resolve if necessary
//pSimpleMapFile->ResolveInstances( CSimpleMapFile::CONVERT_STRUCTURAL_TO_DETAIL, NULL, NULL );
char bspFilename[MAX_PATH];
Q_strncpy( bspFilename, filename, MAX_PATH );
Q_SetExtension( bspFilename, ".bsp", MAX_PATH );
CUtlStreamBuffer outputBSPFile( bspFilename, NULL );
CSimpleBSPFile *pBSPFile = new CSimpleBSPFile();
pBSPFile->CreateFromMapFile( pSimpleMapFile );
m_pMapBuilder->m_nVBSP2Progress = g_ProgressAmounts[4];
SaveToFile( &outputBSPFile, pBSPFile );
outputBSPFile.Close();
delete pBSPFile;
delete pSimpleMapFile;
// Commit all reads/writes before ending task
ThreadMemoryBarrier();
m_pMapBuilder->m_nVBSP2Progress = g_ProgressAmounts[5];
Reply( 0 );
#else
return 0;
#endif
}
}
return 0;
}
private:
CASW_Map_Builder *m_pMapBuilder;
};
CASW_Map_Builder::CASW_Map_Builder() :
m_flStartProcessingTime( 0.0f ),
m_iBuildStage( STAGE_NONE ),
m_bStartedGeneration( false ),
m_flProgress( 0.0f ),
m_pGeneratedMapLayout( NULL ),
m_pBuildingMapLayout( NULL ),
m_pLayoutSystem( NULL ),
m_nLevelGenerationRetryCount( 0 ),
m_pMissionSettings( NULL ),
m_pMissionDefinition( NULL ),
m_pWorkerThread( NULL )
{
m_szLayoutName[0] = '\0';
m_iCurrentBuildSearch = 0;
m_bRunningProcess = false;
m_bFinishedExecution = false;
Q_snprintf( m_szStatusMessage, sizeof( m_szStatusMessage ), "Generating map..." );
m_pMapBuilderOptions = new KeyValues( "map_builder_options" );
m_pMapBuilderOptions->LoadFromFile( g_pFullFileSystem, "resource/map_builder_options.txt", "GAME" );
m_pWorkerThread = new CMapBuilderWorkerThread( this );
m_pWorkerThread->Start();
}
CASW_Map_Builder::~CASW_Map_Builder()
{
m_pMapBuilderOptions->deleteThis();
delete m_pGeneratedMapLayout;
delete m_pBuildingMapLayout;
delete m_pLayoutSystem;
// Tell the worker thread to shutdown and block until finished
m_pWorkerThread->CallWorker( MBC_SHUTDOWN );
delete m_pWorkerThread;
}
void CASW_Map_Builder::Update( float flEngineTime )
{
if ( m_bRunningProcess )
{
ProcessExecution();
}
else if ( m_iBuildStage == STAGE_MAP_BUILD_SCHEDULED )
{
if ( m_flStartProcessingTime < flEngineTime )
{
BuildMap();
}
}
else if ( m_iBuildStage == STAGE_VBSP2 )
{
UpdateVBSP2Progress();
}
else if ( m_iBuildStage == STAGE_GENERATE )
{
if ( m_flStartProcessingTime < flEngineTime )
{
if ( !m_bStartedGeneration )
{
delete m_pGeneratedMapLayout;
delete m_pLayoutSystem;
m_pLayoutSystem = new CLayoutSystem();
AddListeners( m_pLayoutSystem );
m_pGeneratedMapLayout = new CMapLayout( m_pMissionSettings->MakeCopy() );
if ( !m_pLayoutSystem->LoadFromKeyValues( m_pMissionDefinition ) )
{
Log_Warning( LOG_TilegenLayoutSystem, "Failed to load mission from key values definition.\n" );
m_iBuildStage = STAGE_NONE;
return;
}
m_pLayoutSystem->BeginGeneration( m_pGeneratedMapLayout );
m_bStartedGeneration = true;
}
else
{
if ( m_pLayoutSystem->IsGenerating() )
{
m_pLayoutSystem->ExecuteIteration();
// If an error occurred and this map is randomly generated, try again and hope we get a successful layout this time.
if ( m_pLayoutSystem->GenerationErrorOccurred() )
{
if ( m_nLevelGenerationRetryCount < tilegen_retry_count.GetInt() && m_pLayoutSystem->IsRandomlyGenerated() )
{
// Error generating layout
Log_Msg( LOG_TilegenGeneral, "Retrying layout generation...\n" );
m_pGeneratedMapLayout->Clear();
m_pLayoutSystem->BeginGeneration( m_pGeneratedMapLayout );
++ m_nLevelGenerationRetryCount;
}
else
{
Log_Warning( LOG_TilegenGeneral, "Failed to generate valid map layout after %d tries...\n", tilegen_retry_count.GetInt() );
m_iBuildStage = STAGE_NONE;
}
}
}
else
{
Log_Msg( LOG_TilegenGeneral, "Map layout generated\n" );
m_iBuildStage = STAGE_NONE;
char layoutFilename[MAX_PATH];
Q_snprintf( layoutFilename, MAX_PATH, "maps\\%s", m_szLayoutName );
m_pGeneratedMapLayout->SaveMapLayout( layoutFilename );
delete m_pGeneratedMapLayout;
m_pGeneratedMapLayout = NULL;
BuildMap();
}
}
}
}
}
bool CASW_Map_Builder::IsBuildingMission()
{
return m_iBuildStage != STAGE_NONE;
}
void CASW_Map_Builder::Execute(const char* pszCmd, const char* pszCmdLine)
{
m_bFinishedExecution = false;
m_iProcessReturnValue = -1;
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child's STDOUT.
if(CreatePipe(&m_hChildStdoutRd, &m_hChildStdoutWr, &saAttr, 0))
{
if(CreatePipe(&m_hChildStdinRd, &m_hChildStdinWr, &saAttr, 0))
{
if (DuplicateHandle(GetCurrentProcess(),m_hChildStdoutWr, GetCurrentProcess(),&m_hChildStderrWr,0, TRUE,DUPLICATE_SAME_ACCESS))
{
/* Now create the child process. */
STARTUPINFO si;
memset(&si, 0, sizeof si);
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = m_hChildStdinRd;
si.hStdError = m_hChildStderrWr;
si.hStdOutput = m_hChildStdoutWr;
PROCESS_INFORMATION pi;
char cmdbuffer[512];
Q_snprintf(cmdbuffer, sizeof(cmdbuffer), "%s %s", pszCmd, pszCmdLine);
Msg("Sending command %s\n", cmdbuffer);
// run the process from the current game's map directory
char dirbuffer[512];
Q_snprintf(dirbuffer, sizeof(dirbuffer), "%s/maps", engine->GetGameDirectory() );
Msg(" from directory %s\n", dirbuffer);
if(CreateProcess(pszCmd, cmdbuffer, NULL, NULL, TRUE,
DETACHED_PROCESS | BELOW_NORMAL_PRIORITY_CLASS, NULL, dirbuffer, &si, &pi))
{
m_hProcess = pi.hProcess;
m_bRunningProcess = true;
m_bFinishedExecution = false;
// do one process of the execution
ProcessExecution();
}
else
{
Msg("* Could not execute the command:\r\n %s\r\n", cmdbuffer);
m_bRunningProcess = false;
FinishExecution(); // closes all handles
}
}
else
{
// close the 4 handles we've opened so far
CloseHandle(m_hChildStdinRd);
CloseHandle(m_hChildStdinWr);
CloseHandle(m_hChildStdoutRd);
CloseHandle(m_hChildStdoutWr);
}
}
else
{
// close the 2 handles we've opened so far
CloseHandle(m_hChildStdoutRd);
CloseHandle(m_hChildStdoutWr);
}
}
}
void CASW_Map_Builder::ProcessExecution()
{
DWORD dwCount = 0;
DWORD dwRead = 0;
// read from input handle
PeekNamedPipe(m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL);
if (dwCount)
{
dwCount = MIN (dwCount, 4096 - 1);
ReadFile(m_hChildStdoutRd, m_szProcessBuffer, dwCount, &dwRead, NULL);
}
if(dwRead)
{
m_szProcessBuffer[dwRead] = 0;
UpdateProgress();
Msg(m_szProcessBuffer);
}
// check process termination
else if ( WaitForSingleObject(m_hProcess, 1000) != WAIT_TIMEOUT )
{
if(m_bFinishedExecution)
{
m_iProcessReturnValue = 0;
FinishExecution();
}
else
{
m_bFinishedExecution = true;
}
}
}
// called when one of our processes finishes
void CASW_Map_Builder::FinishExecution()
{
m_bRunningProcess = false; // next time we get it
CloseHandle(m_hChildStderrWr);
CloseHandle(m_hChildStdinRd);
CloseHandle(m_hChildStdinWr);
CloseHandle(m_hChildStdoutRd);
CloseHandle(m_hChildStdoutWr);
if (m_iBuildStage == STAGE_VBSP && m_iProcessReturnValue == 0)
{
char buffer[512];
Q_snprintf(buffer, sizeof(buffer), "-game ..\\ %s %s", m_pMapBuilderOptions->GetString( "vvis", "" ), m_szLayoutName); // todo: add code to chop into 256 blocks here
Execute("bin/vvis.exe", buffer);
m_iBuildStage = STAGE_VVIS;
}
else if (m_iBuildStage == STAGE_VVIS && m_iProcessReturnValue == 0)
{
if ( !m_pMapBuilderOptions->FindKey( "vrad", false ) ) // if no vrad key is specified in the map builder options, then skip vrad
{
m_iBuildStage = STAGE_NONE;
Msg("Map Build finished!\n");
m_flProgress = 1.0f;
Q_snprintf( m_szStatusMessage, sizeof( m_szStatusMessage ), "Build complete!" );
}
else
{
char buffer[512];
Q_snprintf(buffer, sizeof(buffer), "-low -game ..\\ %s %s", m_pMapBuilderOptions->GetString( "vrad", "" ), m_szLayoutName);
Execute("bin/vrad.exe", buffer);
m_iBuildStage = STAGE_VRAD;
}
}
else if (m_iBuildStage == STAGE_VRAD && m_iProcessReturnValue == 0)
{
m_iBuildStage = STAGE_NONE;
Msg("Map Build finished!\n");
m_flProgress = 1.0f;
Q_snprintf( m_szStatusMessage, sizeof( m_szStatusMessage ), "Build complete!" );
}
if (m_iProcessReturnValue == -1)
{
//Msg("Map Build error\n");
}
}
// search terms used to work out how far through the build we are
static char const *s_szProgressTerms[]={
"Valve Software - vbsp.exe",
"ProcessBlock_Thread:",
"Processing areas",
"WriteBSP...",
"Displacement Alpha",
"Building Physics collision data",
"Valve Software - vvis.exe",
"BasePortalVis:",
"PortalFlow:",
"Valve Software - vrad.exe",
"BuildFacelights:",
"FinalLightFace:",
"ThreadComputeLeafAmbient:",
"Computing static prop lighting"
};
static char const *s_szStatusLabels[]={
"Creating BSP...",
"Creating BSP...",
"Creating BSP...",
"Creating BSP...",
"Creating BSP...",
"Creating BSP...",
"Calculating visibility...",
"Calculating visibility...",
"Calculating visibility...",
"Calculating lighting...",
"Calculating lighting...",
"Calculating lighting...",
"Calculating lighting...",
"Calculating prop lighting...",
};
// monitor output from our process to determine which part of the build we're in
void CASW_Map_Builder::UpdateProgress()
{
// copy the new chars into our buffer
int newcharslen = Q_strlen(m_szProcessBuffer);
for (int i=0;i<newcharslen;i++)
{
m_szOutputBuffer[m_iOutputBufferPos++] = m_szProcessBuffer[i];
// if we go over the end of our output buffer, then shift everything back by half the buffer and continue
if (m_iOutputBufferPos >= MAP_BUILD_OUTPUT_BUFFER_SIZE)
{
for (int k=0;k<MAP_BUILD_OUTPUT_BUFFER_HALF_SIZE;k++)
{
m_szOutputBuffer[k] = m_szOutputBuffer[k + MAP_BUILD_OUTPUT_BUFFER_HALF_SIZE];
}
m_iOutputBufferPos = MAP_BUILD_OUTPUT_BUFFER_HALF_SIZE;
}
}
// now scan our buffer for progress messages in reverse order
int iNumSearch = NELEMS(s_szProgressTerms);
for (int iSearch=iNumSearch-1;iSearch>m_iCurrentBuildSearch;iSearch--)
{
char *pos = Q_strstr(m_szOutputBuffer, s_szProgressTerms[iSearch]);
if ( pos )
{
//Msg("Output (%s) matched (%s) result %s at %d\n", m_szOutputBuffer, s_szProgressTerms[iSearch], pos, pos - m_szOutputBuffer);
m_iCurrentBuildSearch = iSearch;
m_flProgress = float(iSearch) / float (iNumSearch);
if (Q_strlen(s_szStatusLabels[iSearch]) > 0)
{
Q_snprintf( m_szStatusMessage, sizeof(m_szStatusMessage), "%s", s_szStatusLabels[iSearch] );
}
break;
}
}
}
// schedules a map to be compiled
void CASW_Map_Builder::ScheduleMapBuild(const char* pszMap, const float fTime)
{
if ( m_iBuildStage != STAGE_NONE )
{
Log_Warning( LOG_TilegenGeneral, "Map builder is currently busy, ignoring request to schedule map build for map '%s'", pszMap );
return;
}
Q_strncpy( m_szLayoutName, Q_UnqualifiedFileName( pszMap ), _countof( m_szLayoutName ) );
Q_SetExtension( m_szLayoutName, "layout", _countof( m_szLayoutName ) );
m_flStartProcessingTime = fTime;
m_bStartedGeneration = false;
m_iBuildStage = STAGE_MAP_BUILD_SCHEDULED;
m_flProgress = 0.0f;
2024-08-29 19:27:02 -04:00
Q_snprintf( m_szStatusMessage, sizeof( m_szStatusMessage ), "Generating map..." );
2024-08-29 19:18:30 -04:00
}
// schedules a map to be randomly generated
void CASW_Map_Builder::ScheduleMapGeneration( const char* pszMap, const float fTime, KeyValues *pMissionSettings, KeyValues *pMissionDefinition )
{
if ( m_iBuildStage != STAGE_NONE )
{
Log_Warning( LOG_TilegenGeneral, "Map builder is currently busy, ignoring request to schedule map generation for map '%s'", pszMap );
return;
}
Q_strncpy( m_szLayoutName, Q_UnqualifiedFileName( pszMap ), _countof( m_szLayoutName ) );
Q_SetExtension( m_szLayoutName, "layout", _countof( m_szLayoutName ) );
m_pMissionSettings = pMissionSettings;
m_pMissionDefinition = pMissionDefinition;
m_flStartProcessingTime = fTime;
m_iBuildStage = STAGE_GENERATE;
m_bStartedGeneration = false;
m_nLevelGenerationRetryCount = 0;
m_flProgress = 0.0f;
2024-08-29 19:27:02 -04:00
Q_snprintf( m_szStatusMessage, sizeof( m_szStatusMessage ), "Generating map..." );
2024-08-29 19:18:30 -04:00
}
// Builds a map from a .layout file
void CASW_Map_Builder::BuildMap()
{
char layoutFilename[MAX_PATH];
char vmfFilename[MAX_PATH];
Q_snprintf( layoutFilename, MAX_PATH, "maps\\%s", m_szLayoutName );
Q_strncpy( vmfFilename, m_szLayoutName, MAX_PATH );
Q_SetExtension( vmfFilename, "vmf", MAX_PATH );
Log_Msg( LOG_TilegenGeneral, "Building map from layout: %s, emitting map file: %s\n", layoutFilename, vmfFilename );
// Make sure our themes are loaded
CLevelTheme::LoadLevelThemes();
// Load the .layout from disk
// @TODO: keep this in memory and avoid the round-trip
delete m_pBuildingMapLayout;
m_pBuildingMapLayout = new CMapLayout();
if ( !m_pBuildingMapLayout->LoadMapLayout( layoutFilename ) )
{
delete m_pBuildingMapLayout;
m_pBuildingMapLayout = NULL;
return;
}
// Export it to VMF
VMFExporter *pExporter = new VMFExporter();
bool bSuccess = pExporter->ExportVMF( m_pBuildingMapLayout, m_szLayoutName );
delete pExporter;
if ( !bSuccess )
{
Log_Warning( LOG_TilegenGeneral, "Failed to create VMF from layout '%s'.\n", m_szLayoutName );
delete m_pBuildingMapLayout;
m_pBuildingMapLayout = NULL;
}
if ( asw_vbsp2.GetInt() )
{
m_iBuildStage = STAGE_VBSP2;
m_nVBSP2Progress = 0;
Q_strncpy( m_szVBSP2MapName, vmfFilename, MAX_PATH );
// Guarantee all reads/writes committed before kicking off the thread. Probably unnecessary in practice due to lag between operations, but whatever...
ThreadMemoryBarrier();
// Call with a 0 ms timeout to return immediately
m_pWorkerThread->CallWorker( MBC_PROCESS_MAP, 0 );
}
else
{
// Building map layout is ignored in VBSP1 codepath
delete m_pBuildingMapLayout;
m_pBuildingMapLayout = NULL;
m_iBuildStage = STAGE_VBSP;
char buffer[512];
Q_snprintf( buffer, sizeof(buffer), "-game ..\\ %s %s", m_pMapBuilderOptions->GetString( "vbsp", "" ), vmfFilename );
Execute( "bin/vbsp.exe", buffer );
m_iCurrentBuildSearch = 0;
m_iOutputBufferPos = 0;
Q_memset( &m_szOutputBuffer, 0, sizeof( m_szOutputBuffer ) );
}
}
void CASW_Map_Builder::UpdateVBSP2Progress()
{
// Make sure any reads/writes are committed before reading progress from the background thread.
ThreadMemoryBarrier();
int nProgress = m_nVBSP2Progress;
m_flProgress = ( nProgress == 100 ) ? 1.0f : (float) nProgress / 100.0f;
int nNumProgressLevels = _countof( g_ProgressAmounts );
for ( int i = nNumProgressLevels - 1; i >= 0; -- i )
{
if ( nProgress >= g_ProgressAmounts[i] )
{
Q_strncpy( m_szStatusMessage, g_ProgressLabels[i], _countof( m_szStatusMessage ) );
break;
}
}
if ( nProgress == 100 )
{
delete m_pBuildingMapLayout;
m_pBuildingMapLayout = NULL;
m_iBuildStage = STAGE_NONE;
}
}
static CUniformRandomStream s_Random;
#ifdef SUPPORT_VBSP_2
static void AddFuncInstance( CSimpleMapFile *pInstanceMapFile, CInstanceSpawn *pInstanceSpawn, const Vector &vPosition )
{
CUtlVector< MapEntityKeyValuePair_t > replacePairs;
replacePairs.AddMultipleToTail( pInstanceSpawn->GetAdditionalKeyValueCount() );
for ( int i = 0; i < pInstanceSpawn->GetAdditionalKeyValueCount(); ++ i )
{
replacePairs[i].m_pKey = pInstanceSpawn->GetAdditionalKeyValues()[i].m_Key;
replacePairs[i].m_pValue = pInstanceSpawn->GetAdditionalKeyValues()[i].m_Value;
}
pInstanceMapFile->AddFuncInstance( pInstanceSpawn->GetInstanceFilename(), QAngle( 0, 0, 0 ), vPosition, replacePairs.Base(), replacePairs.Count() );
}
// Sup dawg, I herd u like instances in yo instances...
static void FixupInstance( void *pContext, CSimpleMapFile *pInstanceMapFile, MapEntityKeyValuePair_t *pFuncInstanceKeyValuePairs, int nNumKeyValuePairs )
{
CASW_Map_Builder *pMapBuilder = ( CASW_Map_Builder * )pContext;
CMapLayout *pMapLayout = pMapBuilder->GetCurrentlyBuildingMapLayout();
int nPlacedRoomIndex = 0;
MapEntityKeyValuePair_t *pPair = FindPair( "PlacedRoomIndex", pFuncInstanceKeyValuePairs, nNumKeyValuePairs );
if ( pPair != NULL )
{
nPlacedRoomIndex = atoi( pPair->m_pValue );
}
else
{
// Only fix up placed room instances
return;
}
CUtlVector< Vector > infoNodeLocations;
// Populate the info node list only once
// Technically we don't need to do this if we don't acutally have instances to spawn in this instance
int nIndex = 0;
while ( ( nIndex = pInstanceMapFile->FindEntity( "info_node", NULL, NULL, nIndex ) ) != -1 )
{
const MapEntity_t *pEntity = &pInstanceMapFile->GetEntities()[nIndex];
infoNodeLocations.AddToTail( pEntity->m_vOrigin );
++ nIndex;
}
if ( infoNodeLocations.Count() == 0 )
{
// No places to spawn instances in this instance
return;
}
for ( int i = 0; i < pMapLayout->m_InstanceSpawns.Count(); ++ i )
{
CInstanceSpawn *pInstanceSpawn = &pMapLayout->m_InstanceSpawns[i];
if ( pInstanceSpawn->GetPlacedRoomIndex() == nPlacedRoomIndex )
{
if ( pInstanceSpawn->GetInstanceSpawningMethod() == ISM_ADD_AT_RANDOM_NODE )
{
AddFuncInstance( pInstanceMapFile, pInstanceSpawn, infoNodeLocations[pInstanceSpawn->GetRandomSeed() % infoNodeLocations.Count()] );
}
}
}
}
#endif