#include "VMFExporter.h" #include "KeyValues.h" #include "TileSource/RoomTemplate.h" #include "TileSource/Room.h" #include "TileSource/LevelTheme.h" #include "TileSource/MapLayout.h" #include "TileGenDialog.h" // memdbgon must be the last include file in a .cpp file!!! #include ConVar tilegen_use_instancing( "tilegen_use_instancing", "0", FCVAR_REPLICATED ); // TODO: Read room templates into keyvalues and output them after the whole room template has been loaded // This way we can modify state based on the complete picture of that room, rather than just making changes on a key by key basis VMFExporter::VMFExporter() { m_pMapLayout = NULL; Init(); } VMFExporter::~VMFExporter() { ClearExportErrors(); } void VMFExporter::Init() { m_szLastExporterError[0] = '\0'; m_SideTranslations.Purge(); m_NodeTranslations.Purge(); m_vecLastPlaneOffset = vec3_origin; m_bWritingLevelContainer = false; m_iEntityCount = 1; // 1 already, from the worldspawn m_iSideCount = 0; m_pTemplateKeys = NULL; m_iNextNodeID = 0; m_pRoom = NULL; m_iCurrentRoom = 0; m_iMapExtents_XMin = 0; m_iMapExtents_YMin = 0; m_iMapExtents_XMax = 0; m_iMapExtents_YMax = 0; m_pExportKeys = NULL; m_vecStartRoomOrigin = vec3_origin; ClearExportErrors(); } void VMFExporter::ExportError( const char *pMsg, ... ) { char msg[4096]; va_list marker; va_start( marker, pMsg ); Q_vsnprintf( msg, sizeof( msg ), pMsg, marker ); va_end( marker ); m_ExportErrors.AddToTail( TileGenCopyString( msg ) ); } void VMFExporter::ClearExportErrors() { m_ExportErrors.PurgeAndDeleteElements(); } bool VMFExporter::ShowExportErrors() { if ( m_ExportErrors.Count() <= 0 ) return false; char msg[4096]; msg[0] = 0; for ( int i=0; im_PlacedRooms.Count() <= 0 ) { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to export: No rooms placed in the map layout!\n" ); return false; } // see if we have a start room bool bHasStartRoom = false; for ( int i = 0 ; i < pLayout->m_PlacedRooms.Count() ; i++ ) { if ( pLayout->m_PlacedRooms[i]->m_pRoomTemplate->IsStartRoom() ) { int half_map_size = MAP_LAYOUT_TILES_WIDE * 0.5f; // shift back so the middle of our grid is the origin m_vecStartRoomOrigin.x = ( pLayout->m_PlacedRooms[i]->m_iPosX - half_map_size ) * ASW_TILE_SIZE; m_vecStartRoomOrigin.y = ( pLayout->m_PlacedRooms[i]->m_iPosY - half_map_size ) * ASW_TILE_SIZE; bHasStartRoom = true; break; } } LoadUniqueKeyList(); m_iNextNodeID = 0; m_pExportKeys = new KeyValues( "ExportKeys" ); m_pExportKeys->AddSubKey( GetVersionInfo() ); m_pExportKeys->AddSubKey( GetDefaultVisGroups() ); m_pExportKeys->AddSubKey( GetViewSettings() ); m_pExportWorldKeys = GetDefaultWorldChunk(); if ( !m_pExportWorldKeys ) { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to save world chunk start\n"); return false; } m_pExportKeys->AddSubKey( m_pExportWorldKeys ); // save out the big cube the whole level sits in if ( !AddLevelContainer() ) { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to save level container\n"); return false; } if ( tilegen_use_instancing.GetBool() ) { int nLogicalRooms = m_pMapLayout->m_LogicalRooms.Count(); int nPlacedRooms = m_pMapLayout->m_PlacedRooms.Count(); m_pRoom = NULL; for ( int i = 0; i < nLogicalRooms; ++ i ) { AddRoomInstance( m_pMapLayout->m_LogicalRooms[i] ); } for ( int i = 0; i < nPlacedRooms; ++ i ) { m_pRoom = m_pMapLayout->m_PlacedRooms[i]; AddRoomInstance( m_pRoom->m_pRoomTemplate, i ); } } else { // write out logical room solids int iLogicalRooms = m_pMapLayout->m_LogicalRooms.Count(); m_pRoom = NULL; for ( int i = 0 ; i < iLogicalRooms ; i++ ) { // start logical room IDs at 5000 (assumes we'll never place 5000 real rooms) m_iCurrentRoom = 5000 + i; CRoomTemplate *pRoomTemplate = m_pMapLayout->m_LogicalRooms[i]; if ( !pRoomTemplate ) continue; if ( !AddRoomTemplateSolids( pRoomTemplate ) ) return false; } // go through each CRoom and write out its world solids int iRooms = m_pMapLayout->m_PlacedRooms.Count(); for ( m_iCurrentRoom = 0 ; m_iCurrentRoomm_PlacedRooms[m_iCurrentRoom]; if (!m_pRoom) continue; const CRoomTemplate *pRoomTemplate = m_pRoom->m_pRoomTemplate; if (!pRoomTemplate) continue; if ( !AddRoomTemplateSolids( pRoomTemplate ) ) return false; } // write out logical room entities m_pRoom = NULL; for ( int i = 0 ; i < iLogicalRooms ; i++ ) { // start logical room IDs at 5000 (assumes we'll never place 5000 real rooms) m_iCurrentRoom = 5000 + i; CRoomTemplate *pRoomTemplate = m_pMapLayout->m_LogicalRooms[i]; if ( !pRoomTemplate ) continue; if ( !AddRoomTemplateEntities( pRoomTemplate ) ) return false; } // go through each CRoom and add its entities for ( m_iCurrentRoom = 0 ; m_iCurrentRoomm_PlacedRooms[m_iCurrentRoom]; if (!m_pRoom) continue; const CRoomTemplate *pRoomTemplate = m_pRoom->m_pRoomTemplate; if (!pRoomTemplate) continue; if ( !AddRoomTemplateEntities( pRoomTemplate ) ) return false; } } // add some player starts to the map in the tile the user selected if ( !bHasStartRoom ) { m_pExportKeys->AddSubKey( GetPlayerStarts() ); } m_pExportKeys->AddSubKey( GetGameRulesProxy() ); m_pExportKeys->AddSubKey( GetDefaultCamera() ); // save out the export keys char filename[512]; Q_snprintf( filename, sizeof(filename), "maps\\%s", mapname ); Q_SetExtension( filename, "vmf", sizeof( filename ) ); CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); for ( KeyValues *pKey = m_pExportKeys->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() ) { pKey->RecursiveSaveToFile( buf, 0 ); } if ( !g_pFullFileSystem->WriteFile( filename, "GAME", buf ) ) { Msg( "Failed to SaveToFile %s\n", filename ); return false; } // save the map layout there (so the game can get information about rooms during play) Q_snprintf( filename, sizeof( filename ), "maps\\%s", mapname ); Q_SetExtension( filename, "layout", sizeof( filename ) ); if ( !m_pMapLayout->SaveMapLayout( filename ) ) { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to save .layout file\n"); return false; } return true; } bool VMFExporter::AddRoomTemplateSolids( const CRoomTemplate *pRoomTemplate ) { // open its vmf file char roomvmfname[MAX_PATH]; Q_snprintf(roomvmfname, sizeof(roomvmfname), "tilegen/roomtemplates/%s/%s.vmf", pRoomTemplate->m_pLevelTheme->m_szName, pRoomTemplate->GetFullName() ); m_pTemplateKeys = new KeyValues( "RoomTemplateVMF" ); m_pTemplateKeys->LoadFromFile( g_pFullFileSystem, roomvmfname, "GAME" ); // look for world key for ( KeyValues *pKeys = m_pTemplateKeys; pKeys; pKeys = pKeys->GetNextKey() ) { if ( !Q_stricmp( pKeys->GetName(), "world" ) ) // find the world key in our room template { if ( !ProcessWorld( pKeys ) ) // fix up solid positions { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to copy world from room %s\n", pRoomTemplate->GetFullName() ); return false; } for ( KeyValues *pSubKey = pKeys->GetFirstSubKey(); pSubKey; pSubKey = pSubKey->GetNextKey() ) // convert each solid to a func_detail entity { if ( !Q_stricmp( pSubKey->GetName(), "solid" ) ) { if ( IsDisplacementBrush( pSubKey ) ) { // add to world section m_pExportWorldKeys->AddSubKey( pSubKey->MakeCopy() ); } else { // put into entity section as a func_detail KeyValues *pFuncDetail = new KeyValues( "entity" ); pFuncDetail->SetInt( "id", ++m_iEntityCount ); pFuncDetail->SetString( "classname", "func_detail" ); pFuncDetail->AddSubKey( pSubKey->MakeCopy() ); m_pExportKeys->AddSubKey( pFuncDetail ); } } } } } m_pTemplateKeys->deleteThis(); m_pTemplateKeys = NULL; return true; } bool VMFExporter::AddRoomTemplateEntities( const CRoomTemplate *pRoomTemplate ) { m_SideTranslations.PurgeAndDeleteElements(); m_NodeTranslations.PurgeAndDeleteElements(); // reopen the source vmf char roomvmfname[MAX_PATH]; Q_snprintf( roomvmfname, sizeof(roomvmfname), "tilegen/roomtemplates/%s/%s.vmf", pRoomTemplate->m_pLevelTheme->m_szName, pRoomTemplate->GetFullName() ); m_pTemplateKeys = new KeyValues( "RoomTemplateVMF" ); m_pTemplateKeys->LoadFromFile( g_pFullFileSystem, roomvmfname, "GAME" ); // make all node IDs unique MakeNodeIDsUnique(); // sets priority of objective entities based on the generation options ReorderObjectives( pRoomTemplate, m_pTemplateKeys ); // look for entity keys for ( KeyValues *pKeys = m_pTemplateKeys; pKeys; pKeys = pKeys->GetNextKey() ) { if ( !Q_stricmp( pKeys->GetName(), "entity" ) ) { if ( !ProcessEntity( pKeys ) ) { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to copy entity from room %s\n", pRoomTemplate->GetFullName()); return false; } m_pExportKeys->AddSubKey( pKeys->MakeCopy() ); } } m_pTemplateKeys->deleteThis(); m_pTemplateKeys = NULL; return true; }; bool VMFExporter::IsDisplacementBrush( KeyValues *pSolidKeys ) { // go through each side for ( KeyValues *pKeys = pSolidKeys->GetFirstSubKey(); pKeys; pKeys = pKeys->GetNextKey() ) { if ( !Q_stricmp( pKeys->GetName(), "side" ) && pKeys->FindKey( "dispinfo" ) ) { return true; } } return false; } bool VMFExporter::ProcessWorld( KeyValues *pWorldKeys ) { // look for solid keys KeyValues *pSolidKeys = pWorldKeys->GetFirstSubKey(); while ( pSolidKeys ) { if ( !Q_stricmp( pSolidKeys->GetName(), "solid" ) ) { if ( !ProcessSolid( pSolidKeys ) ) return false; } pSolidKeys = pSolidKeys->GetNextKey(); } return true; } bool VMFExporter::ProcessSolid( KeyValues *pSolidKey ) { pSolidKey->SetInt( "id", ++m_iEntityCount ); // now process each side KeyValues *pKeys = pSolidKey->GetFirstSubKey(); while ( pKeys ) { if ( !Q_stricmp( pKeys->GetName(), "side" ) ) { if ( !ProcessSide( pKeys ) ) return false; } else { if ( !ProcessGenericRecursive( pKeys ) ) return false; } pKeys = pKeys->GetNextKey(); } return true; } bool VMFExporter::ProcessSide( KeyValues *pSideKey ) { for ( KeyValues *pKeys = pSideKey->GetFirstSubKey(); pKeys; pKeys = pKeys->GetNextKey() ) { if ( !Q_stricmp( pKeys->GetName(), "dispinfo" ) ) { if ( !ProcessGenericRecursive( pKeys ) ) return false; } else { if ( !ProcessSideKey( pKeys ) ) return false; } } return true; } bool VMFExporter::ProcessGenericRecursive( KeyValues *pKey ) { const char *szKey = pKey->GetName(); const char *szValue = pKey->GetString(); if ( !Q_stricmp( szKey, "startposition" ) ) { Vector Origin; int nRead = sscanf(szValue, "[%f %f %f]", &Origin[0], &Origin[1], &Origin[2]); if (nRead != 3) return false; // move the points to where our room is Origin += GetCurrentRoomOffset(); char buffer[256]; Q_snprintf(buffer, sizeof(buffer), "[%f %f %f]", Origin[0], Origin[1], Origin[2]); pKey->SetStringValue( buffer ); } if ( pKey->GetFirstSubKey() ) // if this keyvalues entry has subkeys, then process them { for ( KeyValues *pKeys = pKey->GetFirstSubKey(); pKeys; pKeys = pKeys->GetNextKey() ) { if ( !ProcessGenericRecursive( pKeys ) ) return false; } } return true; } bool VMFExporter::ProcessSideKey( KeyValues *pKey ) { const char *szKey = pKey->GetName(); const char *szValue = pKey->GetString(); if (!stricmp(szKey, "id")) { // store the side SideTranslation_t *pSideTranslation = new SideTranslation_t; pSideTranslation->m_iOriginalSide = atoi( szValue ); // give this side a unique ID char buffer[32]; Q_snprintf(buffer, sizeof(buffer), "%d", ++m_iSideCount ); pKey->SetStringValue( buffer ); pSideTranslation->m_iNewSide = m_iSideCount; m_SideTranslations.AddToTail( pSideTranslation ); return true; } else if (!stricmp(szKey, "plane")) { Vector planepts[3]; int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)", &planepts[0][0], &planepts[0][1], &planepts[0][2], &planepts[1][0], &planepts[1][1], &planepts[1][2], &planepts[2][0], &planepts[2][1], &planepts[2][2]); if (nRead != 9) return false; if (m_bWritingLevelContainer) { // we're currently writing out the big cube that the level sits in // need to adjust the x and y coordinates of this box to encase the whole map layout for (int p=0;p<3;p++) { if (planepts[p][0] < 0) planepts[p][0] += m_iMapExtents_XMin * ASW_TILE_SIZE; else planepts[p][0] += m_iMapExtents_XMax * ASW_TILE_SIZE; if (planepts[p][1] < 0) planepts[p][1] += m_iMapExtents_YMin * ASW_TILE_SIZE; else planepts[p][1] += m_iMapExtents_YMax * ASW_TILE_SIZE; } } else { // move the points to where our room is for (int p=0;p<3;p++) { planepts[p] += GetCurrentRoomOffset(); } // store these so we know how much to shift texture alignment m_vecLastPlaneOffset = GetCurrentRoomOffset(); } char buffer[256]; Q_snprintf(buffer, sizeof(buffer), "(%f %f %f) (%f %f %f) (%f %f %f)", planepts[0][0], planepts[0][1], planepts[0][2], planepts[1][0], planepts[1][1], planepts[1][2], planepts[2][0], planepts[2][1], planepts[2][2]); pKey->SetStringValue( buffer ); return true; } else if ( !stricmp(szKey, "uaxis") || !stricmp(szKey, "vaxis") ) // fix texture alignment (similar to moving a brush with texture lock turned on in hammer) { // NOTE: This assumes the plane was read in previously and set m_vecLastPlaneOffset Vector axis; float offset, scale; offset = 0; scale = 1.0f; if ( sscanf( szValue, "[%f %f %f %f] %f", &axis.x, &axis.y, &axis.z, &offset, &scale ) == 5 ) { offset -= m_vecLastPlaneOffset.Dot( axis ) / scale; char buffer[256]; Q_snprintf( buffer, sizeof(buffer), "[%f %f %f %f] %f", axis.x, axis.y, axis.z, offset, scale ); pKey->SetStringValue( buffer ); return true; } else { Msg( "Error loading in uvaxis\n" ); } } return true; } bool VMFExporter::ProcessEntity( KeyValues *pEntityKeys ) { for ( KeyValues *pKeys = pEntityKeys->GetFirstSubKey(); pKeys; ) { if ( pKeys->GetFirstSubKey() ) { if ( !Q_stricmp( pKeys->GetName(), "solid" ) ) { if ( !ProcessSolid( pKeys ) ) return false; } else if ( !Q_stricmp( pKeys->GetName(), "connections" ) ) { if ( !ProcessConnections( pKeys ) ) return false; } else if ( !Q_stricmp( pKeys->GetName(), "editor" ) ) // remove editor keys { KeyValues *pRemoveKey = pKeys; pKeys = pKeys->GetNextKey(); pEntityKeys->RemoveSubKey( pRemoveKey ); pRemoveKey->deleteThis(); continue; } } else { if ( !ProcessEntityKey( pKeys ) ) return false; } pKeys = pKeys->GetNextKey(); } return true; } bool VMFExporter::ProcessEntityKey( KeyValues *pKey ) { const char *szKey = pKey->GetName(); const char *szValue = pKey->GetString(); if (!stricmp(szKey, "id")) { // give this entity a unique ID char buffer[32]; Q_snprintf(buffer, sizeof(buffer), "%d", ++m_iEntityCount ); pKey->SetStringValue( buffer ); return true; } else if (!stricmp(szKey, "origin")) { Vector Origin; int nRead = sscanf(szValue, "%f %f %f", &Origin[0], &Origin[1], &Origin[2]); if (nRead != 3) return false; // move the points to where our room is Origin += GetCurrentRoomOffset(); char buffer[256]; Q_snprintf(buffer, sizeof(buffer), "%f %f %f", Origin[0], Origin[1], Origin[2]); pKey->SetStringValue( buffer ); return true; } // check for overlay values if (!stricmp(szKey, "sides")) { // just deal with one side for now // TODO: handle multiple sides in the value and handle sides in the world block int iSide = atoi(szValue); for (int i=0;im_iOriginalSide == iSide ) { char buffer[32]; Q_snprintf(buffer, sizeof(buffer), "%d", pSideTranslation->m_iNewSide ); pKey->SetStringValue( buffer ); return true; } } } else if (!stricmp(szKey, "BasisOrigin")) { Vector Origin; int nRead = sscanf(szValue, "%f %f %f", &Origin[0], &Origin[1], &Origin[2]); if (nRead != 3) return false; // move the points to where our room is Origin += GetCurrentRoomOffset(); char buffer[256]; Q_snprintf(buffer, sizeof(buffer), "%f %f %f", Origin[0], Origin[1], Origin[2]); pKey->SetStringValue( buffer ); return true; } // check for unique string keys (targetname, etc.) int nCount = m_UniqueKeys.Count(); for ( int i = 0; i < nCount; ++i ) { const char *pUnique = m_UniqueKeys[i]; if ( !stricmp(szKey, pUnique) && szValue && szValue[0] != '@' ) // don't make names unique if they start with special character { // prepend room id to make this unique char buffer[256]; Q_snprintf( buffer, sizeof(buffer), "Room%d_%s", m_iCurrentRoom, szValue ); pKey->SetStringValue( buffer ); return true; } } // remap node IDs nCount = m_NodeIDKeys.Count(); for ( int i = 0; i < nCount; ++i ) { const char *pNodeKey = m_NodeIDKeys[i]; if (!stricmp(szKey, pNodeKey)) { int iNodeID = atoi(szValue); for (int i=0;im_iOriginalNodeID == iNodeID ) { char buffer[32]; Q_snprintf(buffer, sizeof(buffer), "%d", pTranslation->m_iNewNodeID ); pKey->SetStringValue( buffer ); return true; } } } } return true; } bool VMFExporter::ProcessConnections( KeyValues *pConnections ) { for ( KeyValues *pKeys = pConnections->GetFirstSubKey(); pKeys; pKeys = pKeys->GetNextKey() ) { if ( !ProcessConnectionsKey( pKeys ) ) return false; } return true; } bool VMFExporter::ProcessConnectionsKey( KeyValues *pKey ) { const char *szValue = pKey->GetString(); char buffer[256]; // prepend room id to targetname if ( szValue && szValue[0] != '@' ) // don't make names unique if they start with special character { Q_snprintf( buffer, sizeof( buffer ), "Room%d_%s", m_iCurrentRoom, szValue ); } else { Q_snprintf( buffer, sizeof( buffer ), "%s", szValue ); } pKey->SetStringValue( buffer ); return true; } // sets priority of objective entities based on the order the rooms were listed in the mission/objective txt void VMFExporter::ReorderObjectives( const CRoomTemplate *pTemplate, KeyValues *pTemplateKeys ) { KeyValues *pKeys = m_pTemplateKeys; while ( pKeys ) { if ( !Q_stricmp( pKeys->GetName(), "entity" ) && !Q_strnicmp( pKeys->GetString( "classname" ), "asw_objective", 13 ) ) { if ( pKeys->GetFloat( "Priority" ) != 0 ) // if level designer has already set priority, then don't override it { pKeys = pKeys->GetNextKey(); continue; } int iPriority = 100; // We no longer have a requested rooms array, so this code is not valid. Need to replace it with something else to ensure priorities are set correctly. // for ( int i = 0; i < m_pMapLayout->m_pRequestedRooms.Count(); i++ ) // { // if ( m_pMapLayout->m_pRequestedRooms[i]->m_pRoomTemplate == pTemplate ) // { // iPriority = m_pMapLayout->m_pRequestedRooms[i]->m_iMissionTextOrder; // } // } pKeys->SetFloat( "Priority", iPriority ); } pKeys = pKeys->GetNextKey(); } } int VMFExporter::MakeNodeIDsUnique() { int iNodes = 0; KeyValues *pKeys = m_pTemplateKeys; while ( pKeys ) { if ( !Q_stricmp( pKeys->GetName(), "entity" ) ) // go through all entities in this room { KeyValues *pFieldKey = pKeys->GetFirstSubKey(); while ( pFieldKey ) // go through all properties of this entity { if ( !pFieldKey->GetFirstSubKey() ) // leaf { if ( !stricmp(pFieldKey->GetName(), "nodeid" ) ) { NodeTranslation_t *pNodeTranslation = new NodeTranslation_t; // store a translation so we can fix up info links pNodeTranslation->m_iOriginalNodeID = atoi( pFieldKey->GetString() ); pNodeTranslation->m_iNewNodeID = m_iNextNodeID; m_NodeTranslations.AddToTail( pNodeTranslation ); char buffer[16]; Q_snprintf( buffer, sizeof( buffer ), "%d", m_iNextNodeID); pFieldKey->SetStringValue( buffer ); m_iNextNodeID++; iNodes++; } } pFieldKey = pFieldKey->GetNextKey(); } } pKeys = pKeys->GetNextKey(); } return iNodes; } const Vector& VMFExporter::GetCurrentRoomOffset() { static Vector s_vecCurrentRoomOffset = vec3_origin; if ( m_pRoom ) { int half_map_size = MAP_LAYOUT_TILES_WIDE * 0.5f; s_vecCurrentRoomOffset.x = ( m_pRoom->m_iPosX - half_map_size ) * ASW_TILE_SIZE; s_vecCurrentRoomOffset.y = ( m_pRoom->m_iPosY - half_map_size ) * ASW_TILE_SIZE; s_vecCurrentRoomOffset.z = 0; } else { // place logical rooms at the origin and up above the camera s_vecCurrentRoomOffset.x = 0; s_vecCurrentRoomOffset.y = 0; s_vecCurrentRoomOffset.z = 1024; } return s_vecCurrentRoomOffset; } #define UNIQUE_KEYS_FILE "tilegen/unique_keys.txt" void VMFExporter::LoadUniqueKeyList() { m_UniqueKeys.RemoveAll(); m_NodeIDKeys.RemoveAll(); // Open the manifest file, and read the particles specified inside it KeyValues *manifest = new KeyValues( UNIQUE_KEYS_FILE ); if ( manifest->LoadFromFile( g_pFullFileSystem, UNIQUE_KEYS_FILE, "GAME" ) ) { for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) { if ( !Q_stricmp( sub->GetName(), "key" ) ) { m_UniqueKeys.AddToTail( sub->GetString() ); continue; } if ( !Q_stricmp( sub->GetName(), "NodeID" ) ) { m_NodeIDKeys.AddToTail( sub->GetString() ); continue; } Warning( "VMFExporter::LoadUniqueKeyList: Manifest '%s' with bogus file type '%s', expecting 'key' or 'NodeID'\n", UNIQUE_KEYS_FILE, sub->GetName() ); } } else { Warning( "VMFExporter: Unable to load manifest file '%s'\n", UNIQUE_KEYS_FILE ); } manifest->deleteThis(); } void VMFExporter::AddRoomInstance( const CRoomTemplate *pRoomTemplate, int nPlacedRoomIndex ) { KeyValues *pFuncInstance = new KeyValues( "entity" ); pFuncInstance->SetInt( "id", ++ m_iEntityCount ); pFuncInstance->SetString( "classname", "func_instance" ); pFuncInstance->SetString( "angles", "0 0 0" ); // Used to identify rooms for later fixup (e.g. adding/swapping instances and models) if ( nPlacedRoomIndex != -1 ) { pFuncInstance->SetInt( "PlacedRoomIndex", nPlacedRoomIndex ); } char vmfName[MAX_PATH]; Q_snprintf( vmfName, sizeof( vmfName ), "tilegen/roomtemplates/%s/%s.vmf", pRoomTemplate->m_pLevelTheme->m_szName, pRoomTemplate->GetFullName() ); // Convert backslashes to forward slashes to please the VMF parser int nStrLen = Q_strlen( vmfName ); for ( int i = 0; i < nStrLen; ++ i ) { if ( vmfName[i] == '\\' ) vmfName[i] = '/'; } pFuncInstance->SetString( "file", vmfName ); Vector vOrigin = GetCurrentRoomOffset(); char buf[128]; Q_snprintf( buf, 128, "%f %f %f", vOrigin.x, vOrigin.y, vOrigin.z ); pFuncInstance->SetString( "origin", buf ); m_pExportKeys->AddSubKey( pFuncInstance ); } //----------------------------------------------------------------------------- // Purpose: Saves the version information chunk. // Input : *pFile - // Output : Returns ChunkFile_Ok on success, an error code on failure. //----------------------------------------------------------------------------- KeyValues* VMFExporter::GetVersionInfo() { KeyValues *pKeys = new KeyValues( "versioninfo" ); pKeys->SetInt( "editorversion", 400 ); pKeys->SetInt( "editorbuild", 3900 ); pKeys->SetInt( "mapversion", 1 ); pKeys->SetInt( "formatversion", 100 ); pKeys->SetBool( "prefab", false ); return pKeys; } // just save out an empty visgroups section KeyValues* VMFExporter::GetDefaultVisGroups() { KeyValues *pKeys = new KeyValues( "visgroups" ); return pKeys; } KeyValues* VMFExporter::GetViewSettings() { KeyValues *pKeys = new KeyValues( "viewsettings" ); pKeys->SetBool( "bSnapToGrid", true ); pKeys->SetBool( "bShowGrid", true ); pKeys->SetBool( "bShowLogicalGrid", false ); pKeys->SetInt( "nGridSpacing", ASW_TILE_SIZE ); pKeys->SetBool( "bShow3DGrid", false ); return pKeys; } KeyValues* VMFExporter::GetDefaultCamera() { KeyValues *pKeys = new KeyValues( "cameras" ); pKeys->SetInt( "activecamera", 0 ); KeyValues *pSubKeys = new KeyValues( "camera" ); pSubKeys->SetString( "position", "[135.491 -60.314 364.02]" ); pSubKeys->SetString( "look", "[141.195 98.7571 151.25]" ); pKeys->AddSubKey( pSubKeys ); return pKeys; } KeyValues* VMFExporter::GetPlayerStarts() { KeyValues *pKeys = new KeyValues( "entity" ); pKeys->SetInt( "id", m_iEntityCount++ ); pKeys->SetString( "classname", "info_player_start" ); pKeys->SetString( "angles", "0 0 0" ); // put a single player start in the centre of the player start tile selected float player_start_x = 128.0f; float player_start_y = 128.0f; int half_map_size = MAP_LAYOUT_TILES_WIDE * 0.5f; player_start_x += (m_pMapLayout->m_iPlayerStartTileX - half_map_size) * ASW_TILE_SIZE; player_start_y += (m_pMapLayout->m_iPlayerStartTileY - half_map_size) * ASW_TILE_SIZE; char buffer[128]; Q_snprintf(buffer, sizeof(buffer), "%f %f 1.0", player_start_x, player_start_y); pKeys->SetString( "origin", buffer ); return pKeys; } KeyValues* VMFExporter::GetGameRulesProxy() { KeyValues *pKeys = new KeyValues( "entity" ); pKeys->SetInt( "id", m_iEntityCount++ ); pKeys->SetString( "classname", "asw_gamerules" ); pKeys->SetString( "targetname", "@asw_gamerules" ); char buffer[128]; Q_snprintf(buffer, sizeof(buffer), "%f %f %f", m_vecStartRoomOrigin.x, m_vecStartRoomOrigin.y, m_vecStartRoomOrigin.z); pKeys->SetString( "origin", buffer ); KeyValues *pGenerationOptions = m_pMapLayout->GetGenerationOptions(); int nDifficultyModifier = 0; if ( pGenerationOptions != NULL ) { nDifficultyModifier = pGenerationOptions->GetInt( "Difficulty", 5 ) - 5; } pKeys->SetInt( "difficultymodifier", nDifficultyModifier ); return pKeys; } KeyValues* VMFExporter::GetDefaultWorldChunk() { KeyValues *pKeys = new KeyValues( "world" ); pKeys->SetInt( "id", 1 ); pKeys->SetInt( "mapversion", 1 ); pKeys->SetString( "classname", "worldspawn" ); pKeys->SetString( "skyname", "blacksky" ); pKeys->SetInt( "maxpropscreenwidth", -1 ); pKeys->SetString( "detailvbsp", "detail.vbsp" ); pKeys->SetString( "detailmaterial", "detail/detailsprites" ); pKeys->SetInt( "speedruntime", 180 ); return pKeys; } bool VMFExporter::AddLevelContainer() { KeyValues *pLevelContainerKeys = new KeyValues( "LevelContainer" ); if ( !pLevelContainerKeys->LoadFromFile( g_pFullFileSystem, "tilegen/roomtemplates/levelcontainer.vmf.no_func_detail", "GAME" ) ) return false; m_bWritingLevelContainer = true; // set the extents of the map m_pMapLayout->GetExtents(m_iMapExtents_XMin, m_iMapExtents_XMax, m_iMapExtents_YMin, m_iMapExtents_YMax); Msg( "Layout extents: Topleft: %f %f - Lower right: %f %f\n", m_iMapExtents_XMin, m_iMapExtents_YMin, m_iMapExtents_XMax, m_iMapExtents_YMax ); // adjust to be relative to the centre of the map int half_map_size = MAP_LAYOUT_TILES_WIDE * 0.5f; m_iMapExtents_XMin -= half_map_size; m_iMapExtents_XMax -= half_map_size; m_iMapExtents_YMin -= half_map_size; m_iMapExtents_YMax -= half_map_size; // pad them by 2 blocks, so the player doesn't move his camera into the wall when at an edge block m_iMapExtents_XMin -= 2; m_iMapExtents_XMax += 2; m_iMapExtents_YMin -= 2; m_iMapExtents_YMax += 2; Msg( " Adjusted to: Topleft: %d %d - Lower right: %d %d\n", m_iMapExtents_XMin, m_iMapExtents_YMin, m_iMapExtents_XMax, m_iMapExtents_YMax ); // find world keys KeyValues *pWorldKeys = NULL; for ( KeyValues *pKeys = pLevelContainerKeys; pKeys; pKeys = pKeys->GetNextKey() ) { const char *szName = pKeys->GetName(); if ( !Q_stricmp( szName, "world" ) ) { pWorldKeys = pKeys; break; } } if ( !pWorldKeys || !ProcessWorld( pWorldKeys ) ) { Q_snprintf( m_szLastExporterError, sizeof(m_szLastExporterError), "Failed to copy level container\n" ); return false; } m_bWritingLevelContainer = false; for ( KeyValues *pKeys = pWorldKeys->GetFirstSubKey(); pKeys; pKeys = pKeys->GetNextKey() ) { const char *szName = pKeys->GetName(); if ( !Q_stricmp( szName, "solid" ) ) { m_pExportWorldKeys->AddSubKey( pKeys->MakeCopy() ); } } pLevelContainerKeys->deleteThis(); m_pTemplateKeys->deleteThis(); m_pTemplateKeys = NULL; return true; }