File: server\sv_ccmds.c
1 /*
2 Copyright (C) 1997-2001 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 "server.h"
22
23 /*
24 ===============================================================================
25
26 OPERATOR CONSOLE ONLY COMMANDS
27
28 These commands can only be entered from stdin or by a remote operator datagram
29 ===============================================================================
30 */
31
32 /*
33 ====================
34 SV_SetMaster_f
35
36 Specify a list of master servers
37 ====================
38 */
39 void SV_SetMaster_f (void)
40 {
41 int i, slot;
42
43 // only dedicated servers send heartbeats
44 if (!dedicated->value)
45 {
46 Com_Printf ("Only dedicated servers use masters.\n");
47 return;
48 }
49
50 // make sure the server is listed public
51 Cvar_Set ("public", "1");
52
53 for (i=1 ; i<MAX_MASTERS ; i++)
54 memset (&master_adr[i], 0, sizeof(master_adr[i]));
55
56 slot = 1; // slot 0 will always contain the id master
57 for (i=1 ; i<Cmd_Argc() ; i++)
58 {
59 if (slot == MAX_MASTERS)
60 break;
61
62 if (!NET_StringToAdr (Cmd_Argv(i), &master_adr[i]))
63 {
64 Com_Printf ("Bad address: %s\n", Cmd_Argv(i));
65 continue;
66 }
67 if (master_adr[slot].port == 0)
68 master_adr[slot].port = BigShort (PORT_MASTER);
69
70 Com_Printf ("Master server at %s\n", NET_AdrToString (master_adr[slot]));
71
72 Com_Printf ("Sending a ping.\n");
73
74 Netchan_OutOfBandPrint (NS_SERVER, master_adr[slot], "ping");
75
76 slot++;
77 }
78
79 svs.last_heartbeat = -9999999;
80 }
81
82
83
84 /*
85 ==================
86 SV_SetPlayer
87
88 Sets sv_client and sv_player to the player with idnum Cmd_Argv(1)
89 ==================
90 */
91 qboolean SV_SetPlayer (void)
92 {
93 client_t *cl;
94 int i;
95 int idnum;
96 char *s;
97
98 if (Cmd_Argc() < 2)
99 return false;
100
101 s = Cmd_Argv(1);
102
103 // numeric values are just slot numbers
104 if (s[0] >= '0' && s[0] <= '9')
105 {
106 idnum = atoi(Cmd_Argv(1));
107 if (idnum < 0 || idnum >= maxclients->value)
108 {
109 Com_Printf ("Bad client slot: %i\n", idnum);
110 return false;
111 }
112
113 sv_client = &svs.clients[idnum];
114 sv_player = sv_client->edict;
115 if (!sv_client->state)
116 {
117 Com_Printf ("Client %i is not active\n", idnum);
118 return false;
119 }
120 return true;
121 }
122
123 // check for a name match
124 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
125 {
126 if (!cl->state)
127 continue;
128 if (!strcmp(cl->name, s))
129 {
130 sv_client = cl;
131 sv_player = sv_client->edict;
132 return true;
133 }
134 }
135
136 Com_Printf ("Userid %s is not on the server\n", s);
137 return false;
138 }
139
140
141 /*
142 ===============================================================================
143
144 SAVEGAME FILES
145
146 ===============================================================================
147 */
148
149 /*
150 =====================
151 SV_WipeSavegame
152
153 Delete save/<XXX>/
154 =====================
155 */
156 void SV_WipeSavegame (char *savename)
157 {
158 char name[MAX_OSPATH];
159 char *s;
160
161 Com_DPrintf("SV_WipeSaveGame(%s)\n", savename);
162
163 Com_sprintf (name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir (), savename);
164 remove (name);
165 Com_sprintf (name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir (), savename);
166 remove (name);
167
168 Com_sprintf (name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir (), savename);
169 s = Sys_FindFirst( name, 0, 0 );
170 while (s)
171 {
172 remove (s);
173 s = Sys_FindNext( 0, 0 );
174 }
175 Sys_FindClose ();
176 Com_sprintf (name, sizeof(name), "%s/save/%s/*.sv2", FS_Gamedir (), savename);
177 s = Sys_FindFirst(name, 0, 0 );
178 while (s)
179 {
180 remove (s);
181 s = Sys_FindNext( 0, 0 );
182 }
183 Sys_FindClose ();
184 }
185
186
187 /*
188 ================
189 CopyFile
190 ================
191 */
192 void CopyFile (char *src, char *dst)
193 {
194 FILE *f1, *f2;
195 int l;
196 byte buffer[65536];
197
198 Com_DPrintf ("CopyFile (%s, %s)\n", src, dst);
199
200 f1 = fopen (src, "rb");
201 if (!f1)
202 return;
203 f2 = fopen (dst, "wb");
204 if (!f2)
205 {
206 fclose (f1);
207 return;
208 }
209
210 while (1)
211 {
212 l = fread (buffer, 1, sizeof(buffer), f1);
213 if (!l)
214 break;
215 fwrite (buffer, 1, l, f2);
216 }
217
218 fclose (f1);
219 fclose (f2);
220 }
221
222
223 /*
224 ================
225 SV_CopySaveGame
226 ================
227 */
228 void SV_CopySaveGame (char *src, char *dst)
229 {
230 char name[MAX_OSPATH], name2[MAX_OSPATH];
231 int l, len;
232 char *found;
233
234 Com_DPrintf("SV_CopySaveGame(%s, %s)\n", src, dst);
235
236 SV_WipeSavegame (dst);
237
238 // copy the savegame over
239 Com_sprintf (name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), src);
240 Com_sprintf (name2, sizeof(name2), "%s/save/%s/server.ssv", FS_Gamedir(), dst);
241 FS_CreatePath (name2);
242 CopyFile (name, name2);
243
244 Com_sprintf (name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir(), src);
245 Com_sprintf (name2, sizeof(name2), "%s/save/%s/game.ssv", FS_Gamedir(), dst);
246 CopyFile (name, name2);
247
248 Com_sprintf (name, sizeof(name), "%s/save/%s/", FS_Gamedir(), src);
249 len = strlen(name);
250 Com_sprintf (name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir(), src);
251 found = Sys_FindFirst(name, 0, 0 );
252 while (found)
253 {
254 strcpy (name+len, found+len);
255
256 Com_sprintf (name2, sizeof(name2), "%s/save/%s/%s", FS_Gamedir(), dst, found+len);
257 CopyFile (name, name2);
258
259 // change sav to sv2
260 l = strlen(name);
261 strcpy (name+l-3, "sv2");
262 l = strlen(name2);
263 strcpy (name2+l-3, "sv2");
264 CopyFile (name, name2);
265
266 found = Sys_FindNext( 0, 0 );
267 }
268 Sys_FindClose ();
269 }
270
271
272 /*
273 ==============
274 SV_WriteLevelFile
275
276 ==============
277 */
278 void SV_WriteLevelFile (void)
279 {
280 char name[MAX_OSPATH];
281 FILE *f;
282
283 Com_DPrintf("SV_WriteLevelFile()\n");
284
285 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name);
286 f = fopen(name, "wb");
287 if (!f)
288 {
289 Com_Printf ("Failed to open %s\n", name);
290 return;
291 }
292 fwrite (sv.configstrings, sizeof(sv.configstrings), 1, f);
293 CM_WritePortalState (f);
294 fclose (f);
295
296 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
297 ge->WriteLevel (name);
298 }
299
300 /*
301 ==============
302 SV_ReadLevelFile
303
304 ==============
305 */
306 void SV_ReadLevelFile (void)
307 {
308 char name[MAX_OSPATH];
309 FILE *f;
310
311 Com_DPrintf("SV_ReadLevelFile()\n");
312
313 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name);
314 f = fopen(name, "rb");
315 if (!f)
316 {
317 Com_Printf ("Failed to open %s\n", name);
318 return;
319 }
320 FS_Read (sv.configstrings, sizeof(sv.configstrings), f);
321 CM_ReadPortalState (f);
322 fclose (f);
323
324 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
325 ge->ReadLevel (name);
326 }
327
328 /*
329 ==============
330 SV_WriteServerFile
331
332 ==============
333 */
334 void SV_WriteServerFile (qboolean autosave)
335 {
336 FILE *f;
337 cvar_t *var;
338 char name[MAX_OSPATH], string[128];
339 char comment[32];
340 time_t aclock;
341 struct tm *newtime;
342
343 Com_DPrintf("SV_WriteServerFile(%s)\n", autosave ? "true" : "false");
344
345 Com_sprintf (name, sizeof(name), "%s/save/current/server.ssv", FS_Gamedir());
346 f = fopen (name, "wb");
347 if (!f)
348 {
349 Com_Printf ("Couldn't write %s\n", name);
350 return;
351 }
352 // write the comment field
353 memset (comment, 0, sizeof(comment));
354
355 if (!autosave)
356 {
357 time (&aclock);
358 newtime = localtime (&aclock);
359 Com_sprintf (comment,sizeof(comment), "%2i:%i%i %2i/%2i ", newtime->tm_hour
360 , newtime->tm_min/10, newtime->tm_min%10,
361 newtime->tm_mon+1, newtime->tm_mday);
362 strncat (comment, sv.configstrings[CS_NAME], sizeof(comment)-1-strlen(comment) );
363 }
364 else
365 { // autosaved
366 Com_sprintf (comment, sizeof(comment), "ENTERING %s", sv.configstrings[CS_NAME]);
367 }
368
369 fwrite (comment, 1, sizeof(comment), f);
370
371 // write the mapcmd
372 fwrite (svs.mapcmd, 1, sizeof(svs.mapcmd), f);
373
374 // write all CVAR_LATCH cvars
375 // these will be things like coop, skill, deathmatch, etc
376 for (var = cvar_vars ; var ; var=var->next)
377 {
378 if (!(var->flags & CVAR_LATCH))
379 continue;
380 if (strlen(var->name) >= sizeof(name)-1
381 || strlen(var->string) >= sizeof(string)-1)
382 {
383 Com_Printf ("Cvar too long: %s = %s\n", var->name, var->string);
384 continue;
385 }
386 memset (name, 0, sizeof(name));
387 memset (string, 0, sizeof(string));
388 strcpy (name, var->name);
389 strcpy (string, var->string);
390 fwrite (name, 1, sizeof(name), f);
391 fwrite (string, 1, sizeof(string), f);
392 }
393
394 fclose (f);
395
396 // write game state
397 Com_sprintf (name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir());
398 ge->WriteGame (name, autosave);
399 }
400
401 /*
402 ==============
403 SV_ReadServerFile
404
405 ==============
406 */
407 void SV_ReadServerFile (void)
408 {
409 FILE *f;
410 char name[MAX_OSPATH], string[128];
411 char comment[32];
412 char mapcmd[MAX_TOKEN_CHARS];
413
414 Com_DPrintf("SV_ReadServerFile()\n");
415
416 Com_sprintf (name, sizeof(name), "%s/save/current/server.ssv", FS_Gamedir());
417 f = fopen (name, "rb");
418 if (!f)
419 {
420 Com_Printf ("Couldn't read %s\n", name);
421 return;
422 }
423 // read the comment field
424 FS_Read (comment, sizeof(comment), f);
425
426 // read the mapcmd
427 FS_Read (mapcmd, sizeof(mapcmd), f);
428
429 // read all CVAR_LATCH cvars
430 // these will be things like coop, skill, deathmatch, etc
431 while (1)
432 {
433 if (!fread (name, 1, sizeof(name), f))
434 break;
435 FS_Read (string, sizeof(string), f);
436 Com_DPrintf ("Set %s = %s\n", name, string);
437 Cvar_ForceSet (name, string);
438 }
439
440 fclose (f);
441
442 // start a new game fresh with new cvars
443 SV_InitGame ();
444
445 strcpy (svs.mapcmd, mapcmd);
446
447 // read game state
448 Com_sprintf (name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir());
449 ge->ReadGame (name);
450 }
451
452
453 //=========================================================
454
455
456
457
458 /*
459 ==================
460 SV_DemoMap_f
461
462 Puts the server in demo mode on a specific map/cinematic
463 ==================
464 */
465 void SV_DemoMap_f (void)
466 {
467 SV_Map (true, Cmd_Argv(1), false );
468 }
469
470 /*
471 ==================
472 SV_GameMap_f
473
474 Saves the state of the map just being exited and goes to a new map.
475
476 If the initial character of the map string is '*', the next map is
477 in a new unit, so the current savegame directory is cleared of
478 map files.
479
480 Example:
481
482 *inter.cin+jail
483
484 Clears the archived maps, plays the inter.cin cinematic, then
485 goes to map jail.bsp.
486 ==================
487 */
488 void SV_GameMap_f (void)
489 {
490 char *map;
491 int i;
492 client_t *cl;
493 qboolean *savedInuse;
494
495 if (Cmd_Argc() != 2)
496 {
497 Com_Printf ("USAGE: gamemap <map>\n");
498 return;
499 }
500
501 Com_DPrintf("SV_GameMap(%s)\n", Cmd_Argv(1));
502
503 FS_CreatePath (va("%s/save/current/", FS_Gamedir()));
504
505 // check for clearing the current savegame
506 map = Cmd_Argv(1);
507 if (map[0] == '*')
508 {
509 // wipe all the *.sav files
510 SV_WipeSavegame ("current");
511 }
512 else
513 { // save the map just exited
514 if (sv.state == ss_game)
515 {
516 // clear all the client inuse flags before saving so that
517 // when the level is re-entered, the clients will spawn
518 // at spawn points instead of occupying body shells
519 savedInuse = malloc(maxclients->value * sizeof(qboolean));
520 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
521 {
522 savedInuse[i] = cl->edict->inuse;
523 cl->edict->inuse = false;
524 }
525
526 SV_WriteLevelFile ();
527
528 // we must restore these for clients to transfer over correctly
529 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
530 cl->edict->inuse = savedInuse[i];
531 free (savedInuse);
532 }
533 }
534
535 // start up the next map
536 SV_Map (false, Cmd_Argv(1), false );
537
538 // archive server state
539 strncpy (svs.mapcmd, Cmd_Argv(1), sizeof(svs.mapcmd)-1);
540
541 // copy off the level to the autosave slot
542 if (!dedicated->value)
543 {
544 SV_WriteServerFile (true);
545 SV_CopySaveGame ("current", "save0");
546 }
547 }
548
549 /*
550 ==================
551 SV_Map_f
552
553 Goes directly to a given map without any savegame archiving.
554 For development work
555 ==================
556 */
557 void SV_Map_f (void)
558 {
559 char *map;
560 char expanded[MAX_QPATH];
561
562 // if not a pcx, demo, or cinematic, check to make sure the level exists
563 map = Cmd_Argv(1);
564 if (!strstr (map, "."))
565 {
566 Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
567 if (FS_LoadFile (expanded, NULL) == -1)
568 {
569 Com_Printf ("Can't find %s\n", expanded);
570 return;
571 }
572 }
573
574 sv.state = ss_dead; // don't save current level when changing
575 SV_WipeSavegame("current");
576 SV_GameMap_f ();
577 }
578
579 /*
580 =====================================================================
581
582 SAVEGAMES
583
584 =====================================================================
585 */
586
587
588 /*
589 ==============
590 SV_Loadgame_f
591
592 ==============
593 */
594 void SV_Loadgame_f (void)
595 {
596 char name[MAX_OSPATH];
597 FILE *f;
598 char *dir;
599
600 if (Cmd_Argc() != 2)
601 {
602 Com_Printf ("USAGE: loadgame <directory>\n");
603 return;
604 }
605
606 Com_Printf ("Loading game...\n");
607
608 dir = Cmd_Argv(1);
609 if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") )
610 {
611 Com_Printf ("Bad savedir.\n");
612 }
613
614 // make sure the server.ssv file exists
615 Com_sprintf (name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), Cmd_Argv(1));
616 f = fopen (name, "rb");
617 if (!f)
618 {
619 Com_Printf ("No such savegame: %s\n", name);
620 return;
621 }
622 fclose (f);
623
624 SV_CopySaveGame (Cmd_Argv(1), "current");
625
626 SV_ReadServerFile ();
627
628 // go to the map
629 sv.state = ss_dead; // don't save current level when changing
630 SV_Map (false, svs.mapcmd, true);
631 }
632
633
634
635 /*
636 ==============
637 SV_Savegame_f
638
639 ==============
640 */
641 void SV_Savegame_f (void)
642 {
643 char *dir;
644
645 if (sv.state != ss_game)
646 {
647 Com_Printf ("You must be in a game to save.\n");
648 return;
649 }
650
651 if (Cmd_Argc() != 2)
652 {
653 Com_Printf ("USAGE: savegame <directory>\n");
654 return;
655 }
656
657 if (Cvar_VariableValue("deathmatch"))
658 {
659 Com_Printf ("Can't savegame in a deathmatch\n");
660 return;
661 }
662
663 if (!strcmp (Cmd_Argv(1), "current"))
664 {
665 Com_Printf ("Can't save to 'current'\n");
666 return;
667 }
668
669 if (maxclients->value == 1 && svs.clients[0].edict->client->ps.stats[STAT_HEALTH] <= 0)
670 {
671 Com_Printf ("\nCan't savegame while dead!\n");
672 return;
673 }
674
675 dir = Cmd_Argv(1);
676 if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") )
677 {
678 Com_Printf ("Bad savedir.\n");
679 }
680
681 Com_Printf ("Saving game...\n");
682
683 // archive current level, including all client edicts.
684 // when the level is reloaded, they will be shells awaiting
685 // a connecting client
686 SV_WriteLevelFile ();
687
688 // save server state
689 SV_WriteServerFile (false);
690
691 // copy it off
692 SV_CopySaveGame ("current", dir);
693
694 Com_Printf ("Done.\n");
695 }
696
697 //===============================================================
698
699 /*
700 ==================
701 SV_Kick_f
702
703 Kick a user off of the server
704 ==================
705 */
706 void SV_Kick_f (void)
707 {
708 if (!svs.initialized)
709 {
710 Com_Printf ("No server running.\n");
711 return;
712 }
713
714 if (Cmd_Argc() != 2)
715 {
716 Com_Printf ("Usage: kick <userid>\n");
717 return;
718 }
719
720 if (!SV_SetPlayer ())
721 return;
722
723 SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked\n", sv_client->name);
724 // print directly, because the dropped client won't get the
725 // SV_BroadcastPrintf message
726 SV_ClientPrintf (sv_client, PRINT_HIGH, "You were kicked from the game\n");
727 SV_DropClient (sv_client);
728 sv_client->lastmessage = svs.realtime; // min case there is a funny zombie
729 }
730
731
732 /*
733 ================
734 SV_Status_f
735 ================
736 */
737 void SV_Status_f (void)
738 {
739 int i, j, l;
740 client_t *cl;
741 char *s;
742 int ping;
743 if (!svs.clients)
744 {
745 Com_Printf ("No server running.\n");
746 return;
747 }
748 Com_Printf ("map : %s\n", sv.name);
749
750 Com_Printf ("num score ping name lastmsg address qport \n");
751 Com_Printf ("--- ----- ---- --------------- ------- --------------------- ------\n");
752 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
753 {
754 if (!cl->state)
755 continue;
756 Com_Printf ("%3i ", i);
757 Com_Printf ("%5i ", cl->edict->client->ps.stats[STAT_FRAGS]);
758
759 if (cl->state == cs_connected)
760 Com_Printf ("CNCT ");
761 else if (cl->state == cs_zombie)
762 Com_Printf ("ZMBI ");
763 else
764 {
765 ping = cl->ping < 9999 ? cl->ping : 9999;
766 Com_Printf ("%4i ", ping);
767 }
768
769 Com_Printf ("%s", cl->name);
770 l = 16 - strlen(cl->name);
771 for (j=0 ; j<l ; j++)
772 Com_Printf (" ");
773
774 Com_Printf ("%7i ", svs.realtime - cl->lastmessage );
775
776 s = NET_AdrToString ( cl->netchan.remote_address);
777 Com_Printf ("%s", s);
778 l = 22 - strlen(s);
779 for (j=0 ; j<l ; j++)
780 Com_Printf (" ");
781
782 Com_Printf ("%5i", cl->netchan.qport);
783
784 Com_Printf ("\n");
785 }
786 Com_Printf ("\n");
787 }
788
789 /*
790 ==================
791 SV_ConSay_f
792 ==================
793 */
794 void SV_ConSay_f(void)
795 {
796 client_t *client;
797 int j;
798 char *p;
799 char text[1024];
800
801 if (Cmd_Argc () < 2)
802 return;
803
804 strcpy (text, "console: ");
805 p = Cmd_Args();
806
807 if (*p == '"')
808 {
809 p++;
810 p[strlen(p)-1] = 0;
811 }
812
813 strcat(text, p);
814
815 for (j = 0, client = svs.clients; j < maxclients->value; j++, client++)
816 {
817 if (client->state != cs_spawned)
818 continue;
819 SV_ClientPrintf(client, PRINT_CHAT, "%s\n", text);
820 }
821 }
822
823
824 /*
825 ==================
826 SV_Heartbeat_f
827 ==================
828 */
829 void SV_Heartbeat_f (void)
830 {
831 svs.last_heartbeat = -9999999;
832 }
833
834
835 /*
836 ===========
837 SV_Serverinfo_f
838
839 Examine or change the serverinfo string
840 ===========
841 */
842 void SV_Serverinfo_f (void)
843 {
844 Com_Printf ("Server info settings:\n");
845 Info_Print (Cvar_Serverinfo());
846 }
847
848
849 /*
850 ===========
851 SV_DumpUser_f
852
853 Examine all a users info strings
854 ===========
855 */
856 void SV_DumpUser_f (void)
857 {
858 if (Cmd_Argc() != 2)
859 {
860 Com_Printf ("Usage: info <userid>\n");
861 return;
862 }
863
864 if (!SV_SetPlayer ())
865 return;
866
867 Com_Printf ("userinfo\n");
868 Com_Printf ("--------\n");
869 Info_Print (sv_client->userinfo);
870
871 }
872
873
874 /*
875 ==============
876 SV_ServerRecord_f
877
878 Begins server demo recording. Every entity and every message will be
879 recorded, but no playerinfo will be stored. Primarily for demo merging.
880 ==============
881 */
882 void SV_ServerRecord_f (void)
883 {
884 char name[MAX_OSPATH];
885 char buf_data[32768];
886 sizebuf_t buf;
887 int len;
888 int i;
889
890 if (Cmd_Argc() != 2)
891 {
892 Com_Printf ("serverrecord <demoname>\n");
893 return;
894 }
895
896 if (svs.demofile)
897 {
898 Com_Printf ("Already recording.\n");
899 return;
900 }
901
902 if (sv.state != ss_game)
903 {
904 Com_Printf ("You must be in a level to record.\n");
905 return;
906 }
907
908 //
909 // open the demo file
910 //
911 Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1));
912
913 Com_Printf ("recording to %s.\n", name);
914 FS_CreatePath (name);
915 svs.demofile = fopen (name, "wb");
916 if (!svs.demofile)
917 {
918 Com_Printf ("ERROR: couldn't open.\n");
919 return;
920 }
921
922 // setup a buffer to catch all multicasts
923 SZ_Init (&svs.demo_multicast, svs.demo_multicast_buf, sizeof(svs.demo_multicast_buf));
924
925 //
926 // write a single giant fake message with all the startup info
927 //
928 SZ_Init (&buf, buf_data, sizeof(buf_data));
929
930 //
931 // serverdata needs to go over for all types of servers
932 // to make sure the protocol is right, and to set the gamedir
933 //
934 // send the serverdata
935 MSG_WriteByte (&buf, svc_serverdata);
936 MSG_WriteLong (&buf, PROTOCOL_VERSION);
937 MSG_WriteLong (&buf, svs.spawncount);
938 // 2 means server demo
939 MSG_WriteByte (&buf, 2); // demos are always attract loops
940 MSG_WriteString (&buf, Cvar_VariableString ("gamedir"));
941 MSG_WriteShort (&buf, -1);
942 // send full levelname
943 MSG_WriteString (&buf, sv.configstrings[CS_NAME]);
944
945 for (i=0 ; i<MAX_CONFIGSTRINGS ; i++)
946 if (sv.configstrings[i][0])
947 {
948 MSG_WriteByte (&buf, svc_configstring);
949 MSG_WriteShort (&buf, i);
950 MSG_WriteString (&buf, sv.configstrings[i]);
951 }
952
953 // write it to the demo file
954 Com_DPrintf ("signon message length: %i\n", buf.cursize);
955 len = LittleLong (buf.cursize);
956 fwrite (&len, 4, 1, svs.demofile);
957 fwrite (buf.data, buf.cursize, 1, svs.demofile);
958
959 // the rest of the demo file will be individual frames
960 }
961
962
963 /*
964 ==============
965 SV_ServerStop_f
966
967 Ends server demo recording
968 ==============
969 */
970 void SV_ServerStop_f (void)
971 {
972 if (!svs.demofile)
973 {
974 Com_Printf ("Not doing a serverrecord.\n");
975 return;
976 }
977 fclose (svs.demofile);
978 svs.demofile = NULL;
979 Com_Printf ("Recording completed.\n");
980 }
981
982
983 /*
984 ===============
985 SV_KillServer_f
986
987 Kick everyone off, possibly in preparation for a new game
988
989 ===============
990 */
991 void SV_KillServer_f (void)
992 {
993 if (!svs.initialized)
994 return;
995 SV_Shutdown ("Server was killed.\n", false);
996 NET_Config ( false ); // close network sockets
997 }
998
999 /*
1000 ===============
1001 SV_ServerCommand_f
1002
1003 Let the game dll handle a command
1004 ===============
1005 */
1006 void SV_ServerCommand_f (void)
1007 {
1008 if (!ge)
1009 {
1010 Com_Printf ("No game loaded.\n");
1011 return;
1012 }
1013
1014 ge->ServerCommand();
1015 }
1016
1017 //===========================================================
1018
1019 /*
1020 ==================
1021 SV_InitOperatorCommands
1022 ==================
1023 */
1024 void SV_InitOperatorCommands (void)
1025 {
1026 Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);
1027 Cmd_AddCommand ("kick", SV_Kick_f);
1028 Cmd_AddCommand ("status", SV_Status_f);
1029 Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
1030 Cmd_AddCommand ("dumpuser", SV_DumpUser_f);
1031
1032 Cmd_AddCommand ("map", SV_Map_f);
1033 Cmd_AddCommand ("demomap", SV_DemoMap_f);
1034 Cmd_AddCommand ("gamemap", SV_GameMap_f);
1035 Cmd_AddCommand ("setmaster", SV_SetMaster_f);
1036
1037 if ( dedicated->value )
1038 Cmd_AddCommand ("say", SV_ConSay_f);
1039
1040 Cmd_AddCommand ("serverrecord", SV_ServerRecord_f);
1041 Cmd_AddCommand ("serverstop", SV_ServerStop_f);
1042
1043 Cmd_AddCommand ("save", SV_Savegame_f);
1044 Cmd_AddCommand ("load", SV_Loadgame_f);
1045
1046 Cmd_AddCommand ("killserver", SV_KillServer_f);
1047
1048 Cmd_AddCommand ("sv", SV_ServerCommand_f);
1049 }
1050
1051