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