Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

640 lines
18 KiB

#include "MapLayout.h"
#include "Room.h"
#include "ChunkFile.h"
#include "RoomTemplate.h"
#include "KeyValues.h"
#include "LevelTheme.h"
#include "asw_spawn_selection.h"
#include "asw_mission_chooser.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
CInstanceSpawn::CInstanceSpawn() :
m_InstanceSpawningMethod( ISM_INVALID ),
m_nPlacedRoomIndex( -1 )
{
m_InstanceFilename[0] = '\0';
}
void CInstanceSpawn::FixupValues( const char *pFindValue, const char *pReplaceValue )
{
for ( int i = 0; i < m_AdditionalKeyValues.Count(); ++ i )
{
if ( Q_stristr( m_AdditionalKeyValues[i].m_Value, pFindValue ) != NULL )
{
char outputString[MAX_TILEGEN_IDENTIFIER_LENGTH];
Q_StrSubst( m_AdditionalKeyValues[i].m_Value, pFindValue, pReplaceValue, outputString, MAX_TILEGEN_IDENTIFIER_LENGTH );
Q_strncpy( m_AdditionalKeyValues[i].m_Value, outputString, MAX_TILEGEN_IDENTIFIER_LENGTH );
}
}
}
bool CInstanceSpawn::LoadFromKeyValues( KeyValues *pKeyValues )
{
const char *pFilename = pKeyValues->GetString( "filename", NULL );
if ( pFilename == NULL )
{
Log_Warning( LOG_TilegenLayoutSystem, "No 'filename' specified in CInstanceSpawn block.\n" );
return false;
}
Q_strncpy( m_InstanceFilename, pFilename, _countof( m_InstanceFilename ) );
const char *pType = pKeyValues->GetString( "type", NULL );
if ( pType == NULL )
{
Log_Warning( LOG_TilegenLayoutSystem, "No 'type' specified in CInstanceSpawn block.\n" );
return false;
}
else
{
if ( Q_stricmp( pType, "add_at_random_node" ) == 0 )
{
m_InstanceSpawningMethod = ISM_ADD_AT_RANDOM_NODE;
}
else
{
Log_Warning( LOG_TilegenLayoutSystem, "Invalid 'type' specified in CInstanceSpaw block.\n" );
return false;
}
}
m_nPlacedRoomIndex = pKeyValues->GetInt( "room_index", -1 );
m_nRandomSeed = pKeyValues->GetInt( "random_seed", 0 );
for ( KeyValues *pReplaceKV = pKeyValues->GetFirstSubKey(); pReplaceKV != NULL; pReplaceKV = pReplaceKV->GetNextKey() )
{
if ( Q_stricmp( pReplaceKV->GetName(), "key_value" ) == 0 )
{
const char *pKey = pReplaceKV->GetString( "key", "" );
const char *pValue = pReplaceKV->GetString( "value", "" );
int nIndex = m_AdditionalKeyValues.AddToTail();
Q_strncpy( m_AdditionalKeyValues[nIndex].m_Key, pKey, MAX_TILEGEN_IDENTIFIER_LENGTH );
Q_strncpy( m_AdditionalKeyValues[nIndex].m_Value, pValue, MAX_TILEGEN_IDENTIFIER_LENGTH );
}
}
return true;
}
void CInstanceSpawn::SaveToKeyValues( KeyValues *pKeyValues ) const
{
pKeyValues->SetString( "filename", m_InstanceFilename );
switch ( m_InstanceSpawningMethod )
{
case ISM_ADD_AT_RANDOM_NODE:
pKeyValues->SetString( "type", "add_at_random_node" );
break;
default:
Log_Warning( LOG_TilegenLayoutSystem, "Invalid instance spawning method (%d).\n", m_InstanceSpawningMethod );
}
pKeyValues->SetInt( "room_index", m_nPlacedRoomIndex );
pKeyValues->SetInt( "random_seed", m_nRandomSeed );
for ( int i = 0; i < m_AdditionalKeyValues.Count(); ++ i )
{
KeyValues *pNewReplacement = new KeyValues( "key_value" );
pNewReplacement->SetString( "key", m_AdditionalKeyValues[i].m_Key );
pNewReplacement->SetString( "value", m_AdditionalKeyValues[i].m_Value );
pKeyValues->AddSubKey( pNewReplacement );
}
}
CMapLayout::CMapLayout( KeyValues *pGenerationOptions ) :
m_pGenerationOptions( pGenerationOptions )
{
SetCurrentFilename("");
m_iPlayerStartTileX = MAP_LAYOUT_TILES_WIDE * 0.5f;
m_iPlayerStartTileY = MAP_LAYOUT_TILES_WIDE * 0.5f;
// clear our pointer grid
for (int y=0;y<MAP_LAYOUT_TILES_WIDE;y++)
{
for (int x=0;x<MAP_LAYOUT_TILES_WIDE;x++)
{
m_pRoomGrid[x][y] = NULL;
}
}
}
CMapLayout::~CMapLayout()
{
// todo: clear any 'currently selected rooms', etc
Clear();
if ( m_pGenerationOptions != NULL )
{
m_pGenerationOptions->deleteThis();
}
}
void CMapLayout::Clear()
{
// delete all placed rooms
int iRooms = m_PlacedRooms.Count();
for (int i=iRooms-1;i>=0;i--)
{
CRoom *pRoom = m_PlacedRooms[i];
delete pRoom; // destructor removes it from the list
}
m_LogicalRooms.RemoveAll();
m_InstanceSpawns.RemoveAll();
m_Encounters.PurgeAndDeleteElements();
// wipe the grid
for (int x=0;x<MAP_LAYOUT_TILES_WIDE;x++)
{
for (int y=0;y<MAP_LAYOUT_TILES_WIDE;y++)
{
m_pRoomGrid[x][y] = NULL;
}
}
m_iPlayerStartTileX = MAP_LAYOUT_TILES_WIDE * 0.5f;
m_iPlayerStartTileY = MAP_LAYOUT_TILES_WIDE * 0.5f;
}
void CMapLayout::SetGenerationOptions( KeyValues *pNewGenerationOptions )
{
if ( m_pGenerationOptions )
{
m_pGenerationOptions->deleteThis();
}
m_pGenerationOptions = pNewGenerationOptions;
}
bool CMapLayout::SaveMapLayout( const char *filename )
{
KeyValues *pLayoutKeys = new KeyValues( "Layout" );
if ( m_pGenerationOptions )
{
pLayoutKeys->AddSubKey( m_pGenerationOptions->MakeCopy() );
}
KeyValues *pMiscKeys = new KeyValues( "mapmisc" );
pMiscKeys->SetInt( "PlayerStartX", m_iPlayerStartTileX );
pMiscKeys->SetInt( "PlayerStartY", m_iPlayerStartTileY );
pLayoutKeys->AddSubKey( pMiscKeys );
// save out each room
int iRooms = m_PlacedRooms.Count();
for (int i=0;i<iRooms;i++)
{
CRoom *pRoom = m_PlacedRooms[i];
if (!pRoom)
continue;
pLayoutKeys->AddSubKey( pRoom->GetKeyValuesCopy() );
}
// save out logical rooms
iRooms = m_LogicalRooms.Count();
for (int i=0;i<iRooms;i++)
{
KeyValues *pRoomKeys = new KeyValues( "logical_room" );
if ( m_LogicalRooms[i]->m_pLevelTheme )
{
pRoomKeys->SetString( "theme", m_LogicalRooms[i]->m_pLevelTheme->m_szName );
}
pRoomKeys->SetString( "template", m_LogicalRooms[i]->GetFullName() );
pLayoutKeys->AddSubKey( pRoomKeys );
}
for ( int i = 0; i < m_InstanceSpawns.Count(); ++ i )
{
KeyValues *pNewInstanceSpawn = new KeyValues( "instance_spawn" );
m_InstanceSpawns[i].SaveToKeyValues( pNewInstanceSpawn );
pLayoutKeys->AddSubKey( pNewInstanceSpawn );
}
for ( int i = 0; i < m_Encounters.Count(); ++ i )
{
KeyValues *pEncounterKeys = new KeyValues( "npc_encounter" );
m_Encounters[i]->SaveToKeyValues( pEncounterKeys );
pLayoutKeys->AddSubKey( pEncounterKeys );
}
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
for ( KeyValues *pKey = pLayoutKeys->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
{
pKey->RecursiveSaveToFile( buf, 0 );
}
pLayoutKeys->deleteThis();
if ( !g_pFullFileSystem->WriteFile( filename, "GAME", buf ) )
{
Log_Warning( LOG_TilegenLayoutSystem, "Failed to SaveToFile %s\n", filename );
return false;
}
return true;
}
bool CMapLayout::LoadLogicalRoom( KeyValues *pRoomKeys )
{
char szLoadingTheme[256];
char szLoadingTemplate[256];
szLoadingTheme[0] = '\0';
szLoadingTemplate[0] = '\0';
Q_snprintf( szLoadingTheme, sizeof(szLoadingTheme), "%s", pRoomKeys->GetString( "theme" ) );
Q_snprintf( szLoadingTemplate, sizeof(szLoadingTemplate), "%s", pRoomKeys->GetString( "template" ) );
// find pointer to the room template
// first find the theme with matching name
int iThemes = CLevelTheme::s_LevelThemes.Count();
for (int i=0;i<iThemes;i++)
{
CLevelTheme* pTheme = CLevelTheme::s_LevelThemes[i];
if (!pTheme)
continue;
if ( !Q_stricmp( pTheme->m_szName, szLoadingTheme ) )
{
// now find a template within that theme with our template's name
int iTemplates = pTheme->m_RoomTemplates.Count();
for (int t=0;t<iTemplates;t++)
{
if ( !Q_stricmp( pTheme->m_RoomTemplates[t]->GetFullName(), szLoadingTemplate ) )
{
m_LogicalRooms.AddToTail( pTheme->m_RoomTemplates[t] );
return true;
}
}
break;
}
}
return false;
}
bool CMapLayout::LoadMapLayout(const char *filename)
{
// open our output layout file
KeyValues *pLayoutKeys = new KeyValues( "LayoutKeys" );
if ( !pLayoutKeys->LoadFromFile( g_pFullFileSystem, filename, "GAME" ) )
{
//Msg( "Failed to load map layout %s\n", filename );
pLayoutKeys->deleteThis();
return false;
}
while ( pLayoutKeys )
{
if ( !Q_stricmp( pLayoutKeys->GetName(), "room" ) )
{
if ( !CRoom::LoadRoomFromKeyValues( pLayoutKeys, this ) )
return false;
}
else if ( !Q_stricmp( pLayoutKeys->GetName(), "logical_room" ) )
{
LoadLogicalRoom( pLayoutKeys );
}
else if ( !Q_stricmp( pLayoutKeys->GetName(), "mapmisc" ) )
{
m_iPlayerStartTileX = pLayoutKeys->GetInt( "PlayerStartX" );
m_iPlayerStartTileY = pLayoutKeys->GetInt( "PlayerStartY" );
}
else if ( Q_stricmp( pLayoutKeys->GetName(), "mission_settings" ) == 0 )
{
m_pGenerationOptions = pLayoutKeys->MakeCopy();
}
else if ( Q_stricmp( pLayoutKeys->GetName(), "instance_spawn" ) == 0 )
{
m_InstanceSpawns[m_InstanceSpawns.AddToTail()].LoadFromKeyValues( pLayoutKeys );
}
else if ( Q_stricmp( pLayoutKeys->GetName(), "npc_encounter" ) == 0 )
{
CASW_Encounter *pEncounter = new CASW_Encounter();
pEncounter->LoadFromKeyValues( pLayoutKeys );
m_Encounters.AddToTail( pEncounter );
}
pLayoutKeys = pLayoutKeys->GetNextKey();
}
MarkEncounterRooms();
SetCurrentFilename(filename);
pLayoutKeys->deleteThis();
return true;
}
void CMapLayout::MarkEncounterRooms()
{
// mark all rooms with alien encounters
for ( int i = 0; i < m_Encounters.Count(); i++ )
{
CRoom *pRoom = GetRoom( m_Encounters[i]->GetEncounterPosition() );
if ( pRoom )
{
pRoom->SetHasAlienEncounter( true );
}
}
}
void CMapLayout::SetCurrentFilename(const char *szFilename)
{
Q_snprintf(m_szFilename, sizeof(m_szFilename), "%s", szFilename);
}
CRoom* CMapLayout::GetRoom( const Vector &vecPos )
{
int nX = ( vecPos.x / ASW_TILE_SIZE ) + MAP_LAYOUT_TILES_WIDE * 0.5f;
int nY = ( vecPos.y / ASW_TILE_SIZE ) + MAP_LAYOUT_TILES_WIDE * 0.5f;
if ( nX >= 0 && nX < MAP_LAYOUT_TILES_WIDE && nY >= 0 && nY < MAP_LAYOUT_TILES_WIDE )
{
return GetRoom( nX, nY );
}
return NULL;
}
void CMapLayout::GetExtents(int &iTileX_Min, int &iTileX_Max, int &iTileY_Min, int &iTileY_Max)
{
iTileX_Min = MAP_LAYOUT_TILES_WIDE-1;
iTileY_Min = MAP_LAYOUT_TILES_WIDE-1;
iTileX_Max = 0;
iTileY_Max = 0;
for (int i=0;i<m_PlacedRooms.Count();i++)
{
CRoom* pRoom = m_PlacedRooms[i];
if (!pRoom)
continue;
if (pRoom->m_iPosX < iTileX_Min)
iTileX_Min = pRoom->m_iPosX;
if (pRoom->m_iPosY < iTileY_Min)
iTileY_Min = pRoom->m_iPosY;
if (pRoom->m_iPosX + pRoom->m_pRoomTemplate->GetTilesX() > iTileX_Max)
iTileX_Max = pRoom->m_iPosX + pRoom->m_pRoomTemplate->GetTilesX();
if (pRoom->m_iPosY + pRoom->m_pRoomTemplate->GetTilesY() > iTileY_Max)
iTileY_Max = pRoom->m_iPosY + pRoom->m_pRoomTemplate->GetTilesY();
}
// don't return out of bounds
if ( iTileX_Min < 0 )
iTileX_Min = 0;
if ( iTileY_Min < 0 )
iTileY_Min = 0;
if ( iTileX_Max >= MAP_LAYOUT_TILES_WIDE )
iTileX_Max = MAP_LAYOUT_TILES_WIDE - 1;
if ( iTileY_Max >= MAP_LAYOUT_TILES_WIDE )
iTileY_Max = MAP_LAYOUT_TILES_WIDE - 1;
}
bool CMapLayout::SaveMiscMapProperties(CChunkFile *pFile)
{
if (!pFile)
return false;
ChunkFileResult_t eResult = pFile->BeginChunk("mapmisc");
if (eResult != ChunkFile_Ok)
return false;
// write out the player start location
if (pFile->WriteKeyValueInt("PlayerStartX", m_iPlayerStartTileX) != ChunkFile_Ok)
return false;
if (pFile->WriteKeyValueInt("PlayerStartY", m_iPlayerStartTileY) != ChunkFile_Ok)
return false;
if (pFile->EndChunk() != ChunkFile_Ok)
return false;
return true;
}
bool CMapLayout::RoomsOverlap( int x, int y, int w, int h,
int x2, int y2, int w2, int h2 ) const
{
if ( y + h <= y2 )
return false;
if ( y2 + h2 <= y )
return false;
if ( x + w <= x2 )
return false;
if ( x2 + w2 <= x )
return false;
return true;
}
bool CMapLayout::TemplateFits( const CRoomTemplate *pTemplate, int x, int y, bool bAllowNoExits ) const
{
// check it's not out of bounds
if ( x < 0 || y < 0 || x + pTemplate->GetTilesX() > MAP_LAYOUT_TILES_WIDE || y + pTemplate->GetTilesY() > MAP_LAYOUT_TILES_WIDE )
return false;
// check for overlapping any existing rooms
for (int j = 0 ; j < pTemplate->GetTilesX() ; j++)
{
for (int k = 0 ; k < pTemplate->GetTilesY() ; k++)
{
if (m_pRoomGrid[x+j][y+k])
return false;
}
}
// if a template has no exits, assume it's a solid block used for capping and therefore fits anywhere
if ( bAllowNoExits && pTemplate->m_Exits.Count() == 0 )
return true;
// check for matching exits
if ( !CheckExits( pTemplate, x, y ) )
return false;
return true;
}
bool CMapLayout::CheckExits( const CRoomTemplate *pTemplate, int x, int y, CUtlVector< CRoomTemplateExit * > *pMatchingExits ) const
{
int iRoomWide = pTemplate->GetTilesX();
int iRoomTall = pTemplate->GetTilesY();
// check exits of surrounding rooms match ours..
for (int k = 0; k < iRoomWide; k++)
{
// check bottom
if ( y > 0 && m_pRoomGrid[x + k][y - 1] != NULL )
{
if ( !CheckExitsOnSquares( pTemplate, k, iRoomTall - 1, EXITDIR_SOUTH, x + k, y - 1, false, pMatchingExits ) )
return false;
}
// check top
if ( ( y + iRoomTall ) < MAP_LAYOUT_TILES_WIDE && m_pRoomGrid[x + k][y + iRoomTall] != NULL )
{
if ( !CheckExitsOnSquares( pTemplate, k, 0, EXITDIR_NORTH, x + k, y + iRoomTall, false, pMatchingExits ) )
return false;
}
}
for (int j = 0; j < iRoomTall; j++)
{
// check left
if (x > 0 && m_pRoomGrid[x - 1][y + j] != NULL)
{
if ( !CheckExitsOnSquares( pTemplate, 0, (iRoomTall - 1) - j, EXITDIR_WEST, x - 1, y + j, false, pMatchingExits ) )
return false;
}
// check right
if ( ( x + iRoomWide ) < MAP_LAYOUT_TILES_WIDE && m_pRoomGrid[x + iRoomWide][y + j] != NULL )
{
if ( !CheckExitsOnSquares( pTemplate, iRoomWide - 1, (iRoomTall - 1) - j, EXITDIR_EAST, x + iRoomWide, y + j, false, pMatchingExits ) )
return false;
}
}
return true;
}
// checks if these two squares match properly
bool CMapLayout::CheckExitsOnSquares( const CRoomTemplate *pTemplate1, int offset_x, int offset_y, ExitDirection_t Direction, int x2, int y2, bool bRequireConnection, CUtlVector< CRoomTemplateExit * > *pMatchingExits ) const
{
CRoom *pRoom2 = m_pRoomGrid[x2][y2];
Assert( pRoom2 );
const CRoomTemplate *pTemplate2 = pRoom2->m_pRoomTemplate;
Assert( pTemplate2 );
// check if the roomtemplate to be placed has an exit facing this way
CRoomTemplateExit *pTemplate1_Exit = NULL;
for ( int i=0; i < pTemplate1->m_Exits.Count(); i++ )
{
if ( pTemplate1->m_Exits[i]->m_bChokepointGrowSource ) // never connect into a chokepoint grow source
continue;
if ( pTemplate1->m_Exits[i]->m_iXPos == offset_x &&
pTemplate1->m_Exits[i]->m_iYPos == offset_y &&
pTemplate1->m_Exits[i]->m_ExitDirection == Direction )
{
pTemplate1_Exit = pTemplate1->m_Exits[i];
break;
}
}
// check if the already existing room has an exit facing the opposite way
CRoomTemplateExit *pTemplate2_Exit = NULL;
ExitDirection_t OppositeDirection = CRoomTemplateExit::GetOppositeDirection( Direction );
int offset_x2 = x2 - pRoom2->m_iPosX;
int offset_y2 = (pTemplate2->GetTilesY()-1) - (y2 - pRoom2->m_iPosY);
for ( int i=0; i < pTemplate2->m_Exits.Count(); i++ )
{
if ( pTemplate2->m_Exits[i]->m_iXPos == offset_x2 &&
pTemplate2->m_Exits[i]->m_iYPos == offset_y2 &&
pTemplate2->m_Exits[i]->m_ExitDirection == OppositeDirection )
{
pTemplate2_Exit = pTemplate2->m_Exits[i];
break;
}
}
// matching exits
if ( pTemplate1_Exit && pTemplate2_Exit
&& !Q_stricmp( pTemplate1_Exit->m_szExitTag, pTemplate2_Exit->m_szExitTag ) ) // Make sure exit types are equal here
{
if ( pMatchingExits )
pMatchingExits->AddToTail( pTemplate1_Exit );
return true;
}
// matching walls
if ( !pTemplate1_Exit && !pTemplate2_Exit && !bRequireConnection )
return true;
return false;
}
void CMapLayout::PlaceRoom( CRoom *pRoom )
{
Assert( pRoom );
pRoom->m_nPlacementIndex = m_PlacedRooms.AddToTail( pRoom );
pRoom->m_pMapLayout = this;
if ( !pRoom->m_pRoomTemplate )
return;
// fill in our pointer grid to easily access the croom later
for (int x = pRoom->m_iPosX ; x < pRoom->m_iPosX + pRoom->m_pRoomTemplate->GetTilesX() ; x++)
{
for (int y = pRoom->m_iPosY ; y < pRoom->m_iPosY + pRoom->m_pRoomTemplate->GetTilesY() ; y++)
{
m_pRoomGrid[x][y] = pRoom;
}
}
}
void CMapLayout::RemoveRoom( CRoom *pRoom )
{
m_PlacedRooms.FindAndRemove( pRoom );
pRoom->m_pMapLayout = NULL;
if ( !pRoom->m_pRoomTemplate )
return;
// clear the pointer grid at the location of this room
int iTileX = pRoom->m_iPosX;
int iTileY = pRoom->m_iPosY;
for ( int x = iTileX ; x < iTileX + pRoom->m_pRoomTemplate->GetTilesX() ; x++ )
{
for ( int y = iTileY ; y < iTileY + pRoom->m_pRoomTemplate->GetTilesY() ; y++ )
{
m_pRoomGrid[x][y] = NULL;
}
}
}
void CMapLayout::AddLogicalRoom( CRoomTemplate *pRoomTemplate )
{
if ( !pRoomTemplate )
return;
m_LogicalRooms.AddToTail( pRoomTemplate );
}
//-----------------------------------------------------------------------------
// An NPC spawn encounter in the mission
//-----------------------------------------------------------------------------
void CASW_Encounter::AddSpawnDef( CASW_Spawn_Definition *pSpawnDef )
{
m_SpawnDefs.AddToTail( pSpawnDef );
}
void CASW_Encounter::LoadFromKeyValues( KeyValues *pKeys )
{
m_SpawnDefs.Purge();
m_vecPosition.x = pKeys->GetFloat( "PosX" );
m_vecPosition.y = pKeys->GetFloat( "PosY" );
m_vecPosition.z = 0;
m_flEncounterRadius = pKeys->GetFloat( "Radius" );
for ( KeyValues *pKey = pKeys->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
{
if ( !Q_stricmp( pKey->GetName(), "SpawnDef" ) )
{
CASW_Spawn_Definition* pSpawnDef = SpawnSelection()->GetSpawnDefByID( pKey->GetInt() );
if ( pSpawnDef )
{
m_SpawnDefs.AddToTail( pSpawnDef );
}
else
{
Msg( "Failed to load spawn def %d for encounter.\n", pKey->GetInt() );
}
}
}
}
void CASW_Encounter::SaveToKeyValues( KeyValues *pKeys )
{
pKeys->SetFloat( "PosX", m_vecPosition.x );
pKeys->SetFloat( "PosY", m_vecPosition.y );
//pKeys->SetFloat( "PosZ", m_vecPosition.z );
pKeys->SetFloat( "Radius", m_flEncounterRadius );
for ( int i = 0; i < m_SpawnDefs.Count(); i++ )
{
KeyValues *pSpawnKeys = new KeyValues( "SpawnDef" );
pSpawnKeys->SetInt( NULL, m_SpawnDefs[i]->GetID() );
pKeys->AddSubKey( pSpawnKeys );
}
}