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