]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_ccmds.c
(Round 5) Break up host_cmd.c
[xonotic/darkplaces.git] / sv_ccmds.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 <utf8lib.h>
23 #include <server.h>
24 #include <sv_demo.h>
25
26 int current_skill;
27 cvar_t sv_cheats = {CVAR_SERVER | CVAR_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
28 cvar_t sv_adminnick = {CVAR_SERVER | CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
29 cvar_t sv_status_privacy = {CVAR_SERVER | CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
30 cvar_t sv_status_show_qcstatus = {CVAR_SERVER | CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
31 cvar_t sv_namechangetimer = {CVAR_SERVER | CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
32
33 /*
34 ===============================================================================
35
36 SERVER TRANSITIONS
37
38 ===============================================================================
39 */
40
41 /*
42 ======================
43 SV_Map_f
44
45 handle a
46 map <servername>
47 command from the console.  Active clients are kicked off.
48 ======================
49 */
50 static void SV_Map_f(cmd_state_t *cmd)
51 {
52         char level[MAX_QPATH];
53
54         if (Cmd_Argc(cmd) != 2)
55         {
56                 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
57                 return;
58         }
59
60         // GAME_DELUXEQUAKE - clear warpmark (used by QC)
61         if (gamemode == GAME_DELUXEQUAKE)
62                 Cvar_Set(&cvars_all, "warpmark", "");
63
64         cls.demonum = -1;               // stop demo loop in case this fails
65
66         CL_Disconnect ();
67         SV_Shutdown();
68
69         if(svs.maxclients != svs.maxclients_next)
70         {
71                 svs.maxclients = svs.maxclients_next;
72                 if (svs.clients)
73                         Mem_Free(svs.clients);
74                 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
75         }
76
77 #ifdef CONFIG_MENU
78         // remove menu
79         if (key_dest == key_menu || key_dest == key_menu_grabbed)
80                 MR_ToggleMenu(0);
81 #endif
82         key_dest = key_game;
83
84         svs.serverflags = 0;                    // haven't completed an episode yet
85         strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
86         SV_SpawnServer(level);
87         if (sv.active && cls.state == ca_disconnected)
88                 CL_EstablishConnection("local:1", -2);
89 }
90
91 /*
92 ==================
93 SV_Changelevel_f
94
95 Goes to a new map, taking all clients along
96 ==================
97 */
98 static void SV_Changelevel_f(cmd_state_t *cmd)
99 {
100         char level[MAX_QPATH];
101
102         if (Cmd_Argc(cmd) != 2)
103         {
104                 Con_Print("changelevel <levelname> : continue game on a new level\n");
105                 return;
106         }
107         // HACKHACKHACK
108         if (!sv.active) {
109                 SV_Map_f(cmd);
110                 return;
111         }
112
113 #ifdef CONFIG_MENU
114         // remove menu
115         if (key_dest == key_menu || key_dest == key_menu_grabbed)
116                 MR_ToggleMenu(0);
117 #endif
118         key_dest = key_game;
119
120         SV_SaveSpawnparms ();
121         strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
122         SV_SpawnServer(level);
123         if (sv.active && cls.state == ca_disconnected)
124                 CL_EstablishConnection("local:1", -2);
125 }
126
127 /*
128 ==================
129 SV_Restart_f
130
131 Restarts the current server for a dead player
132 ==================
133 */
134 static void SV_Restart_f(cmd_state_t *cmd)
135 {
136         char mapname[MAX_QPATH];
137
138         if (Cmd_Argc(cmd) != 1)
139         {
140                 Con_Print("restart : restart current level\n");
141                 return;
142         }
143         if (!sv.active)
144         {
145                 Con_Print("Only the server may restart\n");
146                 return;
147         }
148
149 #ifdef CONFIG_MENU
150         // remove menu
151         if (key_dest == key_menu || key_dest == key_menu_grabbed)
152                 MR_ToggleMenu(0);
153 #endif
154         key_dest = key_game;
155
156         strlcpy(mapname, sv.name, sizeof(mapname));
157         SV_SpawnServer(mapname);
158         if (sv.active && cls.state == ca_disconnected)
159                 CL_EstablishConnection("local:1", -2);
160 }
161
162 //===========================================================================
163
164 // Disable cheats if sv_cheats is turned off
165 static void SV_DisableCheats_c(char *value)
166 {
167         prvm_prog_t *prog = SVVM_prog;
168         int i = 0;
169
170         if (value[0] == '0' && !value[1])
171         {
172                 while (svs.clients[i].edict)
173                 {
174                         if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
175                                 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
176                         if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
177                                 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
178                         if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
179                                 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
180                         {
181                                 noclip_anglehack = false;
182                                 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
183                         }
184                         i++;
185                 }
186         }
187 }
188
189 /*
190 ==================
191 SV_God_f
192
193 Sets client to godmode
194 ==================
195 */
196 static void SV_God_f(cmd_state_t *cmd)
197 {
198         prvm_prog_t *prog = SVVM_prog;
199
200         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
201         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
202                 SV_ClientPrint("godmode OFF\n");
203         else
204                 SV_ClientPrint("godmode ON\n");
205 }
206
207 qboolean noclip_anglehack;
208
209 static void SV_Noclip_f(cmd_state_t *cmd)
210 {
211         prvm_prog_t *prog = SVVM_prog;
212
213         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
214         {
215                 noclip_anglehack = true;
216                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
217                 SV_ClientPrint("noclip ON\n");
218         }
219         else
220         {
221                 noclip_anglehack = false;
222                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
223                 SV_ClientPrint("noclip OFF\n");
224         }
225 }
226
227 /*
228 ==================
229 SV_Give_f
230 ==================
231 */
232 static void SV_Give_f(cmd_state_t *cmd)
233 {
234         prvm_prog_t *prog = SVVM_prog;
235         const char *t;
236         int v;
237
238         t = Cmd_Argv(cmd, 1);
239         v = atoi (Cmd_Argv(cmd, 2));
240
241         switch (t[0])
242         {
243         case '0':
244         case '1':
245         case '2':
246         case '3':
247         case '4':
248         case '5':
249         case '6':
250         case '7':
251         case '8':
252         case '9':
253                 // MED 01/04/97 added hipnotic give stuff
254                 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
255                 {
256                         if (t[0] == '6')
257                         {
258                                 if (t[1] == 'a')
259                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
260                                 else
261                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
262                         }
263                         else if (t[0] == '9')
264                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
265                         else if (t[0] == '0')
266                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
267                         else if (t[0] >= '2')
268                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
269                 }
270                 else
271                 {
272                         if (t[0] >= '2')
273                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
274                 }
275                 break;
276
277         case 's':
278                 if (gamemode == GAME_ROGUE)
279                         PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
280
281                 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
282                 break;
283         case 'n':
284                 if (gamemode == GAME_ROGUE)
285                 {
286                         PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
287                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
288                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
289                 }
290                 else
291                 {
292                         PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
293                 }
294                 break;
295         case 'l':
296                 if (gamemode == GAME_ROGUE)
297                 {
298                         PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
299                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
300                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
301                 }
302                 break;
303         case 'r':
304                 if (gamemode == GAME_ROGUE)
305                 {
306                         PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
307                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
308                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
309                 }
310                 else
311                 {
312                         PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
313                 }
314                 break;
315         case 'm':
316                 if (gamemode == GAME_ROGUE)
317                 {
318                         PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
319                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
320                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
321                 }
322                 break;
323         case 'h':
324                 PRVM_serveredictfloat(host_client->edict, health) = v;
325                 break;
326         case 'c':
327                 if (gamemode == GAME_ROGUE)
328                 {
329                         PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
330                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
331                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
332                 }
333                 else
334                 {
335                         PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
336                 }
337                 break;
338         case 'p':
339                 if (gamemode == GAME_ROGUE)
340                 {
341                         PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
342                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
343                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
344                 }
345                 break;
346         }
347 }
348
349 /*
350 ==================
351 SV_Fly_f
352
353 Sets client to flymode
354 ==================
355 */
356 static void SV_Fly_f(cmd_state_t *cmd)
357 {
358         prvm_prog_t *prog = SVVM_prog;
359
360         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
361         {
362                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
363                 SV_ClientPrint("flymode ON\n");
364         }
365         else
366         {
367                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
368                 SV_ClientPrint("flymode OFF\n");
369         }
370 }
371
372 static void SV_Notarget_f(cmd_state_t *cmd)
373 {
374         prvm_prog_t *prog = SVVM_prog;
375
376         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
377         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
378                 SV_ClientPrint("notarget OFF\n");
379         else
380                 SV_ClientPrint("notarget ON\n");
381 }
382
383 /*
384 ==================
385 SV_Kill_f
386 ==================
387 */
388 static void SV_Kill_f(cmd_state_t *cmd)
389 {
390         prvm_prog_t *prog = SVVM_prog;
391         if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
392         {
393                 SV_ClientPrint("Can't suicide -- already dead!\n");
394                 return;
395         }
396
397         PRVM_serverglobalfloat(time) = sv.time;
398         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
399         prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
400 }
401
402 /*
403 ==================
404 SV_Pause_f
405 ==================
406 */
407 static void SV_Pause_f(cmd_state_t *cmd)
408 {
409         void (*print) (const char *fmt, ...);
410         if (cmd->source == src_command)
411         {
412                 // if running a client, try to send over network so the pause is handled by the server
413                 if (cls.state == ca_connected)
414                 {
415                         Cmd_ForwardToServer_f(cmd);
416                         return;
417                 }
418                 print = Con_Printf;
419         }
420         else
421                 print = SV_ClientPrintf;
422
423         if (!pausable.integer)
424         {
425                 if (cmd->source == src_client)
426                 {
427                         if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
428                         {
429                                 print("Pause not allowed.\n");
430                                 return;
431                         }
432                 }
433         }
434         
435         sv.paused ^= 1;
436         if (cmd->source != src_command)
437                 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
438         else if(*(sv_adminnick.string))
439                 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
440         else
441                 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
442         // send notification to all clients
443         MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
444         MSG_WriteByte(&sv.reliable_datagram, sv.paused);
445 }
446
447 static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
448 {
449         prvm_prog_t *prog = SVVM_prog;
450         client_t *save;
451         int j, quoted;
452         const char *p1;
453         char *p2;
454         // LadyHavoc: long say messages
455         char text[1024];
456         qboolean fromServer = false;
457
458         if (cmd->source == src_command)
459         {
460                 if (cls.state == ca_dedicated)
461                 {
462                         fromServer = true;
463                         teamonly = false;
464                 }
465                 else
466                 {
467                         Cmd_ForwardToServer_f(cmd);
468                         return;
469                 }
470         }
471
472         if (Cmd_Argc (cmd) < 2)
473                 return;
474
475         if (!teamplay.integer)
476                 teamonly = false;
477
478         p1 = Cmd_Args(cmd);
479         quoted = false;
480         if (*p1 == '\"')
481         {
482                 quoted = true;
483                 p1++;
484         }
485         // note this uses the chat prefix \001
486         if (!fromServer && !teamonly)
487                 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
488         else if (!fromServer && teamonly)
489                 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
490         else if(*(sv_adminnick.string))
491                 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
492         else
493                 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
494         p2 = text + strlen(text);
495         while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
496         {
497                 if (p2[-1] == '\"' && quoted)
498                         quoted = false;
499                 p2[-1] = 0;
500                 p2--;
501         }
502         strlcat(text, "\n", sizeof(text));
503
504         // note: save is not a valid edict if fromServer is true
505         save = host_client;
506         for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
507                 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
508                         SV_ClientPrint(text);
509         host_client = save;
510
511         if (cls.state == ca_dedicated)
512                 Con_Print(&text[1]);
513 }
514
515 static void SV_Say_f(cmd_state_t *cmd)
516 {
517         SV_Say(cmd, false);
518 }
519
520 static void SV_Say_Team_f(cmd_state_t *cmd)
521 {
522         SV_Say(cmd, true);
523 }
524
525 static void SV_Tell_f(cmd_state_t *cmd)
526 {
527         const char *playername_start = NULL;
528         size_t playername_length = 0;
529         int playernumber = 0;
530         client_t *save;
531         int j;
532         const char *p1, *p2;
533         char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
534         qboolean fromServer = false;
535
536         if (cmd->source == src_command)
537         {
538                 if (cls.state == ca_dedicated)
539                         fromServer = true;
540                 else
541                 {
542                         Cmd_ForwardToServer_f(cmd);
543                         return;
544                 }
545         }
546
547         if (Cmd_Argc (cmd) < 2)
548                 return;
549
550         // note this uses the chat prefix \001
551         if (!fromServer)
552                 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
553         else if(*(sv_adminnick.string))
554                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
555         else
556                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
557
558         p1 = Cmd_Args(cmd);
559         p2 = p1 + strlen(p1);
560         // remove the target name
561         while (p1 < p2 && *p1 == ' ')
562                 p1++;
563         if(*p1 == '#')
564         {
565                 ++p1;
566                 while (p1 < p2 && *p1 == ' ')
567                         p1++;
568                 while (p1 < p2 && isdigit(*p1))
569                 {
570                         playernumber = playernumber * 10 + (*p1 - '0');
571                         p1++;
572                 }
573                 --playernumber;
574         }
575         else if(*p1 == '"')
576         {
577                 ++p1;
578                 playername_start = p1;
579                 while (p1 < p2 && *p1 != '"')
580                         p1++;
581                 playername_length = p1 - playername_start;
582                 if(p1 < p2)
583                         p1++;
584         }
585         else
586         {
587                 playername_start = p1;
588                 while (p1 < p2 && *p1 != ' ')
589                         p1++;
590                 playername_length = p1 - playername_start;
591         }
592         while (p1 < p2 && *p1 == ' ')
593                 p1++;
594         if(playername_start)
595         {
596                 // set playernumber to the right client
597                 char namebuf[128];
598                 if(playername_length >= sizeof(namebuf))
599                 {
600                         if (fromServer)
601                                 Con_Print("Host_Tell: too long player name/ID\n");
602                         else
603                                 SV_ClientPrint("Host_Tell: too long player name/ID\n");
604                         return;
605                 }
606                 memcpy(namebuf, playername_start, playername_length);
607                 namebuf[playername_length] = 0;
608                 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
609                 {
610                         if (!svs.clients[playernumber].active)
611                                 continue;
612                         if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
613                                 break;
614                 }
615         }
616         if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
617         {
618                 if (fromServer)
619                         Con_Print("Host_Tell: invalid player name/ID\n");
620                 else
621                         SV_ClientPrint("Host_Tell: invalid player name/ID\n");
622                 return;
623         }
624         // remove trailing newlines
625         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
626                 p2--;
627         // remove quotes if present
628         if (*p1 == '"')
629         {
630                 p1++;
631                 if (p2[-1] == '"')
632                         p2--;
633                 else if (fromServer)
634                         Con_Print("Host_Tell: missing end quote\n");
635                 else
636                         SV_ClientPrint("Host_Tell: missing end quote\n");
637         }
638         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
639                 p2--;
640         if(p1 == p2)
641                 return; // empty say
642         for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
643                 text[j++] = *p1++;
644         text[j++] = '\n';
645         text[j++] = 0;
646
647         save = host_client;
648         host_client = svs.clients + playernumber;
649         SV_ClientPrint(text);
650         host_client = save;
651 }
652
653 /*
654 ==================
655 SV_Ping_f
656
657 ==================
658 */
659 static void SV_Ping_f(cmd_state_t *cmd)
660 {
661         int i;
662         client_t *client;
663         void (*print) (const char *fmt, ...);
664
665         if (cmd->source == src_command)
666         {
667                 // if running a client, try to send over network so the client's ping report parser will see the report
668                 if (cls.state == ca_connected)
669                 {
670                         Cmd_ForwardToServer_f(cmd);
671                         return;
672                 }
673                 print = Con_Printf;
674         }
675         else
676                 print = SV_ClientPrintf;
677
678         if (!sv.active)
679                 return;
680
681         print("Client ping times:\n");
682         for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
683         {
684                 if (!client->active)
685                         continue;
686                 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
687         }
688 }
689
690 /*
691 ====================
692 SV_Pings_f
693
694 Send back ping and packet loss update for all current players to this player
695 ====================
696 */
697 static void SV_Pings_f(cmd_state_t *cmd)
698 {
699         int             i, j, ping, packetloss, movementloss;
700         char temp[128];
701
702         if (!host_client->netconnection)
703                 return;
704
705         if (sv.protocol != PROTOCOL_QUAKEWORLD)
706         {
707                 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
708                 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
709         }
710         for (i = 0;i < svs.maxclients;i++)
711         {
712                 packetloss = 0;
713                 movementloss = 0;
714                 if (svs.clients[i].netconnection)
715                 {
716                         for (j = 0;j < NETGRAPH_PACKETS;j++)
717                                 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
718                                         packetloss++;
719                         for (j = 0;j < NETGRAPH_PACKETS;j++)
720                                 if (svs.clients[i].movement_count[j] < 0)
721                                         movementloss++;
722                 }
723                 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
724                 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
725                 ping = (int)floor(svs.clients[i].ping*1000+0.5);
726                 ping = bound(0, ping, 9999);
727                 if (sv.protocol == PROTOCOL_QUAKEWORLD)
728                 {
729                         // send qw_svc_updateping and qw_svc_updatepl messages
730                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
731                         MSG_WriteShort(&host_client->netconnection->message, ping);
732                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
733                         MSG_WriteByte(&host_client->netconnection->message, packetloss);
734                 }
735                 else
736                 {
737                         // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
738                         if(movementloss)
739                                 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
740                         else
741                                 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
742                         MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
743                 }
744         }
745         if (sv.protocol != PROTOCOL_QUAKEWORLD)
746                 MSG_WriteString(&host_client->netconnection->message, "\n");
747 }
748
749 /*
750 ====================
751 SV_User_f
752
753 user <name or userid>
754
755 Dump userdata / masterdata for a user
756 ====================
757 */
758 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
759 {
760         int             uid;
761         int             i;
762
763         if (Cmd_Argc(cmd) != 2)
764         {
765                 Con_Printf ("Usage: user <username / userid>\n");
766                 return;
767         }
768
769         uid = atoi(Cmd_Argv(cmd, 1));
770
771         for (i = 0;i < cl.maxclients;i++)
772         {
773                 if (!cl.scores[i].name[0])
774                         continue;
775                 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
776                 {
777                         InfoString_Print(cl.scores[i].qw_userinfo);
778                         return;
779                 }
780         }
781         Con_Printf ("User not in server.\n");
782 }
783
784 /*
785 ====================
786 SV_Users_f
787
788 Dump userids for all current players
789 ====================
790 */
791 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
792 {
793         int             i;
794         int             c;
795
796         c = 0;
797         Con_Printf ("userid frags name\n");
798         Con_Printf ("------ ----- ----\n");
799         for (i = 0;i < cl.maxclients;i++)
800         {
801                 if (cl.scores[i].name[0])
802                 {
803                         Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
804                         c++;
805                 }
806         }
807
808         Con_Printf ("%i total users\n", c);
809 }
810
811 /*
812 ==================
813 SV_Status_f
814 ==================
815 */
816 static void SV_Status_f(cmd_state_t *cmd)
817 {
818         prvm_prog_t *prog = SVVM_prog;
819         char qcstatus[256];
820         client_t *client;
821         int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
822         void (*print) (const char *fmt, ...);
823         char ip[48]; // can contain a full length v6 address with [] and a port
824         int frags;
825         char vabuf[1024];
826
827         if (cmd->source == src_command)
828         {
829                 // if running a client, try to send over network so the client's status report parser will see the report
830                 if (cls.state == ca_connected)
831                 {
832                         Cmd_ForwardToServer_f(cmd);
833                         return;
834                 }
835                 print = Con_Printf;
836         }
837         else
838                 print = SV_ClientPrintf;
839
840         if (!sv.active)
841                 return;
842
843         in = 0;
844         if (Cmd_Argc(cmd) == 2)
845         {
846                 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
847                         in = 1;
848                 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
849                         in = 2;
850         }
851
852         for (players = 0, i = 0;i < svs.maxclients;i++)
853                 if (svs.clients[i].active)
854                         players++;
855         print ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
856         print ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
857         print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
858         print ("map:      %s\n", sv.name);
859         print ("timing:   %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
860         print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
861
862         if (in == 1)
863                 print ("^2IP                                             %%pl ping  time   frags  no   name\n");
864         else if (in == 2)
865                 print ("^5IP                                              no   name\n");
866
867         for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
868         {
869                 if (!client->active)
870                         continue;
871
872                 ++k;
873
874                 if (in == 0 || in == 1)
875                 {
876                         seconds = (int)(host.realtime - client->connecttime);
877                         minutes = seconds / 60;
878                         if (minutes)
879                         {
880                                 seconds -= (minutes * 60);
881                                 hours = minutes / 60;
882                                 if (hours)
883                                         minutes -= (hours * 60);
884                         }
885                         else
886                                 hours = 0;
887                         
888                         packetloss = 0;
889                         if (client->netconnection)
890                                 for (j = 0;j < NETGRAPH_PACKETS;j++)
891                                         if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
892                                                 packetloss++;
893                         packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
894                         ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
895                 }
896
897                 if(sv_status_privacy.integer && cmd->source != src_command)
898                         strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
899                 else
900                         strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
901
902                 frags = client->frags;
903
904                 if(sv_status_show_qcstatus.integer)
905                 {
906                         prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
907                         const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
908                         if(str && *str)
909                         {
910                                 char *p;
911                                 const char *q;
912                                 p = qcstatus;
913                                 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
914                                         if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
915                                                 *p++ = *q;
916                                 *p = 0;
917                                 if(*qcstatus)
918                                         frags = atoi(qcstatus);
919                         }
920                 }
921                 
922                 if (in == 0) // default layout
923                 {
924                         if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
925                         {
926                                 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
927                                 print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
928                                 print ("   %s\n", ip);
929                         }
930                         else
931                         {
932                                 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
933                                 print ("#%-3u %-16.16s %4i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
934                                 print ("   %s\n", ip);
935                         }
936                 }
937                 else if (in == 1) // extended layout
938                 {
939                         print ("%s%-47s %2i %4i %2i:%02i:%02i %4i  #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
940                 }
941                 else if (in == 2) // reduced layout
942                 {
943                         print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
944                 }
945         }
946 }
947
948 /*
949 ======================
950 SV_Name_f
951 ======================
952 */
953 static void SV_Name_f(cmd_state_t *cmd)
954 {
955         prvm_prog_t *prog = SVVM_prog;
956         int i, j;
957         qboolean valid_colors;
958         const char *newNameSource;
959         char newName[sizeof(host_client->name)];
960
961         if (Cmd_Argc (cmd) == 1)
962                 return;
963
964         if (Cmd_Argc (cmd) == 2)
965                 newNameSource = Cmd_Argv(cmd, 1);
966         else
967                 newNameSource = Cmd_Args(cmd);
968
969         strlcpy(newName, newNameSource, sizeof(newName));
970
971         if (cmd->source == src_command)
972                 return;
973
974         if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
975         {
976                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
977                 return;
978         }
979
980         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
981
982         // point the string back at updateclient->name to keep it safe
983         strlcpy (host_client->name, newName, sizeof (host_client->name));
984
985         for (i = 0, j = 0;host_client->name[i];i++)
986                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
987                         host_client->name[j++] = host_client->name[i];
988         host_client->name[j] = 0;
989
990         if(host_client->name[0] == 1 || host_client->name[0] == 2)
991         // may interfere with chat area, and will needlessly beep; so let's add a ^7
992         {
993                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
994                 host_client->name[sizeof(host_client->name) - 1] = 0;
995                 host_client->name[0] = STRING_COLOR_TAG;
996                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
997         }
998
999         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1000         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1001         {
1002                 size_t l;
1003                 l = strlen(host_client->name);
1004                 if(l < sizeof(host_client->name) - 1)
1005                 {
1006                         // duplicate the color tag to escape it
1007                         host_client->name[i] = STRING_COLOR_TAG;
1008                         host_client->name[i+1] = 0;
1009                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1010                 }
1011                 else
1012                 {
1013                         // remove the last character to fix the color code
1014                         host_client->name[l-1] = 0;
1015                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1016                 }
1017         }
1018
1019         // find the last color tag offset and decide if we need to add a reset tag
1020         for (i = 0, j = -1;host_client->name[i];i++)
1021         {
1022                 if (host_client->name[i] == STRING_COLOR_TAG)
1023                 {
1024                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1025                         {
1026                                 j = i;
1027                                 // if this happens to be a reset  tag then we don't need one
1028                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1029                                         j = -1;
1030                                 i++;
1031                                 continue;
1032                         }
1033                         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]))
1034                         {
1035                                 j = i;
1036                                 i += 4;
1037                                 continue;
1038                         }
1039                         if (host_client->name[i+1] == STRING_COLOR_TAG)
1040                         {
1041                                 i++;
1042                                 continue;
1043                         }
1044                 }
1045         }
1046         // does not end in the default color string, so add it
1047         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1048                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1049
1050         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1051         if (strcmp(host_client->old_name, host_client->name))
1052         {
1053                 if (host_client->begun)
1054                         SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1055                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1056                 // send notification to all clients
1057                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1058                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1059                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1060                 SV_WriteNetnameIntoDemo(host_client);
1061         }
1062 }
1063
1064 static void SV_Rate_f(cmd_state_t *cmd)
1065 {
1066         int rate;
1067
1068         rate = atoi(Cmd_Argv(cmd, 1));
1069
1070         if (cmd->source == src_command)
1071                 return;
1072
1073         host_client->rate = rate;
1074 }
1075
1076 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
1077 {
1078         int rate_burstsize;
1079
1080         if (Cmd_Argc(cmd) != 2)
1081                 return;
1082
1083         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1084
1085         host_client->rate_burstsize = rate_burstsize;
1086 }
1087
1088 static void SV_Color_f(cmd_state_t *cmd)
1089 {
1090         prvm_prog_t *prog = SVVM_prog;
1091
1092         int top, bottom, playercolor;
1093
1094         top = atoi(Cmd_Argv(cmd, 1));
1095         bottom = atoi(Cmd_Argv(cmd, 2));
1096
1097         playercolor = top*16 + bottom;
1098
1099         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1100         {
1101                 Con_DPrint("Calling SV_ChangeTeam\n");
1102                 prog->globals.fp[OFS_PARM0] = playercolor;
1103                 PRVM_serverglobalfloat(time) = sv.time;
1104                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1105                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1106         }
1107         else
1108         {
1109                 if (host_client->edict)
1110                 {
1111                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1112                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1113                 }
1114                 host_client->colors = playercolor;
1115                 if (host_client->old_colors != host_client->colors)
1116                 {
1117                         host_client->old_colors = host_client->colors;
1118                         // send notification to all clients
1119                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1120                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1121                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1122                 }
1123         }
1124 }
1125
1126 /*
1127 ==================
1128 SV_Kick_f
1129
1130 Kicks a user off of the server
1131 ==================
1132 */
1133 static void SV_Kick_f(cmd_state_t *cmd)
1134 {
1135         const char *who;
1136         const char *message = NULL;
1137         client_t *save;
1138         int i;
1139         qboolean byNumber = false;
1140
1141         if (!sv.active)
1142                 return;
1143
1144         save = host_client;
1145
1146         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1147         {
1148                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1149                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1150                         return;
1151                 byNumber = true;
1152         }
1153         else
1154         {
1155                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1156                 {
1157                         if (!host_client->active)
1158                                 continue;
1159                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1160                                 break;
1161                 }
1162         }
1163
1164         if (i < svs.maxclients)
1165         {
1166                 if (cmd->source == src_command)
1167                 {
1168                         if (cls.state == ca_dedicated)
1169                                 who = "Console";
1170                         else
1171                                 who = name.string;
1172                 }
1173                 else
1174                         who = save->name;
1175
1176                 // can't kick yourself!
1177                 if (host_client == save)
1178                         return;
1179
1180                 if (Cmd_Argc(cmd) > 2)
1181                 {
1182                         message = Cmd_Args(cmd);
1183                         COM_ParseToken_Simple(&message, false, false, true);
1184                         if (byNumber)
1185                         {
1186                                 message++;                                                      // skip the #
1187                                 while (*message == ' ')                         // skip white space
1188                                         message++;
1189                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
1190                         }
1191                         while (*message && *message == ' ')
1192                                 message++;
1193                 }
1194                 if (message)
1195                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1196                 else
1197                         SV_ClientPrintf("Kicked by %s\n", who);
1198                 SV_DropClient (false); // kicked
1199         }
1200
1201         host_client = save;
1202 }
1203
1204 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1205 {
1206         int n;
1207
1208         if (Cmd_Argc(cmd) != 2)
1209         {
1210                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1211                 return;
1212         }
1213
1214         if (sv.active)
1215         {
1216                 Con_Print("maxplayers can not be changed while a server is running.\n");
1217                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1218         }
1219
1220         n = atoi(Cmd_Argv(cmd, 1));
1221         n = bound(1, n, MAX_SCOREBOARD);
1222         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1223
1224         svs.maxclients_next = n;
1225         if (n == 1)
1226                 Cvar_Set (&cvars_all, "deathmatch", "0");
1227         else
1228                 Cvar_Set (&cvars_all, "deathmatch", "1");
1229 }
1230
1231 /*
1232 ===============================================================================
1233
1234 DEBUGGING TOOLS
1235
1236 ===============================================================================
1237 */
1238
1239 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
1240 {
1241         int             i;
1242         prvm_edict_t    *e;
1243
1244         for (i=0 ; i<prog->num_edicts ; i++)
1245         {
1246                 e = PRVM_EDICT_NUM(i);
1247                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1248                         return e;
1249         }
1250         Con_Print("No viewthing on map\n");
1251         return NULL;
1252 }
1253
1254 /*
1255 ==================
1256 SV_Viewmodel_f
1257 ==================
1258 */
1259 static void SV_Viewmodel_f(cmd_state_t *cmd)
1260 {
1261         prvm_prog_t *prog = SVVM_prog;
1262         prvm_edict_t    *e;
1263         dp_model_t      *m;
1264
1265         if (!sv.active)
1266                 return;
1267
1268         e = FindViewthing(prog);
1269         if (e)
1270         {
1271                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1272                 if (m && m->loaded && m->Draw)
1273                 {
1274                         PRVM_serveredictfloat(e, frame) = 0;
1275                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1276                 }
1277                 else
1278                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1279         }
1280 }
1281
1282 /*
1283 ==================
1284 SV_Viewframe_f
1285 ==================
1286 */
1287 static void SV_Viewframe_f(cmd_state_t *cmd)
1288 {
1289         prvm_prog_t *prog = SVVM_prog;
1290         prvm_edict_t    *e;
1291         int             f;
1292         dp_model_t      *m;
1293
1294         if (!sv.active)
1295                 return;
1296
1297         e = FindViewthing(prog);
1298         if (e)
1299         {
1300                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1301
1302                 f = atoi(Cmd_Argv(cmd, 1));
1303                 if (f >= m->numframes)
1304                         f = m->numframes-1;
1305
1306                 PRVM_serveredictfloat(e, frame) = f;
1307         }
1308 }
1309
1310 static void PrintFrameName (dp_model_t *m, int frame)
1311 {
1312         if (m->animscenes)
1313                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1314         else
1315                 Con_Printf("frame %i\n", frame);
1316 }
1317
1318 /*
1319 ==================
1320 SV_Viewnext_f
1321 ==================
1322 */
1323 static void SV_Viewnext_f(cmd_state_t *cmd)
1324 {
1325         prvm_prog_t *prog = SVVM_prog;
1326         prvm_edict_t    *e;
1327         dp_model_t      *m;
1328
1329         if (!sv.active)
1330                 return;
1331
1332         e = FindViewthing(prog);
1333         if (e)
1334         {
1335                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1336
1337                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1338                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1339                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1340
1341                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1342         }
1343 }
1344
1345 /*
1346 ==================
1347 SV_Viewprev_f
1348 ==================
1349 */
1350 static void SV_Viewprev_f(cmd_state_t *cmd)
1351 {
1352         prvm_prog_t *prog = SVVM_prog;
1353         prvm_edict_t    *e;
1354         dp_model_t      *m;
1355
1356         if (!sv.active)
1357                 return;
1358
1359         e = FindViewthing(prog);
1360         if (e)
1361         {
1362                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1363
1364                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1365                 if (PRVM_serveredictfloat(e, frame) < 0)
1366                         PRVM_serveredictfloat(e, frame) = 0;
1367
1368                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1369         }
1370 }
1371
1372 void SV_InitOperatorCommands(void)
1373 {
1374         Cvar_RegisterVariable(&sv_cheats);
1375         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1376         Cvar_RegisterVariable(&sv_adminnick);
1377         Cvar_RegisterVariable(&sv_status_privacy);
1378         Cvar_RegisterVariable(&sv_status_show_qcstatus);
1379         Cvar_RegisterVariable(&sv_namechangetimer);
1380         
1381         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1382         Cmd_AddCommand(CMD_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1383         Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
1384         Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1385         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1386         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1387         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1388         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1389         Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1390         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1391         Cmd_AddCommand(CMD_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1392         Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
1393         Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1394         Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1395         Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1396         Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1397         Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1398         Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1399         Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1400
1401         // commands that do not have automatic forwarding from cmd_client, these are internal details of the network protocol and not of interest to users (if they know what they are doing they can still use a generic "cmd prespawn" or similar)
1402         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1403         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
1404         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "begin", SV_Begin_f, "internal use - signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
1405         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "pings", SV_Pings_f, "internal use - command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
1406
1407         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1408         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1409         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1410         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1411         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1412         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1413         
1414         Cmd_AddCommand(CMD_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1415         Cmd_AddCommand(CMD_USERINFO, "name", SV_Name_f, "change your player name");
1416         Cmd_AddCommand(CMD_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1417         Cmd_AddCommand(CMD_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
1418 }