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 = {CVAR_SERVER | CVAR_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
35 cvar_t sv_adminnick = {CVAR_SERVER | CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
36 cvar_t sv_status_privacy = {CVAR_SERVER | CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
37 cvar_t sv_status_show_qcstatus = {CVAR_SERVER | 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_SERVER | 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_CLIENT | CVAR_SERVER | 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_CLIENT | CVAR_SERVER | 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 = {CVAR_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
42 cvar_t rcon_address = {CVAR_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
43 cvar_t team = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
44 cvar_t skin = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
45 cvar_t noaim = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
46 cvar_t r_fixtrans_auto = {CVAR_CLIENT, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
48 extern qboolean host_shuttingdown;
49 extern cvar_t developer_entityparsing;
57 void Host_Quit_f(cmd_state_t *cmd)
60 Con_Printf("shutting down already!\n");
70 static void Host_Status_f(cmd_state_t *cmd)
72 prvm_prog_t *prog = SVVM_prog;
75 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
76 void (*print) (const char *fmt, ...);
77 char ip[48]; // can contain a full length v6 address with [] and a port
81 if (cmd->source == src_command)
83 // if running a client, try to send over network so the client's status report parser will see the report
84 if (cls.state == ca_connected)
86 Cmd_ForwardToServer_f(cmd);
92 print = SV_ClientPrintf;
98 if (Cmd_Argc(cmd) == 2)
100 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
102 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
106 for (players = 0, i = 0;i < svs.maxclients;i++)
107 if (svs.clients[i].active)
109 print ("host: %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
110 print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
111 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
112 print ("map: %s\n", sv.name);
113 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
114 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
117 print ("^2IP %%pl ping time frags no name\n");
119 print ("^5IP no name\n");
121 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
128 if (in == 0 || in == 1)
130 seconds = (int)(realtime - client->connecttime);
131 minutes = seconds / 60;
134 seconds -= (minutes * 60);
135 hours = minutes / 60;
137 minutes -= (hours * 60);
143 if (client->netconnection)
144 for (j = 0;j < NETGRAPH_PACKETS;j++)
145 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
147 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
148 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
151 if(sv_status_privacy.integer && cmd->source != src_command)
152 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
154 strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
156 frags = client->frags;
158 if(sv_status_show_qcstatus.integer)
160 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
161 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
167 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
168 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
172 frags = atoi(qcstatus);
176 if (in == 0) // default layout
178 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
180 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
181 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
186 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
187 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
191 else if (in == 1) // extended layout
193 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);
195 else if (in == 2) // reduced layout
197 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
207 Sets client to godmode
210 static void Host_God_f(cmd_state_t *cmd)
212 prvm_prog_t *prog = SVVM_prog;
214 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
215 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
216 SV_ClientPrint("godmode OFF\n");
218 SV_ClientPrint("godmode ON\n");
221 static void Host_Notarget_f(cmd_state_t *cmd)
223 prvm_prog_t *prog = SVVM_prog;
225 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
226 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
227 SV_ClientPrint("notarget OFF\n");
229 SV_ClientPrint("notarget ON\n");
232 qboolean noclip_anglehack;
234 static void Host_Noclip_f(cmd_state_t *cmd)
236 prvm_prog_t *prog = SVVM_prog;
238 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
240 noclip_anglehack = true;
241 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
242 SV_ClientPrint("noclip ON\n");
246 noclip_anglehack = false;
247 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
248 SV_ClientPrint("noclip OFF\n");
256 Sets client to flymode
259 static void Host_Fly_f(cmd_state_t *cmd)
261 prvm_prog_t *prog = SVVM_prog;
263 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
265 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
266 SV_ClientPrint("flymode ON\n");
270 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
271 SV_ClientPrint("flymode OFF\n");
282 static void Host_Ping_f(cmd_state_t *cmd)
286 void (*print) (const char *fmt, ...);
288 if (cmd->source == src_command)
290 // if running a client, try to send over network so the client's ping report parser will see the report
291 if (cls.state == ca_connected)
293 Cmd_ForwardToServer_f(cmd);
299 print = SV_ClientPrintf;
304 print("Client ping times:\n");
305 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
309 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
313 // Disable cheats if sv_cheats is turned off
314 static void Host_DisableCheats_c(char *value)
316 prvm_prog_t *prog = SVVM_prog;
319 if (value[0] == '0' && !value[1])
321 while (svs.clients[i].edict)
323 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
324 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
325 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
326 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
327 if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
328 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
330 noclip_anglehack = false;
331 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
339 ===============================================================================
343 ===============================================================================
347 ======================
352 command from the console. Active clients are kicked off.
353 ======================
355 static void Host_Map_f(cmd_state_t *cmd)
357 char level[MAX_QPATH];
359 if (Cmd_Argc(cmd) != 2)
361 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
365 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
366 if (gamemode == GAME_DELUXEQUAKE)
367 Cvar_Set(&cvars_all, "warpmark", "");
369 cls.demonum = -1; // stop demo loop in case this fails
372 Host_ShutdownServer();
374 if(svs.maxclients != svs.maxclients_next)
376 svs.maxclients = svs.maxclients_next;
378 Mem_Free(svs.clients);
379 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
384 if (key_dest == key_menu || key_dest == key_menu_grabbed)
389 svs.serverflags = 0; // haven't completed an episode yet
390 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
391 SV_SpawnServer(level);
392 if (sv.active && cls.state == ca_disconnected)
393 CL_EstablishConnection("local:1", -2);
400 Goes to a new map, taking all clients along
403 static void Host_Changelevel_f(cmd_state_t *cmd)
405 char level[MAX_QPATH];
407 if (Cmd_Argc(cmd) != 2)
409 Con_Print("changelevel <levelname> : continue game on a new level\n");
420 if (key_dest == key_menu || key_dest == key_menu_grabbed)
425 SV_SaveSpawnparms ();
426 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
427 SV_SpawnServer(level);
428 if (sv.active && cls.state == ca_disconnected)
429 CL_EstablishConnection("local:1", -2);
436 Restarts the current server for a dead player
439 static void Host_Restart_f(cmd_state_t *cmd)
441 char mapname[MAX_QPATH];
443 if (Cmd_Argc(cmd) != 1)
445 Con_Print("restart : restart current level\n");
450 Con_Print("Only the server may restart\n");
456 if (key_dest == key_menu || key_dest == key_menu_grabbed)
461 strlcpy(mapname, sv.name, sizeof(mapname));
462 SV_SpawnServer(mapname);
463 if (sv.active && cls.state == ca_disconnected)
464 CL_EstablishConnection("local:1", -2);
471 This command causes the client to wait for the signon messages again.
472 This is sent just before a server changes levels
475 void Host_Reconnect_f(cmd_state_t *cmd)
478 // if not connected, reconnect to the most recent server
481 // if we have connected to a server recently, the userinfo
482 // will still contain its IP address, so get the address...
483 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
485 CL_EstablishConnection(temp, -1);
487 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
490 // if connected, do something based on protocol
491 if (cls.protocol == PROTOCOL_QUAKEWORLD)
493 // quakeworld can just re-login
494 if (cls.qw_downloadmemory) // don't change when downloading
499 if (cls.state == ca_connected)
501 Con_Printf("Server is changing level...\n");
502 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
503 MSG_WriteString(&cls.netcon->message, "new");
508 // netquake uses reconnect on level changes (silly)
509 if (Cmd_Argc(cmd) != 1)
511 Con_Print("reconnect : wait for signon messages again\n");
516 Con_Print("reconnect: no signon, ignoring reconnect\n");
519 cls.signon = 0; // need new connection messages
524 =====================
527 User command to connect to server
528 =====================
530 static void Host_Connect_f(cmd_state_t *cmd)
532 if (Cmd_Argc(cmd) < 2)
534 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
537 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
538 if(rcon_secure.integer <= 0)
539 Cvar_SetQuick(&rcon_password, "");
540 CL_EstablishConnection(Cmd_Argv(cmd, 1), 2);
545 ===============================================================================
549 ===============================================================================
552 #define SAVEGAME_VERSION 5
554 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
557 int i, k, l, numbuffers, lightstyles = 64;
558 char comment[SAVEGAME_COMMENT_LENGTH+1];
559 char line[MAX_INPUTLINE];
563 // first we have to figure out if this can be saved in 64 lightstyles
564 // (for Quake compatibility)
565 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
566 if (sv.lightstyles[i][0])
569 isserver = prog == SVVM_prog;
571 Con_Printf("Saving game to %s...\n", name);
572 f = FS_OpenRealFile(name, "wb", false);
575 Con_Print("ERROR: couldn't open.\n");
579 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
581 memset(comment, 0, sizeof(comment));
583 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));
585 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
586 // convert space to _ to make stdio happy
587 // LadyHavoc: convert control characters to _ as well
588 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
589 if (ISWHITESPACEORCONTROL(comment[i]))
591 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
593 FS_Printf(f, "%s\n", comment);
596 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
597 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
598 FS_Printf(f, "%d\n", current_skill);
599 FS_Printf(f, "%s\n", sv.name);
600 FS_Printf(f, "%f\n",sv.time);
604 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
605 FS_Printf(f, "(dummy)\n");
606 FS_Printf(f, "%d\n", 0);
607 FS_Printf(f, "%s\n", "(dummy)");
608 FS_Printf(f, "%f\n", realtime);
611 // write the light styles
612 for (i=0 ; i<lightstyles ; i++)
614 if (isserver && sv.lightstyles[i][0])
615 FS_Printf(f, "%s\n", sv.lightstyles[i]);
620 PRVM_ED_WriteGlobals (prog, f);
621 for (i=0 ; i<prog->num_edicts ; i++)
623 FS_Printf(f,"// edict %d\n", i);
624 //Con_Printf("edict %d...\n", i);
625 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
630 FS_Printf(f,"// DarkPlaces extended savegame\n");
631 // darkplaces extension - extra lightstyles, support for color lightstyles
632 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
633 if (isserver && sv.lightstyles[i][0])
634 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
636 // darkplaces extension - model precaches
637 for (i=1 ; i<MAX_MODELS ; i++)
638 if (sv.model_precache[i][0])
639 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
641 // darkplaces extension - sound precaches
642 for (i=1 ; i<MAX_SOUNDS ; i++)
643 if (sv.sound_precache[i][0])
644 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
646 // darkplaces extension - save buffers
647 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
648 for (i = 0; i < numbuffers; i++)
650 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
651 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
653 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
654 for(k = 0; k < stringbuffer->num_strings; k++)
656 if (!stringbuffer->strings[k])
658 // Parse the string a bit to turn special characters
659 // (like newline, specifically) into escape codes
660 s = stringbuffer->strings[k];
661 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
688 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
696 Con_Print("done.\n");
704 static void Host_Savegame_f(cmd_state_t *cmd)
706 prvm_prog_t *prog = SVVM_prog;
707 char name[MAX_QPATH];
708 qboolean deadflag = false;
712 Con_Print("Can't save - no server running.\n");
716 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
720 // singleplayer checks
723 Con_Print("Can't save in intermission.\n");
729 Con_Print("Can't savegame with a dead player\n");
734 Con_Warn("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
736 if (Cmd_Argc(cmd) != 2)
738 Con_Print("save <savename> : save a game\n");
742 if (strstr(Cmd_Argv(cmd, 1), ".."))
744 Con_Print("Relative pathnames are not allowed.\n");
748 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
749 FS_DefaultExtension (name, ".sav", sizeof (name));
751 Host_Savegame_to(prog, name);
761 static void Host_Loadgame_f(cmd_state_t *cmd)
763 prvm_prog_t *prog = SVVM_prog;
764 char filename[MAX_QPATH];
765 char mapname[MAX_QPATH];
772 int i, k, numbuffers;
775 float spawn_parms[NUM_SPAWN_PARMS];
776 prvm_stringbuffer_t *stringbuffer;
778 if (Cmd_Argc(cmd) != 2)
780 Con_Print("load <savename> : load a game\n");
784 strlcpy (filename, Cmd_Argv(cmd, 1), sizeof(filename));
785 FS_DefaultExtension (filename, ".sav", sizeof (filename));
787 Con_Printf("Loading game from %s...\n", filename);
789 // stop playing demos
790 if (cls.demoplayback)
795 if (key_dest == key_menu || key_dest == key_menu_grabbed)
800 cls.demonum = -1; // stop demo loop in case this fails
802 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
805 Con_Print("ERROR: couldn't open.\n");
809 if(developer_entityparsing.integer)
810 Con_Printf("Host_Loadgame_f: loading version\n");
813 COM_ParseToken_Simple(&t, false, false, true);
814 version = atoi(com_token);
815 if (version != SAVEGAME_VERSION)
818 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
822 if(developer_entityparsing.integer)
823 Con_Printf("Host_Loadgame_f: loading description\n");
826 COM_ParseToken_Simple(&t, false, false, true);
828 for (i = 0;i < NUM_SPAWN_PARMS;i++)
830 COM_ParseToken_Simple(&t, false, false, true);
831 spawn_parms[i] = atof(com_token);
834 COM_ParseToken_Simple(&t, false, false, true);
835 // this silliness is so we can load 1.06 save files, which have float skill values
836 current_skill = (int)(atof(com_token) + 0.5);
837 Cvar_SetValue (&cvars_all, "skill", (float)current_skill);
839 if(developer_entityparsing.integer)
840 Con_Printf("Host_Loadgame_f: loading mapname\n");
843 COM_ParseToken_Simple(&t, false, false, true);
844 strlcpy (mapname, com_token, sizeof(mapname));
846 if(developer_entityparsing.integer)
847 Con_Printf("Host_Loadgame_f: loading time\n");
850 COM_ParseToken_Simple(&t, false, false, true);
851 time = atof(com_token);
853 if(developer_entityparsing.integer)
854 Con_Printf("Host_Loadgame_f: spawning server\n");
856 SV_SpawnServer (mapname);
860 Con_Print("Couldn't load map\n");
863 sv.paused = true; // pause until all clients connect
866 if(developer_entityparsing.integer)
867 Con_Printf("Host_Loadgame_f: loading light styles\n");
869 // load the light styles
874 for (i = 0;i < MAX_LIGHTSTYLES;i++)
878 COM_ParseToken_Simple(&t, false, false, true);
879 // if this is a 64 lightstyle savegame produced by Quake, stop now
880 // we have to check this because darkplaces may save more than 64
881 if (com_token[0] == '{')
886 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
889 if(developer_entityparsing.integer)
890 Con_Printf("Host_Loadgame_f: skipping until globals\n");
892 // now skip everything before the first opening brace
893 // (this is for forward compatibility, so that older versions (at
894 // least ones with this fix) can load savegames with extra data before the
895 // first brace, as might be produced by a later engine version)
899 if (!COM_ParseToken_Simple(&t, false, false, true))
901 if (com_token[0] == '{')
908 // unlink all entities
909 World_UnlinkAll(&sv.world);
911 // load the edicts out of the savegame file
916 while (COM_ParseToken_Simple(&t, false, false, true))
917 if (!strcmp(com_token, "}"))
919 if (!COM_ParseToken_Simple(&start, false, false, true))
924 if (strcmp(com_token,"{"))
927 Host_Error ("First token isn't a brace");
932 if(developer_entityparsing.integer)
933 Con_Printf("Host_Loadgame_f: loading globals\n");
935 // parse the global vars
936 PRVM_ED_ParseGlobals (prog, start);
938 // restore the autocvar globals
939 Cvar_UpdateAllAutoCvars(prog->console_cmd->cvars);
944 if (entnum >= MAX_EDICTS)
947 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
949 while (entnum >= prog->max_edicts)
950 PRVM_MEM_IncreaseEdicts(prog);
951 ent = PRVM_EDICT_NUM(entnum);
952 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
953 ent->priv.server->free = false;
955 if(developer_entityparsing.integer)
956 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
958 PRVM_ED_ParseEdict (prog, start, ent);
960 // link it into the bsp tree
961 if (!ent->priv.server->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax)))
969 prog->num_edicts = entnum;
972 for (i = 0;i < NUM_SPAWN_PARMS;i++)
973 svs.clients[0].spawn_parms[i] = spawn_parms[i];
975 if(developer_entityparsing.integer)
976 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
978 // read extended data if present
979 // the extended data is stored inside a /* */ comment block, which the
980 // parser intentionally skips, so we have to check for it manually here
983 while (*end == '\r' || *end == '\n')
985 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
987 if(developer_entityparsing.integer)
988 Con_Printf("Host_Loadgame_f: loading extended data\n");
990 Con_Printf("Loading extended DarkPlaces savegame\n");
992 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
993 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
994 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
997 while (COM_ParseToken_Simple(&t, false, false, true))
999 if (!strcmp(com_token, "sv.lightstyles"))
1001 COM_ParseToken_Simple(&t, false, false, true);
1002 i = atoi(com_token);
1003 COM_ParseToken_Simple(&t, false, false, true);
1004 if (i >= 0 && i < MAX_LIGHTSTYLES)
1005 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1007 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1009 else if (!strcmp(com_token, "sv.model_precache"))
1011 COM_ParseToken_Simple(&t, false, false, true);
1012 i = atoi(com_token);
1013 COM_ParseToken_Simple(&t, false, false, true);
1014 if (i >= 0 && i < MAX_MODELS)
1016 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1017 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1020 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1022 else if (!strcmp(com_token, "sv.sound_precache"))
1024 COM_ParseToken_Simple(&t, false, false, true);
1025 i = atoi(com_token);
1026 COM_ParseToken_Simple(&t, false, false, true);
1027 if (i >= 0 && i < MAX_SOUNDS)
1028 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1030 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1032 else if (!strcmp(com_token, "sv.buffer"))
1034 if (COM_ParseToken_Simple(&t, false, false, true))
1036 i = atoi(com_token);
1039 k = STRINGBUFFER_SAVED;
1040 if (COM_ParseToken_Simple(&t, false, false, true))
1041 k |= atoi(com_token);
1042 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1043 Con_Errorf("failed to create stringbuffer %i\n", i);
1046 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1049 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1051 else if (!strcmp(com_token, "sv.bufstr"))
1053 if (!COM_ParseToken_Simple(&t, false, false, true))
1054 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1057 i = atoi(com_token);
1058 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1061 if (COM_ParseToken_Simple(&t, false, false, true))
1063 k = atoi(com_token);
1064 if (COM_ParseToken_Simple(&t, false, false, true))
1065 BufStr_Set(prog, stringbuffer, k, com_token);
1067 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1070 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1073 Con_Errorf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1076 // skip any trailing text or unrecognized commands
1077 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1084 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1085 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1086 for (i = 0; i < numbuffers; i++)
1088 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1089 if (stringbuffer->flags & STRINGBUFFER_TEMP)
1090 BufStr_Del(prog, stringbuffer);
1093 if(developer_entityparsing.integer)
1094 Con_Printf("Host_Loadgame_f: finished\n");
1096 // make sure we're connected to loopback
1097 if (sv.active && cls.state == ca_disconnected)
1098 CL_EstablishConnection("local:1", -2);
1101 //============================================================================
1104 ======================
1106 ======================
1108 cvar_t cl_name = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1109 static void Host_Name_f(cmd_state_t *cmd)
1111 prvm_prog_t *prog = SVVM_prog;
1113 qboolean valid_colors;
1114 const char *newNameSource;
1115 char newName[sizeof(host_client->name)];
1117 if (Cmd_Argc (cmd) == 1)
1119 if (cmd->source == src_command)
1121 Con_Printf("name: %s\n", cl_name.string);
1126 if (Cmd_Argc (cmd) == 2)
1127 newNameSource = Cmd_Argv(cmd, 1);
1129 newNameSource = Cmd_Args(cmd);
1131 strlcpy(newName, newNameSource, sizeof(newName));
1133 if (cmd->source == src_command)
1135 Cvar_Set (&cvars_all, "_cl_name", newName);
1136 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1138 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1139 Con_Printf("name: %s\n", cl_name.string);
1144 if (realtime < host_client->nametime)
1146 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1150 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1152 // point the string back at updateclient->name to keep it safe
1153 strlcpy (host_client->name, newName, sizeof (host_client->name));
1155 for (i = 0, j = 0;host_client->name[i];i++)
1156 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1157 host_client->name[j++] = host_client->name[i];
1158 host_client->name[j] = 0;
1160 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1161 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1163 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1164 host_client->name[sizeof(host_client->name) - 1] = 0;
1165 host_client->name[0] = STRING_COLOR_TAG;
1166 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1169 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1170 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1173 l = strlen(host_client->name);
1174 if(l < sizeof(host_client->name) - 1)
1176 // duplicate the color tag to escape it
1177 host_client->name[i] = STRING_COLOR_TAG;
1178 host_client->name[i+1] = 0;
1179 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1183 // remove the last character to fix the color code
1184 host_client->name[l-1] = 0;
1185 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1189 // find the last color tag offset and decide if we need to add a reset tag
1190 for (i = 0, j = -1;host_client->name[i];i++)
1192 if (host_client->name[i] == STRING_COLOR_TAG)
1194 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1197 // if this happens to be a reset tag then we don't need one
1198 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1203 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]))
1209 if (host_client->name[i+1] == STRING_COLOR_TAG)
1216 // does not end in the default color string, so add it
1217 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1218 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1220 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1221 if (strcmp(host_client->old_name, host_client->name))
1223 if (host_client->begun)
1224 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1225 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1226 // send notification to all clients
1227 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1228 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1229 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1230 SV_WriteNetnameIntoDemo(host_client);
1235 ======================
1237 ======================
1239 cvar_t cl_playermodel = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1240 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1241 static void Host_Playermodel_f(cmd_state_t *cmd)
1243 prvm_prog_t *prog = SVVM_prog;
1245 char newPath[sizeof(host_client->playermodel)];
1247 if (Cmd_Argc (cmd) == 1)
1249 if (cmd->source == src_command)
1251 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1256 if (Cmd_Argc (cmd) == 2)
1257 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1259 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1261 for (i = 0, j = 0;newPath[i];i++)
1262 if (newPath[i] != '\r' && newPath[i] != '\n')
1263 newPath[j++] = newPath[i];
1266 if (cmd->source == src_command)
1268 Cvar_Set (&cvars_all, "_cl_playermodel", newPath);
1273 if (realtime < host_client->nametime)
1275 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1279 host_client->nametime = realtime + 5;
1282 // point the string back at updateclient->name to keep it safe
1283 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1284 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1285 if (strcmp(host_client->old_model, host_client->playermodel))
1287 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1288 /*// send notification to all clients
1289 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1290 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1291 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1296 ======================
1298 ======================
1300 cvar_t cl_playerskin = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1301 static void Host_Playerskin_f(cmd_state_t *cmd)
1303 prvm_prog_t *prog = SVVM_prog;
1305 char newPath[sizeof(host_client->playerskin)];
1307 if (Cmd_Argc (cmd) == 1)
1309 if (cmd->source == src_command)
1311 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1316 if (Cmd_Argc (cmd) == 2)
1317 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1319 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1321 for (i = 0, j = 0;newPath[i];i++)
1322 if (newPath[i] != '\r' && newPath[i] != '\n')
1323 newPath[j++] = newPath[i];
1326 if (cmd->source == src_command)
1328 Cvar_Set (&cvars_all, "_cl_playerskin", newPath);
1333 if (realtime < host_client->nametime)
1335 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1339 host_client->nametime = realtime + 5;
1342 // point the string back at updateclient->name to keep it safe
1343 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1344 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1345 if (strcmp(host_client->old_skin, host_client->playerskin))
1347 //if (host_client->begun)
1348 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1349 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1350 /*// send notification to all clients
1351 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1352 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1353 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1357 static void Host_Version_f(cmd_state_t *cmd)
1359 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1362 static void Host_Say(cmd_state_t *cmd, qboolean teamonly)
1364 prvm_prog_t *prog = SVVM_prog;
1369 // LadyHavoc: long say messages
1371 qboolean fromServer = false;
1373 if (cmd->source == src_command)
1375 if (cls.state == ca_dedicated)
1382 Cmd_ForwardToServer_f(cmd);
1387 if (Cmd_Argc (cmd) < 2)
1390 if (!teamplay.integer)
1400 // note this uses the chat prefix \001
1401 if (!fromServer && !teamonly)
1402 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1403 else if (!fromServer && teamonly)
1404 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1405 else if(*(sv_adminnick.string))
1406 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1408 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1409 p2 = text + strlen(text);
1410 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1412 if (p2[-1] == '\"' && quoted)
1417 strlcat(text, "\n", sizeof(text));
1419 // note: save is not a valid edict if fromServer is true
1421 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1422 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1423 SV_ClientPrint(text);
1426 if (cls.state == ca_dedicated)
1427 Con_Print(&text[1]);
1431 static void Host_Say_f(cmd_state_t *cmd)
1433 Host_Say(cmd, false);
1437 static void Host_Say_Team_f(cmd_state_t *cmd)
1439 Host_Say(cmd, true);
1443 static void Host_Tell_f(cmd_state_t *cmd)
1445 const char *playername_start = NULL;
1446 size_t playername_length = 0;
1447 int playernumber = 0;
1450 const char *p1, *p2;
1451 char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
1452 qboolean fromServer = false;
1454 if (cmd->source == src_command)
1456 if (cls.state == ca_dedicated)
1460 Cmd_ForwardToServer_f(cmd);
1465 if (Cmd_Argc (cmd) < 2)
1468 // note this uses the chat prefix \001
1470 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1471 else if(*(sv_adminnick.string))
1472 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1474 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1477 p2 = p1 + strlen(p1);
1478 // remove the target name
1479 while (p1 < p2 && *p1 == ' ')
1484 while (p1 < p2 && *p1 == ' ')
1486 while (p1 < p2 && isdigit(*p1))
1488 playernumber = playernumber * 10 + (*p1 - '0');
1496 playername_start = p1;
1497 while (p1 < p2 && *p1 != '"')
1499 playername_length = p1 - playername_start;
1505 playername_start = p1;
1506 while (p1 < p2 && *p1 != ' ')
1508 playername_length = p1 - playername_start;
1510 while (p1 < p2 && *p1 == ' ')
1512 if(playername_start)
1514 // set playernumber to the right client
1516 if(playername_length >= sizeof(namebuf))
1519 Con_Print("Host_Tell: too long player name/ID\n");
1521 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1524 memcpy(namebuf, playername_start, playername_length);
1525 namebuf[playername_length] = 0;
1526 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1528 if (!svs.clients[playernumber].active)
1530 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1534 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1537 Con_Print("Host_Tell: invalid player name/ID\n");
1539 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1542 // remove trailing newlines
1543 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1545 // remove quotes if present
1551 else if (fromServer)
1552 Con_Print("Host_Tell: missing end quote\n");
1554 SV_ClientPrint("Host_Tell: missing end quote\n");
1556 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1559 return; // empty say
1560 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1566 host_client = svs.clients + playernumber;
1567 SV_ClientPrint(text);
1577 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1578 static void Host_Color(cmd_state_t *cmd, int changetop, int changebottom)
1580 prvm_prog_t *prog = SVVM_prog;
1581 int top, bottom, playercolor;
1583 // get top and bottom either from the provided values or the current values
1584 // (allows changing only top or bottom, or both at once)
1585 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1586 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1590 // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
1596 playercolor = top*16 + bottom;
1598 if (cmd->source == src_command)
1600 Cvar_SetValueQuick(&cl_color, playercolor);
1604 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1607 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1609 Con_DPrint("Calling SV_ChangeTeam\n");
1610 prog->globals.fp[OFS_PARM0] = playercolor;
1611 PRVM_serverglobalfloat(time) = sv.time;
1612 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1613 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1617 if (host_client->edict)
1619 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1620 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1622 host_client->colors = playercolor;
1623 if (host_client->old_colors != host_client->colors)
1625 host_client->old_colors = host_client->colors;
1626 // send notification to all clients
1627 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1628 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1629 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1634 static void Host_Color_f(cmd_state_t *cmd)
1638 if (Cmd_Argc(cmd) == 1)
1640 if (cmd->source == src_command)
1642 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1643 Con_Print("color <0-15> [0-15]\n");
1648 if (Cmd_Argc(cmd) == 2)
1649 top = bottom = atoi(Cmd_Argv(cmd, 1));
1652 top = atoi(Cmd_Argv(cmd, 1));
1653 bottom = atoi(Cmd_Argv(cmd, 2));
1655 Host_Color(cmd, top, bottom);
1658 static void Host_TopColor_f(cmd_state_t *cmd)
1660 if (Cmd_Argc(cmd) == 1)
1662 if (cmd->source == src_command)
1664 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1665 Con_Print("topcolor <0-15>\n");
1670 Host_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
1673 static void Host_BottomColor_f(cmd_state_t *cmd)
1675 if (Cmd_Argc(cmd) == 1)
1677 if (cmd->source == src_command)
1679 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1680 Con_Print("bottomcolor <0-15>\n");
1685 Host_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
1688 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1689 cvar_t cl_rate_burstsize = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
1690 static void Host_Rate_f(cmd_state_t *cmd)
1694 if (Cmd_Argc(cmd) != 2)
1696 if (cmd->source == src_command)
1698 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1699 Con_Print("rate <bytespersecond>\n");
1704 rate = atoi(Cmd_Argv(cmd, 1));
1706 if (cmd->source == src_command)
1708 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
1712 host_client->rate = rate;
1714 static void Host_Rate_BurstSize_f(cmd_state_t *cmd)
1718 if (Cmd_Argc(cmd) != 2)
1720 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1721 Con_Print("rate_burstsize <bytes>\n");
1725 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1727 if (cmd->source == src_command)
1729 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
1733 host_client->rate_burstsize = rate_burstsize;
1741 static void Host_Kill_f(cmd_state_t *cmd)
1743 prvm_prog_t *prog = SVVM_prog;
1744 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1746 SV_ClientPrint("Can't suicide -- already dead!\n");
1750 PRVM_serverglobalfloat(time) = sv.time;
1751 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1752 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1761 static void Host_Pause_f(cmd_state_t *cmd)
1763 void (*print) (const char *fmt, ...);
1764 if (cmd->source == src_command)
1766 // if running a client, try to send over network so the pause is handled by the server
1767 if (cls.state == ca_connected)
1769 Cmd_ForwardToServer_f(cmd);
1775 print = SV_ClientPrintf;
1777 if (!pausable.integer)
1779 if (cmd->source == src_client)
1781 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1783 print("Pause not allowed.\n");
1790 if (cmd->source != src_command)
1791 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1792 else if(*(sv_adminnick.string))
1793 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
1795 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
1796 // send notification to all clients
1797 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1798 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1802 ======================
1804 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1805 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1806 ======================
1808 cvar_t cl_pmodel = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
1809 static void Host_PModel_f(cmd_state_t *cmd)
1811 prvm_prog_t *prog = SVVM_prog;
1814 if (Cmd_Argc (cmd) == 1)
1816 if (cmd->source == src_command)
1818 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1822 i = atoi(Cmd_Argv(cmd, 1));
1824 if (cmd->source == src_command)
1826 if (cl_pmodel.integer == i)
1828 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
1829 if (cls.state == ca_connected)
1830 Cmd_ForwardToServer_f(cmd);
1834 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1837 //===========================================================================
1845 static void Host_PreSpawn_f(cmd_state_t *cmd)
1847 if (host_client->prespawned)
1849 Con_Print("prespawn not valid -- already prespawned\n");
1852 host_client->prespawned = true;
1854 if (host_client->netconnection)
1856 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1857 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1858 MSG_WriteByte (&host_client->netconnection->message, 2);
1859 host_client->sendsignon = 0; // enable unlimited sends again
1862 // reset the name change timer because the client will send name soon
1863 host_client->nametime = 0;
1871 static void Host_Spawn_f(cmd_state_t *cmd)
1873 prvm_prog_t *prog = SVVM_prog;
1876 int stats[MAX_CL_STATS];
1878 if (!host_client->prespawned)
1880 Con_Print("Spawn not valid -- not yet prespawned\n");
1883 if (host_client->spawned)
1885 Con_Print("Spawn not valid -- already spawned\n");
1888 host_client->spawned = true;
1890 // reset name change timer again because they might want to change name
1891 // again in the first 5 seconds after connecting
1892 host_client->nametime = 0;
1894 // LadyHavoc: moved this above the QC calls at FrikaC's request
1895 // LadyHavoc: commented this out
1896 //if (host_client->netconnection)
1897 // SZ_Clear (&host_client->netconnection->message);
1899 // run the entrance script
1902 // loaded games are fully initialized already
1903 if (PRVM_serverfunction(RestoreGame))
1905 Con_DPrint("Calling RestoreGame\n");
1906 PRVM_serverglobalfloat(time) = sv.time;
1907 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1908 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1913 //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);
1915 // copy spawn parms out of the client_t
1916 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1917 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1919 // call the spawn function
1920 host_client->clientconnectcalled = true;
1921 PRVM_serverglobalfloat(time) = sv.time;
1922 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1923 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1925 if (cls.state == ca_dedicated)
1926 Con_Printf("%s connected\n", host_client->name);
1928 PRVM_serverglobalfloat(time) = sv.time;
1929 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1932 if (!host_client->netconnection)
1935 // send time of update
1936 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1937 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1939 // send all current names, colors, and frag counts
1940 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1942 if (!client->active)
1944 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1945 MSG_WriteByte (&host_client->netconnection->message, i);
1946 MSG_WriteString (&host_client->netconnection->message, client->name);
1947 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1948 MSG_WriteByte (&host_client->netconnection->message, i);
1949 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1950 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1951 MSG_WriteByte (&host_client->netconnection->message, i);
1952 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1955 // send all current light styles
1956 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1958 if (sv.lightstyles[i][0])
1960 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1961 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1962 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1967 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1968 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1969 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1971 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1972 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1973 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1975 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1976 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1977 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1979 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1980 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1981 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1984 // Never send a roll angle, because savegames can catch the server
1985 // in a state where it is expecting the client to correct the angle
1986 // and it won't happen if the game was just loaded, so you wind up
1987 // with a permanent head tilt
1990 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1991 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1992 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1993 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1997 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1998 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1999 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
2000 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
2003 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
2005 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
2006 MSG_WriteByte (&host_client->netconnection->message, 3);
2014 static void Host_Begin_f(cmd_state_t *cmd)
2016 if (!host_client->spawned)
2018 Con_Print("Begin not valid -- not yet spawned\n");
2021 if (host_client->begun)
2023 Con_Print("Begin not valid -- already begun\n");
2026 host_client->begun = true;
2028 // LadyHavoc: note: this code also exists in SV_DropClient
2032 for (i = 0;i < svs.maxclients;i++)
2033 if (svs.clients[i].active && !svs.clients[i].spawned)
2035 if (i == svs.maxclients)
2037 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2038 sv.paused = sv.loadgame = false; // we're basically done with loading now
2043 //===========================================================================
2050 Kicks a user off of the server
2053 static void Host_Kick_f(cmd_state_t *cmd)
2056 const char *message = NULL;
2059 qboolean byNumber = false;
2066 if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
2068 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
2069 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2075 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2077 if (!host_client->active)
2079 if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
2084 if (i < svs.maxclients)
2086 if (cmd->source == src_command)
2088 if (cls.state == ca_dedicated)
2091 who = cl_name.string;
2096 // can't kick yourself!
2097 if (host_client == save)
2100 if (Cmd_Argc(cmd) > 2)
2102 message = Cmd_Args(cmd);
2103 COM_ParseToken_Simple(&message, false, false, true);
2106 message++; // skip the #
2107 while (*message == ' ') // skip white space
2109 message += strlen(Cmd_Argv(cmd, 2)); // skip the number
2111 while (*message && *message == ' ')
2115 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2117 SV_ClientPrintf("Kicked by %s\n", who);
2118 SV_DropClient (false); // kicked
2125 ===============================================================================
2129 ===============================================================================
2137 static void Host_Give_f(cmd_state_t *cmd)
2139 prvm_prog_t *prog = SVVM_prog;
2143 t = Cmd_Argv(cmd, 1);
2144 v = atoi (Cmd_Argv(cmd, 2));
2158 // MED 01/04/97 added hipnotic give stuff
2159 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2164 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2166 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2168 else if (t[0] == '9')
2169 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2170 else if (t[0] == '0')
2171 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2172 else if (t[0] >= '2')
2173 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2178 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2183 if (gamemode == GAME_ROGUE)
2184 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2186 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2189 if (gamemode == GAME_ROGUE)
2191 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2192 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2193 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2197 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2201 if (gamemode == GAME_ROGUE)
2203 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2204 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2205 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2209 if (gamemode == GAME_ROGUE)
2211 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2212 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2213 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2217 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2221 if (gamemode == GAME_ROGUE)
2223 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = 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, health) = v;
2232 if (gamemode == GAME_ROGUE)
2234 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2235 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2236 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2240 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2244 if (gamemode == GAME_ROGUE)
2246 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2247 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2248 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2254 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2259 for (i=0 ; i<prog->num_edicts ; i++)
2261 e = PRVM_EDICT_NUM(i);
2262 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2265 Con_Print("No viewthing on map\n");
2274 static void Host_Viewmodel_f(cmd_state_t *cmd)
2276 prvm_prog_t *prog = SVVM_prog;
2283 e = FindViewthing(prog);
2286 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
2287 if (m && m->loaded && m->Draw)
2289 PRVM_serveredictfloat(e, frame) = 0;
2290 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2293 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
2302 static void Host_Viewframe_f(cmd_state_t *cmd)
2304 prvm_prog_t *prog = SVVM_prog;
2312 e = FindViewthing(prog);
2315 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2317 f = atoi(Cmd_Argv(cmd, 1));
2318 if (f >= m->numframes)
2321 PRVM_serveredictfloat(e, frame) = f;
2326 static void PrintFrameName (dp_model_t *m, int frame)
2329 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2331 Con_Printf("frame %i\n", frame);
2339 static void Host_Viewnext_f(cmd_state_t *cmd)
2341 prvm_prog_t *prog = SVVM_prog;
2348 e = FindViewthing(prog);
2351 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2353 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2354 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2355 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2357 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2366 static void Host_Viewprev_f(cmd_state_t *cmd)
2368 prvm_prog_t *prog = SVVM_prog;
2375 e = FindViewthing(prog);
2378 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2380 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2381 if (PRVM_serveredictfloat(e, frame) < 0)
2382 PRVM_serveredictfloat(e, frame) = 0;
2384 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2389 ===============================================================================
2393 ===============================================================================
2402 static void Host_Startdemos_f(cmd_state_t *cmd)
2406 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2409 c = Cmd_Argc(cmd) - 1;
2412 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2415 Con_DPrintf("%i demo(s) in loop\n", c);
2417 for (i=1 ; i<c+1 ; i++)
2418 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
2420 // LadyHavoc: clear the remaining slots
2421 for (;i <= MAX_DEMOS;i++)
2422 cls.demos[i-1][0] = 0;
2424 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2438 Return to looping demos
2441 static void Host_Demos_f(cmd_state_t *cmd)
2443 if (cls.state == ca_dedicated)
2445 if (cls.demonum == -1)
2447 CL_Disconnect_f (cmd);
2455 Return to looping demos
2458 static void Host_Stopdemo_f(cmd_state_t *cmd)
2460 if (!cls.demoplayback)
2463 Host_ShutdownServer ();
2466 static void Host_SendCvar_f(cmd_state_t *cmd)
2470 const char *cvarname;
2474 if(Cmd_Argc(cmd) != 2)
2476 cvarname = Cmd_Argv(cmd, 1);
2477 if (cls.state == ca_connected)
2479 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
2480 // LadyHavoc: if there is no such cvar or if it is private, send a
2481 // reply indicating that it has no value
2482 if(!c || (c->flags & CVAR_PRIVATE))
2483 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2485 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2488 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2492 if (cls.state != ca_dedicated)
2496 for(;i<svs.maxclients;i++)
2497 if(svs.clients[i].active && svs.clients[i].netconnection)
2499 host_client = &svs.clients[i];
2500 Host_ClientCommands("sendcvar %s\n", cvarname);
2505 static void MaxPlayers_f(cmd_state_t *cmd)
2509 if (Cmd_Argc(cmd) != 2)
2511 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2517 Con_Print("maxplayers can not be changed while a server is running.\n");
2518 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2521 n = atoi(Cmd_Argv(cmd, 1));
2522 n = bound(1, n, MAX_SCOREBOARD);
2523 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2525 svs.maxclients_next = n;
2527 Cvar_Set (&cvars_all, "deathmatch", "0");
2529 Cvar_Set (&cvars_all, "deathmatch", "1");
2533 =====================
2536 ProQuake rcon support
2537 =====================
2539 static void Host_PQRcon_f(cmd_state_t *cmd)
2543 lhnetsocket_t *mysocket;
2545 if (Cmd_Argc(cmd) == 1)
2547 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2551 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2553 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2557 e = strchr(rcon_password.string, ' ');
2558 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2561 cls.rcon_address = cls.netcon->peeraddress;
2564 if (!rcon_address.string[0])
2566 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2569 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2571 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2575 unsigned char bufdata[64];
2578 MSG_WriteLong(&buf, 0);
2579 MSG_WriteByte(&buf, CCREQ_RCON);
2580 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2581 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2582 MSG_WriteString(&buf, Cmd_Args(cmd));
2583 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2584 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
2589 //=============================================================================
2591 // QuakeWorld commands
2594 =====================
2597 Send the rest of the command line over as
2598 an unconnected command.
2599 =====================
2601 static void Host_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2605 lhnetsocket_t *mysocket;
2607 if (Cmd_Argc(cmd) == 1)
2609 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2613 if (!rcon_password.string || !rcon_password.string[0])
2615 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2619 e = strchr(rcon_password.string, ' ');
2620 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2623 cls.rcon_address = cls.netcon->peeraddress;
2626 if (!rcon_address.string[0])
2628 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2631 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2633 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2634 if (mysocket && Cmd_Args(cmd)[0])
2636 // simply put together the rcon packet and send it
2637 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
2639 if(cls.rcon_commands[cls.rcon_ringpos][0])
2642 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2643 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]);
2644 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2647 for (i = 0;i < MAX_RCONS;i++)
2648 if(cls.rcon_commands[i][0])
2649 if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
2653 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
2654 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2655 cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
2656 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2657 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2659 else if(rcon_secure.integer > 0)
2663 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
2664 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2665 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2668 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2669 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
2675 memcpy(buf, "\377\377\377\377", 4);
2676 dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args(cmd));
2677 NetConn_WriteString(mysocket, buf, &cls.rcon_address);
2683 ====================
2686 user <name or userid>
2688 Dump userdata / masterdata for a user
2689 ====================
2691 static void Host_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2696 if (Cmd_Argc(cmd) != 2)
2698 Con_Printf ("Usage: user <username / userid>\n");
2702 uid = atoi(Cmd_Argv(cmd, 1));
2704 for (i = 0;i < cl.maxclients;i++)
2706 if (!cl.scores[i].name[0])
2708 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
2710 InfoString_Print(cl.scores[i].qw_userinfo);
2714 Con_Printf ("User not in server.\n");
2718 ====================
2721 Dump userids for all current players
2722 ====================
2724 static void Host_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2730 Con_Printf ("userid frags name\n");
2731 Con_Printf ("------ ----- ----\n");
2732 for (i = 0;i < cl.maxclients;i++)
2734 if (cl.scores[i].name[0])
2736 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2741 Con_Printf ("%i total users\n", c);
2746 Host_FullServerinfo_f
2748 Sent by server when serverinfo changes
2751 // TODO: shouldn't this be a cvar instead?
2752 static void Host_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2755 if (Cmd_Argc(cmd) != 2)
2757 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2761 strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
2762 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2763 cl.qw_teamplay = atoi(temp);
2770 Allow clients to change userinfo
2774 static void Host_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2780 if (Cmd_Argc(cmd) != 2)
2782 Con_Printf ("fullinfo <complete info string>\n");
2786 s = Cmd_Argv(cmd, 1);
2791 size_t len = strcspn(s, "\\");
2792 if (len >= sizeof(key)) {
2793 len = sizeof(key) - 1;
2795 strlcpy(key, s, len + 1);
2799 Con_Printf ("MISSING VALUE\n");
2802 ++s; // Skip over backslash.
2804 len = strcspn(s, "\\");
2805 if (len >= sizeof(value)) {
2806 len = sizeof(value) - 1;
2808 strlcpy(value, s, len + 1);
2810 CL_SetInfo(key, value, false, false, false, false);
2817 ++s; // Skip over backslash.
2825 Allow clients to change userinfo
2828 static void Host_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2830 if (Cmd_Argc(cmd) == 1)
2832 InfoString_Print(cls.userinfo);
2835 if (Cmd_Argc(cmd) != 3)
2837 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2840 CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
2844 ====================
2847 packet <destination> <contents>
2849 Contents allows \n escape character
2850 ====================
2852 static void Host_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2858 lhnetaddress_t address;
2859 lhnetsocket_t *mysocket;
2861 if (Cmd_Argc(cmd) != 3)
2863 Con_Printf ("packet <destination> <contents>\n");
2867 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
2869 Con_Printf ("Bad address\n");
2873 in = Cmd_Argv(cmd, 2);
2875 send[0] = send[1] = send[2] = send[3] = -1;
2877 l = (int)strlen (in);
2878 for (i=0 ; i<l ; i++)
2880 if (out >= send + sizeof(send) - 1)
2882 if (in[i] == '\\' && in[i+1] == 'n')
2887 else if (in[i] == '\\' && in[i+1] == '0')
2892 else if (in[i] == '\\' && in[i+1] == 't')
2897 else if (in[i] == '\\' && in[i+1] == 'r')
2902 else if (in[i] == '\\' && in[i+1] == '"')
2911 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2913 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2915 NetConn_Write(mysocket, send, out - send, &address);
2919 ====================
2922 Send back ping and packet loss update for all current players to this player
2923 ====================
2925 static void Host_Pings_f(cmd_state_t *cmd)
2927 int i, j, ping, packetloss, movementloss;
2930 if (!host_client->netconnection)
2933 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2935 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2936 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2938 for (i = 0;i < svs.maxclients;i++)
2942 if (svs.clients[i].netconnection)
2944 for (j = 0;j < NETGRAPH_PACKETS;j++)
2945 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2947 for (j = 0;j < NETGRAPH_PACKETS;j++)
2948 if (svs.clients[i].movement_count[j] < 0)
2951 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2952 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2953 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2954 ping = bound(0, ping, 9999);
2955 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2957 // send qw_svc_updateping and qw_svc_updatepl messages
2958 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2959 MSG_WriteShort(&host_client->netconnection->message, ping);
2960 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2961 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2965 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2967 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2969 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2970 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2973 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2974 MSG_WriteString(&host_client->netconnection->message, "\n");
2977 static void Host_PingPLReport_f(cmd_state_t *cmd)
2981 int l = Cmd_Argc(cmd);
2982 if (l > cl.maxclients)
2984 for (i = 0;i < l;i++)
2986 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
2987 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
2988 if(errbyte && *errbyte == ',')
2989 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2991 cl.scores[i].qw_movementloss = 0;
2995 //=============================================================================
3002 void Host_InitCommands (void)
3004 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
3006 Cvar_RegisterVariable(&cl_name);
3007 Cvar_RegisterVariable(&cl_color);
3008 Cvar_RegisterVariable(&cl_rate);
3009 Cvar_RegisterVariable(&cl_rate_burstsize);
3010 Cvar_RegisterVariable(&cl_pmodel);
3011 Cvar_RegisterVariable(&cl_playermodel);
3012 Cvar_RegisterVariable(&cl_playerskin);
3013 Cvar_RegisterVariable(&rcon_password);
3014 Cvar_RegisterVariable(&rcon_address);
3015 Cvar_RegisterVariable(&rcon_secure);
3016 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
3017 Cvar_RegisterVariable(&r_fixtrans_auto);
3018 Cvar_RegisterVariable(&team);
3019 Cvar_RegisterVariable(&skin);
3020 Cvar_RegisterVariable(&noaim);
3021 Cvar_RegisterVariable(&sv_cheats);
3022 Cvar_RegisterCallback(&sv_cheats, Host_DisableCheats_c);
3023 Cvar_RegisterVariable(&sv_adminnick);
3024 Cvar_RegisterVariable(&sv_status_privacy);
3025 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3026 Cvar_RegisterVariable(&sv_namechangetimer);
3028 // client commands - this includes server commands because the client can host a server, so they must exist
3029 Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
3030 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", Host_Status_f, "print server status information");
3031 Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "map", Host_Map_f, "kick everyone off the server and start a new level");
3032 Cmd_AddCommand(CMD_SHARED, "restart", Host_Restart_f, "restart current level");
3033 Cmd_AddCommand(CMD_SHARED, "changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
3034 Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
3035 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", Host_Say_f, "send a chat message to everyone on the server");
3036 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", Host_Say_Team_f, "send a chat message to your team on the server");
3037 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", Host_Tell_f, "send a chat message to only one person on the server");
3038 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", Host_Pause_f, "pause the game (if the server allows pausing)");
3039 Cmd_AddCommand(CMD_SHARED, "kick", Host_Kick_f, "kick a player off the server by number or name");
3040 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", Host_Ping_f, "print ping times of all players on the server");
3041 Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "load", Host_Loadgame_f, "load a saved game file");
3042 Cmd_AddCommand(CMD_SHARED, "save", Host_Savegame_f, "save the game to a file");
3043 Cmd_AddCommand(CMD_SHARED, "viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3044 Cmd_AddCommand(CMD_SHARED, "viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3045 Cmd_AddCommand(CMD_SHARED, "viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3046 Cmd_AddCommand(CMD_SHARED, "viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3047 Cmd_AddCommand(CMD_SHARED, "maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3048 Cmd_AddCommand(CMD_SHARED, "user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3049 Cmd_AddCommand(CMD_SHARED, "users", Host_Users_f, "prints additional information about all players on the scoreboard");
3051 // commands that do not have automatic forwarding from cmd_client, these are internal details of the network protocol and not of interest to users (if they know what they are doing they can still use a generic "cmd prespawn" or similar)
3052 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", Host_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
3053 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "spawn", Host_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3054 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "begin", Host_Begin_f, "internal use - signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
3055 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "pings", Host_Pings_f, "internal use - command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
3057 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", Host_God_f, "god mode (invulnerability)");
3058 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", Host_Notarget_f, "notarget mode (monsters do not see you)");
3059 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", Host_Fly_f, "fly mode (flight)");
3060 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
3061 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", Host_Give_f, "alter inventory");
3062 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", Host_Kill_f, "die instantly");
3063 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", Host_Name_f, "change your player name");
3064 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", Host_Color_f, "change your player shirt and pants colors");
3065 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", Host_Rate_f, "change your network connection speed");
3066 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", Host_Rate_BurstSize_f, "change your network connection speed");
3067 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", Host_PModel_f, "(Nehahra-only) change your player model choice");
3068 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", Host_Playermodel_f, "change your player model");
3069 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", Host_Playerskin_f, "change your player skin number");
3071 Cmd_AddCommand(CMD_CLIENT, "connect", Host_Connect_f, "connect to a server by IP address or hostname");
3072 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "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)");
3073 Cmd_AddCommand(CMD_CLIENT, "startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3074 Cmd_AddCommand(CMD_CLIENT, "demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3075 Cmd_AddCommand(CMD_CLIENT, "stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3076 Cmd_AddCommand(CMD_CLIENT, "sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3077 Cmd_AddCommand(CMD_CLIENT, "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");
3078 Cmd_AddCommand(CMD_CLIENT, "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");
3079 Cmd_AddCommand(CMD_CLIENT, "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)");
3080 Cmd_AddCommand(CMD_CLIENT, "fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3081 Cmd_AddCommand(CMD_CLIENT, "setinfo", Host_SetInfo_f, "modifies your userinfo");
3082 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3083 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3084 Cmd_AddCommand(CMD_CLIENT, "bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3085 Cmd_AddCommand(CMD_CLIENT, "fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
3087 // commands that are only sent by server to client for execution
3088 Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "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)");
3089 Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3092 void Host_NoOperation_f(cmd_state_t *cmd)