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.
1027 lines
28 KiB
1027 lines
28 KiB
#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 <tier0/memdbgon.h> |
|
|
|
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; i<m_ExportErrors.Count(); i++ ) |
|
{ |
|
Q_snprintf( msg, sizeof( msg ), "%s\n%s", msg, m_ExportErrors[i] ); |
|
} |
|
VGUIMessageBox( g_pTileGenDialog, "Export problems:", msg ); |
|
return true; |
|
} |
|
|
|
bool VMFExporter::ExportVMF( CMapLayout* pLayout, const char *mapname, bool bPopupWarnings ) |
|
{ |
|
m_bPopupWarnings = bPopupWarnings; |
|
|
|
Init(); |
|
|
|
m_pMapLayout = pLayout; |
|
|
|
if ( pLayout->m_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_iCurrentRoom<iRooms ; m_iCurrentRoom++) |
|
{ |
|
m_pRoom = m_pMapLayout->m_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_iCurrentRoom<iRooms ; m_iCurrentRoom++) |
|
{ |
|
m_pRoom = m_pMapLayout->m_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;i<m_SideTranslations.Count();i++) |
|
{ |
|
SideTranslation_t *pSideTranslation = m_SideTranslations[i]; |
|
if ( pSideTranslation->m_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;i<m_NodeTranslations.Count();i++) |
|
{ |
|
NodeTranslation_t *pTranslation = m_NodeTranslations[i]; |
|
if ( pTranslation->m_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; |
|
}
|
|
|