]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_save.c
edict: Move free and freetime into prvm_edict_t itself
[xonotic/darkplaces.git] / sv_save.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20
21 #include "quakedef.h"
22 #include "prvm_cmds.h"
23
24 /*
25 ===============================================================================
26
27 LOAD / SAVE GAME
28
29 ===============================================================================
30 */
31
32 #define SAVEGAME_VERSION        5
33
34 void SV_Savegame_to(prvm_prog_t *prog, const char *name)
35 {
36         qfile_t *f;
37         int             i, k, l, numbuffers, lightstyles = 64;
38         char    comment[SAVEGAME_COMMENT_LENGTH+1];
39         char    line[MAX_INPUTLINE];
40         qbool isserver;
41         char    *s;
42
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])
47                         lightstyles = i+1;
48
49         isserver = prog == SVVM_prog;
50
51         Con_Printf("Saving game to %s...\n", name);
52         f = FS_OpenRealFile(name, "wb", false);
53         if (!f)
54         {
55                 Con_Print("ERROR: couldn't open.\n");
56                 return;
57         }
58
59         FS_Printf(f, "%i\n", SAVEGAME_VERSION);
60
61         memset(comment, 0, sizeof(comment));
62         if(isserver)
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));
64         else
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]))
70                         comment[i] = '_';
71         comment[SAVEGAME_COMMENT_LENGTH] = '\0';
72
73         FS_Printf(f, "%s\n", comment);
74         if(isserver)
75         {
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);
81         }
82         else
83         {
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);
89         }
90
91         // write the light styles
92         for (i=0 ; i<lightstyles ; i++)
93         {
94                 if (isserver && sv.lightstyles[i][0])
95                         FS_Printf(f, "%s\n", sv.lightstyles[i]);
96                 else
97                         FS_Print(f,"m\n");
98         }
99
100         PRVM_ED_WriteGlobals (prog, f);
101         for (i=0 ; i<prog->num_edicts ; i++)
102         {
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));
106         }
107
108 #if 1
109         FS_Printf(f,"/*\n");
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]);
115
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]);
120
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]);
125
126         // darkplaces extension - save buffers
127         numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
128         for (i = 0; i < numbuffers; i++)
129         {
130                 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
131                 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
132                 {
133                         FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
134                         for(k = 0; k < stringbuffer->num_strings; k++)
135                         {
136                                 if (!stringbuffer->strings[k])
137                                         continue;
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;)
142                                 {       
143                                         if (*s == '\n')
144                                         {
145                                                 line[l++] = '\\';
146                                                 line[l++] = 'n';
147                                         }
148                                         else if (*s == '\r')
149                                         {
150                                                 line[l++] = '\\';
151                                                 line[l++] = 'r';
152                                         }
153                                         else if (*s == '\\')
154                                         {
155                                                 line[l++] = '\\';
156                                                 line[l++] = '\\';
157                                         }
158                                         else if (*s == '"')
159                                         {
160                                                 line[l++] = '\\';
161                                                 line[l++] = '"';
162                                         }
163                                         else
164                                                 line[l++] = *s;
165                                         s++;
166                                 }
167                                 line[l] = '\0';
168                                 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
169                         }
170                 }
171         }
172         FS_Printf(f,"*/\n");
173 #endif
174
175         FS_Close (f);
176         Con_Print("done.\n");
177 }
178
179 static qbool SV_CanSave(void)
180 {
181         prvm_prog_t *prog = SVVM_prog;
182         if(SV_IsLocalServer() == 1)
183         {
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)))
187                 {
188                         Con_Print("Can't savegame with a dead player\n");
189                         return false;
190                 }
191
192                 if(host.hook.CL_Intermission && host.hook.CL_Intermission())
193                 {
194                         Con_Print("Can't save in intermission.\n");
195                         return false;
196                 }
197         }
198         else
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");
200         return true;
201 }
202
203 /*
204 ===============
205 SV_Savegame_f
206 ===============
207 */
208 void SV_Savegame_f(cmd_state_t *cmd)
209 {
210         prvm_prog_t *prog = SVVM_prog;
211         char    name[MAX_QPATH];
212
213         if (!sv.active)
214         {
215                 Con_Print("Can't save - no server running.\n");
216                 return;
217         }
218
219         if(!SV_CanSave())
220                 return;
221
222         if (Cmd_Argc(cmd) != 2)
223         {
224                 Con_Print("save <savename> : save a game\n");
225                 return;
226         }
227
228         if (strstr(Cmd_Argv(cmd, 1), ".."))
229         {
230                 Con_Print("Relative pathnames are not allowed.\n");
231                 return;
232         }
233
234         strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
235         FS_DefaultExtension (name, ".sav", sizeof (name));
236
237         SV_Savegame_to(prog, name);
238 }
239
240 /*
241 ===============
242 SV_Loadgame_f
243 ===============
244 */
245 void SV_Loadgame_f(cmd_state_t *cmd)
246 {
247         prvm_prog_t *prog = SVVM_prog;
248         char filename[MAX_QPATH];
249         char mapname[MAX_QPATH];
250         float time;
251         const char *start;
252         const char *end;
253         const char *t;
254         char *text;
255         prvm_edict_t *ent;
256         int i, k, numbuffers;
257         int entnum;
258         int version;
259         float spawn_parms[NUM_SPAWN_PARMS];
260         prvm_stringbuffer_t *stringbuffer;
261
262         if (Cmd_Argc(cmd) != 2)
263         {
264                 Con_Print("load <savename> : load a game\n");
265                 return;
266         }
267
268         strlcpy (filename, Cmd_Argv(cmd, 1), sizeof(filename));
269         FS_DefaultExtension (filename, ".sav", sizeof (filename));
270
271         Con_Printf("Loading game from %s...\n", filename);
272
273         // stop playing demos
274         if (cls.demoplayback)
275                 CL_Disconnect ();
276
277 #ifdef CONFIG_MENU
278         // remove menu
279         if (key_dest == key_menu || key_dest == key_menu_grabbed)
280                 MR_ToggleMenu(0);
281 #endif
282         key_dest = key_game;
283
284         cls.demonum = -1;               // stop demo loop in case this fails
285
286         t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
287         if (!text)
288         {
289                 Con_Print("ERROR: couldn't open.\n");
290                 return;
291         }
292
293         if(developer_entityparsing.integer)
294                 Con_Printf("SV_Loadgame_f: loading version\n");
295
296         // version
297         COM_ParseToken_Simple(&t, false, false, true);
298         version = atoi(com_token);
299         if (version != SAVEGAME_VERSION)
300         {
301                 Mem_Free(text);
302                 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
303                 return;
304         }
305
306         if(developer_entityparsing.integer)
307                 Con_Printf("SV_Loadgame_f: loading description\n");
308
309         // description
310         COM_ParseToken_Simple(&t, false, false, true);
311
312         for (i = 0;i < NUM_SPAWN_PARMS;i++)
313         {
314                 COM_ParseToken_Simple(&t, false, false, true);
315                 spawn_parms[i] = atof(com_token);
316         }
317         // skill
318         COM_ParseToken_Simple(&t, false, false, true);
319 // this silliness is so we can load 1.06 save files, which have float skill values
320         current_skill = (int)(atof(com_token) + 0.5);
321         Cvar_SetValue (&cvars_all, "skill", (float)current_skill);
322
323         if(developer_entityparsing.integer)
324                 Con_Printf("SV_Loadgame_f: loading mapname\n");
325
326         // mapname
327         COM_ParseToken_Simple(&t, false, false, true);
328         strlcpy (mapname, com_token, sizeof(mapname));
329
330         if(developer_entityparsing.integer)
331                 Con_Printf("SV_Loadgame_f: loading time\n");
332
333         // time
334         COM_ParseToken_Simple(&t, false, false, true);
335         time = atof(com_token);
336
337         if(developer_entityparsing.integer)
338                 Con_Printf("SV_Loadgame_f: spawning server\n");
339
340         SV_SpawnServer (mapname);
341         if (!sv.active)
342         {
343                 Mem_Free(text);
344                 Con_Print("Couldn't load map\n");
345                 return;
346         }
347         sv.paused = true;               // pause until all clients connect
348         sv.loadgame = true;
349
350         if(developer_entityparsing.integer)
351                 Con_Printf("SV_Loadgame_f: loading light styles\n");
352
353 // load the light styles
354
355         // -1 is the globals
356         entnum = -1;
357
358         for (i = 0;i < MAX_LIGHTSTYLES;i++)
359         {
360                 // light style
361                 start = t;
362                 COM_ParseToken_Simple(&t, false, false, true);
363                 // if this is a 64 lightstyle savegame produced by Quake, stop now
364                 // we have to check this because darkplaces may save more than 64
365                 if (com_token[0] == '{')
366                 {
367                         t = start;
368                         break;
369                 }
370                 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
371         }
372
373         if(developer_entityparsing.integer)
374                 Con_Printf("SV_Loadgame_f: skipping until globals\n");
375
376         // now skip everything before the first opening brace
377         // (this is for forward compatibility, so that older versions (at
378         // least ones with this fix) can load savegames with extra data before the
379         // first brace, as might be produced by a later engine version)
380         for (;;)
381         {
382                 start = t;
383                 if (!COM_ParseToken_Simple(&t, false, false, true))
384                         break;
385                 if (com_token[0] == '{')
386                 {
387                         t = start;
388                         break;
389                 }
390         }
391
392         // unlink all entities
393         World_UnlinkAll(&sv.world);
394
395 // load the edicts out of the savegame file
396         end = t;
397         for (;;)
398         {
399                 start = t;
400                 while (COM_ParseToken_Simple(&t, false, false, true))
401                         if (!strcmp(com_token, "}"))
402                                 break;
403                 if (!COM_ParseToken_Simple(&start, false, false, true))
404                 {
405                         // end of file
406                         break;
407                 }
408                 if (strcmp(com_token,"{"))
409                 {
410                         Mem_Free(text);
411                         Host_Error ("First token isn't a brace");
412                 }
413
414                 if (entnum == -1)
415                 {
416                         if(developer_entityparsing.integer)
417                                 Con_Printf("SV_Loadgame_f: loading globals\n");
418
419                         // parse the global vars
420                         PRVM_ED_ParseGlobals (prog, start);
421
422                         // restore the autocvar globals
423                         Cvar_UpdateAllAutoCvars(prog->console_cmd->cvars);
424                 }
425                 else
426                 {
427                         // parse an edict
428                         if (entnum >= MAX_EDICTS)
429                         {
430                                 Mem_Free(text);
431                                 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
432                         }
433                         while (entnum >= prog->max_edicts)
434                                 PRVM_MEM_IncreaseEdicts(prog);
435                         ent = PRVM_EDICT_NUM(entnum);
436                         memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
437                         ent->free = false;
438
439                         if(developer_entityparsing.integer)
440                                 Con_Printf("SV_Loadgame_f: loading edict %d\n", entnum);
441
442                         PRVM_ED_ParseEdict (prog, start, ent);
443
444                         // link it into the bsp tree
445                         if (!ent->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax)))
446                                 SV_LinkEdict(ent);
447                 }
448
449                 end = t;
450                 entnum++;
451         }
452
453         prog->num_edicts = entnum;
454         sv.time = time;
455
456         for (i = 0;i < NUM_SPAWN_PARMS;i++)
457                 svs.clients[0].spawn_parms[i] = spawn_parms[i];
458
459         if(developer_entityparsing.integer)
460                 Con_Printf("SV_Loadgame_f: skipping until extended data\n");
461
462         // read extended data if present
463         // the extended data is stored inside a /* */ comment block, which the
464         // parser intentionally skips, so we have to check for it manually here
465         if(end)
466         {
467                 while (*end == '\r' || *end == '\n')
468                         end++;
469                 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
470                 {
471                         if(developer_entityparsing.integer)
472                                 Con_Printf("SV_Loadgame_f: loading extended data\n");
473
474                         Con_Printf("Loading extended DarkPlaces savegame\n");
475                         t = end + 2;
476                         memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
477                         memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
478                         memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
479                         BufStr_Flush(prog);
480
481                         while (COM_ParseToken_Simple(&t, false, false, true))
482                         {
483                                 if (!strcmp(com_token, "sv.lightstyles"))
484                                 {
485                                         COM_ParseToken_Simple(&t, false, false, true);
486                                         i = atoi(com_token);
487                                         COM_ParseToken_Simple(&t, false, false, true);
488                                         if (i >= 0 && i < MAX_LIGHTSTYLES)
489                                                 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
490                                         else
491                                                 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
492                                 }
493                                 else if (!strcmp(com_token, "sv.model_precache"))
494                                 {
495                                         COM_ParseToken_Simple(&t, false, false, true);
496                                         i = atoi(com_token);
497                                         COM_ParseToken_Simple(&t, false, false, true);
498                                         if (i >= 0 && i < MAX_MODELS)
499                                         {
500                                                 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
501                                                 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
502                                         }
503                                         else
504                                                 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
505                                 }
506                                 else if (!strcmp(com_token, "sv.sound_precache"))
507                                 {
508                                         COM_ParseToken_Simple(&t, false, false, true);
509                                         i = atoi(com_token);
510                                         COM_ParseToken_Simple(&t, false, false, true);
511                                         if (i >= 0 && i < MAX_SOUNDS)
512                                                 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
513                                         else
514                                                 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
515                                 }
516                                 else if (!strcmp(com_token, "sv.buffer"))
517                                 {
518                                         if (COM_ParseToken_Simple(&t, false, false, true))
519                                         {
520                                                 i = atoi(com_token);
521                                                 if (i >= 0)
522                                                 {
523                                                         k = STRINGBUFFER_SAVED;
524                                                         if (COM_ParseToken_Simple(&t, false, false, true))
525                                                                 k |= atoi(com_token);
526                                                         if (!BufStr_FindCreateReplace(prog, i, k, "string"))
527                                                                 Con_Printf(CON_ERROR "failed to create stringbuffer %i\n", i);
528                                                 }
529                                                 else
530                                                         Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
531                                         }
532                                         else
533                                                 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
534                                 }
535                                 else if (!strcmp(com_token, "sv.bufstr"))
536                                 {
537                                         if (!COM_ParseToken_Simple(&t, false, false, true))
538                                                 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
539                                         else
540                                         {
541                                                 i = atoi(com_token);
542                                                 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
543                                                 if (stringbuffer)
544                                                 {
545                                                         if (COM_ParseToken_Simple(&t, false, false, true))
546                                                         {
547                                                                 k = atoi(com_token);
548                                                                 if (COM_ParseToken_Simple(&t, false, false, true))
549                                                                         BufStr_Set(prog, stringbuffer, k, com_token);
550                                                                 else
551                                                                         Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
552                                                         }
553                                                         else
554                                                                 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
555                                                 }
556                                                 else
557                                                         Con_Printf(CON_ERROR "failed to create stringbuffer %i \"%s\"\n", i, com_token);
558                                         }
559                                 }       
560                                 // skip any trailing text or unrecognized commands
561                                 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
562                                         ;
563                         }
564                 }
565         }
566         Mem_Free(text);
567
568         // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
569         numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
570         for (i = 0; i < numbuffers; i++)
571         {
572                 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
573                         if (stringbuffer->flags & STRINGBUFFER_TEMP)
574                                 BufStr_Del(prog, stringbuffer);
575         }
576
577         if(developer_entityparsing.integer)
578                 Con_Printf("SV_Loadgame_f: finished\n");
579
580         // make sure we're connected to loopback
581         if(sv.active && host.hook.ConnectLocal)
582                 host.hook.ConnectLocal();
583 }