]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_ccmds.c
Fix a potential memory leak with wavefront sounds
[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(cvar_t *var)
167 {
168         prvm_prog_t *prog = SVVM_prog;
169         int i = 0;
170
171         if (var->value == 0)
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                         CL_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                         CL_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                         CL_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                         CL_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         char ip[48]; // can contain a full length v6 address with [] and a port
824         int frags;
825         char vabuf[1024];
826
827         if (!sv.active)
828                 return;
829
830         in = 0;
831         if (Cmd_Argc(cmd) == 2)
832         {
833                 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
834                         in = 1;
835                 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
836                         in = 2;
837         }
838
839         for (players = 0, i = 0;i < svs.maxclients;i++)
840                 if (svs.clients[i].active)
841                         players++;
842         SV_ClientPrintf ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
843         SV_ClientPrintf ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
844         SV_ClientPrintf ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
845         SV_ClientPrintf ("map:      %s\n", sv.name);
846         SV_ClientPrintf ("timing:   %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
847         SV_ClientPrintf ("players:  %i active (%i max)\n\n", players, svs.maxclients);
848
849         if (in == 1)
850                 SV_ClientPrintf ("^2IP                                             %%pl ping  time   frags  no   name\n");
851         else if (in == 2)
852                 SV_ClientPrintf ("^5IP                                              no   name\n");
853
854         for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
855         {
856                 if (!client->active)
857                         continue;
858
859                 ++k;
860
861                 if (in == 0 || in == 1)
862                 {
863                         seconds = (int)(host.realtime - client->connecttime);
864                         minutes = seconds / 60;
865                         if (minutes)
866                         {
867                                 seconds -= (minutes * 60);
868                                 hours = minutes / 60;
869                                 if (hours)
870                                         minutes -= (hours * 60);
871                         }
872                         else
873                                 hours = 0;
874                         
875                         packetloss = 0;
876                         if (client->netconnection)
877                                 for (j = 0;j < NETGRAPH_PACKETS;j++)
878                                         if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
879                                                 packetloss++;
880                         packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
881                         ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
882                 }
883
884                 if(sv_status_privacy.integer && cmd->source != src_command && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
885                         strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
886                 else
887                         strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
888
889                 frags = client->frags;
890
891                 if(sv_status_show_qcstatus.integer)
892                 {
893                         prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
894                         const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
895                         if(str && *str)
896                         {
897                                 char *p;
898                                 const char *q;
899                                 p = qcstatus;
900                                 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
901                                         if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
902                                                 *p++ = *q;
903                                 *p = 0;
904                                 if(*qcstatus)
905                                         frags = atoi(qcstatus);
906                         }
907                 }
908                 
909                 if (in == 0) // default layout
910                 {
911                         if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
912                         {
913                                 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
914                                 SV_ClientPrintf ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
915                                 SV_ClientPrintf ("   %s\n", ip);
916                         }
917                         else
918                         {
919                                 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
920                                 SV_ClientPrintf ("#%-3u %-16.16s %4i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
921                                 SV_ClientPrintf ("   %s\n", ip);
922                         }
923                 }
924                 else if (in == 1) // extended layout
925                 {
926                         SV_ClientPrintf ("%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);
927                 }
928                 else if (in == 2) // reduced layout
929                 {
930                         SV_ClientPrintf ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
931                 }
932         }
933 }
934
935 /*
936 ======================
937 SV_Name_f
938 ======================
939 */
940 static void SV_Name_f(cmd_state_t *cmd)
941 {
942         prvm_prog_t *prog = SVVM_prog;
943         int i, j;
944         qboolean valid_colors;
945         const char *newNameSource;
946         char newName[sizeof(host_client->name)];
947
948         if (Cmd_Argc (cmd) == 1)
949                 return;
950
951         if (Cmd_Argc (cmd) == 2)
952                 newNameSource = Cmd_Argv(cmd, 1);
953         else
954                 newNameSource = Cmd_Args(cmd);
955
956         strlcpy(newName, newNameSource, sizeof(newName));
957
958         if (cmd->source == src_command)
959                 return;
960
961         if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
962         {
963                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
964                 return;
965         }
966
967         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
968
969         // point the string back at updateclient->name to keep it safe
970         strlcpy (host_client->name, newName, sizeof (host_client->name));
971
972         for (i = 0, j = 0;host_client->name[i];i++)
973                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
974                         host_client->name[j++] = host_client->name[i];
975         host_client->name[j] = 0;
976
977         if(host_client->name[0] == 1 || host_client->name[0] == 2)
978         // may interfere with chat area, and will needlessly beep; so let's add a ^7
979         {
980                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
981                 host_client->name[sizeof(host_client->name) - 1] = 0;
982                 host_client->name[0] = STRING_COLOR_TAG;
983                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
984         }
985
986         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
987         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
988         {
989                 size_t l;
990                 l = strlen(host_client->name);
991                 if(l < sizeof(host_client->name) - 1)
992                 {
993                         // duplicate the color tag to escape it
994                         host_client->name[i] = STRING_COLOR_TAG;
995                         host_client->name[i+1] = 0;
996                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
997                 }
998                 else
999                 {
1000                         // remove the last character to fix the color code
1001                         host_client->name[l-1] = 0;
1002                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1003                 }
1004         }
1005
1006         // find the last color tag offset and decide if we need to add a reset tag
1007         for (i = 0, j = -1;host_client->name[i];i++)
1008         {
1009                 if (host_client->name[i] == STRING_COLOR_TAG)
1010                 {
1011                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1012                         {
1013                                 j = i;
1014                                 // if this happens to be a reset  tag then we don't need one
1015                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1016                                         j = -1;
1017                                 i++;
1018                                 continue;
1019                         }
1020                         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]))
1021                         {
1022                                 j = i;
1023                                 i += 4;
1024                                 continue;
1025                         }
1026                         if (host_client->name[i+1] == STRING_COLOR_TAG)
1027                         {
1028                                 i++;
1029                                 continue;
1030                         }
1031                 }
1032         }
1033         // does not end in the default color string, so add it
1034         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1035                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1036
1037         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1038         if (strcmp(host_client->old_name, host_client->name))
1039         {
1040                 if (host_client->begun)
1041                         SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1042                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1043                 // send notification to all clients
1044                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1045                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1046                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1047                 SV_WriteNetnameIntoDemo(host_client);
1048         }
1049 }
1050
1051 static void SV_Rate_f(cmd_state_t *cmd)
1052 {
1053         int rate;
1054
1055         rate = atoi(Cmd_Argv(cmd, 1));
1056
1057         if (cmd->source == src_command)
1058                 return;
1059
1060         host_client->rate = rate;
1061 }
1062
1063 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
1064 {
1065         int rate_burstsize;
1066
1067         if (Cmd_Argc(cmd) != 2)
1068                 return;
1069
1070         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1071
1072         host_client->rate_burstsize = rate_burstsize;
1073 }
1074
1075 static void SV_Color_f(cmd_state_t *cmd)
1076 {
1077         prvm_prog_t *prog = SVVM_prog;
1078
1079         int top, bottom, playercolor;
1080
1081         top = atoi(Cmd_Argv(cmd, 1));
1082         bottom = atoi(Cmd_Argv(cmd, 2));
1083
1084         top &= 15;
1085         bottom &= 15;
1086
1087         playercolor = top*16 + bottom;
1088
1089         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1090         {
1091                 Con_DPrint("Calling SV_ChangeTeam\n");
1092                 prog->globals.fp[OFS_PARM0] = playercolor;
1093                 PRVM_serverglobalfloat(time) = sv.time;
1094                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1095                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1096         }
1097         else
1098         {
1099                 if (host_client->edict)
1100                 {
1101                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1102                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1103                 }
1104                 host_client->colors = playercolor;
1105                 if (host_client->old_colors != host_client->colors)
1106                 {
1107                         host_client->old_colors = host_client->colors;
1108                         // send notification to all clients
1109                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1110                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1111                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1112                 }
1113         }
1114 }
1115
1116 /*
1117 ==================
1118 SV_Kick_f
1119
1120 Kicks a user off of the server
1121 ==================
1122 */
1123 static void SV_Kick_f(cmd_state_t *cmd)
1124 {
1125         const char *who;
1126         const char *message = NULL;
1127         client_t *save;
1128         int i;
1129         qboolean byNumber = false;
1130
1131         if (!sv.active)
1132                 return;
1133
1134         save = host_client;
1135
1136         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1137         {
1138                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1139                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1140                         return;
1141                 byNumber = true;
1142         }
1143         else
1144         {
1145                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1146                 {
1147                         if (!host_client->active)
1148                                 continue;
1149                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1150                                 break;
1151                 }
1152         }
1153
1154         if (i < svs.maxclients)
1155         {
1156                 if (cmd->source == src_command)
1157                 {
1158                         if (cls.state == ca_dedicated)
1159                                 who = "Console";
1160                         else
1161                                 who = cl_name.string;
1162                 }
1163                 else
1164                         who = save->name;
1165
1166                 // can't kick yourself!
1167                 if (host_client == save)
1168                         return;
1169
1170                 if (Cmd_Argc(cmd) > 2)
1171                 {
1172                         message = Cmd_Args(cmd);
1173                         COM_ParseToken_Simple(&message, false, false, true);
1174                         if (byNumber)
1175                         {
1176                                 message++;                                                      // skip the #
1177                                 while (*message == ' ')                         // skip white space
1178                                         message++;
1179                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
1180                         }
1181                         while (*message && *message == ' ')
1182                                 message++;
1183                 }
1184                 if (message)
1185                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1186                 else
1187                         SV_ClientPrintf("Kicked by %s\n", who);
1188                 SV_DropClient (false); // kicked
1189         }
1190
1191         host_client = save;
1192 }
1193
1194 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1195 {
1196         int n;
1197
1198         if (Cmd_Argc(cmd) != 2)
1199         {
1200                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1201                 return;
1202         }
1203
1204         if (sv.active)
1205         {
1206                 Con_Print("maxplayers can not be changed while a server is running.\n");
1207                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1208         }
1209
1210         n = atoi(Cmd_Argv(cmd, 1));
1211         n = bound(1, n, MAX_SCOREBOARD);
1212         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1213
1214         svs.maxclients_next = n;
1215         if (n == 1)
1216                 Cvar_Set (&cvars_all, "deathmatch", "0");
1217         else
1218                 Cvar_Set (&cvars_all, "deathmatch", "1");
1219 }
1220
1221 /*
1222 ======================
1223 SV_Playermodel_f
1224 ======================
1225 */
1226 // the old playermodel in cl_main has been renamed to __cl_playermodel
1227 static void SV_Playermodel_f(cmd_state_t *cmd)
1228 {
1229         prvm_prog_t *prog = SVVM_prog;
1230         int i, j;
1231         char newPath[sizeof(host_client->playermodel)];
1232
1233         if (Cmd_Argc (cmd) == 1)
1234                 return;
1235
1236         if (Cmd_Argc (cmd) == 2)
1237                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1238         else
1239                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1240
1241         for (i = 0, j = 0;newPath[i];i++)
1242                 if (newPath[i] != '\r' && newPath[i] != '\n')
1243                         newPath[j++] = newPath[i];
1244         newPath[j] = 0;
1245
1246         /*
1247         if (host.realtime < host_client->nametime)
1248         {
1249                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1250                 return;
1251         }
1252
1253         host_client->nametime = host.realtime + 5;
1254         */
1255
1256         // point the string back at updateclient->name to keep it safe
1257         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1258         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1259         if (strcmp(host_client->old_model, host_client->playermodel))
1260         {
1261                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1262                 /*// send notification to all clients
1263                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1264                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1265                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1266         }
1267 }
1268
1269 /*
1270 ======================
1271 SV_Playerskin_f
1272 ======================
1273 */
1274 static void SV_Playerskin_f(cmd_state_t *cmd)
1275 {
1276         prvm_prog_t *prog = SVVM_prog;
1277         int i, j;
1278         char newPath[sizeof(host_client->playerskin)];
1279
1280         if (Cmd_Argc (cmd) == 1)
1281                 return;
1282
1283         if (Cmd_Argc (cmd) == 2)
1284                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1285         else
1286                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1287
1288         for (i = 0, j = 0;newPath[i];i++)
1289                 if (newPath[i] != '\r' && newPath[i] != '\n')
1290                         newPath[j++] = newPath[i];
1291         newPath[j] = 0;
1292
1293         /*
1294         if (host.realtime < host_client->nametime)
1295         {
1296                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1297                 return;
1298         }
1299
1300         host_client->nametime = host.realtime + 5;
1301         */
1302
1303         // point the string back at updateclient->name to keep it safe
1304         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1305         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1306         if (strcmp(host_client->old_skin, host_client->playerskin))
1307         {
1308                 //if (host_client->begun)
1309                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1310                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1311                 /*// send notification to all clients
1312                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1313                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1314                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1315         }
1316 }
1317
1318 /*
1319 ======================
1320 SV_PModel_f
1321 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1322 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1323 ======================
1324 */
1325 static void SV_PModel_f(cmd_state_t *cmd)
1326 {
1327         prvm_prog_t *prog = SVVM_prog;
1328
1329         if (Cmd_Argc (cmd) == 1)
1330                 return;
1331
1332         PRVM_serveredictfloat(host_client->edict, pmodel) = atoi(Cmd_Argv(cmd, 1));
1333 }
1334
1335 /*
1336 ===============================================================================
1337
1338 DEBUGGING TOOLS
1339
1340 ===============================================================================
1341 */
1342
1343 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
1344 {
1345         int             i;
1346         prvm_edict_t    *e;
1347
1348         for (i=0 ; i<prog->num_edicts ; i++)
1349         {
1350                 e = PRVM_EDICT_NUM(i);
1351                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1352                         return e;
1353         }
1354         Con_Print("No viewthing on map\n");
1355         return NULL;
1356 }
1357
1358 /*
1359 ==================
1360 SV_Viewmodel_f
1361 ==================
1362 */
1363 static void SV_Viewmodel_f(cmd_state_t *cmd)
1364 {
1365         prvm_prog_t *prog = SVVM_prog;
1366         prvm_edict_t    *e;
1367         dp_model_t      *m;
1368
1369         if (!sv.active)
1370                 return;
1371
1372         e = FindViewthing(prog);
1373         if (e)
1374         {
1375                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1376                 if (m && m->loaded && m->Draw)
1377                 {
1378                         PRVM_serveredictfloat(e, frame) = 0;
1379                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1380                 }
1381                 else
1382                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1383         }
1384 }
1385
1386 /*
1387 ==================
1388 SV_Viewframe_f
1389 ==================
1390 */
1391 static void SV_Viewframe_f(cmd_state_t *cmd)
1392 {
1393         prvm_prog_t *prog = SVVM_prog;
1394         prvm_edict_t    *e;
1395         int             f;
1396         dp_model_t      *m;
1397
1398         if (!sv.active)
1399                 return;
1400
1401         e = FindViewthing(prog);
1402         if (e)
1403         {
1404                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1405
1406                 f = atoi(Cmd_Argv(cmd, 1));
1407                 if (f >= m->numframes)
1408                         f = m->numframes-1;
1409
1410                 PRVM_serveredictfloat(e, frame) = f;
1411         }
1412 }
1413
1414 static void PrintFrameName (dp_model_t *m, int frame)
1415 {
1416         if (m->animscenes)
1417                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1418         else
1419                 Con_Printf("frame %i\n", frame);
1420 }
1421
1422 /*
1423 ==================
1424 SV_Viewnext_f
1425 ==================
1426 */
1427 static void SV_Viewnext_f(cmd_state_t *cmd)
1428 {
1429         prvm_prog_t *prog = SVVM_prog;
1430         prvm_edict_t    *e;
1431         dp_model_t      *m;
1432
1433         if (!sv.active)
1434                 return;
1435
1436         e = FindViewthing(prog);
1437         if (e)
1438         {
1439                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1440
1441                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1442                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1443                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1444
1445                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1446         }
1447 }
1448
1449 /*
1450 ==================
1451 SV_Viewprev_f
1452 ==================
1453 */
1454 static void SV_Viewprev_f(cmd_state_t *cmd)
1455 {
1456         prvm_prog_t *prog = SVVM_prog;
1457         prvm_edict_t    *e;
1458         dp_model_t      *m;
1459
1460         if (!sv.active)
1461                 return;
1462
1463         e = FindViewthing(prog);
1464         if (e)
1465         {
1466                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1467
1468                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1469                 if (PRVM_serveredictfloat(e, frame) < 0)
1470                         PRVM_serveredictfloat(e, frame) = 0;
1471
1472                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1473         }
1474 }
1475
1476 static void SV_SendCvar_f(cmd_state_t *cmd)
1477 {
1478         int i;  
1479         const char *cvarname;
1480         client_t *old;
1481         
1482         if(Cmd_Argc(cmd) != 2)
1483                 return;
1484
1485         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
1486                 return;
1487
1488         cvarname = Cmd_Argv(cmd, 1);
1489
1490         old = host_client;
1491         if (cls.state != ca_dedicated)
1492                 i = 1;
1493         else
1494                 i = 0;
1495         for(;i<svs.maxclients;i++)
1496                 if(svs.clients[i].active && svs.clients[i].netconnection)
1497                 {
1498                         host_client = &svs.clients[i];
1499                         SV_ClientCommands("sendcvar %s\n", cvarname);
1500                 }
1501         host_client = old;
1502 }
1503
1504 void SV_InitOperatorCommands(void)
1505 {
1506         Cvar_RegisterVariable(&sv_cheats);
1507         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1508         Cvar_RegisterVariable(&sv_adminnick);
1509         Cvar_RegisterVariable(&sv_status_privacy);
1510         Cvar_RegisterVariable(&sv_status_show_qcstatus);
1511         Cvar_RegisterVariable(&sv_namechangetimer);
1512         
1513         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1514         Cmd_AddCommand(CMD_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1515         Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
1516         Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1517         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1518         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1519         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1520         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1521         Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1522         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1523         Cmd_AddCommand(CMD_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1524         Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
1525         Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1526         Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1527         Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1528         Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1529         Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1530         Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1531         Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1532         Cmd_AddCommand(CMD_SERVER, "sendcvar", SV_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
1533
1534         // 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)
1535         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1536         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)");
1537         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)");
1538         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)");
1539
1540         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1541         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1542         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1543         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1544         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1545         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1546         
1547         Cmd_AddCommand(CMD_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1548         Cmd_AddCommand(CMD_USERINFO, "name", SV_Name_f, "change your player name");
1549         Cmd_AddCommand(CMD_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1550         Cmd_AddCommand(CMD_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
1551         Cmd_AddCommand(CMD_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
1552         Cmd_AddCommand(CMD_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
1553         Cmd_AddCommand(CMD_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");  
1554 }