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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_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 Host_Savegame_to(prog, name);
760 static void Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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("Host_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 Host_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 Host_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 Host_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 Host_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]);
1430 static void Host_Say_f(cmd_state_t *cmd)
1432 Host_Say(cmd, false);
1436 static void Host_Say_Team_f(cmd_state_t *cmd)
1438 Host_Say(cmd, true);
1442 static void Host_Tell_f(cmd_state_t *cmd)
1444 const char *playername_start = NULL;
1445 size_t playername_length = 0;
1446 int playernumber = 0;
1449 const char *p1, *p2;
1450 char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
1451 qboolean fromServer = false;
1453 if (cmd->source == src_command)
1455 if (cls.state == ca_dedicated)
1459 Cmd_ForwardToServer_f(cmd);
1464 if (Cmd_Argc (cmd) < 2)
1467 // note this uses the chat prefix \001
1469 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1470 else if(*(sv_adminnick.string))
1471 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1473 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1476 p2 = p1 + strlen(p1);
1477 // remove the target name
1478 while (p1 < p2 && *p1 == ' ')
1483 while (p1 < p2 && *p1 == ' ')
1485 while (p1 < p2 && isdigit(*p1))
1487 playernumber = playernumber * 10 + (*p1 - '0');
1495 playername_start = p1;
1496 while (p1 < p2 && *p1 != '"')
1498 playername_length = p1 - playername_start;
1504 playername_start = p1;
1505 while (p1 < p2 && *p1 != ' ')
1507 playername_length = p1 - playername_start;
1509 while (p1 < p2 && *p1 == ' ')
1511 if(playername_start)
1513 // set playernumber to the right client
1515 if(playername_length >= sizeof(namebuf))
1518 Con_Print("Host_Tell: too long player name/ID\n");
1520 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1523 memcpy(namebuf, playername_start, playername_length);
1524 namebuf[playername_length] = 0;
1525 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1527 if (!svs.clients[playernumber].active)
1529 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1533 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1536 Con_Print("Host_Tell: invalid player name/ID\n");
1538 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1541 // remove trailing newlines
1542 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1544 // remove quotes if present
1550 else if (fromServer)
1551 Con_Print("Host_Tell: missing end quote\n");
1553 SV_ClientPrint("Host_Tell: missing end quote\n");
1555 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1558 return; // empty say
1559 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1565 host_client = svs.clients + playernumber;
1566 SV_ClientPrint(text);
1576 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1577 static void Host_Color(cmd_state_t *cmd, int changetop, int changebottom)
1579 prvm_prog_t *prog = SVVM_prog;
1580 int top, bottom, playercolor;
1582 // get top and bottom either from the provided values or the current values
1583 // (allows changing only top or bottom, or both at once)
1584 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1585 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1589 // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
1595 playercolor = top*16 + bottom;
1597 if (cmd->source == src_command)
1599 Cvar_SetValueQuick(&cl_color, playercolor);
1603 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1606 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1608 Con_DPrint("Calling SV_ChangeTeam\n");
1609 prog->globals.fp[OFS_PARM0] = playercolor;
1610 PRVM_serverglobalfloat(time) = sv.time;
1611 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1612 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1616 if (host_client->edict)
1618 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1619 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1621 host_client->colors = playercolor;
1622 if (host_client->old_colors != host_client->colors)
1624 host_client->old_colors = host_client->colors;
1625 // send notification to all clients
1626 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1627 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1628 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1633 static void Host_Color_f(cmd_state_t *cmd)
1637 if (Cmd_Argc(cmd) == 1)
1639 if (cmd->source == src_command)
1641 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1642 Con_Print("color <0-15> [0-15]\n");
1647 if (Cmd_Argc(cmd) == 2)
1648 top = bottom = atoi(Cmd_Argv(cmd, 1));
1651 top = atoi(Cmd_Argv(cmd, 1));
1652 bottom = atoi(Cmd_Argv(cmd, 2));
1654 Host_Color(cmd, top, bottom);
1657 static void Host_TopColor_f(cmd_state_t *cmd)
1659 if (Cmd_Argc(cmd) == 1)
1661 if (cmd->source == src_command)
1663 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1664 Con_Print("topcolor <0-15>\n");
1669 Host_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
1672 static void Host_BottomColor_f(cmd_state_t *cmd)
1674 if (Cmd_Argc(cmd) == 1)
1676 if (cmd->source == src_command)
1678 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1679 Con_Print("bottomcolor <0-15>\n");
1684 Host_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
1687 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1688 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)"};
1689 static void Host_Rate_f(cmd_state_t *cmd)
1693 if (Cmd_Argc(cmd) != 2)
1695 if (cmd->source == src_command)
1697 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1698 Con_Print("rate <bytespersecond>\n");
1703 rate = atoi(Cmd_Argv(cmd, 1));
1705 if (cmd->source == src_command)
1707 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
1711 host_client->rate = rate;
1713 static void Host_Rate_BurstSize_f(cmd_state_t *cmd)
1717 if (Cmd_Argc(cmd) != 2)
1719 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1720 Con_Print("rate_burstsize <bytes>\n");
1724 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1726 if (cmd->source == src_command)
1728 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
1732 host_client->rate_burstsize = rate_burstsize;
1740 static void Host_Kill_f(cmd_state_t *cmd)
1742 prvm_prog_t *prog = SVVM_prog;
1743 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1745 SV_ClientPrint("Can't suicide -- already dead!\n");
1749 PRVM_serverglobalfloat(time) = sv.time;
1750 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1751 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1760 static void Host_Pause_f(cmd_state_t *cmd)
1762 void (*print) (const char *fmt, ...);
1763 if (cmd->source == src_command)
1765 // if running a client, try to send over network so the pause is handled by the server
1766 if (cls.state == ca_connected)
1768 Cmd_ForwardToServer_f(cmd);
1774 print = SV_ClientPrintf;
1776 if (!pausable.integer)
1778 if (cmd->source == src_client)
1780 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1782 print("Pause not allowed.\n");
1789 if (cmd->source != src_command)
1790 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1791 else if(*(sv_adminnick.string))
1792 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
1794 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
1795 // send notification to all clients
1796 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1797 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1801 ======================
1803 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1804 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1805 ======================
1807 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)"};
1808 static void Host_PModel_f(cmd_state_t *cmd)
1810 prvm_prog_t *prog = SVVM_prog;
1813 if (Cmd_Argc (cmd) == 1)
1815 if (cmd->source == src_command)
1817 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1821 i = atoi(Cmd_Argv(cmd, 1));
1823 if (cmd->source == src_command)
1825 if (cl_pmodel.integer == i)
1827 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
1828 if (cls.state == ca_connected)
1829 Cmd_ForwardToServer_f(cmd);
1833 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1836 //===========================================================================
1844 static void Host_PreSpawn_f(cmd_state_t *cmd)
1846 if (host_client->prespawned)
1848 Con_Print("prespawn not valid -- already prespawned\n");
1851 host_client->prespawned = true;
1853 if (host_client->netconnection)
1855 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1856 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1857 MSG_WriteByte (&host_client->netconnection->message, 2);
1858 host_client->sendsignon = 0; // enable unlimited sends again
1861 // reset the name change timer because the client will send name soon
1862 host_client->nametime = 0;
1870 static void Host_Spawn_f(cmd_state_t *cmd)
1872 prvm_prog_t *prog = SVVM_prog;
1875 int stats[MAX_CL_STATS];
1877 if (!host_client->prespawned)
1879 Con_Print("Spawn not valid -- not yet prespawned\n");
1882 if (host_client->spawned)
1884 Con_Print("Spawn not valid -- already spawned\n");
1887 host_client->spawned = true;
1889 // reset name change timer again because they might want to change name
1890 // again in the first 5 seconds after connecting
1891 host_client->nametime = 0;
1893 // LadyHavoc: moved this above the QC calls at FrikaC's request
1894 // LadyHavoc: commented this out
1895 //if (host_client->netconnection)
1896 // SZ_Clear (&host_client->netconnection->message);
1898 // run the entrance script
1901 // loaded games are fully initialized already
1902 if (PRVM_serverfunction(RestoreGame))
1904 Con_DPrint("Calling RestoreGame\n");
1905 PRVM_serverglobalfloat(time) = sv.time;
1906 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1907 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1912 //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);
1914 // copy spawn parms out of the client_t
1915 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1916 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1918 // call the spawn function
1919 host_client->clientconnectcalled = true;
1920 PRVM_serverglobalfloat(time) = sv.time;
1921 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1922 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1924 if (cls.state == ca_dedicated)
1925 Con_Printf("%s connected\n", host_client->name);
1927 PRVM_serverglobalfloat(time) = sv.time;
1928 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1931 if (!host_client->netconnection)
1934 // send time of update
1935 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1936 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1938 // send all current names, colors, and frag counts
1939 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1941 if (!client->active)
1943 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1944 MSG_WriteByte (&host_client->netconnection->message, i);
1945 MSG_WriteString (&host_client->netconnection->message, client->name);
1946 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1947 MSG_WriteByte (&host_client->netconnection->message, i);
1948 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1949 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1950 MSG_WriteByte (&host_client->netconnection->message, i);
1951 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1954 // send all current light styles
1955 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1957 if (sv.lightstyles[i][0])
1959 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1960 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1961 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1966 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1967 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1968 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1970 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1971 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1972 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1974 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1975 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1976 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1978 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1979 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1980 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1983 // Never send a roll angle, because savegames can catch the server
1984 // in a state where it is expecting the client to correct the angle
1985 // and it won't happen if the game was just loaded, so you wind up
1986 // with a permanent head tilt
1989 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1990 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1991 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1992 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1996 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1997 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1998 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1999 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
2002 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
2004 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
2005 MSG_WriteByte (&host_client->netconnection->message, 3);
2013 static void Host_Begin_f(cmd_state_t *cmd)
2015 if (!host_client->spawned)
2017 Con_Print("Begin not valid -- not yet spawned\n");
2020 if (host_client->begun)
2022 Con_Print("Begin not valid -- already begun\n");
2025 host_client->begun = true;
2027 // LadyHavoc: note: this code also exists in SV_DropClient
2031 for (i = 0;i < svs.maxclients;i++)
2032 if (svs.clients[i].active && !svs.clients[i].spawned)
2034 if (i == svs.maxclients)
2036 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2037 sv.paused = sv.loadgame = false; // we're basically done with loading now
2042 //===========================================================================
2049 Kicks a user off of the server
2052 static void Host_Kick_f(cmd_state_t *cmd)
2055 const char *message = NULL;
2058 qboolean byNumber = false;
2065 if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
2067 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
2068 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2074 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2076 if (!host_client->active)
2078 if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
2083 if (i < svs.maxclients)
2085 if (cmd->source == src_command)
2087 if (cls.state == ca_dedicated)
2090 who = cl_name.string;
2095 // can't kick yourself!
2096 if (host_client == save)
2099 if (Cmd_Argc(cmd) > 2)
2101 message = Cmd_Args(cmd);
2102 COM_ParseToken_Simple(&message, false, false, true);
2105 message++; // skip the #
2106 while (*message == ' ') // skip white space
2108 message += strlen(Cmd_Argv(cmd, 2)); // skip the number
2110 while (*message && *message == ' ')
2114 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2116 SV_ClientPrintf("Kicked by %s\n", who);
2117 SV_DropClient (false); // kicked
2124 ===============================================================================
2128 ===============================================================================
2136 static void Host_Give_f(cmd_state_t *cmd)
2138 prvm_prog_t *prog = SVVM_prog;
2142 t = Cmd_Argv(cmd, 1);
2143 v = atoi (Cmd_Argv(cmd, 2));
2157 // MED 01/04/97 added hipnotic give stuff
2158 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2163 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2165 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2167 else if (t[0] == '9')
2168 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2169 else if (t[0] == '0')
2170 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2171 else if (t[0] >= '2')
2172 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2177 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2182 if (gamemode == GAME_ROGUE)
2183 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2185 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2188 if (gamemode == GAME_ROGUE)
2190 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2191 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2192 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2196 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2200 if (gamemode == GAME_ROGUE)
2202 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2203 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2204 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2208 if (gamemode == GAME_ROGUE)
2210 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2211 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2212 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2216 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2220 if (gamemode == GAME_ROGUE)
2222 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2223 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2224 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2228 PRVM_serveredictfloat(host_client->edict, health) = v;
2231 if (gamemode == GAME_ROGUE)
2233 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2234 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2235 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2239 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2243 if (gamemode == GAME_ROGUE)
2245 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2246 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2247 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2253 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2258 for (i=0 ; i<prog->num_edicts ; i++)
2260 e = PRVM_EDICT_NUM(i);
2261 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2264 Con_Print("No viewthing on map\n");
2273 static void Host_Viewmodel_f(cmd_state_t *cmd)
2275 prvm_prog_t *prog = SVVM_prog;
2282 e = FindViewthing(prog);
2285 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
2286 if (m && m->loaded && m->Draw)
2288 PRVM_serveredictfloat(e, frame) = 0;
2289 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2292 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
2301 static void Host_Viewframe_f(cmd_state_t *cmd)
2303 prvm_prog_t *prog = SVVM_prog;
2311 e = FindViewthing(prog);
2314 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2316 f = atoi(Cmd_Argv(cmd, 1));
2317 if (f >= m->numframes)
2320 PRVM_serveredictfloat(e, frame) = f;
2325 static void PrintFrameName (dp_model_t *m, int frame)
2328 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2330 Con_Printf("frame %i\n", frame);
2338 static void Host_Viewnext_f(cmd_state_t *cmd)
2340 prvm_prog_t *prog = SVVM_prog;
2347 e = FindViewthing(prog);
2350 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2352 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2353 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2354 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2356 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2365 static void Host_Viewprev_f(cmd_state_t *cmd)
2367 prvm_prog_t *prog = SVVM_prog;
2374 e = FindViewthing(prog);
2377 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2379 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2380 if (PRVM_serveredictfloat(e, frame) < 0)
2381 PRVM_serveredictfloat(e, frame) = 0;
2383 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2388 ===============================================================================
2392 ===============================================================================
2401 static void Host_Startdemos_f(cmd_state_t *cmd)
2405 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2408 c = Cmd_Argc(cmd) - 1;
2411 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2414 Con_DPrintf("%i demo(s) in loop\n", c);
2416 for (i=1 ; i<c+1 ; i++)
2417 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
2419 // LadyHavoc: clear the remaining slots
2420 for (;i <= MAX_DEMOS;i++)
2421 cls.demos[i-1][0] = 0;
2423 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2437 Return to looping demos
2440 static void Host_Demos_f(cmd_state_t *cmd)
2442 if (cls.state == ca_dedicated)
2444 if (cls.demonum == -1)
2446 CL_Disconnect_f (cmd);
2454 Return to looping demos
2457 static void Host_Stopdemo_f(cmd_state_t *cmd)
2459 if (!cls.demoplayback)
2462 Host_ShutdownServer ();
2465 static void Host_SendCvar_f(cmd_state_t *cmd)
2469 const char *cvarname;
2473 if(Cmd_Argc(cmd) != 2)
2475 cvarname = Cmd_Argv(cmd, 1);
2476 if (cls.state == ca_connected)
2478 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
2479 // LadyHavoc: if there is no such cvar or if it is private, send a
2480 // reply indicating that it has no value
2481 if(!c || (c->flags & CVAR_PRIVATE))
2482 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2484 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2487 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2491 if (cls.state != ca_dedicated)
2495 for(;i<svs.maxclients;i++)
2496 if(svs.clients[i].active && svs.clients[i].netconnection)
2498 host_client = &svs.clients[i];
2499 Host_ClientCommands("sendcvar %s\n", cvarname);
2504 static void MaxPlayers_f(cmd_state_t *cmd)
2508 if (Cmd_Argc(cmd) != 2)
2510 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2516 Con_Print("maxplayers can not be changed while a server is running.\n");
2517 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2520 n = atoi(Cmd_Argv(cmd, 1));
2521 n = bound(1, n, MAX_SCOREBOARD);
2522 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2524 svs.maxclients_next = n;
2526 Cvar_Set (&cvars_all, "deathmatch", "0");
2528 Cvar_Set (&cvars_all, "deathmatch", "1");
2532 =====================
2535 ProQuake rcon support
2536 =====================
2538 static void Host_PQRcon_f(cmd_state_t *cmd)
2542 lhnetsocket_t *mysocket;
2544 if (Cmd_Argc(cmd) == 1)
2546 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2550 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2552 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2556 e = strchr(rcon_password.string, ' ');
2557 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2560 cls.rcon_address = cls.netcon->peeraddress;
2563 if (!rcon_address.string[0])
2565 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2568 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2570 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2574 unsigned char bufdata[64];
2577 MSG_WriteLong(&buf, 0);
2578 MSG_WriteByte(&buf, CCREQ_RCON);
2579 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2580 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2581 MSG_WriteString(&buf, Cmd_Args(cmd));
2582 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2583 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
2588 //=============================================================================
2590 // QuakeWorld commands
2593 =====================
2596 Send the rest of the command line over as
2597 an unconnected command.
2598 =====================
2600 static void Host_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2604 lhnetsocket_t *mysocket;
2606 if (Cmd_Argc(cmd) == 1)
2608 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2612 if (!rcon_password.string || !rcon_password.string[0])
2614 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2618 e = strchr(rcon_password.string, ' ');
2619 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2622 cls.rcon_address = cls.netcon->peeraddress;
2625 if (!rcon_address.string[0])
2627 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2630 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2632 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2633 if (mysocket && Cmd_Args(cmd)[0])
2635 // simply put together the rcon packet and send it
2636 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
2638 if(cls.rcon_commands[cls.rcon_ringpos][0])
2641 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2642 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]);
2643 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2646 for (i = 0;i < MAX_RCONS;i++)
2647 if(cls.rcon_commands[i][0])
2648 if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
2652 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
2653 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2654 cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
2655 cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
2656 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2658 else if(rcon_secure.integer > 0)
2662 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
2663 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2664 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2667 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2668 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
2674 memcpy(buf, "\377\377\377\377", 4);
2675 dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args(cmd));
2676 NetConn_WriteString(mysocket, buf, &cls.rcon_address);
2682 ====================
2685 user <name or userid>
2687 Dump userdata / masterdata for a user
2688 ====================
2690 static void Host_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2695 if (Cmd_Argc(cmd) != 2)
2697 Con_Printf ("Usage: user <username / userid>\n");
2701 uid = atoi(Cmd_Argv(cmd, 1));
2703 for (i = 0;i < cl.maxclients;i++)
2705 if (!cl.scores[i].name[0])
2707 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
2709 InfoString_Print(cl.scores[i].qw_userinfo);
2713 Con_Printf ("User not in server.\n");
2717 ====================
2720 Dump userids for all current players
2721 ====================
2723 static void Host_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2729 Con_Printf ("userid frags name\n");
2730 Con_Printf ("------ ----- ----\n");
2731 for (i = 0;i < cl.maxclients;i++)
2733 if (cl.scores[i].name[0])
2735 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2740 Con_Printf ("%i total users\n", c);
2745 Host_FullServerinfo_f
2747 Sent by server when serverinfo changes
2750 // TODO: shouldn't this be a cvar instead?
2751 static void Host_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2754 if (Cmd_Argc(cmd) != 2)
2756 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2760 strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
2761 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2762 cl.qw_teamplay = atoi(temp);
2769 Allow clients to change userinfo
2773 static void Host_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2779 if (Cmd_Argc(cmd) != 2)
2781 Con_Printf ("fullinfo <complete info string>\n");
2785 s = Cmd_Argv(cmd, 1);
2790 size_t len = strcspn(s, "\\");
2791 if (len >= sizeof(key)) {
2792 len = sizeof(key) - 1;
2794 strlcpy(key, s, len + 1);
2798 Con_Printf ("MISSING VALUE\n");
2801 ++s; // Skip over backslash.
2803 len = strcspn(s, "\\");
2804 if (len >= sizeof(value)) {
2805 len = sizeof(value) - 1;
2807 strlcpy(value, s, len + 1);
2809 CL_SetInfo(key, value, false, false, false, false);
2816 ++s; // Skip over backslash.
2824 Allow clients to change userinfo
2827 static void Host_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2829 if (Cmd_Argc(cmd) == 1)
2831 InfoString_Print(cls.userinfo);
2834 if (Cmd_Argc(cmd) != 3)
2836 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2839 CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
2843 ====================
2846 packet <destination> <contents>
2848 Contents allows \n escape character
2849 ====================
2851 static void Host_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2857 lhnetaddress_t address;
2858 lhnetsocket_t *mysocket;
2860 if (Cmd_Argc(cmd) != 3)
2862 Con_Printf ("packet <destination> <contents>\n");
2866 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
2868 Con_Printf ("Bad address\n");
2872 in = Cmd_Argv(cmd, 2);
2874 send[0] = send[1] = send[2] = send[3] = -1;
2876 l = (int)strlen (in);
2877 for (i=0 ; i<l ; i++)
2879 if (out >= send + sizeof(send) - 1)
2881 if (in[i] == '\\' && in[i+1] == 'n')
2886 else if (in[i] == '\\' && in[i+1] == '0')
2891 else if (in[i] == '\\' && in[i+1] == 't')
2896 else if (in[i] == '\\' && in[i+1] == 'r')
2901 else if (in[i] == '\\' && in[i+1] == '"')
2910 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2912 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2914 NetConn_Write(mysocket, send, out - send, &address);
2918 ====================
2921 Send back ping and packet loss update for all current players to this player
2922 ====================
2924 static void Host_Pings_f(cmd_state_t *cmd)
2926 int i, j, ping, packetloss, movementloss;
2929 if (!host_client->netconnection)
2932 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2934 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2935 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2937 for (i = 0;i < svs.maxclients;i++)
2941 if (svs.clients[i].netconnection)
2943 for (j = 0;j < NETGRAPH_PACKETS;j++)
2944 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2946 for (j = 0;j < NETGRAPH_PACKETS;j++)
2947 if (svs.clients[i].movement_count[j] < 0)
2950 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2951 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2952 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2953 ping = bound(0, ping, 9999);
2954 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2956 // send qw_svc_updateping and qw_svc_updatepl messages
2957 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2958 MSG_WriteShort(&host_client->netconnection->message, ping);
2959 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2960 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2964 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2966 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2968 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2969 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2972 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2973 MSG_WriteString(&host_client->netconnection->message, "\n");
2976 static void Host_PingPLReport_f(cmd_state_t *cmd)
2980 int l = Cmd_Argc(cmd);
2981 if (l > cl.maxclients)
2983 for (i = 0;i < l;i++)
2985 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
2986 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
2987 if(errbyte && *errbyte == ',')
2988 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2990 cl.scores[i].qw_movementloss = 0;
2994 //=============================================================================
3001 void Host_InitCommands (void)
3003 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
3005 Cvar_RegisterVariable(&cl_name);
3006 Cvar_RegisterVariable(&cl_color);
3007 Cvar_RegisterVariable(&cl_rate);
3008 Cvar_RegisterVariable(&cl_rate_burstsize);
3009 Cvar_RegisterVariable(&cl_pmodel);
3010 Cvar_RegisterVariable(&cl_playermodel);
3011 Cvar_RegisterVariable(&cl_playerskin);
3012 Cvar_RegisterVariable(&rcon_password);
3013 Cvar_RegisterVariable(&rcon_address);
3014 Cvar_RegisterVariable(&rcon_secure);
3015 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
3016 Cvar_RegisterVariable(&r_fixtrans_auto);
3017 Cvar_RegisterVariable(&team);
3018 Cvar_RegisterVariable(&skin);
3019 Cvar_RegisterVariable(&noaim);
3020 Cvar_RegisterVariable(&sv_cheats);
3021 Cvar_RegisterCallback(&sv_cheats, Host_DisableCheats_c);
3022 Cvar_RegisterVariable(&sv_adminnick);
3023 Cvar_RegisterVariable(&sv_status_privacy);
3024 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3025 Cvar_RegisterVariable(&sv_namechangetimer);
3027 // client commands - this includes server commands because the client can host a server, so they must exist
3028 Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
3029 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", Host_Status_f, "print server status information");
3030 Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "map", Host_Map_f, "kick everyone off the server and start a new level");
3031 Cmd_AddCommand(CMD_SHARED, "restart", Host_Restart_f, "restart current level");
3032 Cmd_AddCommand(CMD_SHARED, "changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
3033 Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
3034 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", Host_Say_f, "send a chat message to everyone on the server");
3035 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", Host_Say_Team_f, "send a chat message to your team on the server");
3036 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", Host_Tell_f, "send a chat message to only one person on the server");
3037 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", Host_Pause_f, "pause the game (if the server allows pausing)");
3038 Cmd_AddCommand(CMD_SHARED, "kick", Host_Kick_f, "kick a player off the server by number or name");
3039 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", Host_Ping_f, "print ping times of all players on the server");
3040 Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "load", Host_Loadgame_f, "load a saved game file");
3041 Cmd_AddCommand(CMD_SHARED, "save", Host_Savegame_f, "save the game to a file");
3042 Cmd_AddCommand(CMD_SHARED, "viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3043 Cmd_AddCommand(CMD_SHARED, "viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3044 Cmd_AddCommand(CMD_SHARED, "viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3045 Cmd_AddCommand(CMD_SHARED, "viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3046 Cmd_AddCommand(CMD_SHARED, "maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3047 Cmd_AddCommand(CMD_SHARED, "user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3048 Cmd_AddCommand(CMD_SHARED, "users", Host_Users_f, "prints additional information about all players on the scoreboard");
3050 // 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)
3051 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", Host_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
3052 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)");
3053 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)");
3054 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)");
3056 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", Host_God_f, "god mode (invulnerability)");
3057 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", Host_Notarget_f, "notarget mode (monsters do not see you)");
3058 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", Host_Fly_f, "fly mode (flight)");
3059 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
3060 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", Host_Give_f, "alter inventory");
3061 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", Host_Kill_f, "die instantly");
3062 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", Host_Name_f, "change your player name");
3063 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", Host_Color_f, "change your player shirt and pants colors");
3064 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", Host_Rate_f, "change your network connection speed");
3065 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", Host_Rate_BurstSize_f, "change your network connection speed");
3066 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", Host_PModel_f, "(Nehahra-only) change your player model choice");
3067 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", Host_Playermodel_f, "change your player model");
3068 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", Host_Playerskin_f, "change your player skin number");
3070 Cmd_AddCommand(CMD_CLIENT, "connect", Host_Connect_f, "connect to a server by IP address or hostname");
3071 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)");
3072 Cmd_AddCommand(CMD_CLIENT, "startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3073 Cmd_AddCommand(CMD_CLIENT, "demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3074 Cmd_AddCommand(CMD_CLIENT, "stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3075 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");
3076 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");
3077 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");
3078 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)");
3079 Cmd_AddCommand(CMD_CLIENT, "fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3080 Cmd_AddCommand(CMD_CLIENT, "setinfo", Host_SetInfo_f, "modifies your userinfo");
3081 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3082 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3083 Cmd_AddCommand(CMD_CLIENT, "bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3084 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)");
3086 // commands that are only sent by server to client for execution
3087 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)");
3088 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");
3091 void Host_NoOperation_f(cmd_state_t *cmd)