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