sqwarmed/sdk_src/game/missionchooser/VMFExporter.cpp

1028 lines
30 KiB
C++

#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;
}