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 //===========================================================================
606 static void CL_SendCvar_f(cmd_state_t *cmd)
610 const char *cvarname;
614 if(Cmd_Argc(cmd) != 2)
616 cvarname = Cmd_Argv(cmd, 1);
617 if (cls.state == ca_connected)
619 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
620 // LadyHavoc: if there is no such cvar or if it is private, send a
621 // reply indicating that it has no value
622 if(!c || (c->flags & CVAR_PRIVATE))
623 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
625 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
628 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
632 if (cls.state != ca_dedicated)
636 for(;i<svs.maxclients;i++)
637 if(svs.clients[i].active && svs.clients[i].netconnection)
639 host_client = &svs.clients[i];
640 SV_ClientCommands("sendcvar %s\n", cvarname);
646 =====================
649 ProQuake rcon support
650 =====================
652 static void CL_PQRcon_f(cmd_state_t *cmd)
656 lhnetsocket_t *mysocket;
658 if (Cmd_Argc(cmd) == 1)
660 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
664 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
666 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
670 e = strchr(rcon_password.string, ' ');
671 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
674 cls.rcon_address = cls.netcon->peeraddress;
677 if (!rcon_address.string[0])
679 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
682 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
684 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
688 unsigned char bufdata[64];
691 MSG_WriteLong(&buf, 0);
692 MSG_WriteByte(&buf, CCREQ_RCON);
693 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
694 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
695 MSG_WriteString(&buf, Cmd_Args(cmd));
696 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
697 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
702 //=============================================================================
704 // QuakeWorld commands
707 =====================
710 Send the rest of the command line over as
711 an unconnected command.
712 =====================
714 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
718 lhnetsocket_t *mysocket;
720 if (Cmd_Argc(cmd) == 1)
722 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
726 if (!rcon_password.string || !rcon_password.string[0])
728 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
732 e = strchr(rcon_password.string, ' ');
733 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
736 cls.rcon_address = cls.netcon->peeraddress;
739 if (!rcon_address.string[0])
741 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
744 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
746 mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
747 if (mysocket && Cmd_Args(cmd)[0])
749 // simply put together the rcon packet and send it
750 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
752 if(cls.rcon_commands[cls.rcon_ringpos][0])
755 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
756 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]);
757 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
760 for (i = 0;i < MAX_RCONS;i++)
761 if(cls.rcon_commands[i][0])
762 if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
766 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
767 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
768 cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
769 cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
770 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
772 else if(rcon_secure.integer > 0)
776 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
777 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
778 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
781 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
782 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
788 memcpy(buf, "\377\377\377\377", 4);
789 dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args(cmd));
790 NetConn_WriteString(mysocket, buf, &cls.rcon_address);
799 Sent by server when serverinfo changes
802 // TODO: shouldn't this be a cvar instead?
803 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
806 if (Cmd_Argc(cmd) != 2)
808 Con_Printf ("usage: fullserverinfo <complete info string>\n");
812 strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
813 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
814 cl.qw_teamplay = atoi(temp);
821 Allow clients to change userinfo
825 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
831 if (Cmd_Argc(cmd) != 2)
833 Con_Printf ("fullinfo <complete info string>\n");
837 s = Cmd_Argv(cmd, 1);
842 size_t len = strcspn(s, "\\");
843 if (len >= sizeof(key)) {
844 len = sizeof(key) - 1;
846 strlcpy(key, s, len + 1);
850 Con_Printf ("MISSING VALUE\n");
853 ++s; // Skip over backslash.
855 len = strcspn(s, "\\");
856 if (len >= sizeof(value)) {
857 len = sizeof(value) - 1;
859 strlcpy(value, s, len + 1);
861 CL_SetInfo(key, value, false, false, false, false);
868 ++s; // Skip over backslash.
876 Allow clients to change userinfo
879 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
881 if (Cmd_Argc(cmd) == 1)
883 InfoString_Print(cls.userinfo);
886 if (Cmd_Argc(cmd) != 3)
888 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
891 CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
898 packet <destination> <contents>
900 Contents allows \n escape character
903 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
909 lhnetaddress_t address;
910 lhnetsocket_t *mysocket;
912 if (Cmd_Argc(cmd) != 3)
914 Con_Printf ("packet <destination> <contents>\n");
918 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
920 Con_Printf ("Bad address\n");
924 in = Cmd_Argv(cmd, 2);
926 send[0] = send[1] = send[2] = send[3] = -1;
928 l = (int)strlen (in);
929 for (i=0 ; i<l ; i++)
931 if (out >= send + sizeof(send) - 1)
933 if (in[i] == '\\' && in[i+1] == 'n')
938 else if (in[i] == '\\' && in[i+1] == '0')
943 else if (in[i] == '\\' && in[i+1] == 't')
948 else if (in[i] == '\\' && in[i+1] == 'r')
953 else if (in[i] == '\\' && in[i+1] == '"')
962 mysocket = NetConn_ChooseClientSocketForAddress(&address);
964 mysocket = NetConn_ChooseServerSocketForAddress(&address);
966 NetConn_Write(mysocket, send, out - send, &address);
969 static void CL_PingPLReport_f(cmd_state_t *cmd)
973 int l = Cmd_Argc(cmd);
974 if (l > cl.maxclients)
976 for (i = 0;i < l;i++)
978 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
979 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
980 if(errbyte && *errbyte == ',')
981 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
983 cl.scores[i].qw_movementloss = 0;
987 //=============================================================================
994 void Host_InitCommands (void)
996 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
998 Cvar_RegisterVariable(&cl_name);
999 Cvar_RegisterVariable(&cl_color);
1000 Cvar_RegisterVariable(&cl_rate);
1001 Cvar_RegisterVariable(&cl_rate_burstsize);
1002 Cvar_RegisterVariable(&cl_pmodel);
1003 Cvar_RegisterVariable(&cl_playermodel);
1004 Cvar_RegisterVariable(&cl_playerskin);
1005 Cvar_RegisterVariable(&rcon_password);
1006 Cvar_RegisterVariable(&rcon_address);
1007 Cvar_RegisterVariable(&rcon_secure);
1008 Cvar_RegisterVariable(&rcon_secure_challengetimeout);
1009 Cvar_RegisterVariable(&r_fixtrans_auto);
1010 Cvar_RegisterVariable(&team);
1011 Cvar_RegisterVariable(&skin);
1012 Cvar_RegisterVariable(&noaim);
1014 // client commands - this includes server commands because the client can host a server, so they must exist
1015 Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
1016 Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
1018 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", CL_Name_f, "change your player name");
1019 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors");
1020 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", CL_Rate_f, "change your network connection speed");
1021 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", CL_Rate_BurstSize_f, "change your network connection speed");
1022 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", CL_PModel_f, "(Nehahra-only) change your player model choice");
1023 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", CL_Playermodel_f, "change your player model");
1024 Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", CL_Playerskin_f, "change your player skin number");
1026 Cmd_AddCommand(CMD_CLIENT, "connect", CL_Connect_f, "connect to a server by IP address or hostname");
1027 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)");
1028 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");
1029 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");
1030 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");
1031 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)");
1032 Cmd_AddCommand(CMD_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo");
1033 Cmd_AddCommand(CMD_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo");
1034 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string");
1035 Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", CL_TopColor_f, "QW command to set top color without changing bottom color");
1036 Cmd_AddCommand(CMD_CLIENT, "bottomcolor", CL_BottomColor_f, "QW command to set bottom color without changing top color");
1037 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)");
1039 // commands that are only sent by server to client for execution
1040 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)");
1041 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");
1044 void Host_NoOperation_f(cmd_state_t *cmd)