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.
 
 
 
 
 
 

1345 lines
41 KiB

#include "qlimits.h"
#include "tier1/strtools.h"
#include "tier1/utlvector.h"
#include "asw_mission_chooser_source_local.h"
#include "KeyValues.h"
#include "filesystem.h"
#include "convar.h"
#include "asw_system.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static FileFindHandle_t g_hmapfind = FILESYSTEM_INVALID_FIND_HANDLE;
static FileFindHandle_t g_hcampaignfind = FILESYSTEM_INVALID_FIND_HANDLE;
static FileFindHandle_t g_hsavedfind = FILESYSTEM_INVALID_FIND_HANDLE;
#define ASW_SKILL_POINTS_PER_MISSION 2 // keep in sync with asw_shareddefs.h (we need a h shared between missionchooser and game dlls...)
ConVar asw_max_saves("asw_max_saves", "200", FCVAR_ARCHIVE, "Maximum number of multiplayer saves that will be stored on this server.");
namespace
{
//-----------------------------------------------------------------------------
// Purpose: Slightly modified strtok. Does not modify the input string. Does
// not skip over more than one separator at a time. This allows parsing
// strings where tokens between separators may or may not be present:
//
// Door01,,,0 would be parsed as "Door01" "" "" "0"
// Door01,Open,,0 would be parsed as "Door01" "Open" "" "0"
//
// Input : token - Returns with a token, or zero length if the token was missing.
// str - String to parse.
// sep - Character to use as separator. UNDONE: allow multiple separator chars
// Output : Returns a pointer to the next token to be parsed.
//-----------------------------------------------------------------------------
const char *nexttoken(char *token, const char *str, char sep)
{
if ((str == NULL) || (*str == '\0'))
{
*token = '\0';
return(NULL);
}
//
// Copy everything up to the first separator into the return buffer.
// Do not include separators in the return buffer.
//
while ((*str != sep) && (*str != '\0'))
{
*token++ = *str++;
}
*token = '\0';
//
// Advance the pointer unless we hit the end of the input string.
//
if (*str == '\0')
{
return(str);
}
return(++str);
}
};
CASW_Mission_Chooser_Source_Local::CASW_Mission_Chooser_Source_Local()
{
for (int i=0;i<ASW_SAVES_PER_PAGE;i++) // this array needs to be large enough for either ASW_SAVES_PER_PAGE (for when showing save games), or ASW_MISSIONS_PER_PAGE (for when showing missions) or ASW_CAMPAIGNS_PER_SCREEN (for when showing campaigns)
{
Q_snprintf(m_missions[i].m_szMissionName, sizeof(m_missions[i].m_szMissionName), "");
}
m_bBuiltMapList = false;
m_bBuiltCampaignList = false;
m_bBuiltSavedCampaignList = false;
m_bBuildingMapList = false;
m_bBuildingCampaignList = false;
m_bBuildingSavedCampaignList = false;
m_pszMapFind = NULL;
m_pszCampaignFind = NULL;
m_pszSavedFind = NULL;
}
CASW_Mission_Chooser_Source_Local::~CASW_Mission_Chooser_Source_Local()
{
m_MissionDetails.PurgeAndDeleteElements();
m_CampaignDetails.PurgeAndDeleteElements();
}
void CASW_Mission_Chooser_Source_Local::OnSaveDeleted(const char *szSaveName)
{
char fixedname[ 256 ];
Q_strncpy( fixedname, szSaveName, sizeof( fixedname ) );
Q_SetExtension( fixedname, ".campaignsave", sizeof( fixedname ) );
// check if this save is in the list of summaries, if so, remove it
for (int i=0;i<m_SavedCampaignList.Count();i++)
{
if (!Q_stricmp(m_SavedCampaignList[i].m_szSaveName, fixedname))
{
m_SavedCampaignList.Remove(i);
return;
}
}
}
void CASW_Mission_Chooser_Source_Local::OnSaveUpdated(const char *szSaveName)
{
// if we haven't started scanning for saves yet, don't worry about it
if (!m_bBuiltSavedCampaignList && !m_bBuildingSavedCampaignList)
return;
// make sure it has the campaignsave extension
char stripped[256];
V_StripExtension(szSaveName, stripped, sizeof(stripped));
char szWithExtension[256];
Q_snprintf(szWithExtension, sizeof(szWithExtension), "%s.campaignsave", stripped);
// check it's not already in the saved list
for (int i=0;i<m_SavedCampaignList.Count();i++)
{
if (!Q_strcmp(m_SavedCampaignList[i].m_szSaveName, szWithExtension))
{
m_SavedCampaignList.Remove(i);
break;
}
}
Msg("Updating save game summary %s\n", szSaveName);
AddToSavedCampaignList(szWithExtension);
}
void CASW_Mission_Chooser_Source_Local::RefreshSavedCampaigns()
{
m_bBuildingSavedCampaignList = false;
m_bBuiltSavedCampaignList = false;
BuildSavedCampaignList();
}
void CASW_Mission_Chooser_Source_Local::ClearMapList()
{
m_Items.Purge();
}
void CASW_Mission_Chooser_Source_Local::AddToMapList(const char *szMapName)
{
MapListName item;
Q_snprintf(item.szMapName, sizeof(item.szMapName), "%s", szMapName);
m_Items.Insert( item );
// check if it has an overview txt
char stripped[MAX_PATH];
V_StripExtension( szMapName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/overviews/%s.txt", stripped );
bool bHasOverviewOnHost = (g_pFullFileSystem->FileExists(tempfile));
//bool bNoOverview = false;
if ( !bHasOverviewOnHost )
{
// try to load it directly from the maps folder
Q_snprintf( tempfile, sizeof( tempfile ), "maps/%s.txt", stripped );
bHasOverviewOnHost = (g_pFullFileSystem->FileExists(tempfile));
}
// add it to our overview list if it has an overview
if (bHasOverviewOnHost)
{
m_OverviewItems.Insert( item );
}
}
void CASW_Mission_Chooser_Source_Local::BuildMapList()
{
if (m_bBuildingMapList || m_bBuiltMapList) // already building
return;
ClearMapList();
m_bBuildingMapList = true;
// Search the directory structure.
char mapwild[MAX_QPATH];
Q_strncpy(mapwild,"maps/*.bsp", sizeof( mapwild ) );
m_pszMapFind = Sys_FindFirst( g_hmapfind, mapwild, NULL, 0 );
// think will continue the search
}
void CASW_Mission_Chooser_Source_Local::Think()
{
// if we're building the map list, continue our search of files
if (m_bBuildingMapList)
{
if (m_pszMapFind)
{
//Msg("Adding map: %s\n", m_pszMapFind);
AddToMapList(m_pszMapFind);
m_pszMapFind = Sys_FindNext(g_hmapfind, NULL, 0);
}
else
{
//Msg("Ending search for maps\n");
Sys_FindClose(g_hmapfind);
m_bBuildingMapList = false;
m_bBuiltMapList= true;
}
}
// if we're building the campaign list, continue our search of files
if (m_bBuildingCampaignList)
{
if (m_pszCampaignFind)
{
//Msg("Adding campaign: %s\n", m_pszCampaignFind);
AddToCampaignList(m_pszCampaignFind);
m_pszCampaignFind = Sys_FindNext(g_hcampaignfind, NULL, 0);
}
else
{
//Msg("Ending search for campaigns\n");
Sys_FindClose(g_hcampaignfind);
m_bBuildingCampaignList = false;
m_bBuiltCampaignList= true;
}
}
// if we're building the saved campaign list, continue our search of files
if (m_bBuildingSavedCampaignList)
{
if (m_pszSavedFind)
{
//Msg("Adding saved campaign: %s\n", m_pszSavedFind);
AddToSavedCampaignList(m_pszSavedFind);
m_pszSavedFind = Sys_FindNext(g_hsavedfind, NULL, 0);
}
else
{
//Msg("Ending search for saved campaigns\n");
Sys_FindClose(g_hsavedfind);
m_bBuildingSavedCampaignList = false;
m_bBuiltSavedCampaignList= true;
}
}
}
// called when server is in a relatively idle state (like during the briefing)
// we can use this time to scan for missions if we need to
void CASW_Mission_Chooser_Source_Local::IdleThink()
{
// if we're not currently building any lists, try to start building one
if (!m_bBuildingMapList && !m_bBuildingCampaignList && !m_bBuildingSavedCampaignList)
{
if (!m_bBuiltMapList)
{
BuildMapList();
}
else
{
if (!m_bBuiltCampaignList)
{
BuildCampaignList();
}
else if (!m_bBuiltSavedCampaignList)
{
BuildSavedCampaignList();
}
}
}
Think();
}
void CASW_Mission_Chooser_Source_Local::FindMissionsInCampaign( int iCampaignIndex, int nMissionOffset, int iNumSlots )
{
if (!m_bBuiltMapList)
BuildMapList();
ASW_Mission_Chooser_Mission* pCampaign = GetCampaign( iCampaignIndex );
if ( !pCampaign )
return;
KeyValues *pCampaignDetails = GetCampaignDetails( pCampaign->m_szMissionName );
if ( !pCampaignDetails )
return;
CUtlVector<KeyValues*> aMissionKeys;
bool bSkippedFirst = false;
for ( KeyValues *pMission = pCampaignDetails->GetFirstSubKey(); pMission; pMission = pMission->GetNextKey() )
{
if ( !Q_stricmp( pMission->GetName(), "MISSION" ) )
{
if ( !bSkippedFirst )
{
bSkippedFirst = true;
}
else
{
aMissionKeys.AddToTail( pMission );
}
}
}
int max_items = aMissionKeys.Count();
for ( int stored=0;stored<iNumSlots;stored++ )
{
int realoffset = nMissionOffset;
if ( realoffset >= max_items || realoffset < 0 )
{
// no more missions...
Q_snprintf( m_missions[stored].m_szMissionName, sizeof( m_missions[stored].m_szMissionName ), "" );
}
else
{
Q_snprintf( m_missions[stored].m_szMissionName, sizeof( m_missions[stored].m_szMissionName ), "%s", aMissionKeys[realoffset]->GetString( "MapName" ) );
}
nMissionOffset++;
}
}
int CASW_Mission_Chooser_Source_Local::GetNumMissionsInCampaign( int iCampaignIndex )
{
if (!m_bBuiltMapList)
BuildMapList();
ASW_Mission_Chooser_Mission* pCampaign = GetCampaign( iCampaignIndex );
if ( !pCampaign )
return 0;
KeyValues *pCampaignDetails = GetCampaignDetails( pCampaign->m_szMissionName );
if ( !pCampaignDetails )
return 0;
CUtlVector<KeyValues*> aMissionKeys;
bool bSkippedFirst = false;
for ( KeyValues *pMission = pCampaignDetails->GetFirstSubKey(); pMission; pMission = pMission->GetNextKey() )
{
if ( !Q_stricmp( pMission->GetName(), "MISSION" ) )
{
if ( !bSkippedFirst )
{
bSkippedFirst = true;
}
else
{
aMissionKeys.AddToTail( pMission );
}
}
}
return aMissionKeys.Count();
}
void CASW_Mission_Chooser_Source_Local::FindMissions(int nMissionOffset, int iNumSlots, bool bRequireOverview)
{
if (!m_bBuiltMapList)
BuildMapList();
int max_items = bRequireOverview ? m_OverviewItems.Count() : m_Items.Count();
for (int stored=0;stored<iNumSlots;stored++)
{
int realoffset = nMissionOffset;
if (realoffset >= max_items || realoffset < 0)
{
// no more missions...
Q_snprintf(m_missions[stored].m_szMissionName, sizeof(m_missions[stored].m_szMissionName), "");
}
else
{
if (bRequireOverview)
{
Q_snprintf(m_missions[stored].m_szMissionName, sizeof(m_missions[stored].m_szMissionName), "%s", m_OverviewItems[realoffset].szMapName);
}
else
{
Q_snprintf(m_missions[stored].m_szMissionName, sizeof(m_missions[stored].m_szMissionName), "%s", m_Items[realoffset].szMapName);
}
}
nMissionOffset++;
}
}
// pass an array of mission names back
ASW_Mission_Chooser_Mission* CASW_Mission_Chooser_Source_Local::GetMissions()
{
return m_missions;
}
ASW_Mission_Chooser_Mission* CASW_Mission_Chooser_Source_Local::GetMission( int nIndex, bool bRequireOverview )
{
if (!m_bBuiltMapList)
BuildMapList();
int max_items = bRequireOverview ? m_OverviewItems.Count() : m_Items.Count();
static ASW_Mission_Chooser_Mission mission;
if (nIndex >= max_items || nIndex < 0)
{
// no more missions...
Q_snprintf(mission.m_szMissionName, sizeof(mission.m_szMissionName), "");
}
else
{
if (bRequireOverview)
{
Q_snprintf(mission.m_szMissionName, sizeof(mission.m_szMissionName), "%s", m_OverviewItems[nIndex].szMapName);
}
else
{
Q_snprintf(mission.m_szMissionName, sizeof(mission.m_szMissionName), "%s", m_Items[nIndex].szMapName);
}
}
return &mission;
}
int CASW_Mission_Chooser_Source_Local::GetNumMissions(bool bRequireOverview)
{
if (!m_bBuiltMapList)
BuildMapList();
if (bRequireOverview)
return m_OverviewItems.Count();
return m_Items.Count();
}
void CASW_Mission_Chooser_Source_Local::ClearCampaignList()
{
m_CampaignList.Purge();
}
void CASW_Mission_Chooser_Source_Local::AddToCampaignList(const char *szCampaignName)
{
MapListName item;
Q_snprintf(item.szMapName, sizeof(item.szMapName), "%s", szCampaignName);
m_CampaignList.Insert( item );
}
void CASW_Mission_Chooser_Source_Local::BuildCampaignList()
{
if (m_bBuildingCampaignList || m_bBuiltCampaignList) // already building
return;
ClearCampaignList();
m_bBuildingCampaignList= true;
// Search the directory structure.
char mapwild[MAX_QPATH];
Q_strncpy(mapwild,"resource/campaigns/*.txt", sizeof( mapwild ) );
m_pszCampaignFind = Sys_FindFirst( g_hcampaignfind, mapwild, NULL, 0 );
// think will continue the search
}
void CASW_Mission_Chooser_Source_Local::FindCampaigns(int nCampaignOffset, int iNumSlots)
{
if (!m_bBuiltCampaignList)
BuildCampaignList();
int max_items = m_CampaignList.Count();
for (int stored=0;stored<iNumSlots;stored++)
{
int realoffset = nCampaignOffset;
if (stored < ASW_CAMPAIGNS_PER_PAGE)
{
if (realoffset >= max_items || realoffset < 0)
{
Q_snprintf(m_campaigns[stored].m_szMissionName, sizeof(m_campaigns[stored].m_szMissionName), "");
}
else
{
Q_snprintf(m_campaigns[stored].m_szMissionName, sizeof(m_campaigns[stored].m_szMissionName), "%s", m_CampaignList[realoffset].szMapName);
}
nCampaignOffset++;
}
}
}
// Passes an array of campaign names back
ASW_Mission_Chooser_Mission* CASW_Mission_Chooser_Source_Local::GetCampaigns()
{
if (!m_bBuiltCampaignList)
BuildCampaignList();
return m_campaigns;
}
ASW_Mission_Chooser_Mission* CASW_Mission_Chooser_Source_Local::GetCampaign( int nIndex )
{
if (!m_bBuiltCampaignList)
BuildCampaignList();
static ASW_Mission_Chooser_Mission campaign;
int max_items = m_CampaignList.Count();
if (nIndex >= max_items || nIndex < 0)
{
Q_snprintf(campaign.m_szMissionName, sizeof(campaign.m_szMissionName), "");
}
else
{
Q_snprintf(campaign.m_szMissionName, sizeof(campaign.m_szMissionName), "%s", m_CampaignList[nIndex].szMapName);
}
return &campaign;
}
int CASW_Mission_Chooser_Source_Local::GetNumCampaigns()
{
if (!m_bBuiltCampaignList)
BuildCampaignList();
return m_CampaignList.Count();
}
// todo: accept a steam ID and only find saves from that person?
void CASW_Mission_Chooser_Source_Local::FindSavedCampaigns( int nSaveOffset, int iNumSlots, bool bMultiplayer, const char *szFilterID)
{
if (!m_bBuiltSavedCampaignList)
BuildSavedCampaignList();
int skip_entries = nSaveOffset; // how many filter matching entries we're skipping
int max_items = m_SavedCampaignList.Count();
int stored = 0;
int offset = 0;
// count the offset up until we've skipped the desired number of filter matching entries
while (skip_entries > 0 && offset < max_items)
{
if (bMultiplayer == m_SavedCampaignList[offset].m_bMultiplayer && SavePassesFilter(&m_SavedCampaignList[offset], szFilterID))
{
skip_entries--;
}
offset++;
}
while (stored < iNumSlots && offset < max_items)
{
//Msg("testing save %d multi = %d we want multi = %d\n", offset, m_SavedCampaignList[offset].m_bMultiplayer, bMultiplayer);
if (bMultiplayer == m_SavedCampaignList[offset].m_bMultiplayer && SavePassesFilter(&m_SavedCampaignList[offset], szFilterID))
{
Q_snprintf(m_savedcampaigns[stored].m_szSaveName, sizeof(m_savedcampaigns[stored].m_szSaveName), "%s", m_SavedCampaignList[offset].m_szSaveName);
Q_snprintf(m_savedcampaigns[stored].m_szCampaignName, sizeof(m_savedcampaigns[stored].m_szCampaignName), "%s", m_SavedCampaignList[offset].m_szCampaignName);
Q_snprintf(m_savedcampaigns[stored].m_szDateTime, sizeof(m_savedcampaigns[stored].m_szDateTime), "%s", m_SavedCampaignList[offset].m_szDateTime);
Q_snprintf(m_savedcampaigns[stored].m_szPlayerNames, sizeof(m_savedcampaigns[stored].m_szPlayerNames), "%s", m_SavedCampaignList[offset].m_szPlayerNames);
m_savedcampaigns[stored].m_iMissionsComplete = m_SavedCampaignList[offset].m_iMissionsComplete;
stored++;
}
offset++;
}
// blank out any slots that didn't get filled
for (int i=stored;i<iNumSlots;i++)
{
Q_snprintf(m_savedcampaigns[i].m_szSaveName, sizeof(m_savedcampaigns[i].m_szSaveName), "");
Q_snprintf(m_savedcampaigns[i].m_szCampaignName, sizeof(m_savedcampaigns[i].m_szCampaignName), "");
Q_snprintf(m_savedcampaigns[i].m_szDateTime, sizeof(m_savedcampaigns[i].m_szDateTime), "");
Q_snprintf(m_savedcampaigns[i].m_szPlayerNames, sizeof(m_savedcampaigns[i].m_szPlayerNames), "");
m_savedcampaigns[i].m_iMissionsComplete = 0;
}
}
ASW_Mission_Chooser_Saved_Campaign* CASW_Mission_Chooser_Source_Local::GetSavedCampaigns()
{
if (!m_bBuiltSavedCampaignList)
BuildSavedCampaignList();
return m_savedcampaigns;
}
ASW_Mission_Chooser_Saved_Campaign* CASW_Mission_Chooser_Source_Local::GetSavedCampaign( int nIndex, bool bMultiplayer, const char *szFilterID )
{
if (!m_bBuiltSavedCampaignList)
BuildSavedCampaignList();
int skip_entries = nIndex; // how many filter matching entries we're skipping
int max_items = m_SavedCampaignList.Count();
int offset = 0;
// count the offset up until we've skipped the desired number of filter matching entries
while (skip_entries > 0 && offset < max_items)
{
if (bMultiplayer == m_SavedCampaignList[offset].m_bMultiplayer && SavePassesFilter(&m_SavedCampaignList[offset], szFilterID))
{
skip_entries--;
}
offset++;
}
static ASW_Mission_Chooser_Saved_Campaign save;
Q_snprintf(save.m_szSaveName, sizeof(save.m_szSaveName), "%s", m_SavedCampaignList[offset].m_szSaveName);
Q_snprintf(save.m_szCampaignName, sizeof(save.m_szCampaignName), "%s", m_SavedCampaignList[offset].m_szCampaignName);
Q_snprintf(save.m_szDateTime, sizeof(save.m_szDateTime), "%s", m_SavedCampaignList[offset].m_szDateTime);
Q_snprintf(save.m_szPlayerNames, sizeof(save.m_szPlayerNames), "%s", m_SavedCampaignList[offset].m_szPlayerNames);
save.m_iMissionsComplete = m_SavedCampaignList[offset].m_iMissionsComplete;
return &save;
}
bool CASW_Mission_Chooser_Source_Local::SavePassesFilter(ASW_Mission_Chooser_Saved_Campaign* pSaved, const char *szFilterID)
{
if (!pSaved)
return false;
if (!szFilterID || Q_strlen(szFilterID) < 1)
return true;
// check our filterID is present in the playerids with this save
const char *p = pSaved->m_szPlayerIDs;
char token[128];
p = nexttoken( token, p, ' ' );
while ( Q_strlen( token ) > 0 )
{
// found a match
if (!Q_stricmp(szFilterID, token))
return true;
if (p)
p = nexttoken( token, p, ' ' );
else
token[0] = '\0';
}
return false;
}
int CASW_Mission_Chooser_Source_Local::GetNumSavedCampaigns(bool bMultiplayer, const char *szFilterID)
{
int iNumSaves = 0;
for (int i=0;i<m_SavedCampaignList.Count();i++)
{
if (m_SavedCampaignList[i].m_bMultiplayer == bMultiplayer && SavePassesFilter(&m_SavedCampaignList[i], szFilterID))
{
iNumSaves++;
}
}
return iNumSaves;
}
void CASW_Mission_Chooser_Source_Local::ClearSavedCampaignList()
{
m_SavedCampaignList.Purge();
}
void CASW_Mission_Chooser_Source_Local::AddToSavedCampaignList(const char *szSaveName)
{
// find out what campaign this save is for
char szFullFileName[256];
Q_snprintf(szFullFileName, sizeof(szFullFileName), "save/%s", szSaveName);
KeyValues *pSaveKeyValues = new KeyValues( szSaveName );
if (pSaveKeyValues->LoadFromFile(g_pFullFileSystem, szFullFileName))
{
const char *pCampaignName = pSaveKeyValues->GetString("CampaignName");
// check the campaign exists
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/campaigns/%s.txt", pCampaignName );
if (g_pFullFileSystem->FileExists(tempfile))
{
ASW_Mission_Chooser_Saved_Campaign item;
Q_snprintf(item.m_szSaveName, sizeof(item.m_szSaveName), "%s", szSaveName);
Q_snprintf(item.m_szCampaignName, sizeof(item.m_szCampaignName), "%s", pCampaignName);
Q_snprintf(item.m_szDateTime, sizeof(item.m_szDateTime), "%s", pSaveKeyValues->GetString("DateTime"));
//Msg("save %s multiplayer %d\n", szSaveName, pSaveKeyValues->GetInt("Multiplayer"));
item.m_bMultiplayer = (pSaveKeyValues->GetInt("Multiplayer") > 0);
//Msg("item multiplayer = %d\n", item.m_bMultiplayer);
// check subsections for player names and player IDs, concat them into two strings
char namebuffer[256];
char idbuffer[512];
namebuffer[0] = '\0';
idbuffer[0] = '\0';
int namepos = 0;
int idpos = 0;
KeyValues *pkvSubSection = pSaveKeyValues->GetFirstSubKey();
while ( pkvSubSection )
{
if ((Q_stricmp(pkvSubSection->GetName(), "PLAYER")==0) && namepos < 253)
{
const char *pName = pkvSubSection->GetString("PlayerName");
if (pName && pName[0] != '\0')
{
int namelength = Q_strlen(pName);
for (int charcopy=0; charcopy<namelength && namepos<253; charcopy++)
{
namebuffer[namepos] = pName[charcopy];
namepos++;
}
namebuffer[namepos] = ' '; namepos++;
namebuffer[namepos] = '\0';
}
}
if ((Q_stricmp(pkvSubSection->GetName(), "DATA")==0) && idpos < 253)
{
const char *pID = pkvSubSection->GetString("DataBlock");
if (pID && pID[0] != '\0')
{
int idlength = Q_strlen(pID);
for (int charcopy=0; charcopy<idlength && idpos<253; charcopy++)
{
idbuffer[idpos] = pID[charcopy];
idpos++;
}
idbuffer[idpos] = ' '; idpos++;
idbuffer[idpos] = '\0';
}
}
pkvSubSection = pkvSubSection->GetNextKey();
}
Q_snprintf(item.m_szPlayerNames, sizeof(item.m_szPlayerNames), "%s", namebuffer);
Q_snprintf(item.m_szPlayerIDs, sizeof(item.m_szPlayerIDs), "%s", idbuffer);
item.m_iMissionsComplete = pSaveKeyValues->GetInt("NumMissionsComplete");
m_SavedCampaignList.Insert( item );
}
}
pSaveKeyValues->deleteThis();
// check if there's now too many save games
int iNumMultiplayer = GetNumSavedCampaigns(true, NULL);
if (iNumMultiplayer > asw_max_saves.GetInt())
{
// find the oldest one
ASW_Mission_Chooser_Saved_Campaign* pChosen = false;
int iChosen = -1;
for (int i=m_SavedCampaignList.Count()-1; i>=0; i--)
{
if (m_SavedCampaignList[i].m_bMultiplayer)
{
pChosen = &m_SavedCampaignList[i];
iChosen = i;
break;
}
}
// delete if found
if (iChosen != -1 && pChosen)
{
char buffer[MAX_PATH];
Q_snprintf(buffer, sizeof(buffer), "save/%s", pChosen->m_szSaveName);
Msg("Deleting save %s as we have too many\n", buffer);
g_pFullFileSystem->RemoveFile( buffer, "GAME" );
m_SavedCampaignList.Remove(iChosen);
}
}
}
void CASW_Mission_Chooser_Source_Local::BuildSavedCampaignList()
{
// don't start searching for saves until we've loaded in all the campaigns
if (!m_bBuiltCampaignList)
{
BuildCampaignList();
return;
}
if (m_bBuildingSavedCampaignList || m_bBuiltSavedCampaignList)
return;
ClearSavedCampaignList();
m_bBuildingSavedCampaignList = true;
if (m_CampaignList.Count() <= 0)
{
//Msg("Error: Cannot build saved campaign list until the campaign list is built\n");
return;
}
// Search the directory structure for save games
char mapwild[MAX_QPATH];
Q_strncpy(mapwild,"save/*.campaignsave", sizeof( mapwild ) );
m_pszSavedFind = Sys_FindFirst( g_hsavedfind, mapwild, NULL, 0 );
// think will continue the search
}
bool CASW_Mission_Chooser_Source_Local::MissionExists(const char *szMapName, bool bRequireOverview)
{
if ( !szMapName )
return false;
// check it has an overview txt
char stripped[MAX_PATH];
V_StripExtension( szMapName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
if (bRequireOverview)
{
Q_snprintf( tempfile, sizeof( tempfile ), "resource/overviews/%s.txt", stripped );
if (!g_pFullFileSystem->FileExists(tempfile))
return false;
}
// check the map exists
Q_snprintf( tempfile, sizeof( tempfile ), "maps/%s.bsp", stripped );
return (g_pFullFileSystem->FileExists(tempfile));
}
bool CASW_Mission_Chooser_Source_Local::CampaignExists(const char *szCampaignName)
{
if ( !szCampaignName )
return false;
// check the campaign txt exists
char stripped[MAX_PATH];
V_StripExtension( szCampaignName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/campaigns/%s.txt", stripped );
return (g_pFullFileSystem->FileExists(tempfile));
}
bool CASW_Mission_Chooser_Source_Local::SavedCampaignExists(const char *szSaveName)
{
// check the save file exists
char stripped[MAX_PATH];
V_StripExtension( szSaveName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "save/%s.campaignsave", stripped );
return (g_pFullFileSystem->FileExists(tempfile));
}
int CASW_Mission_Chooser_Source_Local::GetNumMissionsCompleted(const char *szSaveName)
{
Msg("GetNumMissionsCompleted %s\n", szSaveName);
// check the save file exists
char stripped[MAX_PATH];
V_StripExtension( szSaveName, stripped, MAX_PATH );
Msg(" stripped = %s\n", stripped);
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "save/%s.campaignsave", stripped );
Msg(" tempfile = %s\n", tempfile);
Q_strlower( tempfile );
Msg(" tempfile lowered = %s\n", tempfile);
if (!g_pFullFileSystem->FileExists(tempfile))
{
Msg(" this save doesn't exist! returning -1 missions\n");
return -1;
}
KeyValues *pSaveKeyValues = new KeyValues( szSaveName );
if (pSaveKeyValues->LoadFromFile(g_pFullFileSystem, tempfile))
{
int iMissions = pSaveKeyValues->GetInt("NumMissionsComplete");
pSaveKeyValues->deleteThis();
Msg(" loaded keyvalues from file and it thinks num missions is %d\n", iMissions);
return iMissions;
}
Msg(" Couldn't load save keyvalues from file, returning -1\n");
if (pSaveKeyValues->LoadFromFile(g_pFullFileSystem, tempfile, "MOD"))
Msg(" but it loaded if we use the MOD path\n");
else if (pSaveKeyValues->LoadFromFile(g_pFullFileSystem, tempfile, "GAME"))
Msg(" but it loaded if we use the GAME path\n");
pSaveKeyValues->deleteThis();
return -1;
}
const char* CASW_Mission_Chooser_Source_Local::GetPrettyMissionName(const char *szMapName)
{
static char szPrettyName[64];
szPrettyName[0] = '\0';
char stripped[MAX_PATH];
V_StripExtension( szMapName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/overviews/%s.txt", stripped );
KeyValues *pOverviewKeyValues = new KeyValues( szMapName );
if (pOverviewKeyValues->LoadFromFile(g_pFullFileSystem, tempfile))
{
Q_snprintf(szPrettyName, sizeof(szPrettyName), "%s", pOverviewKeyValues->GetString("missiontitle"));
}
pOverviewKeyValues->deleteThis();
return szPrettyName;
}
const char* CASW_Mission_Chooser_Source_Local::GetPrettyCampaignName(const char *szCampaignName)
{
static char szPrettyName[64];
szPrettyName[0] = '\0';
char stripped[MAX_PATH];
V_StripExtension( szCampaignName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/campaigns/%s.txt", stripped );
KeyValues *pCampaignKeyValues = new KeyValues( szCampaignName );
if (pCampaignKeyValues->LoadFromFile(g_pFullFileSystem, tempfile))
{
Q_snprintf(szPrettyName, sizeof(szPrettyName), "%s", pCampaignKeyValues->GetString("CampaignName"));
}
pCampaignKeyValues->deleteThis();
return szPrettyName;
}
const char* CASW_Mission_Chooser_Source_Local::GetPrettySavedCampaignName(const char *szSaveName)
{
static char szPrettyName[256];
szPrettyName[0] = '\0';
char stripped[MAX_PATH];
V_StripExtension( szSaveName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "save/%s.campaignsave", stripped );
KeyValues *pSaveKeyValues = new KeyValues( szSaveName );
if (pSaveKeyValues->LoadFromFile(g_pFullFileSystem, tempfile))
{
const char *szCampaignName = pSaveKeyValues->GetString("CampaignName");
const char *szPrettyCampaignName = szCampaignName;
if (szCampaignName && Q_strlen(szCampaignName) > 0)
{
szPrettyCampaignName = GetPrettyCampaignName(szCampaignName);
}
const char *szDate = pSaveKeyValues->GetString("DateTime");
char namebuffer[256];
namebuffer[0] = '\0';
int namepos = 0;
KeyValues *pkvSubSection = pSaveKeyValues->GetFirstSubKey();
while ( pkvSubSection && namepos < 253)
{
if (Q_stricmp(pkvSubSection->GetName(), "PLAYER")==0)
{
const char *pName = pkvSubSection->GetString("PlayerName");
if (pName && pName[0] != '\0')
{
if (namepos != 0)
{
namebuffer[namepos] = ' '; namepos++;
}
int namelength = Q_strlen(pName);
for (int charcopy=0; charcopy<namelength && namepos<253; charcopy++)
{
namebuffer[namepos] = pName[charcopy];
namepos++;
}
namebuffer[namepos] = '\0';
}
}
pkvSubSection = pkvSubSection->GetNextKey();
}
Q_snprintf(szPrettyName, sizeof(szPrettyName),
"%s (%s) (%s)",
szPrettyCampaignName, szDate, namebuffer);
}
pSaveKeyValues->deleteThis();
return szPrettyName;
}
// a new save has been created, add it to our summary list
void CASW_Mission_Chooser_Source_Local::NotifyNewSave(const char *szSaveName)
{
// if we haven't started scanning for saves yet, don't worry about it
if (!m_bBuiltSavedCampaignList && !m_bBuildingSavedCampaignList)
return;
// make sure it has the campaignsave extension
char stripped[256];
V_StripExtension(szSaveName, stripped, sizeof(stripped));
char szWithExtension[256];
Q_snprintf(szWithExtension, sizeof(szWithExtension), "%s.campaignsave", stripped);
// check it's not already in the saved list
for (int i=0;i<m_SavedCampaignList.Count();i++)
{
if (!Q_strcmp(m_SavedCampaignList[i].m_szSaveName, szWithExtension))
return;
}
Msg("New save created, adding it to the list of saved campaigns: %s\n", szSaveName);
AddToSavedCampaignList(szWithExtension);
}
// a new save has been created, add it to our summary list
void CASW_Mission_Chooser_Source_Local::NotifySaveDeleted(const char *szSaveName)
{
// if we haven't started scanning for saves yet, don't worry about it
if (!m_bBuiltSavedCampaignList && !m_bBuildingSavedCampaignList)
return;
// make sure it has the campaignsave extension
char stripped[256];
V_StripExtension(szSaveName, stripped, sizeof(stripped));
char szWithExtension[256];
Q_snprintf(szWithExtension, sizeof(szWithExtension), "%s.campaignsave", stripped);
// find it in the list
for (int i=0;i<m_SavedCampaignList.Count();i++)
{
if (!Q_strcmp(m_SavedCampaignList[i].m_szSaveName, szWithExtension))
{
m_SavedCampaignList.Remove(i);
return;
}
}
}
// !!NOTE!! - these numbers are from asw_campaign_save.h. Duplicated constants for the lose.
#define ASW_MAX_PLAYERS_PER_SAVE 10
#define ASW_MAX_MISSIONS_PER_CAMPAIGN 32
#define ASW_CURRENT_SAVE_VERSION 1
#define ASW_NUM_MARINE_PROFILES 9 // from asw_shareddefs.h
bool CASW_Mission_Chooser_Source_Local::ASW_Campaign_CreateNewSaveGame(char *szFileName, int iFileNameMaxLen, const char *szCampaignName, bool bMultiplayerGame, const char *szStartingMission) // szFileName arg is the desired filename or NULL for an autogenerated one. Function sets szFileName with the filename used.
{
if (!szFileName)
return false;
char stripped[MAX_PATH];
V_StripExtension( szCampaignName, stripped, MAX_PATH );
char szStartingMissionStripped[64];
if ( szStartingMission )
{
V_StripExtension( szStartingMission, szStartingMissionStripped, sizeof( szStartingMissionStripped ) );
}
else
{
szStartingMissionStripped[0] = 0;
}
// check the campaign file exists
char campbuffer[MAX_PATH];
Q_snprintf(campbuffer, sizeof(campbuffer), "resource/campaigns/%s.txt", stripped);
if (!g_pFullFileSystem->FileExists(campbuffer))
{
Msg("No such campaign: %s\n", campbuffer);
return false;
}
// Get the current time and date as a string
char szDateTime[256];
int year, month, dayOfWeek, day, hour, minute, second;
ASW_System_GetCurrentTimeAndDate(&year, &month, &dayOfWeek, &day, &hour, &minute, &second);
Q_snprintf(szDateTime, sizeof(szDateTime), "%02d/%02d/%02d %02d:%02d", month, day, year, hour, minute);
if (szFileName[0] == '\0')
{
// autogenerate a filename based on the current time and date
Q_snprintf(szFileName, iFileNameMaxLen, "%s_save_%02d_%02d_%02d_%02d_%02d_%02d", stripped, year, month, day, hour, minute, second);
}
// make sure the path and extension are correct
Q_SetExtension( szFileName, ".campaignsave", iFileNameMaxLen );
char tempbuffer[256];
Q_snprintf(tempbuffer, sizeof(tempbuffer), "%s", szFileName);
const char *pszNoPathName = Q_UnqualifiedFileName(tempbuffer);
Msg("Unqualified = %s\n", pszNoPathName);
char szFullFileName[256];
Q_snprintf(szFullFileName, sizeof(szFullFileName), "save/%s", pszNoPathName);
Msg("Creating new save with filename: %s\n", szFullFileName);
KeyValues *pSaveKeyValues = new KeyValues( pszNoPathName );
int nMissionsComplete = 0;
if ( szStartingMission && szStartingMission[0] )
{
KeyValues *pCampaignDetails = GetCampaignDetails( stripped );
if ( pCampaignDetails )
{
int nMissionSections = 0;
for ( KeyValues *pMission = pCampaignDetails->GetFirstSubKey(); pMission; pMission = pMission->GetNextKey() )
{
if ( !Q_stricmp( pMission->GetName(), "MISSION" ) )
{
if ( !Q_stricmp( pMission->GetString( "MapName", "" ), szStartingMissionStripped ) )
{
nMissionsComplete = nMissionSections - 1; // skip first dummy mission
break;
}
nMissionSections++;
}
}
}
}
pSaveKeyValues->SetInt("Version", ASW_CURRENT_SAVE_VERSION);
pSaveKeyValues->SetString("CampaignName", stripped);
pSaveKeyValues->SetInt("CurrentPosition", nMissionsComplete + 1); // position squad on the first uncompleted mission
pSaveKeyValues->SetInt("NumMissionsComplete", nMissionsComplete);
pSaveKeyValues->SetInt("InitialNumMissionsComplete", nMissionsComplete);
pSaveKeyValues->SetInt("Multiplayer", bMultiplayerGame ? 1 : 0);
pSaveKeyValues->SetString("DateTime", szDateTime);
pSaveKeyValues->SetInt("NumPlayers", 0);
// write out each mission's status
KeyValues *pSubSection;
for (int i=0; i<ASW_MAX_MISSIONS_PER_CAMPAIGN; i++)
{
pSubSection = new KeyValues("MISSION");
pSubSection->SetInt("MissionID", i);
bool bComplete = ( i != 0 ) && ( i <= nMissionsComplete );
pSubSection->SetInt("MissionComplete", bComplete ? 1 : 0 );
pSaveKeyValues->AddSubKey(pSubSection);
}
const int nInitialSkillPoints = 0;
// write out each marine's stats
for (int i=0; i<ASW_NUM_MARINE_PROFILES; i++)
{
pSubSection = new KeyValues("MARINE");
pSubSection->SetInt("MarineID", i);
pSubSection->SetInt("SkillSlot0", 0);
pSubSection->SetInt("SkillSlot1", 0);
pSubSection->SetInt("SkillSlot2", 0);
pSubSection->SetInt("SkillSlot3", 0);
pSubSection->SetInt("SkillSlot4", 0);
pSubSection->SetInt("SkillSlotSpare", nInitialSkillPoints + nMissionsComplete * ASW_SKILL_POINTS_PER_MISSION );
pSubSection->SetInt("UndoSkillSlot0", 0);
pSubSection->SetInt("UndoSkillSlot1", 0);
pSubSection->SetInt("UndoSkillSlot2", 0);
pSubSection->SetInt("UndoSkillSlot3", 0);
pSubSection->SetInt("UndoSkillSlot4", 0);
pSubSection->SetInt("UndoSkillSlotSpare", nInitialSkillPoints + nMissionsComplete * ASW_SKILL_POINTS_PER_MISSION );
pSubSection->SetString("MissionsCompleted", "");
pSubSection->SetString("Medals", "");
pSubSection->SetInt("Wounded", 0);
pSubSection->SetInt("Dead", 0);
pSubSection->SetInt("ParasitesKilled", 0);
pSaveKeyValues->AddSubKey(pSubSection);
}
// players section is empty at first
// Create the save sub-directory
if (!g_pFullFileSystem->IsDirectory( "save", "MOD" ))
{
g_pFullFileSystem->CreateDirHierarchy( "save", "MOD" );
}
// save it
if (pSaveKeyValues->SaveToFile(g_pFullFileSystem, szFullFileName))
{
// make sure our save summary list adds this to it, if needed
Msg("New save created: %s\n", szFullFileName);
NotifyNewSave(pszNoPathName);
return true;
}
Msg("Save to file failed. Filename=%s\n", szFullFileName);
return false;
}
// normal alphabetical sorting for map/campaigns
bool CASW_Mission_Chooser_Source_Local::MapNameLess::Less( MapListName const& src1, MapListName const& src2, void *pCtx )
{
return !!Q_strcmp(src1.szMapName,src2.szMapName);
}
// sort by the datetime string
bool CASW_Mission_Chooser_Source_Local::SavedCampaignLess::Less( ASW_Mission_Chooser_Saved_Campaign const& src1, ASW_Mission_Chooser_Saved_Campaign const& src2, void *pCtx )
{
int month, day, year, hour, minute;
int month2, day2, year2, hour2, minute2;
if ( sscanf( src1.m_szDateTime, "%d/%d/%d %d:%d", &month, &day, &year, &hour, &minute ) != 5 )
return false;
if ( sscanf( src2.m_szDateTime, "%d/%d/%d %d:%d", &month2, &day2, &year2, &hour2, &minute2 ) != 5 )
return false;
//Msg("src1 month:%d day:%d year:%d hour:%d minute:%d\n", month, day, year, hour, minute);
//Msg("src2 month:%d day:%d year:%d hour:%d minute:%d\n", month2, day2, year2, hour2, minute2);
if (year > year2)
{
//Msg("src1 is newer because of year\n");
return true;
}
else if (year < year2)
{
//Msg("src2 is newer because of year\n");
return false;
}
if (month > month2)
{
//Msg("src1 is newer because of month\n");
return true;
}
else if (month < month2)
{
//Msg("src2 is newer because of month\n");
return false;
}
if (day > day2)
{
//Msg("src1 is newer because of day\n");
return true;
}
else if (day < day2)
{
//Msg("src2 is newer because of day\n");
return false;
}
if (hour > hour2)
{
//Msg("src1 is newer because of hour\n");
return true;
}
else if (hour < hour2)
{
//Msg("src2 is newer because of hour\n");
return false;
}
if (minute > minute2)
{
//Msg("src1 is newer because of minute\n");
return true;
}
else if (minute < minute2)
{
//Msg("src2 is newer because of minute\n");
return false;
}
//if ((year > year2) || (month > month2) || (day > day2) || (hour > hour2) || (minute > minute2))
//return true;
//Msg("neither is newer\n");
return false;
}
#define ASW_DEFAULT_INTRO_MAP "intro_jacob"
const char* CASW_Mission_Chooser_Source_Local::GetCampaignSaveIntroMap(const char *szSaveName)
{
// check the save file exists
char stripped[MAX_PATH];
V_StripExtension( szSaveName, stripped, MAX_PATH );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "save/%s.campaignsave", stripped );
if (!g_pFullFileSystem->FileExists(tempfile))
return ASW_DEFAULT_INTRO_MAP;
KeyValues *pSaveKeyValues = new KeyValues( szSaveName );
const char* pszCampaign = NULL;
if (pSaveKeyValues->LoadFromFile(g_pFullFileSystem, tempfile))
{
pszCampaign = pSaveKeyValues->GetString("CampaignName");
}
if (!pszCampaign)
{
pSaveKeyValues->deleteThis();
return ASW_DEFAULT_INTRO_MAP;
}
char ctempfile[MAX_PATH];
Q_snprintf( ctempfile, sizeof( ctempfile ), "resource/campaigns/%s.txt", pszCampaign );
if (!g_pFullFileSystem->FileExists(ctempfile)) /// check it exists
{
pSaveKeyValues->deleteThis();
return ASW_DEFAULT_INTRO_MAP;
}
// now read in the campaign txt and find the intro map name
KeyValues *pCampaignKeyValues = new KeyValues( pszCampaign );
if (pCampaignKeyValues->LoadFromFile(g_pFullFileSystem, ctempfile))
{
static char s_introname[128];
Q_strncpy( s_introname, pCampaignKeyValues->GetString("IntroMap"), 128 );
// check we actually got a valid intro map name string
if ( Q_strlen(s_introname) > 5 && !Q_strnicmp( s_introname, "intro", 5 ) )
{
pSaveKeyValues->deleteThis();
pCampaignKeyValues->deleteThis();
return s_introname;
}
}
pSaveKeyValues->deleteThis();
pCampaignKeyValues->deleteThis();
return ASW_DEFAULT_INTRO_MAP;
}
KeyValues *CASW_Mission_Chooser_Source_Local::GetMissionDetails( const char *szMissionName )
{
// see if we have this cached already
for ( int i = 0; i < m_MissionDetails.Count(); i++ )
{
if ( !Q_stricmp( m_MissionDetails[ i ]->szMissionName, szMissionName ) )
{
return m_MissionDetails[ i ]->m_pMissionKeys;
}
}
// strip off the extension
char stripped[MAX_PATH];
V_StripExtension( szMissionName, stripped, MAX_PATH );
KeyValues *pMissionKeys = new KeyValues( stripped );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/overviews/%s.txt", stripped );
bool bNoOverview = false;
if ( !pMissionKeys->LoadFromFile( g_pFullFileSystem, tempfile, "GAME" ) )
{
// try to load it directly from the maps folder
Q_snprintf( tempfile, sizeof( tempfile ), "maps/%s.txt", stripped );
if ( !pMissionKeys->LoadFromFile( g_pFullFileSystem, tempfile, "GAME" ) )
{
bNoOverview = true;
}
}
MissionDetails_t *pDetails = new MissionDetails_t;
pDetails->m_pMissionKeys = pMissionKeys;
Q_snprintf( pDetails->szMissionName, sizeof( pDetails->szMissionName ), "%s", szMissionName );
m_MissionDetails.AddToTail( pDetails );
return pMissionKeys;
}
KeyValues *CASW_Mission_Chooser_Source_Local::GetCampaignDetails( const char *szCampaignName )
{
// see if we have this cached already
for ( int i = 0; i < m_CampaignDetails.Count(); i++ )
{
if ( !Q_stricmp( m_CampaignDetails[ i ]->szCampaignName, szCampaignName ) )
{
return m_CampaignDetails[ i ]->m_pCampaignKeys;
}
}
// strip off the extension
char stripped[MAX_PATH];
V_StripExtension( szCampaignName, stripped, MAX_PATH );
KeyValues *pCampaignKeys = new KeyValues( stripped );
char tempfile[MAX_PATH];
Q_snprintf( tempfile, sizeof( tempfile ), "resource/campaigns/%s.txt", stripped );
bool bNoOverview = false;
if ( !pCampaignKeys->LoadFromFile( g_pFullFileSystem, tempfile, "GAME" ) )
{
// try to load it directly from the maps folder
Q_snprintf( tempfile, sizeof( tempfile ), "maps/%s.txt", stripped );
if ( !pCampaignKeys->LoadFromFile( g_pFullFileSystem, tempfile, "GAME" ) )
{
bNoOverview = true;
}
}
CampaignDetails_t *pDetails = new CampaignDetails_t;
pDetails->m_pCampaignKeys = pCampaignKeys;
Q_snprintf( pDetails->szCampaignName, sizeof( pDetails->szCampaignName ), "%s", szCampaignName );
m_CampaignDetails.AddToTail( pDetails );
return pCampaignKeys;
}