diff --git a/Makefile b/Makefile index 24cc33b9..d65d5f07 100644 --- a/Makefile +++ b/Makefile @@ -245,6 +245,7 @@ SERVER_OBJS = \ build/server/sv_game.o \ build/server/sv_init.o \ build/server/sv_main.o \ + build/server/sv_save.o \ build/server/sv_send.o \ build/server/sv_user.o \ build/server/sv_world.o @@ -276,6 +277,7 @@ DEDICATED_SERVER_OBJS = \ build/dedicated_server/sv_game.o \ build/dedicated_server/sv_init.o \ build/dedicated_server/sv_main.o \ + build/dedicated_server/sv_save.o \ build/dedicated_server/sv_send.o \ build/dedicated_server/sv_user.o \ build/dedicated_server/sv_world.o @@ -616,7 +618,10 @@ build/server/sv_init.o : src/server/sv_init.c build/server/sv_main.o : src/server/sv_main.c $(CC) $(CFLAGS_CLIENT) -o $@ -c $< - + +build/server/sv_save.o : src/server/sv_save.c + $(CC) $(CFLAGS_CLIENT) -o $@ -c $< + build/server/sv_send.o : src/server/sv_send.c $(CC) $(CFLAGS_CLIENT) -o $@ -c $< @@ -672,7 +677,10 @@ build/dedicated_server/sv_init.o : src/server/sv_init.c build/dedicated_server/sv_main.o : src/server/sv_main.c $(CC) $(CFLAGS_DEDICATED_SERVER) -o $@ -c $< - + +build/dedicated_server/sv_save.o : src/server/sv_save.c + $(CC) $(CFLAGS_DEDICATED_SERVER) -o $@ -c $< + build/dedicated_server/sv_send.o : src/server/sv_send.c $(CC) $(CFLAGS_DEDICATED_SERVER) -o $@ -c $< diff --git a/src/server/header/server.h b/src/server/header/server.h index c0e35529..eecc6db8 100644 --- a/src/server/header/server.h +++ b/src/server/header/server.h @@ -247,6 +247,14 @@ void SV_InitGameProgs ( void ); void SV_ShutdownGameProgs ( void ); void SV_InitEdict ( edict_t *e ); +/* server side savegame stuff */ +void SV_WipeSavegame ( char *savename ); +void SV_CopySaveGame ( char *src, char *dst ); +void SV_WriteLevelFile ( void ); +void SV_WriteServerFile ( qboolean autosave ); +void SV_Loadgame_f ( void ); +void SV_Savegame_f ( void ); + /* high level object sorting to reduce interaction tests */ void SV_ClearWorld ( void ); diff --git a/src/server/sv_ccmds.c b/src/server/sv_ccmds.c index 69ca4001..ec954e82 100644 --- a/src/server/sv_ccmds.c +++ b/src/server/sv_ccmds.c @@ -144,318 +144,6 @@ SV_SetPlayer ( void ) return ( false ); } -/* - * =============================================================================== - * - * SAVEGAME FILES - * - * =============================================================================== - */ - -/* - * Delete save// - */ -void -SV_WipeSavegame ( char *savename ) -{ - char name [ MAX_OSPATH ]; - char *s; - - Com_DPrintf( "SV_WipeSaveGame(%s)\n", savename ); - - Com_sprintf( name, sizeof ( name ), "%s/save/%s/server.ssv", FS_Gamedir(), savename ); - remove( name ); - Com_sprintf( name, sizeof ( name ), "%s/save/%s/game.ssv", FS_Gamedir(), savename ); - remove( name ); - - Com_sprintf( name, sizeof ( name ), "%s/save/%s/*.sav", FS_Gamedir(), savename ); - s = Sys_FindFirst( name, 0, 0 ); - - while ( s ) - { - remove( s ); - s = Sys_FindNext( 0, 0 ); - } - - Sys_FindClose(); - Com_sprintf( name, sizeof ( name ), "%s/save/%s/*.sv2", FS_Gamedir(), savename ); - s = Sys_FindFirst( name, 0, 0 ); - - while ( s ) - { - remove( s ); - s = Sys_FindNext( 0, 0 ); - } - - Sys_FindClose(); -} - -void -CopyFile ( char *src, char *dst ) -{ - FILE *f1, *f2; - size_t l; - byte buffer [ 65536 ]; - - Com_DPrintf( "CopyFile (%s, %s)\n", src, dst ); - - f1 = fopen( src, "rb" ); - - if ( !f1 ) - { - return; - } - - f2 = fopen( dst, "wb" ); - - if ( !f2 ) - { - fclose( f1 ); - return; - } - - while ( 1 ) - { - l = fread( buffer, 1, sizeof ( buffer ), f1 ); - - if ( !l ) - { - break; - } - - fwrite( buffer, 1, l, f2 ); - } - - fclose( f1 ); - fclose( f2 ); -} - -void -SV_CopySaveGame ( char *src, char *dst ) -{ - char name [ MAX_OSPATH ], name2 [ MAX_OSPATH ]; - size_t l, len; - char *found; - - Com_DPrintf( "SV_CopySaveGame(%s, %s)\n", src, dst ); - - SV_WipeSavegame( dst ); - - /* copy the savegame over */ - Com_sprintf( name, sizeof ( name ), "%s/save/%s/server.ssv", FS_Gamedir(), src ); - Com_sprintf( name2, sizeof ( name2 ), "%s/save/%s/server.ssv", FS_Gamedir(), dst ); - FS_CreatePath( name2 ); - CopyFile( name, name2 ); - - Com_sprintf( name, sizeof ( name ), "%s/save/%s/game.ssv", FS_Gamedir(), src ); - Com_sprintf( name2, sizeof ( name2 ), "%s/save/%s/game.ssv", FS_Gamedir(), dst ); - CopyFile( name, name2 ); - - Com_sprintf( name, sizeof ( name ), "%s/save/%s/", FS_Gamedir(), src ); - len = strlen( name ); - Com_sprintf( name, sizeof ( name ), "%s/save/%s/*.sav", FS_Gamedir(), src ); - found = Sys_FindFirst( name, 0, 0 ); - - while ( found ) - { - strcpy( name + len, found + len ); - - Com_sprintf( name2, sizeof ( name2 ), "%s/save/%s/%s", FS_Gamedir(), dst, found + len ); - CopyFile( name, name2 ); - - /* change sav to sv2 */ - l = strlen( name ); - strcpy( name + l - 3, "sv2" ); - l = strlen( name2 ); - strcpy( name2 + l - 3, "sv2" ); - CopyFile( name, name2 ); - - found = Sys_FindNext( 0, 0 ); - } - - Sys_FindClose(); -} - -void -SV_WriteLevelFile ( void ) -{ - char name [ MAX_OSPATH ]; - FILE *f; - - Com_DPrintf( "SV_WriteLevelFile()\n" ); - - Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name ); - f = fopen( name, "wb" ); - - if ( !f ) - { - Com_Printf( "Failed to open %s\n", name ); - return; - } - - fwrite( sv.configstrings, sizeof ( sv.configstrings ), 1, f ); - CM_WritePortalState( f ); - fclose( f ); - - Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sav", FS_Gamedir(), sv.name ); - ge->WriteLevel( name ); -} - -void CM_ReadPortalState ( fileHandle_t f ); - -void -SV_ReadLevelFile ( void ) -{ - char name [ MAX_OSPATH ]; - fileHandle_t f; - - Com_DPrintf( "SV_ReadLevelFile()\n" ); - - Com_sprintf( name, sizeof ( name ), "save/current/%s.sv2", sv.name ); - FS_FOpenFile( name, &f, FS_READ ); - - if ( !f ) - { - Com_Printf( "Failed to open %s\n", name ); - return; - } - - FS_Read( sv.configstrings, sizeof ( sv.configstrings ), f ); - CM_ReadPortalState( f ); - FS_FCloseFile( f ); - - Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sav", FS_Gamedir(), sv.name ); - ge->ReadLevel( name ); -} - -void -SV_WriteServerFile ( qboolean autosave ) -{ - FILE *f; - cvar_t *var; - char name [ MAX_OSPATH ], string [ 128 ]; - char comment [ 32 ]; - time_t aclock; - struct tm *newtime; - - Com_DPrintf( "SV_WriteServerFile(%s)\n", autosave ? "true" : "false" ); - - Com_sprintf( name, sizeof ( name ), "%s/save/current/server.ssv", FS_Gamedir() ); - f = fopen( name, "wb" ); - - if ( !f ) - { - Com_Printf( "Couldn't write %s\n", name ); - return; - } - - /* write the comment field */ - memset( comment, 0, sizeof ( comment ) ); - - if ( !autosave ) - { - time( &aclock ); - newtime = localtime( &aclock ); - Com_sprintf( comment, sizeof ( comment ), "%2i:%i%i %2i/%2i ", newtime->tm_hour, - newtime->tm_min / 10, newtime->tm_min % 10, - newtime->tm_mon + 1, newtime->tm_mday ); - strncat( comment, sv.configstrings [ CS_NAME ], sizeof ( comment ) - 1 - strlen( comment ) ); - } - else - { - /* autosaved */ - Com_sprintf( comment, sizeof ( comment ), "ENTERING %s", sv.configstrings [ CS_NAME ] ); - } - - fwrite( comment, 1, sizeof ( comment ), f ); - - /* write the mapcmd */ - fwrite( svs.mapcmd, 1, sizeof ( svs.mapcmd ), f ); - - /* write all CVAR_LATCH cvars - these will be things like coop, skill, deathmatch, etc */ - for ( var = cvar_vars; var; var = var->next ) - { - if ( !( var->flags & CVAR_LATCH ) ) - { - continue; - } - - if ( ( strlen( var->name ) >= sizeof ( name ) - 1 ) || - ( strlen( var->string ) >= sizeof ( string ) - 1 ) ) - { - Com_Printf( "Cvar too long: %s = %s\n", var->name, var->string ); - continue; - } - - memset( name, 0, sizeof ( name ) ); - memset( string, 0, sizeof ( string ) ); - strcpy( name, var->name ); - strcpy( string, var->string ); - fwrite( name, 1, sizeof ( name ), f ); - fwrite( string, 1, sizeof ( string ), f ); - } - - fclose( f ); - - /* write game state */ - Com_sprintf( name, sizeof ( name ), "%s/save/current/game.ssv", FS_Gamedir() ); - ge->WriteGame( name, autosave ); -} - -void -SV_ReadServerFile ( void ) -{ - fileHandle_t f; - char name [ MAX_OSPATH ], string [ 128 ]; - char comment [ 32 ]; - char mapcmd [ MAX_TOKEN_CHARS ]; - - Com_DPrintf( "SV_ReadServerFile()\n" ); - - Com_sprintf( name, sizeof ( name ), "save/current/server.ssv" ); - FS_FOpenFile( name, &f, FS_READ ); - - if ( !f ) - { - Com_Printf( "Couldn't read %s\n", name ); - return; - } - - /* read the comment field */ - FS_Read( comment, sizeof ( comment ), f ); - - /* read the mapcmd */ - FS_Read( mapcmd, sizeof ( mapcmd ), f ); - - /* read all CVAR_LATCH cvars - these will be things like coop, skill, deathmatch, etc */ - while ( 1 ) - { - if ( !FS_FRead( name, 1, sizeof ( name ), f ) ) - { - break; - } - - FS_Read( string, sizeof ( string ), f ); - Com_DPrintf( "Set %s = %s\n", name, string ); - Cvar_ForceSet( name, string ); - } - - FS_FCloseFile( f ); - - /* start a new game fresh with new cvars */ - SV_InitGame(); - - strcpy( svs.mapcmd, mapcmd ); - - /* read game state */ - Com_sprintf( name, sizeof ( name ), "%s/save/current/game.ssv", FS_Gamedir() ); - ge->ReadGame( name ); -} - -/* ========================================================= */ - /* * Puts the server in demo mode on a specific map/cinematic */ @@ -596,117 +284,6 @@ SV_Map_f ( void ) SV_GameMap_f(); } -/* - * ===================================================================== - * - * SAVEGAMES - * - * ===================================================================== - */ - -void -SV_Loadgame_f ( void ) -{ - char name [ MAX_OSPATH ]; - FILE *f; - char *dir; - - if ( Cmd_Argc() != 2 ) - { - Com_Printf( "USAGE: loadgame \n" ); - return; - } - - Com_Printf( "Loading game...\n" ); - - dir = Cmd_Argv( 1 ); - - if ( strstr( dir, ".." ) || strstr( dir, "/" ) || strstr( dir, "\\" ) ) - { - Com_Printf( "Bad savedir.\n" ); - } - - /* make sure the server.ssv file exists */ - Com_sprintf( name, sizeof ( name ), "%s/save/%s/server.ssv", FS_Gamedir(), Cmd_Argv( 1 ) ); - f = fopen( name, "rb" ); - - if ( !f ) - { - Com_Printf( "No such savegame: %s\n", name ); - return; - } - - fclose( f ); - - SV_CopySaveGame( Cmd_Argv( 1 ), "current" ); - - SV_ReadServerFile(); - - /* go to the map */ - sv.state = ss_dead; /* don't save current level when changing */ - SV_Map( false, svs.mapcmd, true ); -} - -void -SV_Savegame_f ( void ) -{ - char *dir; - - if ( sv.state != ss_game ) - { - Com_Printf( "You must be in a game to save.\n" ); - return; - } - - if ( Cmd_Argc() != 2 ) - { - Com_Printf( "USAGE: savegame \n" ); - return; - } - - if ( Cvar_VariableValue( "deathmatch" ) ) - { - Com_Printf( "Can't savegame in a deathmatch\n" ); - return; - } - - if ( !strcmp( Cmd_Argv( 1 ), "current" ) ) - { - Com_Printf( "Can't save to 'current'\n" ); - return; - } - - if ( ( maxclients->value == 1 ) && ( svs.clients [ 0 ].edict->client->ps.stats [ STAT_HEALTH ] <= 0 ) ) - { - Com_Printf( "\nCan't savegame while dead!\n" ); - return; - } - - dir = Cmd_Argv( 1 ); - - if ( strstr( dir, ".." ) || strstr( dir, "/" ) || strstr( dir, "\\" ) ) - { - Com_Printf( "Bad savedir.\n" ); - } - - Com_Printf( "Saving game...\n" ); - - /* archive current level, including all client edicts. - when the level is reloaded, they will be shells awaiting - a connecting client */ - SV_WriteLevelFile(); - - /* save server state */ - SV_WriteServerFile( false ); - - /* copy it off */ - SV_CopySaveGame( "current", dir ); - - Com_Printf( "Done.\n" ); -} - -/* =============================================================== */ - /* * Kick a user off of the server */ diff --git a/src/server/sv_save.c b/src/server/sv_save.c new file mode 100644 index 00000000..6fe939ae --- /dev/null +++ b/src/server/sv_save.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * ======================================================================= + * + * Serverside savegame code. + * + * ======================================================================= + */ + +#include "header/server.h" + +void CM_ReadPortalState ( fileHandle_t f ); + +/* + * Delete save// + */ +void +SV_WipeSavegame ( char *savename ) +{ + char name [ MAX_OSPATH ]; + char *s; + + Com_DPrintf( "SV_WipeSaveGame(%s)\n", savename ); + + Com_sprintf( name, sizeof ( name ), "%s/save/%s/server.ssv", FS_Gamedir(), savename ); + remove( name ); + Com_sprintf( name, sizeof ( name ), "%s/save/%s/game.ssv", FS_Gamedir(), savename ); + remove( name ); + + Com_sprintf( name, sizeof ( name ), "%s/save/%s/*.sav", FS_Gamedir(), savename ); + s = Sys_FindFirst( name, 0, 0 ); + + while ( s ) + { + remove( s ); + s = Sys_FindNext( 0, 0 ); + } + + Sys_FindClose(); + Com_sprintf( name, sizeof ( name ), "%s/save/%s/*.sv2", FS_Gamedir(), savename ); + s = Sys_FindFirst( name, 0, 0 ); + + while ( s ) + { + remove( s ); + s = Sys_FindNext( 0, 0 ); + } + + Sys_FindClose(); +} + +void +CopyFile ( char *src, char *dst ) +{ + FILE *f1, *f2; + size_t l; + byte buffer [ 65536 ]; + + Com_DPrintf( "CopyFile (%s, %s)\n", src, dst ); + + f1 = fopen( src, "rb" ); + + if ( !f1 ) + { + return; + } + + f2 = fopen( dst, "wb" ); + + if ( !f2 ) + { + fclose( f1 ); + return; + } + + while ( 1 ) + { + l = fread( buffer, 1, sizeof ( buffer ), f1 ); + + if ( !l ) + { + break; + } + + fwrite( buffer, 1, l, f2 ); + } + + fclose( f1 ); + fclose( f2 ); +} + +void +SV_CopySaveGame ( char *src, char *dst ) +{ + char name [ MAX_OSPATH ], name2 [ MAX_OSPATH ]; + size_t l, len; + char *found; + + Com_DPrintf( "SV_CopySaveGame(%s, %s)\n", src, dst ); + + SV_WipeSavegame( dst ); + + /* copy the savegame over */ + Com_sprintf( name, sizeof ( name ), "%s/save/%s/server.ssv", FS_Gamedir(), src ); + Com_sprintf( name2, sizeof ( name2 ), "%s/save/%s/server.ssv", FS_Gamedir(), dst ); + FS_CreatePath( name2 ); + CopyFile( name, name2 ); + + Com_sprintf( name, sizeof ( name ), "%s/save/%s/game.ssv", FS_Gamedir(), src ); + Com_sprintf( name2, sizeof ( name2 ), "%s/save/%s/game.ssv", FS_Gamedir(), dst ); + CopyFile( name, name2 ); + + Com_sprintf( name, sizeof ( name ), "%s/save/%s/", FS_Gamedir(), src ); + len = strlen( name ); + Com_sprintf( name, sizeof ( name ), "%s/save/%s/*.sav", FS_Gamedir(), src ); + found = Sys_FindFirst( name, 0, 0 ); + + while ( found ) + { + strcpy( name + len, found + len ); + + Com_sprintf( name2, sizeof ( name2 ), "%s/save/%s/%s", FS_Gamedir(), dst, found + len ); + CopyFile( name, name2 ); + + /* change sav to sv2 */ + l = strlen( name ); + strcpy( name + l - 3, "sv2" ); + l = strlen( name2 ); + strcpy( name2 + l - 3, "sv2" ); + CopyFile( name, name2 ); + + found = Sys_FindNext( 0, 0 ); + } + + Sys_FindClose(); +} + +void +SV_WriteLevelFile ( void ) +{ + char name [ MAX_OSPATH ]; + FILE *f; + + Com_DPrintf( "SV_WriteLevelFile()\n" ); + + Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name ); + f = fopen( name, "wb" ); + + if ( !f ) + { + Com_Printf( "Failed to open %s\n", name ); + return; + } + + fwrite( sv.configstrings, sizeof ( sv.configstrings ), 1, f ); + CM_WritePortalState( f ); + fclose( f ); + + Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sav", FS_Gamedir(), sv.name ); + ge->WriteLevel( name ); +} + +void +SV_ReadLevelFile ( void ) +{ + char name [ MAX_OSPATH ]; + fileHandle_t f; + + Com_DPrintf( "SV_ReadLevelFile()\n" ); + + Com_sprintf( name, sizeof ( name ), "save/current/%s.sv2", sv.name ); + FS_FOpenFile( name, &f, FS_READ ); + + if ( !f ) + { + Com_Printf( "Failed to open %s\n", name ); + return; + } + + FS_Read( sv.configstrings, sizeof ( sv.configstrings ), f ); + CM_ReadPortalState( f ); + FS_FCloseFile( f ); + + Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sav", FS_Gamedir(), sv.name ); + ge->ReadLevel( name ); +} + +void +SV_WriteServerFile ( qboolean autosave ) +{ + FILE *f; + cvar_t *var; + char name [ MAX_OSPATH ], string [ 128 ]; + char comment [ 32 ]; + time_t aclock; + struct tm *newtime; + + Com_DPrintf( "SV_WriteServerFile(%s)\n", autosave ? "true" : "false" ); + + Com_sprintf( name, sizeof ( name ), "%s/save/current/server.ssv", FS_Gamedir() ); + f = fopen( name, "wb" ); + + if ( !f ) + { + Com_Printf( "Couldn't write %s\n", name ); + return; + } + + /* write the comment field */ + memset( comment, 0, sizeof ( comment ) ); + + if ( !autosave ) + { + time( &aclock ); + newtime = localtime( &aclock ); + Com_sprintf( comment, sizeof ( comment ), "%2i:%i%i %2i/%2i ", newtime->tm_hour, + newtime->tm_min / 10, newtime->tm_min % 10, + newtime->tm_mon + 1, newtime->tm_mday ); + strncat( comment, sv.configstrings [ CS_NAME ], sizeof ( comment ) - 1 - strlen( comment ) ); + } + else + { + /* autosaved */ + Com_sprintf( comment, sizeof ( comment ), "ENTERING %s", sv.configstrings [ CS_NAME ] ); + } + + fwrite( comment, 1, sizeof ( comment ), f ); + + /* write the mapcmd */ + fwrite( svs.mapcmd, 1, sizeof ( svs.mapcmd ), f ); + + /* write all CVAR_LATCH cvars + these will be things like coop, skill, deathmatch, etc */ + for ( var = cvar_vars; var; var = var->next ) + { + if ( !( var->flags & CVAR_LATCH ) ) + { + continue; + } + + if ( ( strlen( var->name ) >= sizeof ( name ) - 1 ) || + ( strlen( var->string ) >= sizeof ( string ) - 1 ) ) + { + Com_Printf( "Cvar too long: %s = %s\n", var->name, var->string ); + continue; + } + + memset( name, 0, sizeof ( name ) ); + memset( string, 0, sizeof ( string ) ); + strcpy( name, var->name ); + strcpy( string, var->string ); + fwrite( name, 1, sizeof ( name ), f ); + fwrite( string, 1, sizeof ( string ), f ); + } + + fclose( f ); + + /* write game state */ + Com_sprintf( name, sizeof ( name ), "%s/save/current/game.ssv", FS_Gamedir() ); + ge->WriteGame( name, autosave ); +} + +void +SV_ReadServerFile ( void ) +{ + fileHandle_t f; + char name [ MAX_OSPATH ], string [ 128 ]; + char comment [ 32 ]; + char mapcmd [ MAX_TOKEN_CHARS ]; + + Com_DPrintf( "SV_ReadServerFile()\n" ); + + Com_sprintf( name, sizeof ( name ), "save/current/server.ssv" ); + FS_FOpenFile( name, &f, FS_READ ); + + if ( !f ) + { + Com_Printf( "Couldn't read %s\n", name ); + return; + } + + /* read the comment field */ + FS_Read( comment, sizeof ( comment ), f ); + + /* read the mapcmd */ + FS_Read( mapcmd, sizeof ( mapcmd ), f ); + + /* read all CVAR_LATCH cvars + these will be things like coop, skill, deathmatch, etc */ + while ( 1 ) + { + if ( !FS_FRead( name, 1, sizeof ( name ), f ) ) + { + break; + } + + FS_Read( string, sizeof ( string ), f ); + Com_DPrintf( "Set %s = %s\n", name, string ); + Cvar_ForceSet( name, string ); + } + + FS_FCloseFile( f ); + + /* start a new game fresh with new cvars */ + SV_InitGame(); + + strcpy( svs.mapcmd, mapcmd ); + + /* read game state */ + Com_sprintf( name, sizeof ( name ), "%s/save/current/game.ssv", FS_Gamedir() ); + ge->ReadGame( name ); +} + +void +SV_Loadgame_f ( void ) +{ + char name [ MAX_OSPATH ]; + FILE *f; + char *dir; + + if ( Cmd_Argc() != 2 ) + { + Com_Printf( "USAGE: loadgame \n" ); + return; + } + + Com_Printf( "Loading game...\n" ); + + dir = Cmd_Argv( 1 ); + + if ( strstr( dir, ".." ) || strstr( dir, "/" ) || strstr( dir, "\\" ) ) + { + Com_Printf( "Bad savedir.\n" ); + } + + /* make sure the server.ssv file exists */ + Com_sprintf( name, sizeof ( name ), "%s/save/%s/server.ssv", FS_Gamedir(), Cmd_Argv( 1 ) ); + f = fopen( name, "rb" ); + + if ( !f ) + { + Com_Printf( "No such savegame: %s\n", name ); + return; + } + + fclose( f ); + + SV_CopySaveGame( Cmd_Argv( 1 ), "current" ); + + SV_ReadServerFile(); + + /* go to the map */ + sv.state = ss_dead; /* don't save current level when changing */ + SV_Map( false, svs.mapcmd, true ); +} + +void +SV_Savegame_f ( void ) +{ + char *dir; + + if ( sv.state != ss_game ) + { + Com_Printf( "You must be in a game to save.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) + { + Com_Printf( "USAGE: savegame \n" ); + return; + } + + if ( Cvar_VariableValue( "deathmatch" ) ) + { + Com_Printf( "Can't savegame in a deathmatch\n" ); + return; + } + + if ( !strcmp( Cmd_Argv( 1 ), "current" ) ) + { + Com_Printf( "Can't save to 'current'\n" ); + return; + } + + if ( ( maxclients->value == 1 ) && ( svs.clients [ 0 ].edict->client->ps.stats [ STAT_HEALTH ] <= 0 ) ) + { + Com_Printf( "\nCan't savegame while dead!\n" ); + return; + } + + dir = Cmd_Argv( 1 ); + + if ( strstr( dir, ".." ) || strstr( dir, "/" ) || strstr( dir, "\\" ) ) + { + Com_Printf( "Bad savedir.\n" ); + } + + Com_Printf( "Saving game...\n" ); + + /* archive current level, including all client edicts. + when the level is reloaded, they will be shells awaiting + a connecting client */ + SV_WriteLevelFile(); + + /* save server state */ + SV_WriteServerFile( false ); + + /* copy it off */ + SV_CopySaveGame( "current", dir ); + + Com_Printf( "Done.\n" ); +} +