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.
423 lines
12 KiB
423 lines
12 KiB
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "matchext_swarm.h"
|
|
#include "swarm.spa.h"
|
|
|
|
#include "utlvector.h"
|
|
#include "utlstringmap.h"
|
|
#include "fmtstr.h"
|
|
#include "filesystem.h"
|
|
|
|
#define g_pFileSystem g_pFullFileSystem
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
CMatchExtSwarm::CMatchExtSwarm()
|
|
{
|
|
m_pKeyValues = NULL;
|
|
}
|
|
|
|
CMatchExtSwarm::~CMatchExtSwarm()
|
|
{
|
|
if ( m_pKeyValues )
|
|
{
|
|
m_pKeyValues->deleteThis();
|
|
m_pKeyValues = NULL;
|
|
}
|
|
}
|
|
|
|
static CMatchExtSwarm g_MatchExtSwarm;
|
|
CMatchExtSwarm *g_pMatchExtSwarm = &g_MatchExtSwarm;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMatchExtSwarm, IMatchExtSwarm,
|
|
IMATCHEXT_SWARM_INTERFACE, g_MatchExtSwarm );
|
|
|
|
|
|
//
|
|
// Implementation
|
|
//
|
|
|
|
void CMatchExtSwarm::ParseMissionFromFile( char const *szFile, bool bBuiltIn )
|
|
{
|
|
KeyValues *pMissionModes = m_pKeyValues->FindKey( "GameModes" );
|
|
KeyValues *pMissionsRoot = m_pKeyValues->FindKey( "Missions" );
|
|
if ( !pMissionsRoot || !pMissionModes )
|
|
return;
|
|
|
|
// See if we already have this mission
|
|
if ( m_mapFilesLoaded.Find( szFile ) != m_mapFilesLoaded.InvalidIndex() )
|
|
return;
|
|
m_mapFilesLoaded[ szFile ] = NULL;
|
|
|
|
KeyValues *missionKeys = new KeyValues( "mission" );
|
|
KeyValues::AutoDelete autodelete_missionKeys( missionKeys ); // allows for early error-return
|
|
|
|
bool bLoadResult = missionKeys->LoadFromFile( g_pFileSystem, szFile );
|
|
if ( !bLoadResult )
|
|
{
|
|
Warning( "MissionManager: Mission file \"%s\" is malformed, failed to parse.\n", szFile );
|
|
return;
|
|
}
|
|
|
|
const char *name = missionKeys->GetString( "name", NULL );
|
|
const char *campaignVersion = missionKeys->GetString( "version", NULL );
|
|
|
|
if ( !name || !campaignVersion )
|
|
{
|
|
Warning( "MissionManager: Mission file \"%s\" is missing name and version\n", szFile );
|
|
return;
|
|
}
|
|
|
|
// Check invalid characters
|
|
for ( char const *pCharCheck = name; *pCharCheck; ++ pCharCheck )
|
|
{
|
|
char const c = *pCharCheck;
|
|
if ( !( ( c >= 'a' && c <= 'z' ) ||
|
|
( c >= 'A' && c <= 'Z' ) ||
|
|
( c >= '0' && c <= '9' ) ) )
|
|
{
|
|
Warning( "MissionManager: Only alphanumeric characters allowed in mission name: \"%s\" in \"%s\"\n", name, szFile );
|
|
return;
|
|
}
|
|
}
|
|
for ( char const *pCharCheck = campaignVersion; *pCharCheck; ++ pCharCheck )
|
|
{
|
|
char const c = *pCharCheck;
|
|
if ( !( c >= '0' && c <= '9' ) )
|
|
{
|
|
Warning( "MissionManager: Only numeric characters allowed in mission version: \"%s\" in \"%s\"\n", campaignVersion, szFile );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Now go through the game modes
|
|
KeyValues *modes = missionKeys->FindKey( "modes" );
|
|
if ( !modes )
|
|
{
|
|
Warning( "MissionManager: Mission file \"%s\" is missing data for any game modes\n", szFile );
|
|
return;
|
|
}
|
|
|
|
CFmtStr sNameVersion( "%s_%s", name, campaignVersion );
|
|
|
|
// Don't allow duplicates
|
|
if ( m_mapMissionsLoaded.Find( name ) != m_mapMissionsLoaded.InvalidIndex() )
|
|
{
|
|
Warning( "MissionManager: Duplicate mission \"%s\" in file \"%s\", already loaded from \"%s\".\n",
|
|
name, szFile, m_mapMissionsLoaded[ name ]->GetString( "cfgfile" ) );
|
|
return;
|
|
}
|
|
|
|
DevMsg( "\tMission %s ver %s loading...\n", name, campaignVersion );
|
|
|
|
// Validate DisplayTitle and Description
|
|
char const *szDisplayTitle = missionKeys->GetString( "displaytitle" );
|
|
if ( !szDisplayTitle || !*szDisplayTitle )
|
|
missionKeys->SetString( "displaytitle", name );
|
|
|
|
// Set auto-generated fields
|
|
missionKeys->SetName( name );
|
|
missionKeys->SetInt( "builtin", bBuiltIn ? 1 : 0 );
|
|
missionKeys->SetString( "cfgfile", szFile );
|
|
missionKeys->SetString( "cfgtag", sNameVersion );
|
|
|
|
// Load game modes
|
|
int numGameModes = 0;
|
|
for ( KeyValues *modeName = pMissionModes->GetFirstTrueSubKey(); modeName; modeName = modeName->GetNextTrueSubKey() )
|
|
{
|
|
KeyValues *mode = modes->FindKey( modeName->GetName() );
|
|
if ( !mode )
|
|
continue;
|
|
|
|
int numChapters = 0;
|
|
while ( KeyValues *pChapterKey = mode->FindKey( CFmtStr( "%d", numChapters + 1 ) ) )
|
|
{
|
|
// Check required fields
|
|
char const *szMap = pChapterKey->GetString( "map" );
|
|
if ( !szMap || !*szMap )
|
|
{
|
|
Warning( "MissionManager: Mission file \"%s\" has invalid map specified for modes/%s/%s\n",
|
|
szFile, modeName->GetName(), pChapterKey->GetName() );
|
|
numChapters = 0;
|
|
break;
|
|
}
|
|
|
|
// DisplayName
|
|
char const *szDisplayName = pChapterKey->GetString( "displayname" );
|
|
if ( !szDisplayName || !*szDisplayName )
|
|
{
|
|
pChapterKey->SetString( "displayname", CFmtStr( "%s-%s", name, pChapterKey->GetName() ) );
|
|
}
|
|
|
|
// Image
|
|
char const *szImage = pChapterKey->GetString( "image" );
|
|
if ( !szImage || !*szImage )
|
|
{
|
|
pChapterKey->SetString( "image", "maps/unknown" );
|
|
}
|
|
|
|
// Set the automatic fields
|
|
pChapterKey->SetInt( "chapter", numChapters + 1 );
|
|
|
|
// This chapter was valid
|
|
++ numChapters;
|
|
}
|
|
|
|
if ( !numChapters )
|
|
{
|
|
modes->RemoveSubKey( mode );
|
|
mode->deleteThis();
|
|
mode = NULL;
|
|
|
|
Warning( "MissionManager: Mission file \"%s\" has invalid settings for game mode %s\n",
|
|
szFile, modeName->GetName() );
|
|
continue;
|
|
}
|
|
|
|
mode->SetInt( "chapters", numChapters );
|
|
DevMsg( "\t\tloaded %d %s chapters.\n", numChapters, modeName->GetName() );
|
|
|
|
++ numGameModes;
|
|
}
|
|
|
|
if ( !numGameModes )
|
|
{
|
|
Warning( "MissionManager: Mission file \"%s\" does not have valid data for any supported game mode\n", szFile );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Bind the loaded mission keys into the system
|
|
//
|
|
m_mapFilesLoaded[ szFile ] = missionKeys;
|
|
m_mapMissionsLoaded[ name ] = missionKeys;
|
|
|
|
pMissionsRoot->AddSubKey( missionKeys );
|
|
autodelete_missionKeys.Assign( NULL ); // prevent automatic deletion
|
|
|
|
// Register all the loaded game modes
|
|
for ( KeyValues *modeName = pMissionModes->GetFirstTrueSubKey(); modeName; modeName = modeName->GetNextTrueSubKey() )
|
|
{
|
|
KeyValues *mode = modes->FindKey( modeName->GetName() );
|
|
if ( !mode )
|
|
continue;
|
|
|
|
modeName->SetPtr( name, missionKeys );
|
|
}
|
|
|
|
DevMsg( "\tMission %s ver %s loaded %d game modes.\n", name, campaignVersion, numGameModes );
|
|
}
|
|
|
|
void CMatchExtSwarm::MakeGameModeCopy( char const *szGameMode, char const *szCopyName )
|
|
{
|
|
// Fix the GameModes key
|
|
if ( KeyValues *pKeyMode = m_pKeyValues->FindKey( CFmtStr( "GameModes/%s", szGameMode ) ) )
|
|
{
|
|
pKeyMode = pKeyMode->MakeCopy();
|
|
pKeyMode->SetName( szCopyName );
|
|
m_pKeyValues->FindKey( "GameModes" )->AddSubKey( pKeyMode );
|
|
}
|
|
|
|
// Fix all missions
|
|
KeyValues *pMission = GetAllMissions();
|
|
for ( pMission = pMission ? pMission->GetFirstTrueSubKey() : NULL;
|
|
pMission; pMission = pMission->GetNextTrueSubKey() )
|
|
{
|
|
if ( KeyValues *pKeyMode = pMission->FindKey( CFmtStr( "modes/%s", szGameMode ) ) )
|
|
{
|
|
pKeyMode = pKeyMode->MakeCopy();
|
|
pKeyMode->SetName( szCopyName );
|
|
pMission->FindKey( "modes" )->AddSubKey( pKeyMode );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CMatchExtSwarm::Initialize()
|
|
{
|
|
DevMsg( "Loading Mission Data\n" );
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
if ( m_pKeyValues )
|
|
{
|
|
m_pKeyValues->deleteThis();
|
|
m_pKeyValues = NULL;
|
|
}
|
|
|
|
m_mapFilesLoaded.Purge();
|
|
m_mapMissionsLoaded.Purge();
|
|
|
|
m_pKeyValues = KeyValues::FromString(
|
|
"AlienSwarm",
|
|
" GameModes { "
|
|
" coop { } "
|
|
#ifndef _DEMO
|
|
" versus { } "
|
|
" survival { } "
|
|
" scavenge { } "
|
|
#endif
|
|
" } "
|
|
" Missions { "
|
|
// read from mission files
|
|
" } "
|
|
);
|
|
|
|
//
|
|
// Parse built-in missions
|
|
//
|
|
|
|
#ifndef _DEMO
|
|
ParseMissionFromFile( "missions/campaign1.txt", true );
|
|
ParseMissionFromFile( "missions/campaign2.txt", true );
|
|
ParseMissionFromFile( "missions/campaign3.txt", true );
|
|
ParseMissionFromFile( "missions/campaign4.txt", true );
|
|
ParseMissionFromFile( "missions/campaign5.txt", true );
|
|
ParseMissionFromFile( "missions/credits.txt", true );
|
|
|
|
//
|
|
// Search missions using the wildcards
|
|
//
|
|
char szMissionPath[_MAX_PATH];
|
|
Q_snprintf( szMissionPath, sizeof( szMissionPath ), "missions/*.txt" );
|
|
Q_FixSlashes( szMissionPath );
|
|
|
|
FileFindHandle_t handle;
|
|
const char *pFoundFile = g_pFileSystem->FindFirst( szMissionPath, &handle );
|
|
while ( pFoundFile )
|
|
{
|
|
char pFilename[ MAX_PATH ];
|
|
V_snprintf( pFilename, ARRAYSIZE(pFilename), "missions/%s", pFoundFile );
|
|
pFoundFile = g_pFileSystem->FindNext( handle );
|
|
|
|
ParseMissionFromFile( pFilename, false );
|
|
}
|
|
|
|
#else
|
|
ParseMissionFromFile( "missions/demo.txt", true );
|
|
#endif
|
|
|
|
|
|
#ifndef _DEMO
|
|
// Make game mode copies
|
|
MakeGameModeCopy( "versus", "teamversus" );
|
|
MakeGameModeCopy( "scavenge", "teamscavenge" );
|
|
MakeGameModeCopy( "coop", "realism" );
|
|
#endif
|
|
|
|
DevMsg( "Loading Mission Data Finished\n" );
|
|
}
|
|
|
|
void CMatchExtSwarm::DebugPrint()
|
|
{
|
|
KeyValuesDumpAsDevMsg( m_pKeyValues, 1, 0 );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
CON_COMMAND( mission_reload, "Reload mission metadata" )
|
|
{
|
|
g_MatchExtSwarm.Initialize();
|
|
}
|
|
|
|
CON_COMMAND_F( mission_debug_print, "Print all mission metadata", FCVAR_DEVELOPMENTONLY )
|
|
{
|
|
g_MatchExtSwarm.DebugPrint();
|
|
}
|
|
|
|
KeyValues * CMatchExtSwarm::GetAllMissions()
|
|
{
|
|
if ( !m_pKeyValues )
|
|
return NULL;
|
|
|
|
return m_pKeyValues->FindKey( "Missions" );
|
|
}
|
|
|
|
// Get server map information for the session settings
|
|
KeyValues * CMatchExtSwarm::GetMapInfo( KeyValues *pSettings, KeyValues **ppMissionInfo )
|
|
{
|
|
if ( !m_pKeyValues )
|
|
return NULL;
|
|
|
|
char const *szGameMode = pSettings->GetString( "game/mode", NULL );
|
|
if ( !szGameMode || !*szGameMode )
|
|
return NULL;
|
|
|
|
char const *szCampaign = pSettings->GetString( "game/campaign", NULL );
|
|
if ( !szCampaign || !*szCampaign )
|
|
return NULL;
|
|
|
|
int nMapNumber = pSettings->GetInt( "game/chapter", 0 );
|
|
if ( nMapNumber <= 0 )
|
|
return NULL;
|
|
|
|
// Find the campaign key
|
|
KeyValues *pMissionKey = ( KeyValues * ) m_pKeyValues->GetPtr( CFmtStr( "GameModes/%s/%s", szGameMode, szCampaign ), NULL );
|
|
if ( !pMissionKey )
|
|
return NULL;
|
|
|
|
// Find the total number of chapters in that mission's game mode
|
|
int numChapters = pMissionKey->GetInt( CFmtStr( "modes/%s/chapters", szGameMode ), 0 );
|
|
if ( nMapNumber > numChapters )
|
|
return NULL;
|
|
|
|
KeyValues *pChapterKey = pMissionKey->FindKey( CFmtStr( "modes/%s/%d", szGameMode, nMapNumber ) );
|
|
if ( !pChapterKey )
|
|
return NULL;
|
|
|
|
if ( ppMissionInfo )
|
|
*ppMissionInfo = pMissionKey;
|
|
|
|
return pChapterKey;
|
|
}
|
|
|
|
KeyValues * CMatchExtSwarm::GetMapInfoByBspName( KeyValues *pSettings, char const *szBspMapName, KeyValues **ppMissionInfo )
|
|
{
|
|
if ( !m_pKeyValues )
|
|
return NULL;
|
|
|
|
Assert( szBspMapName );
|
|
if ( !szBspMapName || !*szBspMapName )
|
|
return NULL;
|
|
|
|
char const *szGameMode = pSettings->GetString( "game/mode", NULL );
|
|
if ( !szGameMode || !*szGameMode )
|
|
return NULL;
|
|
|
|
// Walk all the missions in that game mode
|
|
KeyValues *pModeMissions = m_pKeyValues->FindKey( CFmtStr( "GameModes/%s", szGameMode ) );
|
|
if ( !pModeMissions )
|
|
return NULL;
|
|
|
|
for ( KeyValues *pMissionName = pModeMissions->GetFirstValue(); pMissionName; pMissionName = pMissionName->GetNextValue() )
|
|
{
|
|
KeyValues *pMission = ( KeyValues * ) pMissionName->GetPtr();
|
|
if ( !pMission )
|
|
continue;
|
|
|
|
KeyValues *pChapters = pMission->FindKey( CFmtStr( "modes/%s", szGameMode ) );
|
|
if ( !pChapters )
|
|
continue;
|
|
|
|
int numChapters = pChapters->GetInt( "chapters" );
|
|
for ( int k = 1; k <= numChapters; ++ k )
|
|
{
|
|
KeyValues *pMap = pChapters->FindKey( CFmtStr( "%d", k ) );
|
|
if ( !pMap )
|
|
break;
|
|
|
|
char const *szBspName = pMap->GetString( "map" );
|
|
if ( !Q_stricmp( szBspName, szBspMapName ) )
|
|
{
|
|
if ( ppMissionInfo )
|
|
*ppMissionInfo = pMission;
|
|
|
|
return pMap;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|