2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 #include "prvm_cmds.h"
25 ===============================================================================
29 ===============================================================================
32 #define SAVEGAME_VERSION 5
34 void SV_Savegame_to(prvm_prog_t *prog, const char *name)
37 int i, k, l, numbuffers, lightstyles = 64;
38 char comment[SAVEGAME_COMMENT_LENGTH+1];
39 char line[MAX_INPUTLINE];
43 // first we have to figure out if this can be saved in 64 lightstyles
44 // (for Quake compatibility)
45 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
46 if (sv.lightstyles[i][0])
49 isserver = prog == SVVM_prog;
51 Con_Printf("Saving game to %s...\n", name);
52 f = FS_OpenRealFile(name, "wb", false);
55 Con_Print("ERROR: couldn't open.\n");
59 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
61 memset(comment, 0, sizeof(comment));
63 dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters));
65 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
66 // convert space to _ to make stdio happy
67 // LadyHavoc: convert control characters to _ as well
68 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
69 if (ISWHITESPACEORCONTROL(comment[i]))
71 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
73 FS_Printf(f, "%s\n", comment);
76 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
77 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
78 FS_Printf(f, "%d\n", current_skill);
79 FS_Printf(f, "%s\n", sv.name);
80 FS_Printf(f, "%f\n",sv.time);
84 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
85 FS_Printf(f, "(dummy)\n");
86 FS_Printf(f, "%d\n", 0);
87 FS_Printf(f, "%s\n", "(dummy)");
88 FS_Printf(f, "%f\n", host.realtime);
91 // write the light styles
92 for (i=0 ; i<lightstyles ; i++)
94 if (isserver && sv.lightstyles[i][0])
95 FS_Printf(f, "%s\n", sv.lightstyles[i]);
100 PRVM_ED_WriteGlobals (prog, f);
101 for (i=0 ; i<prog->num_edicts ; i++)
103 FS_Printf(f,"// edict %d\n", i);
104 //Con_Printf("edict %d...\n", i);
105 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
110 FS_Printf(f,"// DarkPlaces extended savegame\n");
111 // darkplaces extension - extra lightstyles, support for color lightstyles
112 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
113 if (isserver && sv.lightstyles[i][0])
114 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
116 // darkplaces extension - model precaches
117 for (i=1 ; i<MAX_MODELS ; i++)
118 if (sv.model_precache[i][0])
119 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
121 // darkplaces extension - sound precaches
122 for (i=1 ; i<MAX_SOUNDS ; i++)
123 if (sv.sound_precache[i][0])
124 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
126 // darkplaces extension - save buffers
127 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
128 for (i = 0; i < numbuffers; i++)
130 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
131 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
133 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
134 for(k = 0; k < stringbuffer->num_strings; k++)
136 if (!stringbuffer->strings[k])
138 // Parse the string a bit to turn special characters
139 // (like newline, specifically) into escape codes
140 s = stringbuffer->strings[k];
141 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
168 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
176 Con_Print("done.\n");
179 static qbool SV_CanSave(void)
181 prvm_prog_t *prog = SVVM_prog;
182 if(SV_IsLocalServer() == 1)
184 // singleplayer checks
185 // FIXME: This only checks if the first player is dead?
186 if ((svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag)))
188 Con_Print("Can't savegame with a dead player\n");
192 if(host.hook.CL_Intermission && host.hook.CL_Intermission())
194 Con_Print("Can't save in intermission.\n");
199 Con_Print(CON_WARN "Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
208 void SV_Savegame_f(cmd_state_t *cmd)
210 prvm_prog_t *prog = SVVM_prog;
211 char name[MAX_QPATH];
215 Con_Print("Can't save - no server running.\n");
222 if (Cmd_Argc(cmd) != 2)
224 Con_Print("save <savename> : save a game\n");
228 if (strstr(Cmd_Argv(cmd, 1), ".."))
230 Con_Print("Relative pathnames are not allowed.\n");
234 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
235 FS_DefaultExtension (name, ".sav", sizeof (name));
237 SV_Savegame_to(prog, name);
245 void SV_Loadgame_f(cmd_state_t *cmd)
247 prvm_prog_t *prog = SVVM_prog;
248 char filename[MAX_QPATH];
249 char mapname[MAX_QPATH];
256 int i, k, numbuffers;
259 float spawn_parms[NUM_SPAWN_PARMS];
260 prvm_stringbuffer_t *stringbuffer;
262 if (Cmd_Argc(cmd) != 2)
264 Con_Print("load <savename> : load a game\n");
268 strlcpy (filename, Cmd_Argv(cmd, 1), sizeof(filename));
269 FS_DefaultExtension (filename, ".sav", sizeof (filename));
271 Con_Printf("Loading game from %s...\n", filename);
273 if(host.hook.Disconnect)
274 host.hook.Disconnect(false, NULL);
276 if(host.hook.ToggleMenu)
277 host.hook.ToggleMenu();
279 cls.demonum = -1; // stop demo loop in case this fails
281 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
284 Con_Print("ERROR: couldn't open.\n");
288 if(developer_entityparsing.integer)
289 Con_Printf("SV_Loadgame_f: loading version\n");
292 COM_ParseToken_Simple(&t, false, false, true);
293 version = atoi(com_token);
294 if (version != SAVEGAME_VERSION)
297 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
301 if(developer_entityparsing.integer)
302 Con_Printf("SV_Loadgame_f: loading description\n");
305 COM_ParseToken_Simple(&t, false, false, true);
307 for (i = 0;i < NUM_SPAWN_PARMS;i++)
309 COM_ParseToken_Simple(&t, false, false, true);
310 spawn_parms[i] = atof(com_token);
313 COM_ParseToken_Simple(&t, false, false, true);
314 // this silliness is so we can load 1.06 save files, which have float skill values
315 current_skill = (int)(atof(com_token) + 0.5);
316 Cvar_SetValue (&cvars_all, "skill", (float)current_skill);
318 if(developer_entityparsing.integer)
319 Con_Printf("SV_Loadgame_f: loading mapname\n");
322 COM_ParseToken_Simple(&t, false, false, true);
323 strlcpy (mapname, com_token, sizeof(mapname));
325 if(developer_entityparsing.integer)
326 Con_Printf("SV_Loadgame_f: loading time\n");
329 COM_ParseToken_Simple(&t, false, false, true);
330 time = atof(com_token);
332 if(developer_entityparsing.integer)
333 Con_Printf("SV_Loadgame_f: spawning server\n");
335 SV_SpawnServer (mapname);
339 Con_Print("Couldn't load map\n");
342 sv.paused = true; // pause until all clients connect
345 if(developer_entityparsing.integer)
346 Con_Printf("SV_Loadgame_f: loading light styles\n");
348 // load the light styles
353 for (i = 0;i < MAX_LIGHTSTYLES;i++)
357 COM_ParseToken_Simple(&t, false, false, true);
358 // if this is a 64 lightstyle savegame produced by Quake, stop now
359 // we have to check this because darkplaces may save more than 64
360 if (com_token[0] == '{')
365 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
368 if(developer_entityparsing.integer)
369 Con_Printf("SV_Loadgame_f: skipping until globals\n");
371 // now skip everything before the first opening brace
372 // (this is for forward compatibility, so that older versions (at
373 // least ones with this fix) can load savegames with extra data before the
374 // first brace, as might be produced by a later engine version)
378 if (!COM_ParseToken_Simple(&t, false, false, true))
380 if (com_token[0] == '{')
387 // unlink all entities
388 World_UnlinkAll(&sv.world);
390 // load the edicts out of the savegame file
395 while (COM_ParseToken_Simple(&t, false, false, true))
396 if (!strcmp(com_token, "}"))
398 if (!COM_ParseToken_Simple(&start, false, false, true))
403 if (strcmp(com_token,"{"))
406 Host_Error ("First token isn't a brace");
411 if(developer_entityparsing.integer)
412 Con_Printf("SV_Loadgame_f: loading globals\n");
414 // parse the global vars
415 PRVM_ED_ParseGlobals (prog, start);
417 // restore the autocvar globals
418 Cvar_UpdateAllAutoCvars(prog->console_cmd->cvars);
423 if (entnum >= MAX_EDICTS)
426 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
428 while (entnum >= prog->max_edicts)
429 PRVM_MEM_IncreaseEdicts(prog);
430 ent = PRVM_EDICT_NUM(entnum);
431 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
434 if(developer_entityparsing.integer)
435 Con_Printf("SV_Loadgame_f: loading edict %d\n", entnum);
437 PRVM_ED_ParseEdict (prog, start, ent);
439 // link it into the bsp tree
440 if (!ent->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax)))
448 prog->num_edicts = entnum;
451 for (i = 0;i < NUM_SPAWN_PARMS;i++)
452 svs.clients[0].spawn_parms[i] = spawn_parms[i];
454 if(developer_entityparsing.integer)
455 Con_Printf("SV_Loadgame_f: skipping until extended data\n");
457 // read extended data if present
458 // the extended data is stored inside a /* */ comment block, which the
459 // parser intentionally skips, so we have to check for it manually here
462 while (*end == '\r' || *end == '\n')
464 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
466 if(developer_entityparsing.integer)
467 Con_Printf("SV_Loadgame_f: loading extended data\n");
469 Con_Printf("Loading extended DarkPlaces savegame\n");
471 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
472 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
473 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
476 while (COM_ParseToken_Simple(&t, false, false, true))
478 if (!strcmp(com_token, "sv.lightstyles"))
480 COM_ParseToken_Simple(&t, false, false, true);
482 COM_ParseToken_Simple(&t, false, false, true);
483 if (i >= 0 && i < MAX_LIGHTSTYLES)
484 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
486 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
488 else if (!strcmp(com_token, "sv.model_precache"))
490 COM_ParseToken_Simple(&t, false, false, true);
492 COM_ParseToken_Simple(&t, false, false, true);
493 if (i >= 0 && i < MAX_MODELS)
495 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
496 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
499 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
501 else if (!strcmp(com_token, "sv.sound_precache"))
503 COM_ParseToken_Simple(&t, false, false, true);
505 COM_ParseToken_Simple(&t, false, false, true);
506 if (i >= 0 && i < MAX_SOUNDS)
507 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
509 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
511 else if (!strcmp(com_token, "sv.buffer"))
513 if (COM_ParseToken_Simple(&t, false, false, true))
518 k = STRINGBUFFER_SAVED;
519 if (COM_ParseToken_Simple(&t, false, false, true))
520 k |= atoi(com_token);
521 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
522 Con_Printf(CON_ERROR "failed to create stringbuffer %i\n", i);
525 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
528 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
530 else if (!strcmp(com_token, "sv.bufstr"))
532 if (!COM_ParseToken_Simple(&t, false, false, true))
533 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
537 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
540 if (COM_ParseToken_Simple(&t, false, false, true))
543 if (COM_ParseToken_Simple(&t, false, false, true))
544 BufStr_Set(prog, stringbuffer, k, com_token);
546 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
549 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
552 Con_Printf(CON_ERROR "failed to create stringbuffer %i \"%s\"\n", i, com_token);
555 // skip any trailing text or unrecognized commands
556 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
563 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
564 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
565 for (i = 0; i < numbuffers; i++)
567 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
568 if (stringbuffer->flags & STRINGBUFFER_TEMP)
569 BufStr_Del(prog, stringbuffer);
572 if(developer_entityparsing.integer)
573 Con_Printf("SV_Loadgame_f: finished\n");
575 // make sure we're connected to loopback
576 if(sv.active && host.hook.ConnectLocal)
577 host.hook.ConnectLocal();