]> git.xonotic.org Git - xonotic/darkplaces.git/blob - host_cmd.c
(Round 2) Break up host_cmd.c
[xonotic/darkplaces.git] / host_cmd.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20
21 #include "quakedef.h"
22 #include "sv_demo.h"
23 #include "image.h"
24
25 #include "prvm_cmds.h"
26 #include "utf8lib.h"
27
28 // for secure rcon authentication
29 #include "hmac.h"
30 #include "mdfour.h"
31 #include <time.h>
32
33 int current_skill;
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)"};
46
47 extern cvar_t developer_entityparsing;
48
49 /*
50 ==================
51 Host_Quit_f
52 ==================
53 */
54
55 void Host_Quit_f(cmd_state_t *cmd)
56 {
57         if(host.state == host_shutdown)
58                 Con_Printf("shutting down already!\n");
59         else
60                 host.state = host_shutdown;
61 }
62
63 /*
64 ==================
65 CL_Reconnect_f
66
67 This command causes the client to wait for the signon messages again.
68 This is sent just before a server changes levels
69 ==================
70 */
71 void CL_Reconnect_f(cmd_state_t *cmd)
72 {
73         char temp[128];
74         // if not connected, reconnect to the most recent server
75         if (!cls.netcon)
76         {
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));
80                 if (temp[0])
81                         CL_EstablishConnection(temp, -1);
82                 else
83                         Con_Printf("Reconnect to what server?  (you have not connected to a server yet)\n");
84                 return;
85         }
86         // if connected, do something based on protocol
87         if (cls.protocol == PROTOCOL_QUAKEWORLD)
88         {
89                 // quakeworld can just re-login
90                 if (cls.qw_downloadmemory)  // don't change when downloading
91                         return;
92
93                 S_StopAllSounds();
94
95                 if (cls.state == ca_connected)
96                 {
97                         Con_Printf("Server is changing level...\n");
98                         MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
99                         MSG_WriteString(&cls.netcon->message, "new");
100                 }
101         }
102         else
103         {
104                 // netquake uses reconnect on level changes (silly)
105                 if (Cmd_Argc(cmd) != 1)
106                 {
107                         Con_Print("reconnect : wait for signon messages again\n");
108                         return;
109                 }
110                 if (!cls.signon)
111                 {
112                         Con_Print("reconnect: no signon, ignoring reconnect\n");
113                         return;
114                 }
115                 cls.signon = 0;         // need new connection messages
116         }
117 }
118
119 /*
120 =====================
121 CL_Connect_f
122
123 User command to connect to server
124 =====================
125 */
126 static void CL_Connect_f(cmd_state_t *cmd)
127 {
128         if (Cmd_Argc(cmd) < 2)
129         {
130                 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
131                 return;
132         }
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);
137 }
138
139
140 //============================================================================
141
142 /*
143 ======================
144 CL_Name_f
145 ======================
146 */
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)
149 {
150         prvm_prog_t *prog = SVVM_prog;
151         int i, j;
152         qboolean valid_colors;
153         const char *newNameSource;
154         char newName[sizeof(host_client->name)];
155
156         if (Cmd_Argc (cmd) == 1)
157         {
158                 if (cmd->source == src_command)
159                 {
160                         Con_Printf("name: %s\n", cl_name.string);
161                 }
162                 return;
163         }
164
165         if (Cmd_Argc (cmd) == 2)
166                 newNameSource = Cmd_Argv(cmd, 1);
167         else
168                 newNameSource = Cmd_Args(cmd);
169
170         strlcpy(newName, newNameSource, sizeof(newName));
171
172         if (cmd->source == src_command)
173         {
174                 Cvar_Set (&cvars_all, "_cl_name", newName);
175                 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
176                 {
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);
179                 }
180                 return;
181         }
182
183         if (host.realtime < host_client->nametime)
184         {
185                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
186                 return;
187         }
188
189         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
190
191         // point the string back at updateclient->name to keep it safe
192         strlcpy (host_client->name, newName, sizeof (host_client->name));
193
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;
198
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
201         {
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;
206         }
207
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
210         {
211                 size_t l;
212                 l = strlen(host_client->name);
213                 if(l < sizeof(host_client->name) - 1)
214                 {
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");
219                 }
220                 else
221                 {
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");
225                 }
226         }
227
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++)
230         {
231                 if (host_client->name[i] == STRING_COLOR_TAG)
232                 {
233                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
234                         {
235                                 j = i;
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)
238                                         j = -1;
239                                 i++;
240                                 continue;
241                         }
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]))
243                         {
244                                 j = i;
245                                 i += 4;
246                                 continue;
247                         }
248                         if (host_client->name[i+1] == STRING_COLOR_TAG)
249                         {
250                                 i++;
251                                 continue;
252                         }
253                 }
254         }
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);
258
259         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
260         if (strcmp(host_client->old_name, host_client->name))
261         {
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);
270         }
271 }
272
273 /*
274 ======================
275 CL_Playermodel_f
276 ======================
277 */
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)
281 {
282         prvm_prog_t *prog = SVVM_prog;
283         int i, j;
284         char newPath[sizeof(host_client->playermodel)];
285
286         if (Cmd_Argc (cmd) == 1)
287         {
288                 if (cmd->source == src_command)
289                 {
290                         Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
291                 }
292                 return;
293         }
294
295         if (Cmd_Argc (cmd) == 2)
296                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
297         else
298                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
299
300         for (i = 0, j = 0;newPath[i];i++)
301                 if (newPath[i] != '\r' && newPath[i] != '\n')
302                         newPath[j++] = newPath[i];
303         newPath[j] = 0;
304
305         if (cmd->source == src_command)
306         {
307                 Cvar_Set (&cvars_all, "_cl_playermodel", newPath);
308                 return;
309         }
310
311         /*
312         if (host.realtime < host_client->nametime)
313         {
314                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
315                 return;
316         }
317
318         host_client->nametime = host.realtime + 5;
319         */
320
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))
325         {
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);*/
331         }
332 }
333
334 /*
335 ======================
336 CL_Playerskin_f
337 ======================
338 */
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)
341 {
342         prvm_prog_t *prog = SVVM_prog;
343         int i, j;
344         char newPath[sizeof(host_client->playerskin)];
345
346         if (Cmd_Argc (cmd) == 1)
347         {
348                 if (cmd->source == src_command)
349                 {
350                         Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
351                 }
352                 return;
353         }
354
355         if (Cmd_Argc (cmd) == 2)
356                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
357         else
358                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
359
360         for (i = 0, j = 0;newPath[i];i++)
361                 if (newPath[i] != '\r' && newPath[i] != '\n')
362                         newPath[j++] = newPath[i];
363         newPath[j] = 0;
364
365         if (cmd->source == src_command)
366         {
367                 Cvar_Set (&cvars_all, "_cl_playerskin", newPath);
368                 return;
369         }
370
371         /*
372         if (host.realtime < host_client->nametime)
373         {
374                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
375                 return;
376         }
377
378         host_client->nametime = host.realtime + 5;
379         */
380
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))
385         {
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);*/
393         }
394 }
395
396 static void Host_Version_f(cmd_state_t *cmd)
397 {
398         Con_Printf("Version: %s build %s\n", gamename, buildstring);
399 }
400
401 /*
402 ==================
403 CL_Color_f
404 ==================
405 */
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)
408 {
409         prvm_prog_t *prog = SVVM_prog;
410         int top, bottom, playercolor;
411
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;
416
417         top &= 15;
418         bottom &= 15;
419         // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
420         //if (top > 13)
421         //      top = 13;
422         //if (bottom > 13)
423         //      bottom = 13;
424
425         playercolor = top*16 + bottom;
426
427         if (cmd->source == src_command)
428         {
429                 Cvar_SetValueQuick(&cl_color, playercolor);
430                 return;
431         }
432
433         if (cls.protocol == PROTOCOL_QUAKEWORLD)
434                 return;
435
436         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
437         {
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");
443         }
444         else
445         {
446                 if (host_client->edict)
447                 {
448                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
449                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
450                 }
451                 host_client->colors = playercolor;
452                 if (host_client->old_colors != host_client->colors)
453                 {
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);
459                 }
460         }
461 }
462
463 static void CL_Color_f(cmd_state_t *cmd)
464 {
465         int             top, bottom;
466
467         if (Cmd_Argc(cmd) == 1)
468         {
469                 if (cmd->source == src_command)
470                 {
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");
473                 }
474                 return;
475         }
476
477         if (Cmd_Argc(cmd) == 2)
478                 top = bottom = atoi(Cmd_Argv(cmd, 1));
479         else
480         {
481                 top = atoi(Cmd_Argv(cmd, 1));
482                 bottom = atoi(Cmd_Argv(cmd, 2));
483         }
484         CL_Color(cmd, top, bottom);
485 }
486
487 static void CL_TopColor_f(cmd_state_t *cmd)
488 {
489         if (Cmd_Argc(cmd) == 1)
490         {
491                 if (cmd->source == src_command)
492                 {
493                         Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
494                         Con_Print("topcolor <0-15>\n");
495                 }
496                 return;
497         }
498
499         CL_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
500 }
501
502 static void CL_BottomColor_f(cmd_state_t *cmd)
503 {
504         if (Cmd_Argc(cmd) == 1)
505         {
506                 if (cmd->source == src_command)
507                 {
508                         Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
509                         Con_Print("bottomcolor <0-15>\n");
510                 }
511                 return;
512         }
513
514         CL_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
515 }
516
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)
520 {
521         int rate;
522
523         if (Cmd_Argc(cmd) != 2)
524         {
525                 if (cmd->source == src_command)
526                 {
527                         Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
528                         Con_Print("rate <bytespersecond>\n");
529                 }
530                 return;
531         }
532
533         rate = atoi(Cmd_Argv(cmd, 1));
534
535         if (cmd->source == src_command)
536         {
537                 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
538                 return;
539         }
540
541         host_client->rate = rate;
542 }
543
544 static void CL_Rate_BurstSize_f(cmd_state_t *cmd)
545 {
546         int rate_burstsize;
547
548         if (Cmd_Argc(cmd) != 2)
549         {
550                 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
551                 Con_Print("rate_burstsize <bytes>\n");
552                 return;
553         }
554
555         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
556
557         if (cmd->source == src_command)
558         {
559                 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
560                 return;
561         }
562
563         host_client->rate_burstsize = rate_burstsize;
564 }
565
566 /*
567 ======================
568 CL_PModel_f
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 ======================
572 */
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)
575 {
576         prvm_prog_t *prog = SVVM_prog;
577         int i;
578
579         if (Cmd_Argc (cmd) == 1)
580         {
581                 if (cmd->source == src_command)
582                 {
583                         Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
584                 }
585                 return;
586         }
587         i = atoi(Cmd_Argv(cmd, 1));
588
589         if (cmd->source == src_command)
590         {
591                 if (cl_pmodel.integer == i)
592                         return;
593                 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
594                 if (cls.state == ca_connected)
595                         Cmd_ForwardToServer_f(cmd);
596                 return;
597         }
598
599         PRVM_serveredictfloat(host_client->edict, pmodel) = i;
600 }
601
602 //===========================================================================
603
604 //===========================================================================
605
606 static void CL_SendCvar_f(cmd_state_t *cmd)
607 {
608         int             i;
609         cvar_t  *c;
610         const char *cvarname;
611         client_t *old;
612         char vabuf[1024];
613
614         if(Cmd_Argc(cmd) != 2)
615                 return;
616         cvarname = Cmd_Argv(cmd, 1);
617         if (cls.state == ca_connected)
618         {
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));
624                 else
625                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
626                 return;
627         }
628         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
629                 return;
630
631         old = host_client;
632         if (cls.state != ca_dedicated)
633                 i = 1;
634         else
635                 i = 0;
636         for(;i<svs.maxclients;i++)
637                 if(svs.clients[i].active && svs.clients[i].netconnection)
638                 {
639                         host_client = &svs.clients[i];
640                         SV_ClientCommands("sendcvar %s\n", cvarname);
641                 }
642         host_client = old;
643 }
644
645 /*
646 =====================
647 CL_PQRcon_f
648
649 ProQuake rcon support
650 =====================
651 */
652 static void CL_PQRcon_f(cmd_state_t *cmd)
653 {
654         int n;
655         const char *e;
656         lhnetsocket_t *mysocket;
657
658         if (Cmd_Argc(cmd) == 1)
659         {
660                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
661                 return;
662         }
663
664         if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
665         {
666                 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
667                 return;
668         }
669
670         e = strchr(rcon_password.string, ' ');
671         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
672
673         if (cls.netcon)
674                 cls.rcon_address = cls.netcon->peeraddress;
675         else
676         {
677                 if (!rcon_address.string[0])
678                 {
679                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
680                         return;
681                 }
682                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
683         }
684         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
685         if (mysocket)
686         {
687                 sizebuf_t buf;
688                 unsigned char bufdata[64];
689                 buf.data = bufdata;
690                 SZ_Clear(&buf);
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);
698                 SZ_Clear(&buf);
699         }
700 }
701
702 //=============================================================================
703
704 // QuakeWorld commands
705
706 /*
707 =====================
708 CL_Rcon_f
709
710   Send the rest of the command line over as
711   an unconnected command.
712 =====================
713 */
714 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
715 {
716         int i, n;
717         const char *e;
718         lhnetsocket_t *mysocket;
719
720         if (Cmd_Argc(cmd) == 1)
721         {
722                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
723                 return;
724         }
725
726         if (!rcon_password.string || !rcon_password.string[0])
727         {
728                 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
729                 return;
730         }
731
732         e = strchr(rcon_password.string, ' ');
733         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
734
735         if (cls.netcon)
736                 cls.rcon_address = cls.netcon->peeraddress;
737         else
738         {
739                 if (!rcon_address.string[0])
740                 {
741                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
742                         return;
743                 }
744                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
745         }
746         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
747         if (mysocket && Cmd_Args(cmd)[0])
748         {
749                 // simply put together the rcon packet and send it
750                 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
751                 {
752                         if(cls.rcon_commands[cls.rcon_ringpos][0])
753                         {
754                                 char s[128];
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;
758                                 --cls.rcon_trying;
759                         }
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]))
763                                                 break;
764                         ++cls.rcon_trying;
765                         if(i >= MAX_RCONS)
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;
771                 }
772                 else if(rcon_secure.integer > 0)
773                 {
774                         char buf[1500];
775                         char argbuf[1500];
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))
779                         {
780                                 buf[40] = ' ';
781                                 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
782                                 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
783                         }
784                 }
785                 else
786                 {
787                         char buf[1500];
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);
791                 }
792         }
793 }
794
795 /*
796 ==================
797 CL_FullServerinfo_f
798
799 Sent by server when serverinfo changes
800 ==================
801 */
802 // TODO: shouldn't this be a cvar instead?
803 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
804 {
805         char temp[512];
806         if (Cmd_Argc(cmd) != 2)
807         {
808                 Con_Printf ("usage: fullserverinfo <complete info string>\n");
809                 return;
810         }
811
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);
815 }
816
817 /*
818 ==================
819 CL_FullInfo_f
820
821 Allow clients to change userinfo
822 ==================
823 Casey was here :)
824 */
825 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
826 {
827         char key[512];
828         char value[512];
829         const char *s;
830
831         if (Cmd_Argc(cmd) != 2)
832         {
833                 Con_Printf ("fullinfo <complete info string>\n");
834                 return;
835         }
836
837         s = Cmd_Argv(cmd, 1);
838         if (*s == '\\')
839                 s++;
840         while (*s)
841         {
842                 size_t len = strcspn(s, "\\");
843                 if (len >= sizeof(key)) {
844                         len = sizeof(key) - 1;
845                 }
846                 strlcpy(key, s, len + 1);
847                 s += len;
848                 if (!*s)
849                 {
850                         Con_Printf ("MISSING VALUE\n");
851                         return;
852                 }
853                 ++s; // Skip over backslash.
854
855                 len = strcspn(s, "\\");
856                 if (len >= sizeof(value)) {
857                         len = sizeof(value) - 1;
858                 }
859                 strlcpy(value, s, len + 1);
860
861                 CL_SetInfo(key, value, false, false, false, false);
862
863                 s += len;
864                 if (!*s)
865                 {
866                         break;
867                 }
868                 ++s; // Skip over backslash.
869         }
870 }
871
872 /*
873 ==================
874 CL_SetInfo_f
875
876 Allow clients to change userinfo
877 ==================
878 */
879 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
880 {
881         if (Cmd_Argc(cmd) == 1)
882         {
883                 InfoString_Print(cls.userinfo);
884                 return;
885         }
886         if (Cmd_Argc(cmd) != 3)
887         {
888                 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
889                 return;
890         }
891         CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
892 }
893
894 /*
895 ====================
896 CL_Packet_f
897
898 packet <destination> <contents>
899
900 Contents allows \n escape character
901 ====================
902 */
903 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
904 {
905         char send[2048];
906         int i, l;
907         const char *in;
908         char *out;
909         lhnetaddress_t address;
910         lhnetsocket_t *mysocket;
911
912         if (Cmd_Argc(cmd) != 3)
913         {
914                 Con_Printf ("packet <destination> <contents>\n");
915                 return;
916         }
917
918         if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
919         {
920                 Con_Printf ("Bad address\n");
921                 return;
922         }
923
924         in = Cmd_Argv(cmd, 2);
925         out = send+4;
926         send[0] = send[1] = send[2] = send[3] = -1;
927
928         l = (int)strlen (in);
929         for (i=0 ; i<l ; i++)
930         {
931                 if (out >= send + sizeof(send) - 1)
932                         break;
933                 if (in[i] == '\\' && in[i+1] == 'n')
934                 {
935                         *out++ = '\n';
936                         i++;
937                 }
938                 else if (in[i] == '\\' && in[i+1] == '0')
939                 {
940                         *out++ = '\0';
941                         i++;
942                 }
943                 else if (in[i] == '\\' && in[i+1] == 't')
944                 {
945                         *out++ = '\t';
946                         i++;
947                 }
948                 else if (in[i] == '\\' && in[i+1] == 'r')
949                 {
950                         *out++ = '\r';
951                         i++;
952                 }
953                 else if (in[i] == '\\' && in[i+1] == '"')
954                 {
955                         *out++ = '\"';
956                         i++;
957                 }
958                 else
959                         *out++ = in[i];
960         }
961
962         mysocket = NetConn_ChooseClientSocketForAddress(&address);
963         if (!mysocket)
964                 mysocket = NetConn_ChooseServerSocketForAddress(&address);
965         if (mysocket)
966                 NetConn_Write(mysocket, send, out - send, &address);
967 }
968
969 static void CL_PingPLReport_f(cmd_state_t *cmd)
970 {
971         char *errbyte;
972         int i;
973         int l = Cmd_Argc(cmd);
974         if (l > cl.maxclients)
975                 l = cl.maxclients;
976         for (i = 0;i < l;i++)
977         {
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);
982                 else
983                         cl.scores[i].qw_movementloss = 0;
984         }
985 }
986
987 //=============================================================================
988
989 /*
990 ==================
991 Host_InitCommands
992 ==================
993 */
994 void Host_InitCommands (void)
995 {
996         dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
997
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);
1013
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");
1017
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");
1025
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)");
1038
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");
1042 }
1043
1044 void Host_NoOperation_f(cmd_state_t *cmd)
1045 {
1046 }