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 extern cvar_t sv_adminnick;
35 extern cvar_t sv_status_privacy;
36 extern cvar_t sv_status_show_qcstatus;
37 extern cvar_t sv_namechangetimer;
38 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"};
39 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"};
40 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"};
41 cvar_t rcon_address = {CVAR_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
42 cvar_t team = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
43 cvar_t skin = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
44 cvar_t noaim = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
45 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)"};
47 extern cvar_t developer_entityparsing;
55 void Host_Quit_f(cmd_state_t *cmd)
57 if(host.state == host_shutdown)
58 Con_Printf("shutting down already!\n");
60 host.state = host_shutdown;
67 This command causes the client to wait for the signon messages again.
68 This is sent just before a server changes levels
71 void CL_Reconnect_f(cmd_state_t *cmd)
74 // if not connected, reconnect to the most recent server
77 // if we have connected to a server recently, the userinfo
78 // will still contain its IP address, so get the address...
79 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
81 CL_EstablishConnection(temp, -1);
83 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
86 // if connected, do something based on protocol
87 if (cls.protocol == PROTOCOL_QUAKEWORLD)
89 // quakeworld can just re-login
90 if (cls.qw_downloadmemory) // don't change when downloading
95 if (cls.state == ca_connected)
97 Con_Printf("Server is changing level...\n");
98 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
99 MSG_WriteString(&cls.netcon->message, "new");
104 // netquake uses reconnect on level changes (silly)
105 if (Cmd_Argc(cmd) != 1)
107 Con_Print("reconnect : wait for signon messages again\n");
112 Con_Print("reconnect: no signon, ignoring reconnect\n");
115 cls.signon = 0; // need new connection messages
120 =====================
123 User command to connect to server
124 =====================
126 static void CL_Connect_f(cmd_state_t *cmd)
128 if (Cmd_Argc(cmd) < 2)
130 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
133 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
134 if(rcon_secure.integer <= 0)
135 Cvar_SetQuick(&rcon_password, "");
136 CL_EstablishConnection(Cmd_Argv(cmd, 1), 2);
140 //============================================================================
143 ======================
145 ======================
147 cvar_t cl_name = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
148 static void CL_Name_f(cmd_state_t *cmd)
150 prvm_prog_t *prog = SVVM_prog;
152 qboolean valid_colors;
153 const char *newNameSource;
154 char newName[sizeof(host_client->name)];
156 if (Cmd_Argc (cmd) == 1)
158 if (cmd->source == src_command)
160 Con_Printf("name: %s\n", cl_name.string);
165 if (Cmd_Argc (cmd) == 2)
166 newNameSource = Cmd_Argv(cmd, 1);
168 newNameSource = Cmd_Args(cmd);
170 strlcpy(newName, newNameSource, sizeof(newName));
172 if (cmd->source == src_command)
174 Cvar_Set (&cvars_all, "_cl_name", newName);
175 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
177 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
178 Con_Printf("name: %s\n", cl_name.string);
183 if (host.realtime < host_client->nametime)
185 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
189 host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
191 // point the string back at updateclient->name to keep it safe
192 strlcpy (host_client->name, newName, sizeof (host_client->name));
194 for (i = 0, j = 0;host_client->name[i];i++)
195 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
196 host_client->name[j++] = host_client->name[i];
197 host_client->name[j] = 0;
199 if(host_client->name[0] == 1 || host_client->name[0] == 2)
200 // may interfere with chat area, and will needlessly beep; so let's add a ^7
202 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
203 host_client->name[sizeof(host_client->name) - 1] = 0;
204 host_client->name[0] = STRING_COLOR_TAG;
205 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
208 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
209 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
212 l = strlen(host_client->name);
213 if(l < sizeof(host_client->name) - 1)
215 // duplicate the color tag to escape it
216 host_client->name[i] = STRING_COLOR_TAG;
217 host_client->name[i+1] = 0;
218 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
222 // remove the last character to fix the color code
223 host_client->name[l-1] = 0;
224 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
228 // find the last color tag offset and decide if we need to add a reset tag
229 for (i = 0, j = -1;host_client->name[i];i++)
231 if (host_client->name[i] == STRING_COLOR_TAG)
233 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
236 // if this happens to be a reset tag then we don't need one
237 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
242 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]))
248 if (host_client->name[i+1] == STRING_COLOR_TAG)
255 // does not end in the default color string, so add it
256 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
257 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
259 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
260 if (strcmp(host_client->old_name, host_client->name))
262 if (host_client->begun)
263 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
264 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
265 // send notification to all clients
266 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
267 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
268 MSG_WriteString (&sv.reliable_datagram, host_client->name);
269 SV_WriteNetnameIntoDemo(host_client);
274 ======================
276 ======================
278 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)"};
279 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
280 static void CL_Playermodel_f(cmd_state_t *cmd)
282 prvm_prog_t *prog = SVVM_prog;
284 char newPath[sizeof(host_client->playermodel)];
286 if (Cmd_Argc (cmd) == 1)
288 if (cmd->source == src_command)
290 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
295 if (Cmd_Argc (cmd) == 2)
296 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
298 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
300 for (i = 0, j = 0;newPath[i];i++)
301 if (newPath[i] != '\r' && newPath[i] != '\n')
302 newPath[j++] = newPath[i];
305 if (cmd->source == src_command)
307 Cvar_Set (&cvars_all, "_cl_playermodel", newPath);
312 if (host.realtime < host_client->nametime)
314 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
318 host_client->nametime = host.realtime + 5;
321 // point the string back at updateclient->name to keep it safe
322 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
323 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
324 if (strcmp(host_client->old_model, host_client->playermodel))
326 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
327 /*// send notification to all clients
328 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
329 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
330 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
335 ======================
337 ======================
339 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)"};
340 static void CL_Playerskin_f(cmd_state_t *cmd)
342 prvm_prog_t *prog = SVVM_prog;
344 char newPath[sizeof(host_client->playerskin)];
346 if (Cmd_Argc (cmd) == 1)
348 if (cmd->source == src_command)
350 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
355 if (Cmd_Argc (cmd) == 2)
356 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
358 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
360 for (i = 0, j = 0;newPath[i];i++)
361 if (newPath[i] != '\r' && newPath[i] != '\n')
362 newPath[j++] = newPath[i];
365 if (cmd->source == src_command)
367 Cvar_Set (&cvars_all, "_cl_playerskin", newPath);
372 if (host.realtime < host_client->nametime)
374 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
378 host_client->nametime = host.realtime + 5;
381 // point the string back at updateclient->name to keep it safe
382 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
383 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
384 if (strcmp(host_client->old_skin, host_client->playerskin))
386 //if (host_client->begun)
387 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
388 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
389 /*// send notification to all clients
390 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
391 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
392 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
396 static void Host_Version_f(cmd_state_t *cmd)
398 Con_Printf("Version: %s build %s\n", gamename, buildstring);
406 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
407 static void CL_Color(cmd_state_t *cmd, int changetop, int changebottom)
409 prvm_prog_t *prog = SVVM_prog;
410 int top, bottom, playercolor;
412 // get top and bottom either from the provided values or the current values
413 // (allows changing only top or bottom, or both at once)
414 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
415 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
419 // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
425 playercolor = top*16 + bottom;
427 if (cmd->source == src_command)
429 Cvar_SetValueQuick(&cl_color, playercolor);
433 if (cls.protocol == PROTOCOL_QUAKEWORLD)
436 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
438 Con_DPrint("Calling SV_ChangeTeam\n");
439 prog->globals.fp[OFS_PARM0] = playercolor;
440 PRVM_serverglobalfloat(time) = sv.time;
441 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
442 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
446 if (host_client->edict)
448 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
449 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
451 host_client->colors = playercolor;
452 if (host_client->old_colors != host_client->colors)
454 host_client->old_colors = host_client->colors;
455 // send notification to all clients
456 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
457 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
458 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
463 static void CL_Color_f(cmd_state_t *cmd)
467 if (Cmd_Argc(cmd) == 1)
469 if (cmd->source == src_command)
471 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
472 Con_Print("color <0-15> [0-15]\n");
477 if (Cmd_Argc(cmd) == 2)
478 top = bottom = atoi(Cmd_Argv(cmd, 1));
481 top = atoi(Cmd_Argv(cmd, 1));
482 bottom = atoi(Cmd_Argv(cmd, 2));
484 CL_Color(cmd, top, bottom);
487 static void CL_TopColor_f(cmd_state_t *cmd)
489 if (Cmd_Argc(cmd) == 1)
491 if (cmd->source == src_command)
493 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
494 Con_Print("topcolor <0-15>\n");
499 CL_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
502 static void CL_BottomColor_f(cmd_state_t *cmd)
504 if (Cmd_Argc(cmd) == 1)
506 if (cmd->source == src_command)
508 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
509 Con_Print("bottomcolor <0-15>\n");
514 CL_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
517 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
518 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)"};
519 static void CL_Rate_f(cmd_state_t *cmd)
523 if (Cmd_Argc(cmd) != 2)
525 if (cmd->source == src_command)
527 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
528 Con_Print("rate <bytespersecond>\n");
533 rate = atoi(Cmd_Argv(cmd, 1));
535 if (cmd->source == src_command)
537 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
541 host_client->rate = rate;
544 static void CL_Rate_BurstSize_f(cmd_state_t *cmd)
548 if (Cmd_Argc(cmd) != 2)
550 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
551 Con_Print("rate_burstsize <bytes>\n");
555 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
557 if (cmd->source == src_command)
559 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
563 host_client->rate_burstsize = rate_burstsize;
567 ======================
569 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
570 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
571 ======================
573 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)"};
574 static void CL_PModel_f(cmd_state_t *cmd)
576 prvm_prog_t *prog = SVVM_prog;
579 if (Cmd_Argc (cmd) == 1)
581 if (cmd->source == src_command)
583 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
587 i = atoi(Cmd_Argv(cmd, 1));
589 if (cmd->source == src_command)
591 if (cl_pmodel.integer == i)
593 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
594 if (cls.state == ca_connected)
595 Cmd_ForwardToServer_f(cmd);
599 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
602 //===========================================================================
604 //===========================================================================
607 ===============================================================================
611 ===============================================================================
620 static void CL_Startdemos_f(cmd_state_t *cmd)
624 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
627 c = Cmd_Argc(cmd) - 1;
630 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
633 Con_DPrintf("%i demo(s) in loop\n", c);
635 for (i=1 ; i<c+1 ; i++)
636 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
638 // LadyHavoc: clear the remaining slots
639 for (;i <= MAX_DEMOS;i++)
640 cls.demos[i-1][0] = 0;
642 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
656 Return to looping demos
659 static void CL_Demos_f(cmd_state_t *cmd)
661 if (cls.state == ca_dedicated)
663 if (cls.demonum == -1)
665 CL_Disconnect_f (cmd);
673 Return to looping demos
676 static void CL_Stopdemo_f(cmd_state_t *cmd)
678 if (!cls.demoplayback)
684 static void CL_SendCvar_f(cmd_state_t *cmd)
688 const char *cvarname;
692 if(Cmd_Argc(cmd) != 2)
694 cvarname = Cmd_Argv(cmd, 1);
695 if (cls.state == ca_connected)
697 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
698 // LadyHavoc: if there is no such cvar or if it is private, send a
699 // reply indicating that it has no value
700 if(!c || (c->flags & CVAR_PRIVATE))
701 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
703 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
706 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
710 if (cls.state != ca_dedicated)
714 for(;i<svs.maxclients;i++)
715 if(svs.clients[i].active && svs.clients[i].netconnection)
717 host_client = &svs.clients[i];
718 SV_ClientCommands("sendcvar %s\n", cvarname);
724 =====================
727 ProQuake rcon support
728 =====================
730 static void CL_PQRcon_f(cmd_state_t *cmd)
734 lhnetsocket_t *mysocket;
736 if (Cmd_Argc(cmd) == 1)
738 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
742 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
744 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
748 e = strchr(rcon_password.string, ' ');
749 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
752 cls.rcon_address = cls.netcon->peeraddress;
755 if (!rcon_address.string[0])
757 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
760 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
762 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
766 unsigned char bufdata[64];
769 MSG_WriteLong(&buf, 0);
770 MSG_WriteByte(&buf, CCREQ_RCON);
771 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
772 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
773 MSG_WriteString(&buf, Cmd_Args(cmd));
774 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
775 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
780 //=============================================================================
782 // QuakeWorld commands
785 =====================
788 Send the rest of the command line over as
789 an unconnected command.
790 =====================
792 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
796 lhnetsocket_t *mysocket;
798 if (Cmd_Argc(cmd) == 1)
800 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
804 if (!rcon_password.string || !rcon_password.string[0])
806 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
810 e = strchr(rcon_password.string, ' ');
811 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
814 cls.rcon_address = cls.netcon->peeraddress;
817 if (!rcon_address.string[0])
819 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
822 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
824 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
825 if (mysocket && Cmd_Args(cmd)[0])
827 // simply put together the rcon packet and send it
828 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
830 if(cls.rcon_commands[cls.rcon_ringpos][0])
833 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
834 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]);
835 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
838 for (i = 0;i < MAX_RCONS;i++)
839 if(cls.rcon_commands[i][0])
840 if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
844 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
845 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
846 cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
847 cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
848 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
850 else if(rcon_secure.integer > 0)
854 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
855 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
856 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
859 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
860 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
866 memcpy(buf, "\377\377\377\377", 4);
867 dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args(cmd));
868 NetConn_WriteString(mysocket, buf, &cls.rcon_address);
877 Sent by server when serverinfo changes
880 // TODO: shouldn't this be a cvar instead?
881 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
884 if (Cmd_Argc(cmd) != 2)
886 Con_Printf ("usage: fullserverinfo <complete info string>\n");
890 strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
891 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
892 cl.qw_teamplay = atoi(temp);
899 Allow clients to change userinfo
903 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
909 if (Cmd_Argc(cmd) != 2)
911 Con_Printf ("fullinfo <complete info string>\n");
915 s = Cmd_Argv(cmd, 1);
920 size_t len = strcspn(s, "\\");
921 if (len >= sizeof(key)) {
922 len = sizeof(key) - 1;
924 strlcpy(key, s, len + 1);
928 Con_Printf ("MISSING VALUE\n");
931 ++s; // Skip over backslash.
933 len = strcspn(s, "\\");
934 if (len >= sizeof(value)) {
935 len = sizeof(value) - 1;
937 strlcpy(value, s, len + 1);
939 CL_SetInfo(key, value, false, false, false, false);
946 ++s; // Skip over backslash.
954 Allow clients to change userinfo
957 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
959 if (Cmd_Argc(cmd) == 1)
961 InfoString_Print(cls.userinfo);
964 if (Cmd_Argc(cmd) != 3)
966 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
969 CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
976 packet <destination> <contents>
978 Contents allows \n escape character
981 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
987 lhnetaddress_t address;
988 lhnetsocket_t *mysocket;
990 if (Cmd_Argc(cmd) != 3)
992 Con_Printf ("packet <destination> <contents>\n");
996 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
998 Con_Printf ("Bad address\n");
1002 in = Cmd_Argv(cmd, 2);
1004 send[0] = send[1] = send[2] = send[3] = -1;
1006 l = (int)strlen (in);
1007 for (i=0 ; i<l ; i++)
1009 if (out >= send + sizeof(send) - 1)
1011 if (in[i] == '\\' && in[i+1] == 'n')
1016 else if (in[i] == '\\' && in[i+1] == '0')
1021 else if (in[i] == '\\' && in[i+1] == 't')
1026 else if (in[i] == '\\' && in[i+1] == 'r')
1031 else if (in[i] == '\\' && in[i+1] == '"')
1040 mysocket = NetConn_ChooseClientSocketForAddress(&address);
1042 mysocket = NetConn_ChooseServerSocketForAddress(&address);
1044 NetConn_Write(mysocket, send, out - send, &address);
1047 static void CL_PingPLReport_f(cmd_state_t *cmd)
1051 int l = Cmd_Argc(cmd);
1052 if (l > cl.maxclients)
1054 for (i = 0;i < l;i++)
1056 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
1057 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
1058 if(errbyte && *errbyte == ',')
1059 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
1061 cl.scores[i].qw_movementloss = 0;
1065 //=============================================================================
1072 void Host_InitCommands (void)
1074 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
1076 Cvar_RegisterVariable(&cl_name);
1077 Cvar_RegisterVariable(&cl_color);
1078 Cvar_RegisterVariable(&cl_rate);
1079 Cvar_RegisterVariable(&cl_rate_burstsize);
1080 Cvar_RegisterVariable(&cl_pmodel);
1081 Cvar_RegisterVariable(&cl_playermodel);
1082 Cvar_RegisterVariable(&cl_playerskin);
1083 Cvar_RegisterVariable(&rcon_password);
1084 Cvar_RegisterVariable(&rcon_address);
1085 Cvar_RegisterVariable(&rcon_secure);
1086 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
1087 Cvar_RegisterVariable(&r_fixtrans_auto);
1088 Cvar_RegisterVariable(&team);
1089 Cvar_RegisterVariable(&skin);
1090 Cvar_RegisterVariable(&noaim);
1092 // client commands - this includes server commands because the client can host a server, so they must exist
1093 Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
1094 Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
1096 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", CL_Name_f, "change your player name");
1097 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors");
1098 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", CL_Rate_f, "change your network connection speed");
1099 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", CL_Rate_BurstSize_f, "change your network connection speed");
1100 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", CL_PModel_f, "(Nehahra-only) change your player model choice");
1101 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", CL_Playermodel_f, "change your player model");
1102 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", CL_Playerskin_f, "change your player skin number");
1104 Cmd_AddCommand(CMD_CLIENT, "connect", CL_Connect_f, "connect to a server by IP address or hostname");
1105 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)");
1106 Cmd_AddCommand(CMD_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
1107 Cmd_AddCommand(CMD_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
1108 Cmd_AddCommand(CMD_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
1109 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");
1110 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");
1111 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");
1112 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)");
1113 Cmd_AddCommand(CMD_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo");
1114 Cmd_AddCommand(CMD_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo");
1115 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string");
1116 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", CL_TopColor_f, "QW command to set top color without changing bottom color");
1117 Cmd_AddCommand(CMD_CLIENT, "bottomcolor", CL_BottomColor_f, "QW command to set bottom color without changing top color");
1118 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)");
1120 // commands that are only sent by server to client for execution
1121 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)");
1122 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");
1125 void Host_NoOperation_f(cmd_state_t *cmd)