2022-09-18 15:37:21 +00:00
|
|
|
|
/*
|
|
|
|
|
===========================================================================
|
|
|
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
|
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
|
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
|
|
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
|
|
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
|
|
|
under the terms of the GNU General Public License version 2 as
|
|
|
|
|
published by the Free Software Foundation.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
===========================================================================
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Filename:- sv_savegame.cpp
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include "../server/exe_headers.h"
|
|
|
|
|
|
|
|
|
|
#define JPEG_IMAGE_QUALITY 95
|
|
|
|
|
|
2022-11-30 19:23:00 +00:00
|
|
|
|
#define USE_LAST_SAVE_FROM_THIS_MAP // enable this if you want to use the last explicity-loaded savegame from this map
|
2022-09-18 15:37:21 +00:00
|
|
|
|
// when respawning after dying, else it'll just load "auto" regardless
|
|
|
|
|
// (EF1 behaviour). I should maybe time/date check them though?
|
|
|
|
|
|
|
|
|
|
#include "server.h"
|
|
|
|
|
#include "../qcommon/stringed_ingame.h"
|
|
|
|
|
#include "../game/statindex.h"
|
|
|
|
|
#include "../game/weapons.h"
|
|
|
|
|
#include "../game/g_items.h"
|
|
|
|
|
|
|
|
|
|
#include <map>
|
|
|
|
|
|
|
|
|
|
#include "qcommon/ojk_saved_game.h"
|
|
|
|
|
#include "qcommon/ojk_saved_game_helper.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char saveGameComment[iSG_COMMENT_SIZE];
|
|
|
|
|
|
|
|
|
|
//#define SG_PROFILE // enable for debug save stats if you want
|
|
|
|
|
|
|
|
|
|
int giSaveGameVersion; // filled in when a savegame file is opened
|
|
|
|
|
|
|
|
|
|
SavedGameJustLoaded_e eSavedGameJustLoaded = eNO;
|
|
|
|
|
|
|
|
|
|
char sLastSaveFileLoaded[MAX_QPATH]={0};
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
#define iSG_MAPCMD_SIZE (MAX_TOKEN_CHARS)
|
|
|
|
|
#else
|
|
|
|
|
#define iSG_MAPCMD_SIZE (MAX_QPATH)
|
|
|
|
|
#endif // JK2_MODE
|
|
|
|
|
|
|
|
|
|
static char *SG_GetSaveGameMapName(const char *psPathlessBaseName);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SG_PROFILE
|
|
|
|
|
|
|
|
|
|
class CChid
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
int m_iCount;
|
|
|
|
|
int m_iSize;
|
|
|
|
|
public:
|
|
|
|
|
CChid()
|
|
|
|
|
{
|
|
|
|
|
m_iCount = 0;
|
|
|
|
|
m_iSize = 0;
|
|
|
|
|
}
|
|
|
|
|
void Add(int iLength)
|
|
|
|
|
{
|
|
|
|
|
m_iCount++;
|
|
|
|
|
m_iSize += iLength;
|
|
|
|
|
}
|
|
|
|
|
int GetCount()
|
|
|
|
|
{
|
|
|
|
|
return m_iCount;
|
|
|
|
|
}
|
|
|
|
|
int GetSize()
|
|
|
|
|
{
|
|
|
|
|
return m_iSize;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef map<unsigned int, CChid> CChidInfo_t;
|
|
|
|
|
CChidInfo_t save_info;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static const char *GetString_FailedToOpenSaveGame(const char *psFilename, qboolean bOpen)
|
|
|
|
|
{
|
|
|
|
|
static char sTemp[256];
|
|
|
|
|
|
|
|
|
|
strcpy(sTemp,S_COLOR_RED);
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
const char *psReference = bOpen ? "MENUS3_FAILED_TO_OPEN_SAVEGAME" : "MENUS3_FAILED_TO_CREATE_SAVEGAME";
|
|
|
|
|
#else
|
|
|
|
|
const char *psReference = bOpen ? "MENUS_FAILED_TO_OPEN_SAVEGAME" : "MENUS3_FAILED_TO_CREATE_SAVEGAME";
|
|
|
|
|
#endif
|
|
|
|
|
Q_strncpyz(sTemp + strlen(sTemp), va( SE_GetString(psReference), psFilename),sizeof(sTemp));
|
|
|
|
|
strcat(sTemp,"\n");
|
|
|
|
|
return sTemp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SG_WipeSavegame(
|
|
|
|
|
const char* psPathlessBaseName)
|
|
|
|
|
{
|
|
|
|
|
ojk::SavedGame::remove(
|
|
|
|
|
psPathlessBaseName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// called from the ERR_DROP stuff just in case the error occured during loading of a saved game, because if
|
|
|
|
|
// we didn't do this then we'd run out of quake file handles after the 8th load fail...
|
|
|
|
|
//
|
|
|
|
|
void SG_Shutdown()
|
|
|
|
|
{
|
|
|
|
|
ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
|
|
|
|
|
|
|
|
|
|
saved_game.close();
|
|
|
|
|
|
|
|
|
|
eSavedGameJustLoaded = eNO;
|
|
|
|
|
// important to do this if we ERR_DROP during loading, else next map you load after
|
|
|
|
|
// a bad save-file you'll arrive at dead :-)
|
|
|
|
|
|
|
|
|
|
// and this bit stops people messing up the laoder by repeatedly stabbing at the load key during loads...
|
|
|
|
|
//
|
|
|
|
|
extern qboolean gbAlreadyDoingLoad;
|
|
|
|
|
gbAlreadyDoingLoad = qfalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SV_WipeGame_f(void)
|
|
|
|
|
{
|
|
|
|
|
if (Cmd_Argc() != 2)
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "USAGE: wipe <name>\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!Q_stricmp (Cmd_Argv(1), "auto") )
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "Can't wipe 'auto'\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
SG_WipeSavegame(Cmd_Argv(1));
|
2022-11-30 19:23:00 +00:00
|
|
|
|
// Com_Printf("%s has been wiped\n", Cmd_Argv(1)); // wurde gel<65>scht in german, but we've only got one string
|
2022-09-18 15:37:21 +00:00
|
|
|
|
// Com_Printf("Ok\n"); // no localization of this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// Store given string in saveGameComment for later use when game is
|
|
|
|
|
// actually saved
|
|
|
|
|
*/
|
|
|
|
|
void SG_StoreSaveGameComment(const char *sComment)
|
|
|
|
|
{
|
|
|
|
|
memmove(saveGameComment,sComment,iSG_COMMENT_SIZE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean SV_TryLoadTransition( const char *mapname )
|
|
|
|
|
{
|
|
|
|
|
char *psFilename = va( "hub/%s", mapname );
|
|
|
|
|
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "Restoring game \"%s\"...\n", psFilename);
|
|
|
|
|
|
|
|
|
|
if ( !SG_ReadSavegame( psFilename ) )
|
|
|
|
|
{//couldn't load a savegame
|
|
|
|
|
return qfalse;
|
|
|
|
|
}
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "Done.\n");
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE"));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return qtrue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean gbAlreadyDoingLoad = qfalse;
|
|
|
|
|
void SV_LoadGame_f(void)
|
|
|
|
|
{
|
|
|
|
|
if (gbAlreadyDoingLoad)
|
|
|
|
|
{
|
|
|
|
|
Com_DPrintf ("( Already loading, ignoring extra 'load' commands... )\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// // check server is running
|
|
|
|
|
// //
|
|
|
|
|
// if ( !com_sv_running->integer )
|
|
|
|
|
// {
|
|
|
|
|
// Com_Printf( "Server is not running\n" );
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
if (Cmd_Argc() != 2)
|
|
|
|
|
{
|
|
|
|
|
Com_Printf ("USAGE: load <filename>\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *psFilename = Cmd_Argv(1);
|
|
|
|
|
if (strstr (psFilename, "..") || strstr (psFilename, "/") || strstr (psFilename, "\\") )
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "Bad loadgame name.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Q_stricmp (psFilename, "current"))
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "Can't load from \"current\"\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// special case, if doing a respawn then check that the available auto-save (if any) is from the same map
|
|
|
|
|
// as we're currently on (if in a map at all), if so, load that "auto", else re-load the last-loaded file...
|
|
|
|
|
//
|
|
|
|
|
if (!Q_stricmp(psFilename, "*respawn"))
|
|
|
|
|
{
|
|
|
|
|
psFilename = "auto"; // default to standard respawn behaviour
|
|
|
|
|
|
|
|
|
|
// see if there's a last-loaded file to even check against as regards loading...
|
|
|
|
|
//
|
|
|
|
|
if ( sLastSaveFileLoaded[0] )
|
|
|
|
|
{
|
|
|
|
|
const char *psServerInfo = sv.configstrings[CS_SERVERINFO];
|
|
|
|
|
const char *psMapName = Info_ValueForKey( psServerInfo, "mapname" );
|
|
|
|
|
|
|
|
|
|
char *psMapNameOfAutoSave = SG_GetSaveGameMapName("auto");
|
|
|
|
|
|
|
|
|
|
if ( !Q_stricmp(psMapName,"_brig") )
|
|
|
|
|
{//if you're in the brig and there is no autosave, load the last loaded savegame
|
|
|
|
|
if ( !psMapNameOfAutoSave )
|
|
|
|
|
{
|
|
|
|
|
psFilename = sLastSaveFileLoaded;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
#ifdef USE_LAST_SAVE_FROM_THIS_MAP
|
|
|
|
|
// if the map name within the name of the last save file we explicitly loaded is the same
|
|
|
|
|
// as the current map, then use that...
|
|
|
|
|
//
|
|
|
|
|
char *psMapNameOfLastSaveFileLoaded = SG_GetSaveGameMapName(sLastSaveFileLoaded);
|
|
|
|
|
|
2022-11-30 19:23:00 +00:00
|
|
|
|
if (!Q_stricmp(psMapName,psMapNameOfLastSaveFileLoaded))
|
2022-09-18 15:37:21 +00:00
|
|
|
|
{
|
|
|
|
|
psFilename = sLastSaveFileLoaded;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
if (!(psMapName && psMapNameOfAutoSave && !Q_stricmp(psMapName,psMapNameOfAutoSave)))
|
|
|
|
|
{
|
|
|
|
|
// either there's no auto file, or it's from a different map to the one we've just died on...
|
|
|
|
|
//
|
|
|
|
|
psFilename = sLastSaveFileLoaded;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//default will continue to load auto
|
|
|
|
|
}
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "Loading game \"%s\"...\n", psFilename);
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "%s\n",va(SE_GetString("MENUS_LOADING_MAPNAME"), psFilename));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
gbAlreadyDoingLoad = qtrue;
|
|
|
|
|
if (!SG_ReadSavegame(psFilename)) {
|
|
|
|
|
gbAlreadyDoingLoad = qfalse; // do NOT do this here now, need to wait until client spawn, unless the load failed.
|
|
|
|
|
} else
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "Done.\n");
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE"));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean SG_GameAllowedToSaveHere(qboolean inCamera);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//JLF notes
|
|
|
|
|
// save game will be in charge of creating a new directory
|
|
|
|
|
void SV_SaveGame_f(void)
|
|
|
|
|
{
|
|
|
|
|
// check server is running
|
|
|
|
|
//
|
|
|
|
|
if ( !com_sv_running->integer )
|
|
|
|
|
{
|
|
|
|
|
Com_Printf( S_COLOR_RED "Server is not running\n" );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sv.state != SS_GAME)
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "You must be in a game to save.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check args...
|
|
|
|
|
//
|
|
|
|
|
if ( Cmd_Argc() != 2 )
|
|
|
|
|
{
|
|
|
|
|
Com_Printf( "USAGE: save <filename>\n" );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0)
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n");
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_RED "\n%s\n", SE_GetString("SP_INGAME_CANT_SAVE_DEAD"));
|
|
|
|
|
#endif
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//this check catches deaths even the instant you die, like during a slo-mo death!
|
|
|
|
|
gentity_t *svent;
|
|
|
|
|
svent = SV_GentityNum(0);
|
|
|
|
|
if (svent->client->stats[STAT_HEALTH]<=0)
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n");
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_RED "\n%s\n", SE_GetString("SP_INGAME_CANT_SAVE_DEAD"));
|
|
|
|
|
#endif
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *psFilename = Cmd_Argv(1);
|
|
|
|
|
char filename[MAX_QPATH] = {0};
|
|
|
|
|
|
|
|
|
|
Q_strncpyz(filename, psFilename, sizeof(filename));
|
|
|
|
|
|
|
|
|
|
if (!Q_stricmp (filename, "current"))
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "Can't save to 'current'\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strstr (filename, "..") || strstr (filename, "/") || strstr (filename, "\\") )
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (S_COLOR_RED "Bad savegame name.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!SG_GameAllowedToSaveHere(qfalse)) //full check
|
|
|
|
|
return; // this prevents people saving via quick-save now during cinematics.
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
if ( !Q_stricmp (filename, "quik*") || !Q_stricmp (filename, "auto*") )
|
|
|
|
|
{
|
|
|
|
|
SCR_PrecacheScreenshot();
|
|
|
|
|
if ( filename[4]=='*' )
|
|
|
|
|
filename[4]=0; //remove the *
|
|
|
|
|
SG_StoreSaveGameComment(""); // clear previous comment/description, which will force time/date comment.
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
if ( !Q_stricmp (filename, "auto") )
|
|
|
|
|
{
|
|
|
|
|
SG_StoreSaveGameComment(""); // clear previous comment/description, which will force time/date comment.
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "Saving game \"%s\"...\n", filename);
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "%s \"%s\"...\n", SE_GetString("CON_TEXT_SAVING_GAME"), filename);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (SG_WriteSavegame(filename, qfalse))
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "Done.\n");
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE"));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Com_Printf (S_COLOR_RED "Failed.\n");
|
|
|
|
|
#else
|
|
|
|
|
Com_Printf (S_COLOR_RED "%s.\n",SE_GetString("MENUS_FAILED_TO_OPEN_SAVEGAME"));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//---------------
|
|
|
|
|
static void WriteGame(qboolean autosave)
|
|
|
|
|
{
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk<int32_t>(
|
|
|
|
|
INT_ID('G', 'A', 'M', 'E'),
|
|
|
|
|
autosave);
|
|
|
|
|
|
|
|
|
|
if (autosave)
|
|
|
|
|
{
|
|
|
|
|
// write out player ammo level, health, etc...
|
|
|
|
|
//
|
|
|
|
|
extern void SV_Player_EndOfLevelSave(void);
|
|
|
|
|
SV_Player_EndOfLevelSave(); // this sets up the various cvars needed, so we can then write them to disk
|
|
|
|
|
//
|
|
|
|
|
char s[MAX_STRING_CHARS];
|
|
|
|
|
|
|
|
|
|
// write health/armour etc...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) );
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('C', 'V', 'S', 'V'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
// write ammo...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
Cvar_VariableStringBuffer( "playerammo", s, sizeof(s) );
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('A', 'M', 'M', 'O'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
// write inventory...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
Cvar_VariableStringBuffer( "playerinv", s, sizeof(s) );
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('I', 'V', 'T', 'Y'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
// the new JK2 stuff - force powers, etc...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
Cvar_VariableStringBuffer( "playerfplvl", s, sizeof(s) );
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('F', 'P', 'L', 'V'),
|
|
|
|
|
s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static qboolean ReadGame (void)
|
|
|
|
|
{
|
|
|
|
|
qboolean qbAutoSave = qfalse;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk<int32_t>(
|
|
|
|
|
INT_ID('G', 'A', 'M', 'E'),
|
|
|
|
|
qbAutoSave);
|
|
|
|
|
|
|
|
|
|
if (qbAutoSave)
|
|
|
|
|
{
|
|
|
|
|
char s[MAX_STRING_CHARS]={0};
|
|
|
|
|
|
|
|
|
|
// read health/armour etc...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('C', 'V', 'S', 'V'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
Cvar_Set( sCVARNAME_PLAYERSAVE, s );
|
|
|
|
|
|
|
|
|
|
// read ammo...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('A', 'M', 'M', 'O'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
Cvar_Set( "playerammo", s);
|
|
|
|
|
|
|
|
|
|
// read inventory...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('I', 'V', 'T', 'Y'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
Cvar_Set( "playerinv", s);
|
|
|
|
|
|
|
|
|
|
// read force powers...
|
|
|
|
|
//
|
|
|
|
|
memset(s,0,sizeof(s));
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('F', 'P', 'L', 'V'),
|
|
|
|
|
s);
|
|
|
|
|
|
|
|
|
|
Cvar_Set( "playerfplvl", s );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return qbAutoSave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//---------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// write all CVAR_SAVEGAME cvars
|
|
|
|
|
// these will be things like model, name, ...
|
|
|
|
|
//
|
|
|
|
|
extern cvar_t *cvar_vars; // I know this is really unpleasant, but I need access for scanning/writing latched cvars during save games
|
|
|
|
|
|
|
|
|
|
void SG_WriteCvars(void)
|
|
|
|
|
{
|
|
|
|
|
cvar_t *var;
|
|
|
|
|
int iCount = 0;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
// count the cvars...
|
|
|
|
|
//
|
|
|
|
|
for (var = cvar_vars; var; var = var->next)
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
if (!(var->flags & (CVAR_SAVEGAME|CVAR_USERINFO)))
|
|
|
|
|
#else
|
|
|
|
|
if (!(var->flags & CVAR_SAVEGAME))
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
iCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// store count...
|
|
|
|
|
//
|
|
|
|
|
saved_game.write_chunk<int32_t>(
|
|
|
|
|
INT_ID('C', 'V', 'C', 'N'),
|
|
|
|
|
iCount);
|
|
|
|
|
|
|
|
|
|
// write 'em...
|
|
|
|
|
//
|
|
|
|
|
for (var = cvar_vars; var; var = var->next)
|
|
|
|
|
{
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
if (!(var->flags & (CVAR_SAVEGAME|CVAR_USERINFO)))
|
|
|
|
|
#else
|
|
|
|
|
if (!(var->flags & CVAR_SAVEGAME))
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('C', 'V', 'A', 'R'),
|
|
|
|
|
var->name,
|
|
|
|
|
static_cast<int>(strlen(var->name) + 1));
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('V', 'A', 'L', 'U'),
|
|
|
|
|
var->string,
|
|
|
|
|
static_cast<int>(strlen(var->string) + 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SG_ReadCvars()
|
|
|
|
|
{
|
|
|
|
|
int iCount = 0;
|
|
|
|
|
std::string psName;
|
|
|
|
|
const char* psValue;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk<int32_t>(
|
|
|
|
|
INT_ID('C', 'V', 'C', 'N'),
|
|
|
|
|
iCount);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < iCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('C', 'V', 'A', 'R'));
|
|
|
|
|
|
|
|
|
|
psName = reinterpret_cast<const char*>(
|
|
|
|
|
saved_game.get_buffer_data());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('V', 'A', 'L', 'U'));
|
|
|
|
|
|
|
|
|
|
psValue = reinterpret_cast<const char*>(
|
|
|
|
|
saved_game.get_buffer_data());
|
|
|
|
|
|
|
|
|
|
::Cvar_Set(psName.c_str(), psValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SG_WriteServerConfigStrings()
|
|
|
|
|
{
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
int iCount = 0;
|
|
|
|
|
int i; // not in FOR statement in case compiler goes weird by reg-optimising it then failing to get the address later
|
|
|
|
|
|
|
|
|
|
// count how many non-blank server strings there are...
|
|
|
|
|
//
|
|
|
|
|
for ( i=0; i<MAX_CONFIGSTRINGS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i!=CS_SYSTEMINFO)
|
|
|
|
|
{
|
|
|
|
|
if (sv.configstrings[i] && strlen(sv.configstrings[i])) // paranoia... <g>
|
|
|
|
|
{
|
|
|
|
|
iCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk<int32_t>(
|
|
|
|
|
INT_ID('C', 'S', 'C', 'N'),
|
|
|
|
|
iCount);
|
|
|
|
|
|
|
|
|
|
// now write 'em...
|
|
|
|
|
//
|
|
|
|
|
for (i=0; i<MAX_CONFIGSTRINGS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i!=CS_SYSTEMINFO)
|
|
|
|
|
{
|
|
|
|
|
if (sv.configstrings[i] && strlen(sv.configstrings[i]))
|
|
|
|
|
{
|
|
|
|
|
saved_game.write_chunk<int32_t>(
|
|
|
|
|
INT_ID('C', 'S', 'I', 'N'),
|
|
|
|
|
i);
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('C', 'S', 'D', 'A'),
|
|
|
|
|
::sv.configstrings[i],
|
|
|
|
|
static_cast<int>(strlen(::sv.configstrings[i]) + 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SG_ReadServerConfigStrings( void )
|
|
|
|
|
{
|
|
|
|
|
// trash the whole table...
|
|
|
|
|
//
|
|
|
|
|
for (int i=0; i<MAX_CONFIGSTRINGS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i!=CS_SYSTEMINFO)
|
|
|
|
|
{
|
|
|
|
|
if ( sv.configstrings[i] )
|
|
|
|
|
{
|
|
|
|
|
Z_Free( sv.configstrings[i] );
|
|
|
|
|
}
|
|
|
|
|
sv.configstrings[i] = CopyString("");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now read the replacement ones...
|
|
|
|
|
//
|
|
|
|
|
int iCount = 0;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk<int32_t>(
|
|
|
|
|
INT_ID('C', 'S', 'C', 'N'),
|
|
|
|
|
iCount);
|
|
|
|
|
|
|
|
|
|
Com_DPrintf( "Reading %d configstrings...\n",iCount);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i<iCount; i++)
|
|
|
|
|
{
|
|
|
|
|
int iIndex = 0;
|
|
|
|
|
const char *psName;
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk<int32_t>(
|
|
|
|
|
INT_ID('C', 'S', 'I', 'N'),
|
|
|
|
|
iIndex);
|
|
|
|
|
|
|
|
|
|
saved_game.read_chunk(
|
|
|
|
|
INT_ID('C', 'S', 'D', 'A'));
|
|
|
|
|
|
|
|
|
|
psName = reinterpret_cast<const char*>(
|
|
|
|
|
saved_game.get_buffer_data());
|
|
|
|
|
|
|
|
|
|
Com_DPrintf( "Cfg str %d = %s\n",iIndex, psName);
|
|
|
|
|
|
|
|
|
|
//sv.configstrings[iIndex] = psName;
|
|
|
|
|
SV_SetConfigstring(iIndex, psName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned int SG_UnixTimestamp ( const time_t& t )
|
|
|
|
|
{
|
|
|
|
|
return static_cast<unsigned int>(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SG_WriteComment(qboolean qbAutosave, const char *psMapName)
|
|
|
|
|
{
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
char sComment[iSG_COMMENT_SIZE];
|
|
|
|
|
|
|
|
|
|
if ( qbAutosave || !*saveGameComment)
|
|
|
|
|
{
|
|
|
|
|
Com_sprintf( sComment, sizeof(sComment), "---> %s", psMapName );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Q_strncpyz(sComment,saveGameComment, sizeof(sComment));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('C', 'O', 'M', 'M'),
|
|
|
|
|
sComment);
|
|
|
|
|
|
|
|
|
|
// Add Date/Time/Map stamp
|
|
|
|
|
unsigned int timestamp = SG_UnixTimestamp (time (NULL));
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk<uint32_t>(
|
|
|
|
|
INT_ID('C', 'M', 'T', 'M'),
|
|
|
|
|
timestamp);
|
|
|
|
|
|
|
|
|
|
Com_DPrintf("Saving: current (%s)\n", sComment);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static time_t SG_GetTime ( unsigned int timestamp )
|
|
|
|
|
{
|
|
|
|
|
return static_cast<time_t>(timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test to see if the given file name is in the save game directory
|
|
|
|
|
// then grab the comment if it's there
|
|
|
|
|
//
|
|
|
|
|
int SG_GetSaveGameComment(
|
|
|
|
|
const char* psPathlessBaseName,
|
|
|
|
|
char* sComment,
|
|
|
|
|
char* sMapName)
|
|
|
|
|
{
|
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper sgh(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
if (!saved_game.open(
|
|
|
|
|
psPathlessBaseName))
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_succeed = true;
|
|
|
|
|
|
|
|
|
|
// Read description
|
|
|
|
|
//
|
|
|
|
|
is_succeed = sgh.try_read_chunk(
|
|
|
|
|
INT_ID('C', 'O', 'M', 'M'));
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
if (sComment)
|
|
|
|
|
{
|
|
|
|
|
if (sgh.get_buffer_size() == iSG_COMMENT_SIZE)
|
|
|
|
|
{
|
|
|
|
|
std::uninitialized_copy_n(
|
|
|
|
|
static_cast<const char*>(sgh.get_buffer_data()),
|
|
|
|
|
iSG_COMMENT_SIZE,
|
|
|
|
|
sComment);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sComment[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read timestamp
|
|
|
|
|
//
|
|
|
|
|
time_t tFileTime = ::SG_GetTime(0);
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
unsigned int fileTime = 0;
|
|
|
|
|
|
|
|
|
|
is_succeed = sgh.try_read_chunk<uint32_t>(
|
|
|
|
|
INT_ID('C', 'M', 'T', 'M'),
|
|
|
|
|
fileTime);
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
tFileTime = ::SG_GetTime(
|
|
|
|
|
fileTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
// Read screenshot
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
size_t iScreenShotLength;
|
|
|
|
|
|
|
|
|
|
is_succeed = sgh.try_read_chunk<uint32_t>(
|
|
|
|
|
INT_ID('S', 'H', 'L', 'N'),
|
|
|
|
|
iScreenShotLength);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
is_succeed = sgh.try_read_chunk(
|
|
|
|
|
INT_ID('S', 'H', 'O', 'T'));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Read mapname
|
|
|
|
|
//
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
is_succeed = sgh.try_read_chunk(
|
|
|
|
|
INT_ID('M', 'P', 'C', 'M'));
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
if (sMapName)
|
|
|
|
|
{
|
|
|
|
|
if (sgh.get_buffer_size() == iSG_MAPCMD_SIZE)
|
|
|
|
|
{
|
|
|
|
|
std::uninitialized_copy_n(
|
|
|
|
|
static_cast<const char*>(sgh.get_buffer_data()),
|
|
|
|
|
iSG_MAPCMD_SIZE,
|
|
|
|
|
sMapName);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sMapName[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = tFileTime;
|
|
|
|
|
|
|
|
|
|
saved_game.close();
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// read the mapname field from the supplied savegame file
|
|
|
|
|
//
|
|
|
|
|
// returns NULL if not found
|
|
|
|
|
//
|
|
|
|
|
static char *SG_GetSaveGameMapName(const char *psPathlessBaseName)
|
|
|
|
|
{
|
|
|
|
|
static char sMapName[iSG_MAPCMD_SIZE]={0};
|
|
|
|
|
char *psReturn = NULL;
|
|
|
|
|
if (SG_GetSaveGameComment(psPathlessBaseName, NULL, sMapName))
|
|
|
|
|
{
|
|
|
|
|
psReturn = sMapName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return psReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// pass in qtrue to set as loading screen, else pass in pvDest to read it into there...
|
|
|
|
|
//
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
static bool SG_ReadScreenshot(
|
|
|
|
|
bool set_as_loading_screen,
|
|
|
|
|
void* screenshot_ptr)
|
|
|
|
|
{
|
|
|
|
|
bool is_succeed = true;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
// get JPG screenshot data length...
|
|
|
|
|
//
|
|
|
|
|
size_t screenshot_length = 0;
|
|
|
|
|
|
|
|
|
|
is_succeed = saved_game.try_read_chunk<uint32_t>(
|
|
|
|
|
INT_ID('S', 'H', 'L', 'N'),
|
|
|
|
|
screenshot_length);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// alloc enough space plus extra 4K for sloppy JPG-decode reader to not do memory access violation...
|
|
|
|
|
//
|
|
|
|
|
byte* jpeg_data = nullptr;
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
jpeg_data = static_cast<byte*>(::Z_Malloc(
|
|
|
|
|
static_cast<int>(screenshot_length + 4096),
|
|
|
|
|
TAG_TEMP_WORKSPACE,
|
|
|
|
|
qfalse));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// now read the JPG data...
|
|
|
|
|
//
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
is_succeed = saved_game.try_read_chunk(
|
|
|
|
|
INT_ID('S', 'H', 'O', 'T'),
|
|
|
|
|
jpeg_data,
|
|
|
|
|
static_cast<int>(screenshot_length));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// decompress JPG data...
|
|
|
|
|
//
|
|
|
|
|
byte* image = NULL;
|
|
|
|
|
int width;
|
|
|
|
|
int height;
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
::re.LoadJPGFromBuffer(
|
|
|
|
|
jpeg_data,
|
|
|
|
|
screenshot_length,
|
|
|
|
|
&image,
|
|
|
|
|
&width,
|
|
|
|
|
&height);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// if the loaded image is the same size as the game is expecting, then copy it to supplied arg (if present)...
|
|
|
|
|
//
|
|
|
|
|
if (width == SG_SCR_WIDTH && height == SG_SCR_HEIGHT)
|
|
|
|
|
{
|
|
|
|
|
if (screenshot_ptr)
|
|
|
|
|
{
|
|
|
|
|
::memcpy(
|
|
|
|
|
screenshot_ptr,
|
|
|
|
|
image,
|
|
|
|
|
SG_SCR_WIDTH * SG_SCR_HEIGHT * 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (set_as_loading_screen)
|
|
|
|
|
{
|
|
|
|
|
::SCR_SetScreenshot(
|
|
|
|
|
image,
|
|
|
|
|
SG_SCR_WIDTH,
|
|
|
|
|
SG_SCR_HEIGHT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
is_succeed = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (jpeg_data)
|
|
|
|
|
{
|
|
|
|
|
::Z_Free(jpeg_data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (image)
|
|
|
|
|
{
|
|
|
|
|
::Z_Free(image);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return is_succeed;
|
|
|
|
|
}
|
|
|
|
|
// Gets the savegame screenshot
|
|
|
|
|
//
|
|
|
|
|
qboolean SG_GetSaveImage(
|
|
|
|
|
const char* base_name,
|
|
|
|
|
void* image_ptr)
|
|
|
|
|
{
|
|
|
|
|
if (!base_name)
|
|
|
|
|
{
|
|
|
|
|
return qfalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
|
|
|
|
|
|
|
|
|
|
if (!saved_game.open(base_name))
|
|
|
|
|
{
|
|
|
|
|
return qfalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_succeed = true;
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper sgh(
|
|
|
|
|
&saved_game);
|
|
|
|
|
|
|
|
|
|
is_succeed = sgh.try_read_chunk(
|
|
|
|
|
INT_ID('C', 'O', 'M', 'M'));
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
is_succeed = sgh.try_read_chunk(
|
|
|
|
|
INT_ID('C', 'M', 'T', 'M'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_succeed)
|
|
|
|
|
{
|
|
|
|
|
is_succeed = SG_ReadScreenshot(
|
|
|
|
|
false,
|
|
|
|
|
image_ptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saved_game.close();
|
|
|
|
|
|
|
|
|
|
return is_succeed ? qtrue : qfalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void SG_WriteScreenshot(qboolean qbAutosave, const char *psMapName)
|
|
|
|
|
{
|
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
|
|
|
&ojk::SavedGame::get_instance());
|
|
|
|
|
|
|
|
|
|
byte *pbRawScreenShot = NULL;
|
|
|
|
|
byte *byBlank = NULL;
|
|
|
|
|
|
|
|
|
|
if( qbAutosave )
|
|
|
|
|
{
|
|
|
|
|
// try to read a levelshot (any valid TGA/JPG etc named the same as the map)...
|
|
|
|
|
//
|
|
|
|
|
int iWidth = SG_SCR_WIDTH;
|
|
|
|
|
int iHeight= SG_SCR_HEIGHT;
|
|
|
|
|
const size_t bySize = SG_SCR_WIDTH * SG_SCR_HEIGHT * 4;
|
|
|
|
|
byte *src, *dst;
|
|
|
|
|
|
|
|
|
|
byBlank = new byte[bySize];
|
|
|
|
|
pbRawScreenShot = SCR_TempRawImage_ReadFromFile(va("levelshots/%s.tga",psMapName), &iWidth, &iHeight, byBlank, qtrue); // qtrue = vert flip
|
|
|
|
|
|
|
|
|
|
if (pbRawScreenShot)
|
|
|
|
|
{
|
|
|
|
|
for (int y = 0; y < iHeight; y++)
|
|
|
|
|
{
|
|
|
|
|
for (int x = 0; x < iWidth; x++)
|
|
|
|
|
{
|
|
|
|
|
src = pbRawScreenShot + 4 * (y * iWidth + x);
|
|
|
|
|
dst = pbRawScreenShot + 3 * (y * iWidth + x);
|
|
|
|
|
dst[0] = src[0];
|
|
|
|
|
dst[1] = src[1];
|
|
|
|
|
dst[2] = src[2];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!pbRawScreenShot)
|
|
|
|
|
{
|
|
|
|
|
pbRawScreenShot = SCR_GetScreenshot(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t iJPGDataSize = 0;
|
|
|
|
|
size_t bufSize = SG_SCR_WIDTH * SG_SCR_HEIGHT * 3;
|
|
|
|
|
byte *pJPGData = (byte *)Z_Malloc( static_cast<int>(bufSize), TAG_TEMP_WORKSPACE, qfalse, 4 );
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
bool flip_vertical = true;
|
|
|
|
|
#else
|
|
|
|
|
bool flip_vertical = false;
|
|
|
|
|
#endif // JK2_MODE
|
|
|
|
|
|
|
|
|
|
iJPGDataSize = re.SaveJPGToBuffer(pJPGData, bufSize, JPEG_IMAGE_QUALITY, SG_SCR_WIDTH, SG_SCR_HEIGHT, pbRawScreenShot, 0, flip_vertical );
|
|
|
|
|
if ( qbAutosave )
|
|
|
|
|
delete[] byBlank;
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk<uint32_t>(
|
|
|
|
|
INT_ID('S', 'H', 'L', 'N'),
|
|
|
|
|
iJPGDataSize);
|
|
|
|
|
|
|
|
|
|
saved_game.write_chunk(
|
|
|
|
|
INT_ID('S', 'H', 'O', 'T'),
|
|
|
|
|
pJPGData,
|
|
|
|
|
static_cast<int>(iJPGDataSize));
|
|
|
|
|
|
|
|
|
|
Z_Free(pJPGData);
|
|
|
|
|
SCR_TempRawImage_CleanUp();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
qboolean SG_GameAllowedToSaveHere(qboolean inCamera)
|
|
|
|
|
{
|
|
|
|
|
if (!inCamera) {
|
|
|
|
|
if ( !com_sv_running || !com_sv_running->integer )
|
|
|
|
|
{
|
|
|
|
|
return qfalse; // Com_Printf( S_COLOR_RED "Server is not running\n" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CL_IsRunningInGameCinematic())
|
|
|
|
|
{
|
|
|
|
|
return qfalse; //nope, not during a video
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sv.state != SS_GAME)
|
|
|
|
|
{
|
|
|
|
|
return qfalse; // Com_Printf (S_COLOR_RED "You must be in a game to save.\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//No savegames from "_" maps
|
|
|
|
|
if ( !sv_mapname || (sv_mapname->string != NULL && sv_mapname->string[0] == '_') )
|
|
|
|
|
{
|
|
|
|
|
return qfalse; // Com_Printf (S_COLOR_RED "Cannot save on holodeck or brig.\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0)
|
|
|
|
|
{
|
|
|
|
|
return qfalse; // Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!ge)
|
|
|
|
|
return inCamera; // only happens when called to test if inCamera
|
|
|
|
|
|
|
|
|
|
return ge->GameAllowedToSaveHere();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean SG_WriteSavegame(const char *psPathlessBaseName, qboolean qbAutosave)
|
|
|
|
|
{
|
|
|
|
|
if (!qbAutosave && !SG_GameAllowedToSaveHere(qfalse)) //full check
|
|
|
|
|
return qfalse; // this prevents people saving via quick-save now during cinematics
|
|
|
|
|
|
|
|
|
|
int iPrevTestSave = sv_testsave->integer;
|
|
|
|
|
sv_testsave->integer = 0;
|
|
|
|
|
|
|
|
|
|
// Write out server data...
|
|
|
|
|
//
|
|
|
|
|
const char *psServerInfo = sv.configstrings[CS_SERVERINFO];
|
|
|
|
|
const char *psMapName = Info_ValueForKey( psServerInfo, "mapname" );
|
|
|
|
|
//JLF
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
if ( !strcmp("quik",psPathlessBaseName))
|
|
|
|
|
#else
|
|
|
|
|
if ( !strcmp("quick",psPathlessBaseName))
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
SG_StoreSaveGameComment(va("--> %s <--",psMapName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
|
|
|
|
|
|
|
|
|
|
if(!saved_game.create( "current" ))
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (GetString_FailedToOpenSaveGame("current",qfalse));//S_COLOR_RED "Failed to create savegame\n");
|
|
|
|
|
SG_WipeSavegame( "current" );
|
|
|
|
|
sv_testsave->integer = iPrevTestSave;
|
|
|
|
|
return qfalse;
|
|
|
|
|
}
|
|
|
|
|
//END JLF
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper sgh(
|
|
|
|
|
&saved_game);
|
|
|
|
|
|
|
|
|
|
char sMapCmd[iSG_MAPCMD_SIZE]={0};
|
|
|
|
|
Q_strncpyz( sMapCmd,psMapName, sizeof(sMapCmd)); // need as array rather than ptr because const strlen needed for MPCM chunk
|
|
|
|
|
|
|
|
|
|
SG_WriteComment(qbAutosave, sMapCmd);
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
SG_WriteScreenshot(qbAutosave, sMapCmd);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
sgh.write_chunk(
|
|
|
|
|
INT_ID('M', 'P', 'C', 'M'),
|
|
|
|
|
sMapCmd);
|
|
|
|
|
|
|
|
|
|
SG_WriteCvars();
|
|
|
|
|
|
|
|
|
|
WriteGame (qbAutosave);
|
|
|
|
|
|
|
|
|
|
// Write out all the level data...
|
|
|
|
|
//
|
|
|
|
|
if (!qbAutosave)
|
|
|
|
|
{
|
|
|
|
|
sgh.write_chunk<int32_t>(
|
|
|
|
|
INT_ID('T', 'I', 'M', 'E'),
|
|
|
|
|
::sv.time);
|
|
|
|
|
|
|
|
|
|
sgh.write_chunk<int32_t>(
|
|
|
|
|
INT_ID('T', 'I', 'M', 'R'),
|
|
|
|
|
::sv.timeResidual);
|
|
|
|
|
|
|
|
|
|
CM_WritePortalState();
|
|
|
|
|
SG_WriteServerConfigStrings();
|
|
|
|
|
}
|
|
|
|
|
ge->WriteLevel(qbAutosave); // always done now, but ent saver only does player if auto
|
|
|
|
|
|
|
|
|
|
bool is_write_failed = saved_game.is_failed();
|
|
|
|
|
|
|
|
|
|
saved_game.close();
|
|
|
|
|
|
|
|
|
|
if (is_write_failed)
|
|
|
|
|
{
|
|
|
|
|
Com_Printf (GetString_FailedToOpenSaveGame("current",qfalse));//S_COLOR_RED "Failed to write savegame!\n");
|
|
|
|
|
SG_WipeSavegame( "current" );
|
|
|
|
|
sv_testsave->integer = iPrevTestSave;
|
|
|
|
|
return qfalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ojk::SavedGame::rename(
|
|
|
|
|
"current",
|
|
|
|
|
psPathlessBaseName);
|
|
|
|
|
|
|
|
|
|
sv_testsave->integer = iPrevTestSave;
|
2022-11-30 19:23:00 +00:00
|
|
|
|
// Store current save as "last loaded" so that it is loaded after respawn instead of
|
|
|
|
|
// actually last loaded save (or checkpoint if load was not done since start of level)
|
|
|
|
|
::Q_strncpyz(::sLastSaveFileLoaded, psPathlessBaseName, sizeof(sLastSaveFileLoaded));
|
2022-09-18 15:37:21 +00:00
|
|
|
|
return qtrue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean SG_ReadSavegame(
|
|
|
|
|
const char* psPathlessBaseName)
|
|
|
|
|
{
|
|
|
|
|
char sComment[iSG_COMMENT_SIZE];
|
|
|
|
|
char sMapCmd[iSG_MAPCMD_SIZE];
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
Cvar_Set(
|
|
|
|
|
"cg_missionstatusscreen",
|
|
|
|
|
"0");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
|
|
|
|
|
|
|
|
|
|
ojk::SavedGameHelper sgh(
|
|
|
|
|
&saved_game);
|
|
|
|
|
|
|
|
|
|
const int iPrevTestSave = ::sv_testsave->integer;
|
|
|
|
|
|
|
|
|
|
ojk::ScopeGuard scope_guard(
|
|
|
|
|
[&]()
|
|
|
|
|
{
|
|
|
|
|
::sv_testsave->integer = 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
[&]()
|
|
|
|
|
{
|
|
|
|
|
saved_game.close();
|
|
|
|
|
|
|
|
|
|
::sv_testsave->integer = iPrevTestSave;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!saved_game.open(psPathlessBaseName))
|
|
|
|
|
{
|
|
|
|
|
//S_COLOR_RED "Failed to open savegame \"%s\"\n", psPathlessBaseName);
|
|
|
|
|
::Com_Printf(
|
|
|
|
|
::GetString_FailedToOpenSaveGame(
|
|
|
|
|
psPathlessBaseName,
|
|
|
|
|
qtrue));
|
|
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this check isn't really necessary, but it reminds me that these two strings may actually be the same physical one.
|
|
|
|
|
//
|
|
|
|
|
if (psPathlessBaseName != sLastSaveFileLoaded)
|
|
|
|
|
{
|
|
|
|
|
::Q_strncpyz(
|
|
|
|
|
::sLastSaveFileLoaded,
|
|
|
|
|
psPathlessBaseName,
|
|
|
|
|
sizeof(sLastSaveFileLoaded));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read in all the server data...
|
|
|
|
|
//
|
|
|
|
|
sgh.read_chunk(
|
|
|
|
|
INT_ID('C', 'O', 'M', 'M'),
|
|
|
|
|
sComment);
|
|
|
|
|
|
|
|
|
|
::Com_DPrintf(
|
|
|
|
|
"Reading: %s\n",
|
|
|
|
|
sComment);
|
|
|
|
|
|
|
|
|
|
sgh.read_chunk(
|
|
|
|
|
INT_ID('C', 'M', 'T', 'M'));
|
|
|
|
|
|
|
|
|
|
#ifdef JK2_MODE
|
|
|
|
|
::SG_ReadScreenshot(
|
|
|
|
|
true,
|
|
|
|
|
nullptr);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
sgh.read_chunk(
|
|
|
|
|
INT_ID('M', 'P', 'C', 'M'),
|
|
|
|
|
sMapCmd);
|
|
|
|
|
|
|
|
|
|
::SG_ReadCvars();
|
|
|
|
|
|
|
|
|
|
// read game state
|
|
|
|
|
const qboolean qbAutosave = ::ReadGame();
|
|
|
|
|
|
|
|
|
|
::eSavedGameJustLoaded = (qbAutosave ? eAUTO : eFULL);
|
|
|
|
|
|
|
|
|
|
// note that this also trashes the whole G_Alloc pool as well (of course)
|
|
|
|
|
::SV_SpawnServer(
|
|
|
|
|
sMapCmd,
|
|
|
|
|
eForceReload_NOTHING,
|
|
|
|
|
(::eSavedGameJustLoaded != eFULL ? qtrue : qfalse));
|
|
|
|
|
|
|
|
|
|
// read in all the level data...
|
|
|
|
|
//
|
|
|
|
|
if (!qbAutosave)
|
|
|
|
|
{
|
|
|
|
|
sgh.read_chunk<int32_t>(
|
|
|
|
|
INT_ID('T', 'I', 'M', 'E'),
|
|
|
|
|
::sv.time);
|
|
|
|
|
|
|
|
|
|
sgh.read_chunk<int32_t>(
|
|
|
|
|
INT_ID('T', 'I', 'M', 'R'),
|
|
|
|
|
::sv.timeResidual);
|
|
|
|
|
|
|
|
|
|
::CM_ReadPortalState();
|
|
|
|
|
::SG_ReadServerConfigStrings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// always done now, but ent reader only does player if auto
|
|
|
|
|
::ge->ReadLevel(
|
|
|
|
|
qbAutosave,
|
|
|
|
|
qbLoadTransition);
|
|
|
|
|
|
|
|
|
|
return qtrue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SG_TestSave(void)
|
|
|
|
|
{
|
|
|
|
|
if (sv_testsave->integer && sv.state == SS_GAME)
|
|
|
|
|
{
|
|
|
|
|
WriteGame(qfalse);
|
|
|
|
|
ge->WriteLevel(qfalse);
|
|
|
|
|
}
|
|
|
|
|
}
|