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 cvar_t developer_entityparsing;
56 void Host_Quit_f(cmd_state_t *cmd)
58 if(host.state == host_shutdown)
59 Con_Printf("shutting down already!\n");
61 host.state = host_shutdown;
69 static void SV_Status_f(cmd_state_t *cmd)
71 prvm_prog_t *prog = SVVM_prog;
74 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
75 void (*print) (const char *fmt, ...);
76 char ip[48]; // can contain a full length v6 address with [] and a port
80 if (cmd->source == src_command)
82 // if running a client, try to send over network so the client's status report parser will see the report
83 if (cls.state == ca_connected)
85 Cmd_ForwardToServer_f(cmd);
91 print = SV_ClientPrintf;
97 if (Cmd_Argc(cmd) == 2)
99 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
101 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
105 for (players = 0, i = 0;i < svs.maxclients;i++)
106 if (svs.clients[i].active)
108 print ("host: %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
109 print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
110 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
111 print ("map: %s\n", sv.name);
112 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
113 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
116 print ("^2IP %%pl ping time frags no name\n");
118 print ("^5IP no name\n");
120 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
127 if (in == 0 || in == 1)
129 seconds = (int)(host.realtime - client->connecttime);
130 minutes = seconds / 60;
133 seconds -= (minutes * 60);
134 hours = minutes / 60;
136 minutes -= (hours * 60);
142 if (client->netconnection)
143 for (j = 0;j < NETGRAPH_PACKETS;j++)
144 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
146 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
147 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
150 if(sv_status_privacy.integer && cmd->source != src_command)
151 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
153 strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
155 frags = client->frags;
157 if(sv_status_show_qcstatus.integer)
159 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
160 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
166 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
167 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
171 frags = atoi(qcstatus);
175 if (in == 0) // default layout
177 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
179 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
180 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
185 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
186 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
190 else if (in == 1) // extended layout
192 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);
194 else if (in == 2) // reduced layout
196 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
206 Sets client to godmode
209 static void SV_God_f(cmd_state_t *cmd)
211 prvm_prog_t *prog = SVVM_prog;
213 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
214 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
215 SV_ClientPrint("godmode OFF\n");
217 SV_ClientPrint("godmode ON\n");
220 static void SV_Notarget_f(cmd_state_t *cmd)
222 prvm_prog_t *prog = SVVM_prog;
224 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
225 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
226 SV_ClientPrint("notarget OFF\n");
228 SV_ClientPrint("notarget ON\n");
231 qboolean noclip_anglehack;
233 static void SV_Noclip_f(cmd_state_t *cmd)
235 prvm_prog_t *prog = SVVM_prog;
237 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
239 noclip_anglehack = true;
240 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
241 SV_ClientPrint("noclip ON\n");
245 noclip_anglehack = false;
246 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
247 SV_ClientPrint("noclip OFF\n");
255 Sets client to flymode
258 static void SV_Fly_f(cmd_state_t *cmd)
260 prvm_prog_t *prog = SVVM_prog;
262 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
264 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
265 SV_ClientPrint("flymode ON\n");
269 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
270 SV_ClientPrint("flymode OFF\n");
281 static void SV_Ping_f(cmd_state_t *cmd)
285 void (*print) (const char *fmt, ...);
287 if (cmd->source == src_command)
289 // if running a client, try to send over network so the client's ping report parser will see the report
290 if (cls.state == ca_connected)
292 Cmd_ForwardToServer_f(cmd);
298 print = SV_ClientPrintf;
303 print("Client ping times:\n");
304 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
308 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
312 // Disable cheats if sv_cheats is turned off
313 static void SV_DisableCheats_c(char *value)
315 prvm_prog_t *prog = SVVM_prog;
318 if (value[0] == '0' && !value[1])
320 while (svs.clients[i].edict)
322 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
323 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
324 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
325 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
326 if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
327 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
329 noclip_anglehack = false;
330 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
338 ===============================================================================
342 ===============================================================================
346 ======================
351 command from the console. Active clients are kicked off.
352 ======================
354 static void SV_Map_f(cmd_state_t *cmd)
356 char level[MAX_QPATH];
358 if (Cmd_Argc(cmd) != 2)
360 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
364 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
365 if (gamemode == GAME_DELUXEQUAKE)
366 Cvar_Set(&cvars_all, "warpmark", "");
368 cls.demonum = -1; // stop demo loop in case this fails
371 Host_ShutdownServer();
373 if(svs.maxclients != svs.maxclients_next)
375 svs.maxclients = svs.maxclients_next;
377 Mem_Free(svs.clients);
378 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
383 if (key_dest == key_menu || key_dest == key_menu_grabbed)
388 svs.serverflags = 0; // haven't completed an episode yet
389 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
390 SV_SpawnServer(level);
391 if (sv.active && cls.state == ca_disconnected)
392 CL_EstablishConnection("local:1", -2);
399 Goes to a new map, taking all clients along
402 static void SV_Changelevel_f(cmd_state_t *cmd)
404 char level[MAX_QPATH];
406 if (Cmd_Argc(cmd) != 2)
408 Con_Print("changelevel <levelname> : continue game on a new level\n");
419 if (key_dest == key_menu || key_dest == key_menu_grabbed)
424 SV_SaveSpawnparms ();
425 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
426 SV_SpawnServer(level);
427 if (sv.active && cls.state == ca_disconnected)
428 CL_EstablishConnection("local:1", -2);
435 Restarts the current server for a dead player
438 static void SV_Restart_f(cmd_state_t *cmd)
440 char mapname[MAX_QPATH];
442 if (Cmd_Argc(cmd) != 1)
444 Con_Print("restart : restart current level\n");
449 Con_Print("Only the server may restart\n");
455 if (key_dest == key_menu || key_dest == key_menu_grabbed)
460 strlcpy(mapname, sv.name, sizeof(mapname));
461 SV_SpawnServer(mapname);
462 if (sv.active && cls.state == ca_disconnected)
463 CL_EstablishConnection("local:1", -2);
470 This command causes the client to wait for the signon messages again.
471 This is sent just before a server changes levels
474 void CL_Reconnect_f(cmd_state_t *cmd)
477 // if not connected, reconnect to the most recent server
480 // if we have connected to a server recently, the userinfo
481 // will still contain its IP address, so get the address...
482 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
484 CL_EstablishConnection(temp, -1);
486 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
489 // if connected, do something based on protocol
490 if (cls.protocol == PROTOCOL_QUAKEWORLD)
492 // quakeworld can just re-login
493 if (cls.qw_downloadmemory) // don't change when downloading
498 if (cls.state == ca_connected)
500 Con_Printf("Server is changing level...\n");
501 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
502 MSG_WriteString(&cls.netcon->message, "new");
507 // netquake uses reconnect on level changes (silly)
508 if (Cmd_Argc(cmd) != 1)
510 Con_Print("reconnect : wait for signon messages again\n");
515 Con_Print("reconnect: no signon, ignoring reconnect\n");
518 cls.signon = 0; // need new connection messages
523 =====================
526 User command to connect to server
527 =====================
529 static void CL_Connect_f(cmd_state_t *cmd)
531 if (Cmd_Argc(cmd) < 2)
533 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
536 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
537 if(rcon_secure.integer <= 0)
538 Cvar_SetQuick(&rcon_password, "");
539 CL_EstablishConnection(Cmd_Argv(cmd, 1), 2);
544 ===============================================================================
548 ===============================================================================
551 #define SAVEGAME_VERSION 5
553 void SV_Savegame_to(prvm_prog_t *prog, const char *name)
556 int i, k, l, numbuffers, lightstyles = 64;
557 char comment[SAVEGAME_COMMENT_LENGTH+1];
558 char line[MAX_INPUTLINE];
562 // first we have to figure out if this can be saved in 64 lightstyles
563 // (for Quake compatibility)
564 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
565 if (sv.lightstyles[i][0])
568 isserver = prog == SVVM_prog;
570 Con_Printf("Saving game to %s...\n", name);
571 f = FS_OpenRealFile(name, "wb", false);
574 Con_Print("ERROR: couldn't open.\n");
578 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
580 memset(comment, 0, sizeof(comment));
582 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));
584 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
585 // convert space to _ to make stdio happy
586 // LadyHavoc: convert control characters to _ as well
587 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
588 if (ISWHITESPACEORCONTROL(comment[i]))
590 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
592 FS_Printf(f, "%s\n", comment);
595 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
596 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
597 FS_Printf(f, "%d\n", current_skill);
598 FS_Printf(f, "%s\n", sv.name);
599 FS_Printf(f, "%f\n",sv.time);
603 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
604 FS_Printf(f, "(dummy)\n");
605 FS_Printf(f, "%d\n", 0);
606 FS_Printf(f, "%s\n", "(dummy)");
607 FS_Printf(f, "%f\n", host.realtime);
610 // write the light styles
611 for (i=0 ; i<lightstyles ; i++)
613 if (isserver && sv.lightstyles[i][0])
614 FS_Printf(f, "%s\n", sv.lightstyles[i]);
619 PRVM_ED_WriteGlobals (prog, f);
620 for (i=0 ; i<prog->num_edicts ; i++)
622 FS_Printf(f,"// edict %d\n", i);
623 //Con_Printf("edict %d...\n", i);
624 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
629 FS_Printf(f,"// DarkPlaces extended savegame\n");
630 // darkplaces extension - extra lightstyles, support for color lightstyles
631 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
632 if (isserver && sv.lightstyles[i][0])
633 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
635 // darkplaces extension - model precaches
636 for (i=1 ; i<MAX_MODELS ; i++)
637 if (sv.model_precache[i][0])
638 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
640 // darkplaces extension - sound precaches
641 for (i=1 ; i<MAX_SOUNDS ; i++)
642 if (sv.sound_precache[i][0])
643 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
645 // darkplaces extension - save buffers
646 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
647 for (i = 0; i < numbuffers; i++)
649 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
650 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
652 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
653 for(k = 0; k < stringbuffer->num_strings; k++)
655 if (!stringbuffer->strings[k])
657 // Parse the string a bit to turn special characters
658 // (like newline, specifically) into escape codes
659 s = stringbuffer->strings[k];
660 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
687 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
695 Con_Print("done.\n");
703 static void SV_Savegame_f(cmd_state_t *cmd)
705 prvm_prog_t *prog = SVVM_prog;
706 char name[MAX_QPATH];
707 qboolean deadflag = false;
711 Con_Print("Can't save - no server running.\n");
715 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
719 // singleplayer checks
722 Con_Print("Can't save in intermission.\n");
728 Con_Print("Can't savegame with a dead player\n");
733 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");
735 if (Cmd_Argc(cmd) != 2)
737 Con_Print("save <savename> : save a game\n");
741 if (strstr(Cmd_Argv(cmd, 1), ".."))
743 Con_Print("Relative pathnames are not allowed.\n");
747 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
748 FS_DefaultExtension (name, ".sav", sizeof (name));
750 SV_Savegame_to(prog, name);
760 static void SV_Loadgame_f(cmd_state_t *cmd)
762 prvm_prog_t *prog = SVVM_prog;
763 char filename[MAX_QPATH];
764 char mapname[MAX_QPATH];
771 int i, k, numbuffers;
774 float spawn_parms[NUM_SPAWN_PARMS];
775 prvm_stringbuffer_t *stringbuffer;
777 if (Cmd_Argc(cmd) != 2)
779 Con_Print("load <savename> : load a game\n");
783 strlcpy (filename, Cmd_Argv(cmd, 1), sizeof(filename));
784 FS_DefaultExtension (filename, ".sav", sizeof (filename));
786 Con_Printf("Loading game from %s...\n", filename);
788 // stop playing demos
789 if (cls.demoplayback)
794 if (key_dest == key_menu || key_dest == key_menu_grabbed)
799 cls.demonum = -1; // stop demo loop in case this fails
801 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
804 Con_Print("ERROR: couldn't open.\n");
808 if(developer_entityparsing.integer)
809 Con_Printf("SV_Loadgame_f: loading version\n");
812 COM_ParseToken_Simple(&t, false, false, true);
813 version = atoi(com_token);
814 if (version != SAVEGAME_VERSION)
817 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
821 if(developer_entityparsing.integer)
822 Con_Printf("SV_Loadgame_f: loading description\n");
825 COM_ParseToken_Simple(&t, false, false, true);
827 for (i = 0;i < NUM_SPAWN_PARMS;i++)
829 COM_ParseToken_Simple(&t, false, false, true);
830 spawn_parms[i] = atof(com_token);
833 COM_ParseToken_Simple(&t, false, false, true);
834 // this silliness is so we can load 1.06 save files, which have float skill values
835 current_skill = (int)(atof(com_token) + 0.5);
836 Cvar_SetValue (&cvars_all, "skill", (float)current_skill);
838 if(developer_entityparsing.integer)
839 Con_Printf("SV_Loadgame_f: loading mapname\n");
842 COM_ParseToken_Simple(&t, false, false, true);
843 strlcpy (mapname, com_token, sizeof(mapname));
845 if(developer_entityparsing.integer)
846 Con_Printf("SV_Loadgame_f: loading time\n");
849 COM_ParseToken_Simple(&t, false, false, true);
850 time = atof(com_token);
852 if(developer_entityparsing.integer)
853 Con_Printf("SV_Loadgame_f: spawning server\n");
855 SV_SpawnServer (mapname);
859 Con_Print("Couldn't load map\n");
862 sv.paused = true; // pause until all clients connect
865 if(developer_entityparsing.integer)
866 Con_Printf("SV_Loadgame_f: loading light styles\n");
868 // load the light styles
873 for (i = 0;i < MAX_LIGHTSTYLES;i++)
877 COM_ParseToken_Simple(&t, false, false, true);
878 // if this is a 64 lightstyle savegame produced by Quake, stop now
879 // we have to check this because darkplaces may save more than 64
880 if (com_token[0] == '{')
885 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
888 if(developer_entityparsing.integer)
889 Con_Printf("SV_Loadgame_f: skipping until globals\n");
891 // now skip everything before the first opening brace
892 // (this is for forward compatibility, so that older versions (at
893 // least ones with this fix) can load savegames with extra data before the
894 // first brace, as might be produced by a later engine version)
898 if (!COM_ParseToken_Simple(&t, false, false, true))
900 if (com_token[0] == '{')
907 // unlink all entities
908 World_UnlinkAll(&sv.world);
910 // load the edicts out of the savegame file
915 while (COM_ParseToken_Simple(&t, false, false, true))
916 if (!strcmp(com_token, "}"))
918 if (!COM_ParseToken_Simple(&start, false, false, true))
923 if (strcmp(com_token,"{"))
926 Host_Error ("First token isn't a brace");
931 if(developer_entityparsing.integer)
932 Con_Printf("SV_Loadgame_f: loading globals\n");
934 // parse the global vars
935 PRVM_ED_ParseGlobals (prog, start);
937 // restore the autocvar globals
938 Cvar_UpdateAllAutoCvars(prog->console_cmd->cvars);
943 if (entnum >= MAX_EDICTS)
946 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
948 while (entnum >= prog->max_edicts)
949 PRVM_MEM_IncreaseEdicts(prog);
950 ent = PRVM_EDICT_NUM(entnum);
951 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
952 ent->priv.server->free = false;
954 if(developer_entityparsing.integer)
955 Con_Printf("SV_Loadgame_f: loading edict %d\n", entnum);
957 PRVM_ED_ParseEdict (prog, start, ent);
959 // link it into the bsp tree
960 if (!ent->priv.server->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax)))
968 prog->num_edicts = entnum;
971 for (i = 0;i < NUM_SPAWN_PARMS;i++)
972 svs.clients[0].spawn_parms[i] = spawn_parms[i];
974 if(developer_entityparsing.integer)
975 Con_Printf("SV_Loadgame_f: skipping until extended data\n");
977 // read extended data if present
978 // the extended data is stored inside a /* */ comment block, which the
979 // parser intentionally skips, so we have to check for it manually here
982 while (*end == '\r' || *end == '\n')
984 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
986 if(developer_entityparsing.integer)
987 Con_Printf("SV_Loadgame_f: loading extended data\n");
989 Con_Printf("Loading extended DarkPlaces savegame\n");
991 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
992 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
993 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
996 while (COM_ParseToken_Simple(&t, false, false, true))
998 if (!strcmp(com_token, "sv.lightstyles"))
1000 COM_ParseToken_Simple(&t, false, false, true);
1001 i = atoi(com_token);
1002 COM_ParseToken_Simple(&t, false, false, true);
1003 if (i >= 0 && i < MAX_LIGHTSTYLES)
1004 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1006 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1008 else if (!strcmp(com_token, "sv.model_precache"))
1010 COM_ParseToken_Simple(&t, false, false, true);
1011 i = atoi(com_token);
1012 COM_ParseToken_Simple(&t, false, false, true);
1013 if (i >= 0 && i < MAX_MODELS)
1015 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1016 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1019 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1021 else if (!strcmp(com_token, "sv.sound_precache"))
1023 COM_ParseToken_Simple(&t, false, false, true);
1024 i = atoi(com_token);
1025 COM_ParseToken_Simple(&t, false, false, true);
1026 if (i >= 0 && i < MAX_SOUNDS)
1027 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1029 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1031 else if (!strcmp(com_token, "sv.buffer"))
1033 if (COM_ParseToken_Simple(&t, false, false, true))
1035 i = atoi(com_token);
1038 k = STRINGBUFFER_SAVED;
1039 if (COM_ParseToken_Simple(&t, false, false, true))
1040 k |= atoi(com_token);
1041 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1042 Con_Errorf("failed to create stringbuffer %i\n", i);
1045 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1048 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1050 else if (!strcmp(com_token, "sv.bufstr"))
1052 if (!COM_ParseToken_Simple(&t, false, false, true))
1053 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1056 i = atoi(com_token);
1057 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1060 if (COM_ParseToken_Simple(&t, false, false, true))
1062 k = atoi(com_token);
1063 if (COM_ParseToken_Simple(&t, false, false, true))
1064 BufStr_Set(prog, stringbuffer, k, com_token);
1066 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1069 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1072 Con_Errorf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1075 // skip any trailing text or unrecognized commands
1076 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1083 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1084 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1085 for (i = 0; i < numbuffers; i++)
1087 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1088 if (stringbuffer->flags & STRINGBUFFER_TEMP)
1089 BufStr_Del(prog, stringbuffer);
1092 if(developer_entityparsing.integer)
1093 Con_Printf("SV_Loadgame_f: finished\n");
1095 // make sure we're connected to loopback
1096 if (sv.active && cls.state == ca_disconnected)
1097 CL_EstablishConnection("local:1", -2);
1100 //============================================================================
1103 ======================
1105 ======================
1107 cvar_t cl_name = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1108 static void CL_Name_f(cmd_state_t *cmd)
1110 prvm_prog_t *prog = SVVM_prog;
1112 qboolean valid_colors;
1113 const char *newNameSource;
1114 char newName[sizeof(host_client->name)];
1116 if (Cmd_Argc (cmd) == 1)
1118 if (cmd->source == src_command)
1120 Con_Printf("name: %s\n", cl_name.string);
1125 if (Cmd_Argc (cmd) == 2)
1126 newNameSource = Cmd_Argv(cmd, 1);
1128 newNameSource = Cmd_Args(cmd);
1130 strlcpy(newName, newNameSource, sizeof(newName));
1132 if (cmd->source == src_command)
1134 Cvar_Set (&cvars_all, "_cl_name", newName);
1135 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1137 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1138 Con_Printf("name: %s\n", cl_name.string);
1143 if (host.realtime < host_client->nametime)
1145 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1149 host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
1151 // point the string back at updateclient->name to keep it safe
1152 strlcpy (host_client->name, newName, sizeof (host_client->name));
1154 for (i = 0, j = 0;host_client->name[i];i++)
1155 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1156 host_client->name[j++] = host_client->name[i];
1157 host_client->name[j] = 0;
1159 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1160 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1162 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1163 host_client->name[sizeof(host_client->name) - 1] = 0;
1164 host_client->name[0] = STRING_COLOR_TAG;
1165 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1168 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1169 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1172 l = strlen(host_client->name);
1173 if(l < sizeof(host_client->name) - 1)
1175 // duplicate the color tag to escape it
1176 host_client->name[i] = STRING_COLOR_TAG;
1177 host_client->name[i+1] = 0;
1178 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1182 // remove the last character to fix the color code
1183 host_client->name[l-1] = 0;
1184 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1188 // find the last color tag offset and decide if we need to add a reset tag
1189 for (i = 0, j = -1;host_client->name[i];i++)
1191 if (host_client->name[i] == STRING_COLOR_TAG)
1193 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1196 // if this happens to be a reset tag then we don't need one
1197 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1202 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]))
1208 if (host_client->name[i+1] == STRING_COLOR_TAG)
1215 // does not end in the default color string, so add it
1216 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1217 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1219 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1220 if (strcmp(host_client->old_name, host_client->name))
1222 if (host_client->begun)
1223 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1224 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1225 // send notification to all clients
1226 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1227 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1228 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1229 SV_WriteNetnameIntoDemo(host_client);
1234 ======================
1236 ======================
1238 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)"};
1239 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1240 static void CL_Playermodel_f(cmd_state_t *cmd)
1242 prvm_prog_t *prog = SVVM_prog;
1244 char newPath[sizeof(host_client->playermodel)];
1246 if (Cmd_Argc (cmd) == 1)
1248 if (cmd->source == src_command)
1250 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1255 if (Cmd_Argc (cmd) == 2)
1256 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1258 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1260 for (i = 0, j = 0;newPath[i];i++)
1261 if (newPath[i] != '\r' && newPath[i] != '\n')
1262 newPath[j++] = newPath[i];
1265 if (cmd->source == src_command)
1267 Cvar_Set (&cvars_all, "_cl_playermodel", newPath);
1272 if (host.realtime < host_client->nametime)
1274 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1278 host_client->nametime = host.realtime + 5;
1281 // point the string back at updateclient->name to keep it safe
1282 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1283 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1284 if (strcmp(host_client->old_model, host_client->playermodel))
1286 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1287 /*// send notification to all clients
1288 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1289 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1290 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1295 ======================
1297 ======================
1299 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)"};
1300 static void CL_Playerskin_f(cmd_state_t *cmd)
1302 prvm_prog_t *prog = SVVM_prog;
1304 char newPath[sizeof(host_client->playerskin)];
1306 if (Cmd_Argc (cmd) == 1)
1308 if (cmd->source == src_command)
1310 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1315 if (Cmd_Argc (cmd) == 2)
1316 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1318 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1320 for (i = 0, j = 0;newPath[i];i++)
1321 if (newPath[i] != '\r' && newPath[i] != '\n')
1322 newPath[j++] = newPath[i];
1325 if (cmd->source == src_command)
1327 Cvar_Set (&cvars_all, "_cl_playerskin", newPath);
1332 if (host.realtime < host_client->nametime)
1334 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1338 host_client->nametime = host.realtime + 5;
1341 // point the string back at updateclient->name to keep it safe
1342 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1343 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1344 if (strcmp(host_client->old_skin, host_client->playerskin))
1346 //if (host_client->begun)
1347 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1348 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1349 /*// send notification to all clients
1350 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1351 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1352 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1356 static void Host_Version_f(cmd_state_t *cmd)
1358 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1361 static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
1363 prvm_prog_t *prog = SVVM_prog;
1368 // LadyHavoc: long say messages
1370 qboolean fromServer = false;
1372 if (cmd->source == src_command)
1374 if (cls.state == ca_dedicated)
1381 Cmd_ForwardToServer_f(cmd);
1386 if (Cmd_Argc (cmd) < 2)
1389 if (!teamplay.integer)
1399 // note this uses the chat prefix \001
1400 if (!fromServer && !teamonly)
1401 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1402 else if (!fromServer && teamonly)
1403 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1404 else if(*(sv_adminnick.string))
1405 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1407 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1408 p2 = text + strlen(text);
1409 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1411 if (p2[-1] == '\"' && quoted)
1416 strlcat(text, "\n", sizeof(text));
1418 // note: save is not a valid edict if fromServer is true
1420 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1421 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1422 SV_ClientPrint(text);
1425 if (cls.state == ca_dedicated)
1426 Con_Print(&text[1]);
1429 static void SV_Say_f(cmd_state_t *cmd)
1434 static void SV_Say_Team_f(cmd_state_t *cmd)
1439 static void SV_Tell_f(cmd_state_t *cmd)
1441 const char *playername_start = NULL;
1442 size_t playername_length = 0;
1443 int playernumber = 0;
1446 const char *p1, *p2;
1447 char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
1448 qboolean fromServer = false;
1450 if (cmd->source == src_command)
1452 if (cls.state == ca_dedicated)
1456 Cmd_ForwardToServer_f(cmd);
1461 if (Cmd_Argc (cmd) < 2)
1464 // note this uses the chat prefix \001
1466 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1467 else if(*(sv_adminnick.string))
1468 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1470 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1473 p2 = p1 + strlen(p1);
1474 // remove the target name
1475 while (p1 < p2 && *p1 == ' ')
1480 while (p1 < p2 && *p1 == ' ')
1482 while (p1 < p2 && isdigit(*p1))
1484 playernumber = playernumber * 10 + (*p1 - '0');
1492 playername_start = p1;
1493 while (p1 < p2 && *p1 != '"')
1495 playername_length = p1 - playername_start;
1501 playername_start = p1;
1502 while (p1 < p2 && *p1 != ' ')
1504 playername_length = p1 - playername_start;
1506 while (p1 < p2 && *p1 == ' ')
1508 if(playername_start)
1510 // set playernumber to the right client
1512 if(playername_length >= sizeof(namebuf))
1515 Con_Print("Host_Tell: too long player name/ID\n");
1517 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1520 memcpy(namebuf, playername_start, playername_length);
1521 namebuf[playername_length] = 0;
1522 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1524 if (!svs.clients[playernumber].active)
1526 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1530 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1533 Con_Print("Host_Tell: invalid player name/ID\n");
1535 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1538 // remove trailing newlines
1539 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1541 // remove quotes if present
1547 else if (fromServer)
1548 Con_Print("Host_Tell: missing end quote\n");
1550 SV_ClientPrint("Host_Tell: missing end quote\n");
1552 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1555 return; // empty say
1556 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1562 host_client = svs.clients + playernumber;
1563 SV_ClientPrint(text);
1572 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1573 static void CL_Color(cmd_state_t *cmd, int changetop, int changebottom)
1575 prvm_prog_t *prog = SVVM_prog;
1576 int top, bottom, playercolor;
1578 // get top and bottom either from the provided values or the current values
1579 // (allows changing only top or bottom, or both at once)
1580 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1581 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1585 // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
1591 playercolor = top*16 + bottom;
1593 if (cmd->source == src_command)
1595 Cvar_SetValueQuick(&cl_color, playercolor);
1599 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1602 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1604 Con_DPrint("Calling SV_ChangeTeam\n");
1605 prog->globals.fp[OFS_PARM0] = playercolor;
1606 PRVM_serverglobalfloat(time) = sv.time;
1607 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1608 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1612 if (host_client->edict)
1614 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1615 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1617 host_client->colors = playercolor;
1618 if (host_client->old_colors != host_client->colors)
1620 host_client->old_colors = host_client->colors;
1621 // send notification to all clients
1622 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1623 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1624 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1629 static void CL_Color_f(cmd_state_t *cmd)
1633 if (Cmd_Argc(cmd) == 1)
1635 if (cmd->source == src_command)
1637 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1638 Con_Print("color <0-15> [0-15]\n");
1643 if (Cmd_Argc(cmd) == 2)
1644 top = bottom = atoi(Cmd_Argv(cmd, 1));
1647 top = atoi(Cmd_Argv(cmd, 1));
1648 bottom = atoi(Cmd_Argv(cmd, 2));
1650 CL_Color(cmd, top, bottom);
1653 static void CL_TopColor_f(cmd_state_t *cmd)
1655 if (Cmd_Argc(cmd) == 1)
1657 if (cmd->source == src_command)
1659 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1660 Con_Print("topcolor <0-15>\n");
1665 CL_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
1668 static void CL_BottomColor_f(cmd_state_t *cmd)
1670 if (Cmd_Argc(cmd) == 1)
1672 if (cmd->source == src_command)
1674 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1675 Con_Print("bottomcolor <0-15>\n");
1680 CL_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
1683 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1684 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)"};
1685 static void CL_Rate_f(cmd_state_t *cmd)
1689 if (Cmd_Argc(cmd) != 2)
1691 if (cmd->source == src_command)
1693 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1694 Con_Print("rate <bytespersecond>\n");
1699 rate = atoi(Cmd_Argv(cmd, 1));
1701 if (cmd->source == src_command)
1703 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
1707 host_client->rate = rate;
1710 static void CL_Rate_BurstSize_f(cmd_state_t *cmd)
1714 if (Cmd_Argc(cmd) != 2)
1716 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1717 Con_Print("rate_burstsize <bytes>\n");
1721 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1723 if (cmd->source == src_command)
1725 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
1729 host_client->rate_burstsize = rate_burstsize;
1737 static void SV_Kill_f(cmd_state_t *cmd)
1739 prvm_prog_t *prog = SVVM_prog;
1740 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1742 SV_ClientPrint("Can't suicide -- already dead!\n");
1746 PRVM_serverglobalfloat(time) = sv.time;
1747 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1748 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1757 static void SV_Pause_f(cmd_state_t *cmd)
1759 void (*print) (const char *fmt, ...);
1760 if (cmd->source == src_command)
1762 // if running a client, try to send over network so the pause is handled by the server
1763 if (cls.state == ca_connected)
1765 Cmd_ForwardToServer_f(cmd);
1771 print = SV_ClientPrintf;
1773 if (!pausable.integer)
1775 if (cmd->source == src_client)
1777 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1779 print("Pause not allowed.\n");
1786 if (cmd->source != src_command)
1787 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1788 else if(*(sv_adminnick.string))
1789 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
1791 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
1792 // send notification to all clients
1793 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1794 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1798 ======================
1800 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1801 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1802 ======================
1804 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)"};
1805 static void CL_PModel_f(cmd_state_t *cmd)
1807 prvm_prog_t *prog = SVVM_prog;
1810 if (Cmd_Argc (cmd) == 1)
1812 if (cmd->source == src_command)
1814 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1818 i = atoi(Cmd_Argv(cmd, 1));
1820 if (cmd->source == src_command)
1822 if (cl_pmodel.integer == i)
1824 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
1825 if (cls.state == ca_connected)
1826 Cmd_ForwardToServer_f(cmd);
1830 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1833 //===========================================================================
1841 static void SV_PreSpawn_f(cmd_state_t *cmd)
1843 if (host_client->prespawned)
1845 Con_Print("prespawn not valid -- already prespawned\n");
1848 host_client->prespawned = true;
1850 if (host_client->netconnection)
1852 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1853 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1854 MSG_WriteByte (&host_client->netconnection->message, 2);
1855 host_client->sendsignon = 0; // enable unlimited sends again
1858 // reset the name change timer because the client will send name soon
1859 host_client->nametime = 0;
1867 static void SV_Spawn_f(cmd_state_t *cmd)
1869 prvm_prog_t *prog = SVVM_prog;
1872 int stats[MAX_CL_STATS];
1874 if (!host_client->prespawned)
1876 Con_Print("Spawn not valid -- not yet prespawned\n");
1879 if (host_client->spawned)
1881 Con_Print("Spawn not valid -- already spawned\n");
1884 host_client->spawned = true;
1886 // reset name change timer again because they might want to change name
1887 // again in the first 5 seconds after connecting
1888 host_client->nametime = 0;
1890 // LadyHavoc: moved this above the QC calls at FrikaC's request
1891 // LadyHavoc: commented this out
1892 //if (host_client->netconnection)
1893 // SZ_Clear (&host_client->netconnection->message);
1895 // run the entrance script
1898 // loaded games are fully initialized already
1899 if (PRVM_serverfunction(RestoreGame))
1901 Con_DPrint("Calling RestoreGame\n");
1902 PRVM_serverglobalfloat(time) = sv.time;
1903 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1904 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1909 //Con_Printf("SV_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);
1911 // copy spawn parms out of the client_t
1912 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1913 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1915 // call the spawn function
1916 host_client->clientconnectcalled = true;
1917 PRVM_serverglobalfloat(time) = sv.time;
1918 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1919 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1921 if (cls.state == ca_dedicated)
1922 Con_Printf("%s connected\n", host_client->name);
1924 PRVM_serverglobalfloat(time) = sv.time;
1925 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1928 if (!host_client->netconnection)
1931 // send time of update
1932 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1933 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1935 // send all current names, colors, and frag counts
1936 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1938 if (!client->active)
1940 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1941 MSG_WriteByte (&host_client->netconnection->message, i);
1942 MSG_WriteString (&host_client->netconnection->message, client->name);
1943 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1944 MSG_WriteByte (&host_client->netconnection->message, i);
1945 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1946 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1947 MSG_WriteByte (&host_client->netconnection->message, i);
1948 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1951 // send all current light styles
1952 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1954 if (sv.lightstyles[i][0])
1956 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1957 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1958 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1963 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1964 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1965 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1967 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1968 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1969 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1971 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1972 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1973 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1975 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1976 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1977 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1980 // Never send a roll angle, because savegames can catch the server
1981 // in a state where it is expecting the client to correct the angle
1982 // and it won't happen if the game was just loaded, so you wind up
1983 // with a permanent head tilt
1986 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1987 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1988 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1989 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1993 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1994 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1995 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1996 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1999 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
2001 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
2002 MSG_WriteByte (&host_client->netconnection->message, 3);
2010 static void SV_Begin_f(cmd_state_t *cmd)
2012 if (!host_client->spawned)
2014 Con_Print("Begin not valid -- not yet spawned\n");
2017 if (host_client->begun)
2019 Con_Print("Begin not valid -- already begun\n");
2022 host_client->begun = true;
2024 // LadyHavoc: note: this code also exists in SV_DropClient
2028 for (i = 0;i < svs.maxclients;i++)
2029 if (svs.clients[i].active && !svs.clients[i].spawned)
2031 if (i == svs.maxclients)
2033 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2034 sv.paused = sv.loadgame = false; // we're basically done with loading now
2039 //===========================================================================
2046 Kicks a user off of the server
2049 static void SV_Kick_f(cmd_state_t *cmd)
2052 const char *message = NULL;
2055 qboolean byNumber = false;
2062 if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
2064 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
2065 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2071 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2073 if (!host_client->active)
2075 if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
2080 if (i < svs.maxclients)
2082 if (cmd->source == src_command)
2084 if (cls.state == ca_dedicated)
2087 who = cl_name.string;
2092 // can't kick yourself!
2093 if (host_client == save)
2096 if (Cmd_Argc(cmd) > 2)
2098 message = Cmd_Args(cmd);
2099 COM_ParseToken_Simple(&message, false, false, true);
2102 message++; // skip the #
2103 while (*message == ' ') // skip white space
2105 message += strlen(Cmd_Argv(cmd, 2)); // skip the number
2107 while (*message && *message == ' ')
2111 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2113 SV_ClientPrintf("Kicked by %s\n", who);
2114 SV_DropClient (false); // kicked
2121 ===============================================================================
2125 ===============================================================================
2133 static void SV_Give_f(cmd_state_t *cmd)
2135 prvm_prog_t *prog = SVVM_prog;
2139 t = Cmd_Argv(cmd, 1);
2140 v = atoi (Cmd_Argv(cmd, 2));
2154 // MED 01/04/97 added hipnotic give stuff
2155 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2160 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2162 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2164 else if (t[0] == '9')
2165 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2166 else if (t[0] == '0')
2167 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2168 else if (t[0] >= '2')
2169 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2174 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2179 if (gamemode == GAME_ROGUE)
2180 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2182 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2185 if (gamemode == GAME_ROGUE)
2187 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2188 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2189 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2193 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2197 if (gamemode == GAME_ROGUE)
2199 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2200 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2201 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2205 if (gamemode == GAME_ROGUE)
2207 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2208 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2209 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2213 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2217 if (gamemode == GAME_ROGUE)
2219 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2220 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2221 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2225 PRVM_serveredictfloat(host_client->edict, health) = v;
2228 if (gamemode == GAME_ROGUE)
2230 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2231 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2232 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2236 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2240 if (gamemode == GAME_ROGUE)
2242 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2243 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2244 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2250 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2255 for (i=0 ; i<prog->num_edicts ; i++)
2257 e = PRVM_EDICT_NUM(i);
2258 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2261 Con_Print("No viewthing on map\n");
2270 static void SV_Viewmodel_f(cmd_state_t *cmd)
2272 prvm_prog_t *prog = SVVM_prog;
2279 e = FindViewthing(prog);
2282 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
2283 if (m && m->loaded && m->Draw)
2285 PRVM_serveredictfloat(e, frame) = 0;
2286 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2289 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
2298 static void SV_Viewframe_f(cmd_state_t *cmd)
2300 prvm_prog_t *prog = SVVM_prog;
2308 e = FindViewthing(prog);
2311 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2313 f = atoi(Cmd_Argv(cmd, 1));
2314 if (f >= m->numframes)
2317 PRVM_serveredictfloat(e, frame) = f;
2322 static void PrintFrameName (dp_model_t *m, int frame)
2325 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2327 Con_Printf("frame %i\n", frame);
2335 static void SV_Viewnext_f(cmd_state_t *cmd)
2337 prvm_prog_t *prog = SVVM_prog;
2344 e = FindViewthing(prog);
2347 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2349 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2350 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2351 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2353 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2362 static void SV_Viewprev_f(cmd_state_t *cmd)
2364 prvm_prog_t *prog = SVVM_prog;
2371 e = FindViewthing(prog);
2374 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2376 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2377 if (PRVM_serveredictfloat(e, frame) < 0)
2378 PRVM_serveredictfloat(e, frame) = 0;
2380 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2385 ===============================================================================
2389 ===============================================================================
2398 static void CL_Startdemos_f(cmd_state_t *cmd)
2402 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2405 c = Cmd_Argc(cmd) - 1;
2408 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2411 Con_DPrintf("%i demo(s) in loop\n", c);
2413 for (i=1 ; i<c+1 ; i++)
2414 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
2416 // LadyHavoc: clear the remaining slots
2417 for (;i <= MAX_DEMOS;i++)
2418 cls.demos[i-1][0] = 0;
2420 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2434 Return to looping demos
2437 static void CL_Demos_f(cmd_state_t *cmd)
2439 if (cls.state == ca_dedicated)
2441 if (cls.demonum == -1)
2443 CL_Disconnect_f (cmd);
2451 Return to looping demos
2454 static void CL_Stopdemo_f(cmd_state_t *cmd)
2456 if (!cls.demoplayback)
2459 Host_ShutdownServer ();
2462 static void CL_SendCvar_f(cmd_state_t *cmd)
2466 const char *cvarname;
2470 if(Cmd_Argc(cmd) != 2)
2472 cvarname = Cmd_Argv(cmd, 1);
2473 if (cls.state == ca_connected)
2475 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
2476 // LadyHavoc: if there is no such cvar or if it is private, send a
2477 // reply indicating that it has no value
2478 if(!c || (c->flags & CVAR_PRIVATE))
2479 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2481 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2484 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2488 if (cls.state != ca_dedicated)
2492 for(;i<svs.maxclients;i++)
2493 if(svs.clients[i].active && svs.clients[i].netconnection)
2495 host_client = &svs.clients[i];
2496 Host_ClientCommands("sendcvar %s\n", cvarname);
2501 static void SV_MaxPlayers_f(cmd_state_t *cmd)
2505 if (Cmd_Argc(cmd) != 2)
2507 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2513 Con_Print("maxplayers can not be changed while a server is running.\n");
2514 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2517 n = atoi(Cmd_Argv(cmd, 1));
2518 n = bound(1, n, MAX_SCOREBOARD);
2519 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2521 svs.maxclients_next = n;
2523 Cvar_Set (&cvars_all, "deathmatch", "0");
2525 Cvar_Set (&cvars_all, "deathmatch", "1");
2529 =====================
2532 ProQuake rcon support
2533 =====================
2535 static void CL_PQRcon_f(cmd_state_t *cmd)
2539 lhnetsocket_t *mysocket;
2541 if (Cmd_Argc(cmd) == 1)
2543 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2547 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2549 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2553 e = strchr(rcon_password.string, ' ');
2554 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2557 cls.rcon_address = cls.netcon->peeraddress;
2560 if (!rcon_address.string[0])
2562 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2565 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2567 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2571 unsigned char bufdata[64];
2574 MSG_WriteLong(&buf, 0);
2575 MSG_WriteByte(&buf, CCREQ_RCON);
2576 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2577 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2578 MSG_WriteString(&buf, Cmd_Args(cmd));
2579 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2580 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
2585 //=============================================================================
2587 // QuakeWorld commands
2590 =====================
2593 Send the rest of the command line over as
2594 an unconnected command.
2595 =====================
2597 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2601 lhnetsocket_t *mysocket;
2603 if (Cmd_Argc(cmd) == 1)
2605 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2609 if (!rcon_password.string || !rcon_password.string[0])
2611 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2615 e = strchr(rcon_password.string, ' ');
2616 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2619 cls.rcon_address = cls.netcon->peeraddress;
2622 if (!rcon_address.string[0])
2624 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2627 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2629 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2630 if (mysocket && Cmd_Args(cmd)[0])
2632 // simply put together the rcon packet and send it
2633 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
2635 if(cls.rcon_commands[cls.rcon_ringpos][0])
2638 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2639 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]);
2640 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2643 for (i = 0;i < MAX_RCONS;i++)
2644 if(cls.rcon_commands[i][0])
2645 if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
2649 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
2650 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2651 cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
2652 cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
2653 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2655 else if(rcon_secure.integer > 0)
2659 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
2660 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2661 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2664 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2665 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
2671 memcpy(buf, "\377\377\377\377", 4);
2672 dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args(cmd));
2673 NetConn_WriteString(mysocket, buf, &cls.rcon_address);
2679 ====================
2682 user <name or userid>
2684 Dump userdata / masterdata for a user
2685 ====================
2687 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2692 if (Cmd_Argc(cmd) != 2)
2694 Con_Printf ("Usage: user <username / userid>\n");
2698 uid = atoi(Cmd_Argv(cmd, 1));
2700 for (i = 0;i < cl.maxclients;i++)
2702 if (!cl.scores[i].name[0])
2704 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
2706 InfoString_Print(cl.scores[i].qw_userinfo);
2710 Con_Printf ("User not in server.\n");
2714 ====================
2717 Dump userids for all current players
2718 ====================
2720 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2726 Con_Printf ("userid frags name\n");
2727 Con_Printf ("------ ----- ----\n");
2728 for (i = 0;i < cl.maxclients;i++)
2730 if (cl.scores[i].name[0])
2732 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2737 Con_Printf ("%i total users\n", c);
2744 Sent by server when serverinfo changes
2747 // TODO: shouldn't this be a cvar instead?
2748 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2751 if (Cmd_Argc(cmd) != 2)
2753 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2757 strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
2758 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2759 cl.qw_teamplay = atoi(temp);
2766 Allow clients to change userinfo
2770 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2776 if (Cmd_Argc(cmd) != 2)
2778 Con_Printf ("fullinfo <complete info string>\n");
2782 s = Cmd_Argv(cmd, 1);
2787 size_t len = strcspn(s, "\\");
2788 if (len >= sizeof(key)) {
2789 len = sizeof(key) - 1;
2791 strlcpy(key, s, len + 1);
2795 Con_Printf ("MISSING VALUE\n");
2798 ++s; // Skip over backslash.
2800 len = strcspn(s, "\\");
2801 if (len >= sizeof(value)) {
2802 len = sizeof(value) - 1;
2804 strlcpy(value, s, len + 1);
2806 CL_SetInfo(key, value, false, false, false, false);
2813 ++s; // Skip over backslash.
2821 Allow clients to change userinfo
2824 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2826 if (Cmd_Argc(cmd) == 1)
2828 InfoString_Print(cls.userinfo);
2831 if (Cmd_Argc(cmd) != 3)
2833 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2836 CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
2840 ====================
2843 packet <destination> <contents>
2845 Contents allows \n escape character
2846 ====================
2848 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2854 lhnetaddress_t address;
2855 lhnetsocket_t *mysocket;
2857 if (Cmd_Argc(cmd) != 3)
2859 Con_Printf ("packet <destination> <contents>\n");
2863 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
2865 Con_Printf ("Bad address\n");
2869 in = Cmd_Argv(cmd, 2);
2871 send[0] = send[1] = send[2] = send[3] = -1;
2873 l = (int)strlen (in);
2874 for (i=0 ; i<l ; i++)
2876 if (out >= send + sizeof(send) - 1)
2878 if (in[i] == '\\' && in[i+1] == 'n')
2883 else if (in[i] == '\\' && in[i+1] == '0')
2888 else if (in[i] == '\\' && in[i+1] == 't')
2893 else if (in[i] == '\\' && in[i+1] == 'r')
2898 else if (in[i] == '\\' && in[i+1] == '"')
2907 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2909 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2911 NetConn_Write(mysocket, send, out - send, &address);
2915 ====================
2918 Send back ping and packet loss update for all current players to this player
2919 ====================
2921 static void SV_Pings_f(cmd_state_t *cmd)
2923 int i, j, ping, packetloss, movementloss;
2926 if (!host_client->netconnection)
2929 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2931 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2932 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2934 for (i = 0;i < svs.maxclients;i++)
2938 if (svs.clients[i].netconnection)
2940 for (j = 0;j < NETGRAPH_PACKETS;j++)
2941 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2943 for (j = 0;j < NETGRAPH_PACKETS;j++)
2944 if (svs.clients[i].movement_count[j] < 0)
2947 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2948 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2949 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2950 ping = bound(0, ping, 9999);
2951 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2953 // send qw_svc_updateping and qw_svc_updatepl messages
2954 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2955 MSG_WriteShort(&host_client->netconnection->message, ping);
2956 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2957 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2961 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2963 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2965 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2966 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2969 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2970 MSG_WriteString(&host_client->netconnection->message, "\n");
2973 static void CL_PingPLReport_f(cmd_state_t *cmd)
2977 int l = Cmd_Argc(cmd);
2978 if (l > cl.maxclients)
2980 for (i = 0;i < l;i++)
2982 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
2983 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
2984 if(errbyte && *errbyte == ',')
2985 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2987 cl.scores[i].qw_movementloss = 0;
2991 //=============================================================================
2998 void Host_InitCommands (void)
3000 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
3002 Cvar_RegisterVariable(&cl_name);
3003 Cvar_RegisterVariable(&cl_color);
3004 Cvar_RegisterVariable(&cl_rate);
3005 Cvar_RegisterVariable(&cl_rate_burstsize);
3006 Cvar_RegisterVariable(&cl_pmodel);
3007 Cvar_RegisterVariable(&cl_playermodel);
3008 Cvar_RegisterVariable(&cl_playerskin);
3009 Cvar_RegisterVariable(&rcon_password);
3010 Cvar_RegisterVariable(&rcon_address);
3011 Cvar_RegisterVariable(&rcon_secure);
3012 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
3013 Cvar_RegisterVariable(&r_fixtrans_auto);
3014 Cvar_RegisterVariable(&team);
3015 Cvar_RegisterVariable(&skin);
3016 Cvar_RegisterVariable(&noaim);
3017 Cvar_RegisterVariable(&sv_cheats);
3018 Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
3019 Cvar_RegisterVariable(&sv_adminnick);
3020 Cvar_RegisterVariable(&sv_status_privacy);
3021 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3022 Cvar_RegisterVariable(&sv_namechangetimer);
3024 // client commands - this includes server commands because the client can host a server, so they must exist
3025 Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
3026 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
3027 Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "map", SV_Map_f, "kick everyone off the server and start a new level");
3028 Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
3029 Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
3030 Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
3031 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
3032 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
3033 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
3034 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
3035 Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
3036 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
3037 Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "load", SV_Loadgame_f, "load a saved game file");
3038 Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
3039 Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
3040 Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
3041 Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3042 Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3043 Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3044 Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
3045 Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
3047 // 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)
3048 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
3049 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3050 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "begin", SV_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)");
3051 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "pings", SV_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)");
3053 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
3054 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
3055 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
3056 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
3057 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
3058 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
3059 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", CL_Name_f, "change your player name");
3060 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors");
3061 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", CL_Rate_f, "change your network connection speed");
3062 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", CL_Rate_BurstSize_f, "change your network connection speed");
3063 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", CL_PModel_f, "(Nehahra-only) change your player model choice");
3064 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", CL_Playermodel_f, "change your player model");
3065 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", CL_Playerskin_f, "change your player skin number");
3067 Cmd_AddCommand(CMD_CLIENT, "connect", CL_Connect_f, "connect to a server by IP address or hostname");
3068 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "reconnect", CL_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)");
3069 Cmd_AddCommand(CMD_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3070 Cmd_AddCommand(CMD_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
3071 Cmd_AddCommand(CMD_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3072 Cmd_AddCommand(CMD_CLIENT, "sendcvar", CL_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3073 Cmd_AddCommand(CMD_CLIENT, "rcon", CL_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");
3074 Cmd_AddCommand(CMD_CLIENT, "srcon", CL_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");
3075 Cmd_AddCommand(CMD_CLIENT, "pqrcon", CL_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)");
3076 Cmd_AddCommand(CMD_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo");
3077 Cmd_AddCommand(CMD_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo");
3078 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string");
3079 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", CL_TopColor_f, "QW command to set top color without changing bottom color");
3080 Cmd_AddCommand(CMD_CLIENT, "bottomcolor", CL_BottomColor_f, "QW command to set bottom color without changing top color");
3081 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)");
3083 // commands that are only sent by server to client for execution
3084 Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "pingplreport", CL_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)");
3085 Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "fullserverinfo", CL_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3088 void Host_NoOperation_f(cmd_state_t *cmd)