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.
25 #include "prvm_cmds.h"
28 // for secure rcon authentication
34 cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
35 cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
36 cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
37 cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
38 cvar_t sv_namechangetimer = {CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
39 cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
40 cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
41 cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
42 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
43 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
44 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
45 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
46 cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
47 qboolean allowcheats = false;
49 extern qboolean host_shuttingdown;
50 extern cvar_t developer_entityparsing;
58 void Host_Quit_f (void)
61 Con_Printf("shutting down already!\n");
71 static void Host_Status_f (void)
73 prvm_prog_t *prog = SVVM_prog;
76 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
77 void (*print) (const char *fmt, ...);
78 char ip[48]; // can contain a full length v6 address with [] and a port
82 if (cmd_source == src_command)
84 // if running a client, try to send over network so the client's status report parser will see the report
85 if (cls.state == ca_connected)
87 Cmd_ForwardToServer ();
93 print = SV_ClientPrintf;
101 if (strcmp(Cmd_Argv(1), "1") == 0)
103 else if (strcmp(Cmd_Argv(1), "2") == 0)
107 for (players = 0, i = 0;i < svs.maxclients;i++)
108 if (svs.clients[i].active)
110 print ("host: %s\n", Cvar_VariableString ("hostname"));
111 print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
112 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
113 print ("map: %s\n", sv.name);
114 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
115 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
118 print ("^2IP %%pl ping time frags no name\n");
120 print ("^5IP no name\n");
122 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
129 if (in == 0 || in == 1)
131 seconds = (int)(realtime - client->connecttime);
132 minutes = seconds / 60;
135 seconds -= (minutes * 60);
136 hours = minutes / 60;
138 minutes -= (hours * 60);
144 if (client->netconnection)
145 for (j = 0;j < NETGRAPH_PACKETS;j++)
146 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
148 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
149 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
152 if(sv_status_privacy.integer && cmd_source != src_command)
153 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
155 strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48);
157 frags = client->frags;
159 if(sv_status_show_qcstatus.integer)
161 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
162 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
168 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
169 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
173 frags = atoi(qcstatus);
177 if (in == 0) // default layout
179 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
181 // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
182 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
187 // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
188 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
192 else if (in == 1) // extended layout
194 print ("%s%-47s %2i %4i %2i:%02i:%02i %4i #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
196 else if (in == 2) // reduced layout
198 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
208 Sets client to godmode
211 static void Host_God_f (void)
213 prvm_prog_t *prog = SVVM_prog;
216 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
220 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
221 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
222 SV_ClientPrint("godmode OFF\n");
224 SV_ClientPrint("godmode ON\n");
227 static void Host_Notarget_f (void)
229 prvm_prog_t *prog = SVVM_prog;
232 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
236 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
237 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
238 SV_ClientPrint("notarget OFF\n");
240 SV_ClientPrint("notarget ON\n");
243 qboolean noclip_anglehack;
245 static void Host_Noclip_f (void)
247 prvm_prog_t *prog = SVVM_prog;
250 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
254 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
256 noclip_anglehack = true;
257 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
258 SV_ClientPrint("noclip ON\n");
262 noclip_anglehack = false;
263 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
264 SV_ClientPrint("noclip OFF\n");
272 Sets client to flymode
275 static void Host_Fly_f (void)
277 prvm_prog_t *prog = SVVM_prog;
280 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
284 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
286 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
287 SV_ClientPrint("flymode ON\n");
291 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
292 SV_ClientPrint("flymode OFF\n");
303 void Host_Pings_f (void); // called by Host_Ping_f
304 static void Host_Ping_f (void)
308 void (*print) (const char *fmt, ...);
310 if (cmd_source == src_command)
312 // if running a client, try to send over network so the client's ping report parser will see the report
313 if (cls.state == ca_connected)
315 Cmd_ForwardToServer ();
321 print = SV_ClientPrintf;
326 print("Client ping times:\n");
327 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
331 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
334 // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
335 // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
340 ===============================================================================
344 ===============================================================================
348 ======================
353 command from the console. Active clients are kicked off.
354 ======================
356 static void Host_Map_f (void)
358 char level[MAX_QPATH];
362 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
366 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
367 if (gamemode == GAME_DELUXEQUAKE)
368 Cvar_Set("warpmark", "");
370 cls.demonum = -1; // stop demo loop in case this fails
373 Host_ShutdownServer();
375 if(svs.maxclients != svs.maxclients_next)
377 svs.maxclients = svs.maxclients_next;
379 Mem_Free(svs.clients);
380 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
385 if (key_dest == key_menu || key_dest == key_menu_grabbed)
390 svs.serverflags = 0; // haven't completed an episode yet
391 allowcheats = sv_cheats.integer != 0;
392 strlcpy(level, Cmd_Argv(1), sizeof(level));
393 SV_SpawnServer(level);
394 if (sv.active && cls.state == ca_disconnected)
395 CL_EstablishConnection("local:1", -2);
402 Goes to a new map, taking all clients along
405 static void Host_Changelevel_f (void)
407 char level[MAX_QPATH];
411 Con_Print("changelevel <levelname> : continue game on a new level\n");
422 if (key_dest == key_menu || key_dest == key_menu_grabbed)
427 SV_SaveSpawnparms ();
428 allowcheats = sv_cheats.integer != 0;
429 strlcpy(level, Cmd_Argv(1), sizeof(level));
430 SV_SpawnServer(level);
431 if (sv.active && cls.state == ca_disconnected)
432 CL_EstablishConnection("local:1", -2);
439 Restarts the current server for a dead player
442 static void Host_Restart_f (void)
444 char mapname[MAX_QPATH];
448 Con_Print("restart : restart current level\n");
453 Con_Print("Only the server may restart\n");
459 if (key_dest == key_menu || key_dest == key_menu_grabbed)
464 allowcheats = sv_cheats.integer != 0;
465 strlcpy(mapname, sv.name, sizeof(mapname));
466 SV_SpawnServer(mapname);
467 if (sv.active && cls.state == ca_disconnected)
468 CL_EstablishConnection("local:1", -2);
475 This command causes the client to wait for the signon messages again.
476 This is sent just before a server changes levels
479 void Host_Reconnect_f (void)
482 // if not connected, reconnect to the most recent server
485 // if we have connected to a server recently, the userinfo
486 // will still contain its IP address, so get the address...
487 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
489 CL_EstablishConnection(temp, -1);
491 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
494 // if connected, do something based on protocol
495 if (cls.protocol == PROTOCOL_QUAKEWORLD)
497 // quakeworld can just re-login
498 if (cls.qw_downloadmemory) // don't change when downloading
503 if (cls.state == ca_connected && cls.signon < SIGNONS)
505 Con_Printf("reconnecting...\n");
506 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
507 MSG_WriteString(&cls.netcon->message, "new");
512 // netquake uses reconnect on level changes (silly)
515 Con_Print("reconnect : wait for signon messages again\n");
520 Con_Print("reconnect: no signon, ignoring reconnect\n");
523 cls.signon = 0; // need new connection messages
528 =====================
531 User command to connect to server
532 =====================
534 static void Host_Connect_f (void)
538 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
541 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
542 if(rcon_secure.integer <= 0)
543 Cvar_SetQuick(&rcon_password, "");
544 CL_EstablishConnection(Cmd_Argv(1), 2);
549 ===============================================================================
553 ===============================================================================
556 #define SAVEGAME_VERSION 5
558 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
561 int i, k, l, numbuffers, lightstyles = 64;
562 char comment[SAVEGAME_COMMENT_LENGTH+1];
563 char line[MAX_INPUTLINE];
567 // first we have to figure out if this can be saved in 64 lightstyles
568 // (for Quake compatibility)
569 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
570 if (sv.lightstyles[i][0])
573 isserver = prog == SVVM_prog;
575 Con_Printf("Saving game to %s...\n", name);
576 f = FS_OpenRealFile(name, "wb", false);
579 Con_Print("ERROR: couldn't open.\n");
583 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
585 memset(comment, 0, sizeof(comment));
587 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));
589 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
590 // convert space to _ to make stdio happy
591 // LordHavoc: convert control characters to _ as well
592 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
593 if (ISWHITESPACEORCONTROL(comment[i]))
595 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
597 FS_Printf(f, "%s\n", comment);
600 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
601 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
602 FS_Printf(f, "%d\n", current_skill);
603 FS_Printf(f, "%s\n", sv.name);
604 FS_Printf(f, "%f\n",sv.time);
608 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
609 FS_Printf(f, "(dummy)\n");
610 FS_Printf(f, "%d\n", 0);
611 FS_Printf(f, "%s\n", "(dummy)");
612 FS_Printf(f, "%f\n", realtime);
615 // write the light styles
616 for (i=0 ; i<lightstyles ; i++)
618 if (isserver && sv.lightstyles[i][0])
619 FS_Printf(f, "%s\n", sv.lightstyles[i]);
624 PRVM_ED_WriteGlobals (prog, f);
625 for (i=0 ; i<prog->num_edicts ; i++)
627 FS_Printf(f,"// edict %d\n", i);
628 //Con_Printf("edict %d...\n", i);
629 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
634 FS_Printf(f,"// DarkPlaces extended savegame\n");
635 // darkplaces extension - extra lightstyles, support for color lightstyles
636 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
637 if (isserver && sv.lightstyles[i][0])
638 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
640 // darkplaces extension - model precaches
641 for (i=1 ; i<MAX_MODELS ; i++)
642 if (sv.model_precache[i][0])
643 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
645 // darkplaces extension - sound precaches
646 for (i=1 ; i<MAX_SOUNDS ; i++)
647 if (sv.sound_precache[i][0])
648 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
650 // darkplaces extension - save buffers
651 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
652 for (i = 0; i < numbuffers; i++)
654 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
655 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
657 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
658 for(k = 0; k < stringbuffer->num_strings; k++)
660 if (!stringbuffer->strings[k])
662 // Parse the string a bit to turn special characters
663 // (like newline, specifically) into escape codes
664 s = stringbuffer->strings[k];
665 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
692 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
700 Con_Print("done.\n");
708 static void Host_Savegame_f (void)
710 prvm_prog_t *prog = SVVM_prog;
711 char name[MAX_QPATH];
712 qboolean deadflag = false;
716 Con_Print("Can't save - no server running.\n");
720 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
724 // singleplayer checks
727 Con_Print("Can't save in intermission.\n");
733 Con_Print("Can't savegame with a dead player\n");
738 Con_Print("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");
742 Con_Print("save <savename> : save a game\n");
746 if (strstr(Cmd_Argv(1), ".."))
748 Con_Print("Relative pathnames are not allowed.\n");
752 strlcpy (name, Cmd_Argv(1), sizeof (name));
753 FS_DefaultExtension (name, ".sav", sizeof (name));
755 Host_Savegame_to(prog, name);
765 static void Host_Loadgame_f (void)
767 prvm_prog_t *prog = SVVM_prog;
768 char filename[MAX_QPATH];
769 char mapname[MAX_QPATH];
776 int i, k, numbuffers;
779 float spawn_parms[NUM_SPAWN_PARMS];
780 prvm_stringbuffer_t *stringbuffer;
784 Con_Print("load <savename> : load a game\n");
788 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
789 FS_DefaultExtension (filename, ".sav", sizeof (filename));
791 Con_Printf("Loading game from %s...\n", filename);
793 // stop playing demos
794 if (cls.demoplayback)
799 if (key_dest == key_menu || key_dest == key_menu_grabbed)
804 cls.demonum = -1; // stop demo loop in case this fails
806 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
809 Con_Print("ERROR: couldn't open.\n");
813 if(developer_entityparsing.integer)
814 Con_Printf("Host_Loadgame_f: loading version\n");
817 COM_ParseToken_Simple(&t, false, false, true);
818 version = atoi(com_token);
819 if (version != SAVEGAME_VERSION)
822 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
826 if(developer_entityparsing.integer)
827 Con_Printf("Host_Loadgame_f: loading description\n");
830 COM_ParseToken_Simple(&t, false, false, true);
832 for (i = 0;i < NUM_SPAWN_PARMS;i++)
834 COM_ParseToken_Simple(&t, false, false, true);
835 spawn_parms[i] = atof(com_token);
838 COM_ParseToken_Simple(&t, false, false, true);
839 // this silliness is so we can load 1.06 save files, which have float skill values
840 current_skill = (int)(atof(com_token) + 0.5);
841 Cvar_SetValue ("skill", (float)current_skill);
843 if(developer_entityparsing.integer)
844 Con_Printf("Host_Loadgame_f: loading mapname\n");
847 COM_ParseToken_Simple(&t, false, false, true);
848 strlcpy (mapname, com_token, sizeof(mapname));
850 if(developer_entityparsing.integer)
851 Con_Printf("Host_Loadgame_f: loading time\n");
854 COM_ParseToken_Simple(&t, false, false, true);
855 time = atof(com_token);
857 allowcheats = sv_cheats.integer != 0;
859 if(developer_entityparsing.integer)
860 Con_Printf("Host_Loadgame_f: spawning server\n");
862 SV_SpawnServer (mapname);
866 Con_Print("Couldn't load map\n");
869 sv.paused = true; // pause until all clients connect
872 if(developer_entityparsing.integer)
873 Con_Printf("Host_Loadgame_f: loading light styles\n");
875 // load the light styles
880 for (i = 0;i < MAX_LIGHTSTYLES;i++)
884 COM_ParseToken_Simple(&t, false, false, true);
885 // if this is a 64 lightstyle savegame produced by Quake, stop now
886 // we have to check this because darkplaces may save more than 64
887 if (com_token[0] == '{')
892 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
895 if(developer_entityparsing.integer)
896 Con_Printf("Host_Loadgame_f: skipping until globals\n");
898 // now skip everything before the first opening brace
899 // (this is for forward compatibility, so that older versions (at
900 // least ones with this fix) can load savegames with extra data before the
901 // first brace, as might be produced by a later engine version)
905 if (!COM_ParseToken_Simple(&t, false, false, true))
907 if (com_token[0] == '{')
914 // unlink all entities
915 World_UnlinkAll(&sv.world);
917 // load the edicts out of the savegame file
922 while (COM_ParseToken_Simple(&t, false, false, true))
923 if (!strcmp(com_token, "}"))
925 if (!COM_ParseToken_Simple(&start, false, false, true))
930 if (strcmp(com_token,"{"))
933 Host_Error ("First token isn't a brace");
938 if(developer_entityparsing.integer)
939 Con_Printf("Host_Loadgame_f: loading globals\n");
941 // parse the global vars
942 PRVM_ED_ParseGlobals (prog, start);
944 // restore the autocvar globals
945 Cvar_UpdateAllAutoCvars();
950 if (entnum >= MAX_EDICTS)
953 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
955 while (entnum >= prog->max_edicts)
956 PRVM_MEM_IncreaseEdicts(prog);
957 ent = PRVM_EDICT_NUM(entnum);
958 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
959 ent->priv.server->free = false;
961 if(developer_entityparsing.integer)
962 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
964 PRVM_ED_ParseEdict (prog, start, ent);
966 // link it into the bsp tree
967 if (!ent->priv.server->free)
975 prog->num_edicts = entnum;
978 for (i = 0;i < NUM_SPAWN_PARMS;i++)
979 svs.clients[0].spawn_parms[i] = spawn_parms[i];
981 if(developer_entityparsing.integer)
982 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
984 // read extended data if present
985 // the extended data is stored inside a /* */ comment block, which the
986 // parser intentionally skips, so we have to check for it manually here
989 while (*end == '\r' || *end == '\n')
991 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
993 if(developer_entityparsing.integer)
994 Con_Printf("Host_Loadgame_f: loading extended data\n");
996 Con_Printf("Loading extended DarkPlaces savegame\n");
998 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
999 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
1000 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
1003 while (COM_ParseToken_Simple(&t, false, false, true))
1005 if (!strcmp(com_token, "sv.lightstyles"))
1007 COM_ParseToken_Simple(&t, false, false, true);
1008 i = atoi(com_token);
1009 COM_ParseToken_Simple(&t, false, false, true);
1010 if (i >= 0 && i < MAX_LIGHTSTYLES)
1011 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1013 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1015 else if (!strcmp(com_token, "sv.model_precache"))
1017 COM_ParseToken_Simple(&t, false, false, true);
1018 i = atoi(com_token);
1019 COM_ParseToken_Simple(&t, false, false, true);
1020 if (i >= 0 && i < MAX_MODELS)
1022 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1023 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1026 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1028 else if (!strcmp(com_token, "sv.sound_precache"))
1030 COM_ParseToken_Simple(&t, false, false, true);
1031 i = atoi(com_token);
1032 COM_ParseToken_Simple(&t, false, false, true);
1033 if (i >= 0 && i < MAX_SOUNDS)
1034 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1036 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1038 else if (!strcmp(com_token, "sv.buffer"))
1040 if (COM_ParseToken_Simple(&t, false, false, true))
1042 i = atoi(com_token);
1045 k = STRINGBUFFER_SAVED;
1046 if (COM_ParseToken_Simple(&t, false, false, true))
1047 k |= atoi(com_token);
1048 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1049 Con_Printf("failed to create stringbuffer %i\n", i);
1052 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1055 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1057 else if (!strcmp(com_token, "sv.bufstr"))
1059 if (!COM_ParseToken_Simple(&t, false, false, true))
1060 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1063 i = atoi(com_token);
1064 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1067 if (COM_ParseToken_Simple(&t, false, false, true))
1069 k = atoi(com_token);
1070 if (COM_ParseToken_Simple(&t, false, false, true))
1071 BufStr_Set(prog, stringbuffer, k, com_token);
1073 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1076 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1079 Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1082 // skip any trailing text or unrecognized commands
1083 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1090 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1091 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1092 for (i = 0; i < numbuffers; i++)
1094 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1095 if (stringbuffer->flags & STRINGBUFFER_TEMP)
1096 BufStr_Del(prog, stringbuffer);
1099 if(developer_entityparsing.integer)
1100 Con_Printf("Host_Loadgame_f: finished\n");
1102 // make sure we're connected to loopback
1103 if (sv.active && cls.state == ca_disconnected)
1104 CL_EstablishConnection("local:1", -2);
1107 //============================================================================
1110 ======================
1112 ======================
1114 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1115 static void Host_Name_f (void)
1117 prvm_prog_t *prog = SVVM_prog;
1119 qboolean valid_colors;
1120 const char *newNameSource;
1121 char newName[sizeof(host_client->name)];
1123 if (Cmd_Argc () == 1)
1125 if (cmd_source == src_command)
1127 Con_Printf("name: %s\n", cl_name.string);
1132 if (Cmd_Argc () == 2)
1133 newNameSource = Cmd_Argv(1);
1135 newNameSource = Cmd_Args();
1137 strlcpy(newName, newNameSource, sizeof(newName));
1139 if (cmd_source == src_command)
1141 Cvar_Set ("_cl_name", newName);
1142 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1144 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1145 Con_Printf("name: %s\n", cl_name.string);
1150 if (realtime < host_client->nametime)
1152 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1156 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1158 // point the string back at updateclient->name to keep it safe
1159 strlcpy (host_client->name, newName, sizeof (host_client->name));
1161 for (i = 0, j = 0;host_client->name[i];i++)
1162 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1163 host_client->name[j++] = host_client->name[i];
1164 host_client->name[j] = 0;
1166 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1167 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1169 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1170 host_client->name[sizeof(host_client->name) - 1] = 0;
1171 host_client->name[0] = STRING_COLOR_TAG;
1172 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1175 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1176 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1179 l = strlen(host_client->name);
1180 if(l < sizeof(host_client->name) - 1)
1182 // duplicate the color tag to escape it
1183 host_client->name[i] = STRING_COLOR_TAG;
1184 host_client->name[i+1] = 0;
1185 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1189 // remove the last character to fix the color code
1190 host_client->name[l-1] = 0;
1191 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1195 // find the last color tag offset and decide if we need to add a reset tag
1196 for (i = 0, j = -1;host_client->name[i];i++)
1198 if (host_client->name[i] == STRING_COLOR_TAG)
1200 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1203 // if this happens to be a reset tag then we don't need one
1204 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1209 if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4]))
1215 if (host_client->name[i+1] == STRING_COLOR_TAG)
1222 // does not end in the default color string, so add it
1223 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1224 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1226 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1227 if (strcmp(host_client->old_name, host_client->name))
1229 if (host_client->begun)
1230 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1231 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1232 // send notification to all clients
1233 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1234 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1235 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1236 SV_WriteNetnameIntoDemo(host_client);
1241 ======================
1243 ======================
1245 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1246 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1247 static void Host_Playermodel_f (void)
1249 prvm_prog_t *prog = SVVM_prog;
1251 char newPath[sizeof(host_client->playermodel)];
1253 if (Cmd_Argc () == 1)
1255 if (cmd_source == src_command)
1257 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1262 if (Cmd_Argc () == 2)
1263 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1265 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1267 for (i = 0, j = 0;newPath[i];i++)
1268 if (newPath[i] != '\r' && newPath[i] != '\n')
1269 newPath[j++] = newPath[i];
1272 if (cmd_source == src_command)
1274 Cvar_Set ("_cl_playermodel", newPath);
1279 if (realtime < host_client->nametime)
1281 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1285 host_client->nametime = realtime + 5;
1288 // point the string back at updateclient->name to keep it safe
1289 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1290 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1291 if (strcmp(host_client->old_model, host_client->playermodel))
1293 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1294 /*// send notification to all clients
1295 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1296 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1297 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1302 ======================
1304 ======================
1306 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1307 static void Host_Playerskin_f (void)
1309 prvm_prog_t *prog = SVVM_prog;
1311 char newPath[sizeof(host_client->playerskin)];
1313 if (Cmd_Argc () == 1)
1315 if (cmd_source == src_command)
1317 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1322 if (Cmd_Argc () == 2)
1323 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1325 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1327 for (i = 0, j = 0;newPath[i];i++)
1328 if (newPath[i] != '\r' && newPath[i] != '\n')
1329 newPath[j++] = newPath[i];
1332 if (cmd_source == src_command)
1334 Cvar_Set ("_cl_playerskin", newPath);
1339 if (realtime < host_client->nametime)
1341 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1345 host_client->nametime = realtime + 5;
1348 // point the string back at updateclient->name to keep it safe
1349 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1350 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1351 if (strcmp(host_client->old_skin, host_client->playerskin))
1353 //if (host_client->begun)
1354 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1355 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1356 /*// send notification to all clients
1357 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1358 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1359 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1363 static void Host_Version_f (void)
1365 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1368 static void Host_Say(qboolean teamonly)
1370 prvm_prog_t *prog = SVVM_prog;
1375 // LordHavoc: long say messages
1377 qboolean fromServer = false;
1379 if (cmd_source == src_command)
1381 if (cls.state == ca_dedicated)
1388 Cmd_ForwardToServer ();
1393 if (Cmd_Argc () < 2)
1396 if (!teamplay.integer)
1406 // note this uses the chat prefix \001
1407 if (!fromServer && !teamonly)
1408 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1409 else if (!fromServer && teamonly)
1410 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1411 else if(*(sv_adminnick.string))
1412 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1414 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1415 p2 = text + strlen(text);
1416 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1418 if (p2[-1] == '\"' && quoted)
1423 strlcat(text, "\n", sizeof(text));
1425 // note: save is not a valid edict if fromServer is true
1427 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1428 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1429 SV_ClientPrint(text);
1432 if (cls.state == ca_dedicated)
1433 Con_Print(&text[1]);
1437 static void Host_Say_f(void)
1443 static void Host_Say_Team_f(void)
1449 static void Host_Tell_f(void)
1451 const char *playername_start = NULL;
1452 size_t playername_length = 0;
1453 int playernumber = 0;
1456 const char *p1, *p2;
1457 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1458 qboolean fromServer = false;
1460 if (cmd_source == src_command)
1462 if (cls.state == ca_dedicated)
1466 Cmd_ForwardToServer ();
1471 if (Cmd_Argc () < 2)
1474 // note this uses the chat prefix \001
1476 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1477 else if(*(sv_adminnick.string))
1478 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1480 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1483 p2 = p1 + strlen(p1);
1484 // remove the target name
1485 while (p1 < p2 && *p1 == ' ')
1490 while (p1 < p2 && *p1 == ' ')
1492 while (p1 < p2 && isdigit(*p1))
1494 playernumber = playernumber * 10 + (*p1 - '0');
1502 playername_start = p1;
1503 while (p1 < p2 && *p1 != '"')
1505 playername_length = p1 - playername_start;
1511 playername_start = p1;
1512 while (p1 < p2 && *p1 != ' ')
1514 playername_length = p1 - playername_start;
1516 while (p1 < p2 && *p1 == ' ')
1518 if(playername_start)
1520 // set playernumber to the right client
1522 if(playername_length >= sizeof(namebuf))
1525 Con_Print("Host_Tell: too long player name/ID\n");
1527 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1530 memcpy(namebuf, playername_start, playername_length);
1531 namebuf[playername_length] = 0;
1532 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1534 if (!svs.clients[playernumber].active)
1536 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1540 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1543 Con_Print("Host_Tell: invalid player name/ID\n");
1545 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1548 // remove trailing newlines
1549 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1551 // remove quotes if present
1557 else if (fromServer)
1558 Con_Print("Host_Tell: missing end quote\n");
1560 SV_ClientPrint("Host_Tell: missing end quote\n");
1562 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1565 return; // empty say
1566 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1572 host_client = svs.clients + playernumber;
1573 SV_ClientPrint(text);
1583 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1584 static void Host_Color(int changetop, int changebottom)
1586 prvm_prog_t *prog = SVVM_prog;
1587 int top, bottom, playercolor;
1589 // get top and bottom either from the provided values or the current values
1590 // (allows changing only top or bottom, or both at once)
1591 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1592 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1596 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1602 playercolor = top*16 + bottom;
1604 if (cmd_source == src_command)
1606 Cvar_SetValueQuick(&cl_color, playercolor);
1610 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1613 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1615 Con_DPrint("Calling SV_ChangeTeam\n");
1616 prog->globals.fp[OFS_PARM0] = playercolor;
1617 PRVM_serverglobalfloat(time) = sv.time;
1618 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1619 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1623 if (host_client->edict)
1625 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1626 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1628 host_client->colors = playercolor;
1629 if (host_client->old_colors != host_client->colors)
1631 host_client->old_colors = host_client->colors;
1632 // send notification to all clients
1633 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1634 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1635 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1640 static void Host_Color_f(void)
1644 if (Cmd_Argc() == 1)
1646 if (cmd_source == src_command)
1648 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1649 Con_Print("color <0-15> [0-15]\n");
1654 if (Cmd_Argc() == 2)
1655 top = bottom = atoi(Cmd_Argv(1));
1658 top = atoi(Cmd_Argv(1));
1659 bottom = atoi(Cmd_Argv(2));
1661 Host_Color(top, bottom);
1664 static void Host_TopColor_f(void)
1666 if (Cmd_Argc() == 1)
1668 if (cmd_source == src_command)
1670 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1671 Con_Print("topcolor <0-15>\n");
1676 Host_Color(atoi(Cmd_Argv(1)), -1);
1679 static void Host_BottomColor_f(void)
1681 if (Cmd_Argc() == 1)
1683 if (cmd_source == src_command)
1685 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1686 Con_Print("bottomcolor <0-15>\n");
1691 Host_Color(-1, atoi(Cmd_Argv(1)));
1694 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1695 cvar_t cl_rate_burstsize = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
1696 static void Host_Rate_f(void)
1700 if (Cmd_Argc() != 2)
1702 if (cmd_source == src_command)
1704 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1705 Con_Print("rate <bytespersecond>\n");
1710 rate = atoi(Cmd_Argv(1));
1712 if (cmd_source == src_command)
1714 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1718 host_client->rate = rate;
1720 static void Host_Rate_BurstSize_f(void)
1724 if (Cmd_Argc() != 2)
1726 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1727 Con_Print("rate_burstsize <bytes>\n");
1731 rate_burstsize = atoi(Cmd_Argv(1));
1733 if (cmd_source == src_command)
1735 Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
1739 host_client->rate_burstsize = rate_burstsize;
1747 static void Host_Kill_f (void)
1749 prvm_prog_t *prog = SVVM_prog;
1750 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1752 SV_ClientPrint("Can't suicide -- already dead!\n");
1756 PRVM_serverglobalfloat(time) = sv.time;
1757 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1758 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1767 static void Host_Pause_f (void)
1769 void (*print) (const char *fmt, ...);
1770 if (cmd_source == src_command)
1772 // if running a client, try to send over network so the pause is handled by the server
1773 if (cls.state == ca_connected)
1775 Cmd_ForwardToServer ();
1781 print = SV_ClientPrintf;
1783 if (!pausable.integer)
1785 if (cmd_source == src_client)
1787 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1789 print("Pause not allowed.\n");
1796 if (cmd_source != src_command)
1797 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1798 else if(*(sv_adminnick.string))
1799 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
1801 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
1802 // send notification to all clients
1803 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1804 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1808 ======================
1810 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1811 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1812 ======================
1814 cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
1815 static void Host_PModel_f (void)
1817 prvm_prog_t *prog = SVVM_prog;
1820 if (Cmd_Argc () == 1)
1822 if (cmd_source == src_command)
1824 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1828 i = atoi(Cmd_Argv(1));
1830 if (cmd_source == src_command)
1832 if (cl_pmodel.integer == i)
1834 Cvar_SetValue ("_cl_pmodel", i);
1835 if (cls.state == ca_connected)
1836 Cmd_ForwardToServer ();
1840 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1843 //===========================================================================
1851 static void Host_PreSpawn_f (void)
1853 if (host_client->prespawned)
1855 Con_Print("prespawn not valid -- already prespawned\n");
1858 host_client->prespawned = true;
1860 if (host_client->netconnection)
1862 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1863 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1864 MSG_WriteByte (&host_client->netconnection->message, 2);
1865 host_client->sendsignon = 0; // enable unlimited sends again
1868 // reset the name change timer because the client will send name soon
1869 host_client->nametime = 0;
1877 static void Host_Spawn_f (void)
1879 prvm_prog_t *prog = SVVM_prog;
1882 int stats[MAX_CL_STATS];
1884 if (!host_client->prespawned)
1886 Con_Print("Spawn not valid -- not yet prespawned\n");
1889 if (host_client->spawned)
1891 Con_Print("Spawn not valid -- already spawned\n");
1894 host_client->spawned = true;
1896 // reset name change timer again because they might want to change name
1897 // again in the first 5 seconds after connecting
1898 host_client->nametime = 0;
1900 // LordHavoc: moved this above the QC calls at FrikaC's request
1901 // LordHavoc: commented this out
1902 //if (host_client->netconnection)
1903 // SZ_Clear (&host_client->netconnection->message);
1905 // run the entrance script
1908 // loaded games are fully initialized already
1909 if (PRVM_serverfunction(RestoreGame))
1911 Con_DPrint("Calling RestoreGame\n");
1912 PRVM_serverglobalfloat(time) = sv.time;
1913 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1914 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1919 //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name);
1921 // copy spawn parms out of the client_t
1922 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1923 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1925 // call the spawn function
1926 host_client->clientconnectcalled = true;
1927 PRVM_serverglobalfloat(time) = sv.time;
1928 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1929 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1931 if (cls.state == ca_dedicated)
1932 Con_Printf("%s connected\n", host_client->name);
1934 PRVM_serverglobalfloat(time) = sv.time;
1935 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1938 if (!host_client->netconnection)
1941 // send time of update
1942 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1943 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1945 // send all current names, colors, and frag counts
1946 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1948 if (!client->active)
1950 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1951 MSG_WriteByte (&host_client->netconnection->message, i);
1952 MSG_WriteString (&host_client->netconnection->message, client->name);
1953 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1954 MSG_WriteByte (&host_client->netconnection->message, i);
1955 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1956 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1957 MSG_WriteByte (&host_client->netconnection->message, i);
1958 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1961 // send all current light styles
1962 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1964 if (sv.lightstyles[i][0])
1966 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1967 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1968 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1973 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1974 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1975 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1977 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1978 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1979 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1981 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1982 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1983 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1985 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1986 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1987 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1990 // Never send a roll angle, because savegames can catch the server
1991 // in a state where it is expecting the client to correct the angle
1992 // and it won't happen if the game was just loaded, so you wind up
1993 // with a permanent head tilt
1996 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1997 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1998 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1999 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
2003 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
2004 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
2005 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
2006 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
2009 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
2011 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
2012 MSG_WriteByte (&host_client->netconnection->message, 3);
2020 static void Host_Begin_f (void)
2022 if (!host_client->spawned)
2024 Con_Print("Begin not valid -- not yet spawned\n");
2027 if (host_client->begun)
2029 Con_Print("Begin not valid -- already begun\n");
2032 host_client->begun = true;
2034 // LordHavoc: note: this code also exists in SV_DropClient
2038 for (i = 0;i < svs.maxclients;i++)
2039 if (svs.clients[i].active && !svs.clients[i].spawned)
2041 if (i == svs.maxclients)
2043 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2044 sv.paused = sv.loadgame = false; // we're basically done with loading now
2049 //===========================================================================
2056 Kicks a user off of the server
2059 static void Host_Kick_f (void)
2062 const char *message = NULL;
2065 qboolean byNumber = false;
2072 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2074 i = (int)(atof(Cmd_Argv(2)) - 1);
2075 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2081 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2083 if (!host_client->active)
2085 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2090 if (i < svs.maxclients)
2092 if (cmd_source == src_command)
2094 if (cls.state == ca_dedicated)
2097 who = cl_name.string;
2102 // can't kick yourself!
2103 if (host_client == save)
2108 message = Cmd_Args();
2109 COM_ParseToken_Simple(&message, false, false, true);
2112 message++; // skip the #
2113 while (*message == ' ') // skip white space
2115 message += strlen(Cmd_Argv(2)); // skip the number
2117 while (*message && *message == ' ')
2121 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2123 SV_ClientPrintf("Kicked by %s\n", who);
2124 SV_DropClient (false); // kicked
2131 ===============================================================================
2135 ===============================================================================
2143 static void Host_Give_f (void)
2145 prvm_prog_t *prog = SVVM_prog;
2151 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2156 v = atoi (Cmd_Argv(2));
2170 // MED 01/04/97 added hipnotic give stuff
2171 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2176 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2178 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2180 else if (t[0] == '9')
2181 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2182 else if (t[0] == '0')
2183 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2184 else if (t[0] >= '2')
2185 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2190 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2195 if (gamemode == GAME_ROGUE)
2196 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2198 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2201 if (gamemode == GAME_ROGUE)
2203 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2204 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2205 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2209 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2213 if (gamemode == GAME_ROGUE)
2215 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2216 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2217 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2221 if (gamemode == GAME_ROGUE)
2223 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2224 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2225 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2229 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2233 if (gamemode == GAME_ROGUE)
2235 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2236 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2237 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2241 PRVM_serveredictfloat(host_client->edict, health) = v;
2244 if (gamemode == GAME_ROGUE)
2246 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2247 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2248 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2252 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2256 if (gamemode == GAME_ROGUE)
2258 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2259 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2260 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2266 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2271 for (i=0 ; i<prog->num_edicts ; i++)
2273 e = PRVM_EDICT_NUM(i);
2274 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2277 Con_Print("No viewthing on map\n");
2286 static void Host_Viewmodel_f (void)
2288 prvm_prog_t *prog = SVVM_prog;
2295 e = FindViewthing(prog);
2298 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2299 if (m && m->loaded && m->Draw)
2301 PRVM_serveredictfloat(e, frame) = 0;
2302 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2305 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2314 static void Host_Viewframe_f (void)
2316 prvm_prog_t *prog = SVVM_prog;
2324 e = FindViewthing(prog);
2327 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2329 f = atoi(Cmd_Argv(1));
2330 if (f >= m->numframes)
2333 PRVM_serveredictfloat(e, frame) = f;
2338 static void PrintFrameName (dp_model_t *m, int frame)
2341 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2343 Con_Printf("frame %i\n", frame);
2351 static void Host_Viewnext_f (void)
2353 prvm_prog_t *prog = SVVM_prog;
2360 e = FindViewthing(prog);
2363 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2365 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2366 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2367 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2369 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2378 static void Host_Viewprev_f (void)
2380 prvm_prog_t *prog = SVVM_prog;
2387 e = FindViewthing(prog);
2390 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2392 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2393 if (PRVM_serveredictfloat(e, frame) < 0)
2394 PRVM_serveredictfloat(e, frame) = 0;
2396 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2401 ===============================================================================
2405 ===============================================================================
2414 static void Host_Startdemos_f (void)
2418 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2424 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2427 Con_DPrintf("%i demo(s) in loop\n", c);
2429 for (i=1 ; i<c+1 ; i++)
2430 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2432 // LordHavoc: clear the remaining slots
2433 for (;i <= MAX_DEMOS;i++)
2434 cls.demos[i-1][0] = 0;
2436 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2450 Return to looping demos
2453 static void Host_Demos_f (void)
2455 if (cls.state == ca_dedicated)
2457 if (cls.demonum == -1)
2467 Return to looping demos
2470 static void Host_Stopdemo_f (void)
2472 if (!cls.demoplayback)
2475 Host_ShutdownServer ();
2478 static void Host_SendCvar_f (void)
2482 const char *cvarname;
2488 cvarname = Cmd_Argv(1);
2489 if (cls.state == ca_connected)
2491 c = Cvar_FindVar(cvarname);
2492 // LordHavoc: if there is no such cvar or if it is private, send a
2493 // reply indicating that it has no value
2494 if(!c || (c->flags & CVAR_PRIVATE))
2495 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2497 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2500 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2504 if (cls.state != ca_dedicated)
2508 for(;i<svs.maxclients;i++)
2509 if(svs.clients[i].active && svs.clients[i].netconnection)
2511 host_client = &svs.clients[i];
2512 Host_ClientCommands("sendcvar %s\n", cvarname);
2517 static void MaxPlayers_f(void)
2521 if (Cmd_Argc() != 2)
2523 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2529 Con_Print("maxplayers can not be changed while a server is running.\n");
2530 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2533 n = atoi(Cmd_Argv(1));
2534 n = bound(1, n, MAX_SCOREBOARD);
2535 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2537 svs.maxclients_next = n;
2539 Cvar_Set ("deathmatch", "0");
2541 Cvar_Set ("deathmatch", "1");
2545 =====================
2548 ProQuake rcon support
2549 =====================
2551 static void Host_PQRcon_f (void)
2556 lhnetsocket_t *mysocket;
2557 char peer_address[64];
2559 if (Cmd_Argc() == 1)
2561 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2565 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2567 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2571 e = strchr(rcon_password.string, ' ');
2572 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2576 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2580 if (!rcon_address.string[0])
2582 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2585 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2587 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2588 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2592 unsigned char bufdata[64];
2595 MSG_WriteLong(&buf, 0);
2596 MSG_WriteByte(&buf, CCREQ_RCON);
2597 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2598 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2599 MSG_WriteString(&buf, Cmd_Args());
2600 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2601 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2606 //=============================================================================
2608 // QuakeWorld commands
2611 =====================
2614 Send the rest of the command line over as
2615 an unconnected command.
2616 =====================
2618 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2623 lhnetsocket_t *mysocket;
2626 if (Cmd_Argc() == 1)
2628 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2632 if (!rcon_password.string || !rcon_password.string[0])
2634 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2638 e = strchr(rcon_password.string, ' ');
2639 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2642 to = cls.netcon->peeraddress;
2645 if (!rcon_address.string[0])
2647 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2650 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2652 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2653 if (mysocket && Cmd_Args()[0])
2655 // simply put together the rcon packet and send it
2656 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2658 if(cls.rcon_commands[cls.rcon_ringpos][0])
2661 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2662 Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]);
2663 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2666 for (i = 0;i < MAX_RCONS;i++)
2667 if(cls.rcon_commands[i][0])
2668 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2672 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2673 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2674 cls.rcon_addresses[cls.rcon_ringpos] = to;
2675 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2676 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2678 else if(rcon_secure.integer > 0)
2682 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2683 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2684 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2687 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2688 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &to);
2693 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2699 ====================
2702 user <name or userid>
2704 Dump userdata / masterdata for a user
2705 ====================
2707 static void Host_User_f (void) // credit: taken from QuakeWorld
2712 if (Cmd_Argc() != 2)
2714 Con_Printf ("Usage: user <username / userid>\n");
2718 uid = atoi(Cmd_Argv(1));
2720 for (i = 0;i < cl.maxclients;i++)
2722 if (!cl.scores[i].name[0])
2724 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2726 InfoString_Print(cl.scores[i].qw_userinfo);
2730 Con_Printf ("User not in server.\n");
2734 ====================
2737 Dump userids for all current players
2738 ====================
2740 static void Host_Users_f (void) // credit: taken from QuakeWorld
2746 Con_Printf ("userid frags name\n");
2747 Con_Printf ("------ ----- ----\n");
2748 for (i = 0;i < cl.maxclients;i++)
2750 if (cl.scores[i].name[0])
2752 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2757 Con_Printf ("%i total users\n", c);
2762 Host_FullServerinfo_f
2764 Sent by server when serverinfo changes
2767 // TODO: shouldn't this be a cvar instead?
2768 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2771 if (Cmd_Argc() != 2)
2773 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2777 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2778 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2779 cl.qw_teamplay = atoi(temp);
2786 Allow clients to change userinfo
2790 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2796 if (Cmd_Argc() != 2)
2798 Con_Printf ("fullinfo <complete info string>\n");
2807 size_t len = strcspn(s, "\\");
2808 if (len >= sizeof(key)) {
2809 len = sizeof(key) - 1;
2811 strlcpy(key, s, len + 1);
2815 Con_Printf ("MISSING VALUE\n");
2818 ++s; // Skip over backslash.
2820 len = strcspn(s, "\\");
2821 if (len >= sizeof(value)) {
2822 len = sizeof(value) - 1;
2824 strlcpy(value, s, len + 1);
2826 CL_SetInfo(key, value, false, false, false, false);
2833 ++s; // Skip over backslash.
2841 Allow clients to change userinfo
2844 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2846 if (Cmd_Argc() == 1)
2848 InfoString_Print(cls.userinfo);
2851 if (Cmd_Argc() != 3)
2853 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2856 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2860 ====================
2863 packet <destination> <contents>
2865 Contents allows \n escape character
2866 ====================
2868 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2874 lhnetaddress_t address;
2875 lhnetsocket_t *mysocket;
2877 if (Cmd_Argc() != 3)
2879 Con_Printf ("packet <destination> <contents>\n");
2883 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2885 Con_Printf ("Bad address\n");
2891 send[0] = send[1] = send[2] = send[3] = -1;
2893 l = (int)strlen (in);
2894 for (i=0 ; i<l ; i++)
2896 if (out >= send + sizeof(send) - 1)
2898 if (in[i] == '\\' && in[i+1] == 'n')
2903 else if (in[i] == '\\' && in[i+1] == '0')
2908 else if (in[i] == '\\' && in[i+1] == 't')
2913 else if (in[i] == '\\' && in[i+1] == 'r')
2918 else if (in[i] == '\\' && in[i+1] == '"')
2927 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2929 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2931 NetConn_Write(mysocket, send, out - send, &address);
2935 ====================
2938 Send back ping and packet loss update for all current players to this player
2939 ====================
2941 void Host_Pings_f (void)
2943 int i, j, ping, packetloss, movementloss;
2946 if (!host_client->netconnection)
2949 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2951 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2952 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2954 for (i = 0;i < svs.maxclients;i++)
2958 if (svs.clients[i].netconnection)
2960 for (j = 0;j < NETGRAPH_PACKETS;j++)
2961 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2963 for (j = 0;j < NETGRAPH_PACKETS;j++)
2964 if (svs.clients[i].movement_count[j] < 0)
2967 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2968 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2969 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2970 ping = bound(0, ping, 9999);
2971 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2973 // send qw_svc_updateping and qw_svc_updatepl messages
2974 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2975 MSG_WriteShort(&host_client->netconnection->message, ping);
2976 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2977 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2981 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2983 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2985 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2986 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2989 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2990 MSG_WriteString(&host_client->netconnection->message, "\n");
2993 static void Host_PingPLReport_f(void)
2998 if (l > cl.maxclients)
3000 for (i = 0;i < l;i++)
3002 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
3003 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
3004 if(errbyte && *errbyte == ',')
3005 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
3007 cl.scores[i].qw_movementloss = 0;
3011 //=============================================================================
3018 void Host_InitCommands (void)
3020 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
3022 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
3023 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
3024 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
3025 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
3026 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
3027 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
3028 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
3029 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
3030 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
3031 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
3032 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
3033 Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)");
3034 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
3035 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
3036 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
3037 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
3038 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
3039 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
3040 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
3041 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
3042 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
3043 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
3045 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3046 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3047 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3049 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3050 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3051 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3052 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3054 Cvar_RegisterVariable (&cl_name);
3055 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
3056 Cvar_RegisterVariable (&cl_color);
3057 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
3058 Cvar_RegisterVariable (&cl_rate);
3059 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3060 Cvar_RegisterVariable (&cl_rate_burstsize);
3061 Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
3062 Cvar_RegisterVariable (&cl_pmodel);
3063 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3065 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3066 Cvar_RegisterVariable (&cl_playermodel);
3067 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3068 Cvar_RegisterVariable (&cl_playerskin);
3069 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3071 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3072 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3073 Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
3074 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3076 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3078 Cvar_RegisterVariable (&rcon_password);
3079 Cvar_RegisterVariable (&rcon_address);
3080 Cvar_RegisterVariable (&rcon_secure);
3081 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3082 Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP");
3083 Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP");
3084 Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)");
3085 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3086 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3087 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3088 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3089 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3090 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3091 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3092 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3094 Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
3095 Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)");
3097 Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
3098 Cvar_RegisterVariable (&r_fixtrans_auto);
3100 Cvar_RegisterVariable (&team);
3101 Cvar_RegisterVariable (&skin);
3102 Cvar_RegisterVariable (&noaim);
3104 Cvar_RegisterVariable(&sv_cheats);
3105 Cvar_RegisterVariable(&sv_adminnick);
3106 Cvar_RegisterVariable(&sv_status_privacy);
3107 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3108 Cvar_RegisterVariable(&sv_namechangetimer);
3111 void Host_NoOperation_f(void)