]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_ccmds.c
server: Eliminate references to cls.state in server code
[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 = {CF_SERVER | CF_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
28 cvar_t sv_adminnick = {CF_SERVER | CF_ARCHIVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
29 cvar_t sv_status_privacy = {CF_SERVER | CF_ARCHIVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
30 cvar_t sv_status_show_qcstatus = {CF_SERVER | CF_ARCHIVE, "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 = {CF_SERVER | CF_ARCHIVE, "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
88         if(sv.active && host.hook.ConnectLocal != NULL)
89                 host.hook.ConnectLocal();
90 }
91
92 /*
93 ==================
94 SV_Changelevel_f
95
96 Goes to a new map, taking all clients along
97 ==================
98 */
99 static void SV_Changelevel_f(cmd_state_t *cmd)
100 {
101         char level[MAX_QPATH];
102
103         if (Cmd_Argc(cmd) != 2)
104         {
105                 Con_Print("changelevel <levelname> : continue game on a new level\n");
106                 return;
107         }
108
109         if (!sv.active)
110         {
111                 Con_Printf("You must be running a server to changelevel. Use 'map %s' instead\n", Cmd_Argv(cmd, 1));
112                 return;
113         }
114
115 #ifdef CONFIG_MENU
116         // remove menu
117         if (key_dest == key_menu || key_dest == key_menu_grabbed)
118                 MR_ToggleMenu(0);
119 #endif
120         key_dest = key_game;
121
122         SV_SaveSpawnparms ();
123         strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
124         SV_SpawnServer(level);
125         
126         if(sv.active && host.hook.ConnectLocal != NULL)
127                 host.hook.ConnectLocal();
128 }
129
130 /*
131 ==================
132 SV_Restart_f
133
134 Restarts the current server for a dead player
135 ==================
136 */
137 static void SV_Restart_f(cmd_state_t *cmd)
138 {
139         char mapname[MAX_QPATH];
140
141         if (Cmd_Argc(cmd) != 1)
142         {
143                 Con_Print("restart : restart current level\n");
144                 return;
145         }
146         if (!sv.active)
147         {
148                 Con_Print("Only the server may restart\n");
149                 return;
150         }
151
152 #ifdef CONFIG_MENU
153         // remove menu
154         if (key_dest == key_menu || key_dest == key_menu_grabbed)
155                 MR_ToggleMenu(0);
156 #endif
157         key_dest = key_game;
158
159         strlcpy(mapname, sv.name, sizeof(mapname));
160         SV_SpawnServer(mapname);
161         
162         if(sv.active && host.hook.ConnectLocal != NULL)
163                 host.hook.ConnectLocal();
164 }
165
166 //===========================================================================
167
168 // Disable cheats if sv_cheats is turned off
169 static void SV_DisableCheats_c(cvar_t *var)
170 {
171         prvm_prog_t *prog = SVVM_prog;
172         int i = 0;
173
174         if (var->value == 0)
175         {
176                 while (svs.clients[i].edict)
177                 {
178                         if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
179                                 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
180                         if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
181                                 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
182                         if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
183                                 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
184                         {
185                                 noclip_anglehack = false;
186                                 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
187                         }
188                         i++;
189                 }
190         }
191 }
192
193 /*
194 ==================
195 SV_God_f
196
197 Sets client to godmode
198 ==================
199 */
200 static void SV_God_f(cmd_state_t *cmd)
201 {
202         prvm_prog_t *prog = SVVM_prog;
203
204         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
205         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
206                 SV_ClientPrint("godmode OFF\n");
207         else
208                 SV_ClientPrint("godmode ON\n");
209 }
210
211 qbool noclip_anglehack;
212
213 static void SV_Noclip_f(cmd_state_t *cmd)
214 {
215         prvm_prog_t *prog = SVVM_prog;
216
217         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
218         {
219                 noclip_anglehack = true;
220                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
221                 SV_ClientPrint("noclip ON\n");
222         }
223         else
224         {
225                 noclip_anglehack = false;
226                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
227                 SV_ClientPrint("noclip OFF\n");
228         }
229 }
230
231 /*
232 ==================
233 SV_Give_f
234 ==================
235 */
236 static void SV_Give_f(cmd_state_t *cmd)
237 {
238         prvm_prog_t *prog = SVVM_prog;
239         const char *t;
240         int v;
241
242         t = Cmd_Argv(cmd, 1);
243         v = atoi (Cmd_Argv(cmd, 2));
244
245         switch (t[0])
246         {
247         case '0':
248         case '1':
249         case '2':
250         case '3':
251         case '4':
252         case '5':
253         case '6':
254         case '7':
255         case '8':
256         case '9':
257                 // MED 01/04/97 added hipnotic give stuff
258                 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
259                 {
260                         if (t[0] == '6')
261                         {
262                                 if (t[1] == 'a')
263                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
264                                 else
265                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
266                         }
267                         else if (t[0] == '9')
268                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
269                         else if (t[0] == '0')
270                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
271                         else if (t[0] >= '2')
272                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
273                 }
274                 else
275                 {
276                         if (t[0] >= '2')
277                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
278                 }
279                 break;
280
281         case 's':
282                 if (gamemode == GAME_ROGUE)
283                         PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
284
285                 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
286                 break;
287         case 'n':
288                 if (gamemode == GAME_ROGUE)
289                 {
290                         PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
291                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
292                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
293                 }
294                 else
295                 {
296                         PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
297                 }
298                 break;
299         case 'l':
300                 if (gamemode == GAME_ROGUE)
301                 {
302                         PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
303                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
304                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
305                 }
306                 break;
307         case 'r':
308                 if (gamemode == GAME_ROGUE)
309                 {
310                         PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
311                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
312                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
313                 }
314                 else
315                 {
316                         PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
317                 }
318                 break;
319         case 'm':
320                 if (gamemode == GAME_ROGUE)
321                 {
322                         PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
323                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
324                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
325                 }
326                 break;
327         case 'h':
328                 PRVM_serveredictfloat(host_client->edict, health) = v;
329                 break;
330         case 'c':
331                 if (gamemode == GAME_ROGUE)
332                 {
333                         PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
334                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
335                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
336                 }
337                 else
338                 {
339                         PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
340                 }
341                 break;
342         case 'p':
343                 if (gamemode == GAME_ROGUE)
344                 {
345                         PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
346                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
347                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
348                 }
349                 break;
350         }
351 }
352
353 /*
354 ==================
355 SV_Fly_f
356
357 Sets client to flymode
358 ==================
359 */
360 static void SV_Fly_f(cmd_state_t *cmd)
361 {
362         prvm_prog_t *prog = SVVM_prog;
363
364         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
365         {
366                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
367                 SV_ClientPrint("flymode ON\n");
368         }
369         else
370         {
371                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
372                 SV_ClientPrint("flymode OFF\n");
373         }
374 }
375
376 static void SV_Notarget_f(cmd_state_t *cmd)
377 {
378         prvm_prog_t *prog = SVVM_prog;
379
380         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
381         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
382                 SV_ClientPrint("notarget OFF\n");
383         else
384                 SV_ClientPrint("notarget ON\n");
385 }
386
387 /*
388 ==================
389 SV_Kill_f
390 ==================
391 */
392 static void SV_Kill_f(cmd_state_t *cmd)
393 {
394         prvm_prog_t *prog = SVVM_prog;
395         if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
396         {
397                 SV_ClientPrint("Can't suicide -- already dead!\n");
398                 return;
399         }
400
401         PRVM_serverglobalfloat(time) = sv.time;
402         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
403         prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
404 }
405
406 /*
407 ==================
408 SV_Pause_f
409 ==================
410 */
411 static void SV_Pause_f(cmd_state_t *cmd)
412 {
413         void (*print) (const char *fmt, ...);
414         if (cmd->source == src_local)
415                 print = Con_Printf;
416         else
417                 print = SV_ClientPrintf;
418
419         if (!pausable.integer && cmd->source == src_client && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
420         {
421                 print("Pause not allowed.\n");
422                 return;
423         }
424         
425         sv.paused ^= 1;
426         if (cmd->source != src_local)
427                 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
428         else if(*(sv_adminnick.string))
429                 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
430         else
431                 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
432         // send notification to all clients
433         MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
434         MSG_WriteByte(&sv.reliable_datagram, sv.paused);
435 }
436
437 static void SV_Say(cmd_state_t *cmd, qbool teamonly)
438 {
439         prvm_prog_t *prog = SVVM_prog;
440         client_t *save;
441         int j, quoted;
442         const char *p1;
443         char *p2;
444         // LadyHavoc: long say messages
445         char text[1024];
446         qbool fromServer = false;
447
448         if (cmd->source == src_local)
449         {
450                 fromServer = true;
451                 teamonly = false;
452         }
453
454         if (Cmd_Argc (cmd) < 2)
455                 return;
456
457         if (!teamplay.integer)
458                 teamonly = false;
459
460         p1 = Cmd_Args(cmd);
461         quoted = false;
462         if (*p1 == '\"')
463         {
464                 quoted = true;
465                 p1++;
466         }
467         // note this uses the chat prefix \001
468         if (!fromServer && !teamonly)
469                 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
470         else if (!fromServer && teamonly)
471                 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
472         else if(*(sv_adminnick.string))
473                 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
474         else
475                 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
476         p2 = text + strlen(text);
477         while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
478         {
479                 if (p2[-1] == '\"' && quoted)
480                         quoted = false;
481                 p2[-1] = 0;
482                 p2--;
483         }
484         strlcat(text, "\n", sizeof(text));
485
486         // note: save is not a valid edict if fromServer is true
487         save = host_client;
488         for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
489                 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
490                         SV_ClientPrint(text);
491         host_client = save;
492
493         if(!host_isclient.integer)
494                 Con_Print(&text[1]);
495 }
496
497 static void SV_Say_f(cmd_state_t *cmd)
498 {
499         SV_Say(cmd, false);
500 }
501
502 static void SV_Say_Team_f(cmd_state_t *cmd)
503 {
504         SV_Say(cmd, true);
505 }
506
507 static void SV_Tell_f(cmd_state_t *cmd)
508 {
509         const char *playername_start = NULL;
510         size_t playername_length = 0;
511         int playernumber = 0;
512         client_t *save;
513         int j;
514         const char *p1, *p2;
515         char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
516         qbool fromServer = false;
517
518         if (cmd->source == src_local)
519                 fromServer = true;
520
521         if (Cmd_Argc (cmd) < 2)
522                 return;
523
524         // note this uses the chat prefix \001
525         if (!fromServer)
526                 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
527         else if(*(sv_adminnick.string))
528                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
529         else
530                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
531
532         p1 = Cmd_Args(cmd);
533         p2 = p1 + strlen(p1);
534         // remove the target name
535         while (p1 < p2 && *p1 == ' ')
536                 p1++;
537         if(*p1 == '#')
538         {
539                 ++p1;
540                 while (p1 < p2 && *p1 == ' ')
541                         p1++;
542                 while (p1 < p2 && isdigit(*p1))
543                 {
544                         playernumber = playernumber * 10 + (*p1 - '0');
545                         p1++;
546                 }
547                 --playernumber;
548         }
549         else if(*p1 == '"')
550         {
551                 ++p1;
552                 playername_start = p1;
553                 while (p1 < p2 && *p1 != '"')
554                         p1++;
555                 playername_length = p1 - playername_start;
556                 if(p1 < p2)
557                         p1++;
558         }
559         else
560         {
561                 playername_start = p1;
562                 while (p1 < p2 && *p1 != ' ')
563                         p1++;
564                 playername_length = p1 - playername_start;
565         }
566         while (p1 < p2 && *p1 == ' ')
567                 p1++;
568         if(playername_start)
569         {
570                 // set playernumber to the right client
571                 char namebuf[128];
572                 if(playername_length >= sizeof(namebuf))
573                 {
574                         if (fromServer)
575                                 Con_Print("Host_Tell: too long player name/ID\n");
576                         else
577                                 SV_ClientPrint("Host_Tell: too long player name/ID\n");
578                         return;
579                 }
580                 memcpy(namebuf, playername_start, playername_length);
581                 namebuf[playername_length] = 0;
582                 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
583                 {
584                         if (!svs.clients[playernumber].active)
585                                 continue;
586                         if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
587                                 break;
588                 }
589         }
590         if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
591         {
592                 if (fromServer)
593                         Con_Print("Host_Tell: invalid player name/ID\n");
594                 else
595                         SV_ClientPrint("Host_Tell: invalid player name/ID\n");
596                 return;
597         }
598         // remove trailing newlines
599         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
600                 p2--;
601         // remove quotes if present
602         if (*p1 == '"')
603         {
604                 p1++;
605                 if (p2[-1] == '"')
606                         p2--;
607                 else if (fromServer)
608                         Con_Print("Host_Tell: missing end quote\n");
609                 else
610                         SV_ClientPrint("Host_Tell: missing end quote\n");
611         }
612         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
613                 p2--;
614         if(p1 == p2)
615                 return; // empty say
616         for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
617                 text[j++] = *p1++;
618         text[j++] = '\n';
619         text[j++] = 0;
620
621         save = host_client;
622         host_client = svs.clients + playernumber;
623         SV_ClientPrint(text);
624         host_client = save;
625 }
626
627 /*
628 ==================
629 SV_Ping_f
630
631 ==================
632 */
633 static void SV_Ping_f(cmd_state_t *cmd)
634 {
635         int i;
636         client_t *client;
637         void (*print) (const char *fmt, ...);
638
639         if (cmd->source == src_local)
640                 print = Con_Printf;
641         else
642                 print = SV_ClientPrintf;
643
644         if (!sv.active)
645                 return;
646
647         print("Client ping times:\n");
648         for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
649         {
650                 if (!client->active)
651                         continue;
652                 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
653         }
654 }
655
656 /*
657 ====================
658 SV_Pings_f
659
660 Send back ping and packet loss update for all current players to this player
661 ====================
662 */
663 static void SV_Pings_f(cmd_state_t *cmd)
664 {
665         int             i, j, ping, packetloss, movementloss;
666         char temp[128];
667
668         if (!host_client->netconnection)
669                 return;
670
671         if (sv.protocol != PROTOCOL_QUAKEWORLD)
672         {
673                 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
674                 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
675         }
676         for (i = 0;i < svs.maxclients;i++)
677         {
678                 packetloss = 0;
679                 movementloss = 0;
680                 if (svs.clients[i].netconnection)
681                 {
682                         for (j = 0;j < NETGRAPH_PACKETS;j++)
683                                 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
684                                         packetloss++;
685                         for (j = 0;j < NETGRAPH_PACKETS;j++)
686                                 if (svs.clients[i].movement_count[j] < 0)
687                                         movementloss++;
688                 }
689                 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
690                 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
691                 ping = (int)floor(svs.clients[i].ping*1000+0.5);
692                 ping = bound(0, ping, 9999);
693                 if (sv.protocol == PROTOCOL_QUAKEWORLD)
694                 {
695                         // send qw_svc_updateping and qw_svc_updatepl messages
696                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
697                         MSG_WriteShort(&host_client->netconnection->message, ping);
698                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
699                         MSG_WriteByte(&host_client->netconnection->message, packetloss);
700                 }
701                 else
702                 {
703                         // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
704                         if(movementloss)
705                                 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
706                         else
707                                 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
708                         MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
709                 }
710         }
711         if (sv.protocol != PROTOCOL_QUAKEWORLD)
712                 MSG_WriteString(&host_client->netconnection->message, "\n");
713 }
714
715 /*
716 ====================
717 SV_User_f
718
719 user <name or userid>
720
721 Dump userdata / masterdata for a user
722 ====================
723 */
724 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
725 {
726         int             uid;
727         int             i;
728
729         if (Cmd_Argc(cmd) != 2)
730         {
731                 Con_Printf ("Usage: user <username / userid>\n");
732                 return;
733         }
734
735         uid = atoi(Cmd_Argv(cmd, 1));
736
737         for (i = 0;i < cl.maxclients;i++)
738         {
739                 if (!cl.scores[i].name[0])
740                         continue;
741                 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
742                 {
743                         InfoString_Print(cl.scores[i].qw_userinfo);
744                         return;
745                 }
746         }
747         Con_Printf ("User not in server.\n");
748 }
749
750 /*
751 ====================
752 SV_Users_f
753
754 Dump userids for all current players
755 ====================
756 */
757 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
758 {
759         int             i;
760         int             c;
761
762         c = 0;
763         Con_Printf ("userid frags name\n");
764         Con_Printf ("------ ----- ----\n");
765         for (i = 0;i < cl.maxclients;i++)
766         {
767                 if (cl.scores[i].name[0])
768                 {
769                         Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
770                         c++;
771                 }
772         }
773
774         Con_Printf ("%i total users\n", c);
775 }
776
777 /*
778 ==================
779 SV_Status_f
780 ==================
781 */
782 static void SV_Status_f(cmd_state_t *cmd)
783 {
784         prvm_prog_t *prog = SVVM_prog;
785         char qcstatus[256];
786         client_t *client;
787         int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
788         void (*print) (const char *fmt, ...);
789         char ip[48]; // can contain a full length v6 address with [] and a port
790         int frags;
791         char vabuf[1024];
792
793         if (cmd->source == src_local)
794                 print = Con_Printf;
795         else
796                 print = SV_ClientPrintf;
797
798         if (!sv.active)
799                 return;
800
801         in = 0;
802         if (Cmd_Argc(cmd) == 2)
803         {
804                 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
805                         in = 1;
806                 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
807                         in = 2;
808         }
809
810         for (players = 0, i = 0;i < svs.maxclients;i++)
811                 if (svs.clients[i].active)
812                         players++;
813         print ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CF_SERVER));
814         print ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
815         print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
816         print ("map:      %s\n", sv.name);
817         print ("timing:   %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
818         print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
819
820         if (in == 1)
821                 print ("^2IP                                             %%pl ping  time   frags  no   name\n");
822         else if (in == 2)
823                 print ("^5IP                                              no   name\n");
824
825         for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
826         {
827                 if (!client->active)
828                         continue;
829
830                 ++k;
831
832                 if (in == 0 || in == 1)
833                 {
834                         seconds = (int)(host.realtime - client->connecttime);
835                         minutes = seconds / 60;
836                         if (minutes)
837                         {
838                                 seconds -= (minutes * 60);
839                                 hours = minutes / 60;
840                                 if (hours)
841                                         minutes -= (hours * 60);
842                         }
843                         else
844                                 hours = 0;
845                         
846                         packetloss = 0;
847                         if (client->netconnection)
848                                 for (j = 0;j < NETGRAPH_PACKETS;j++)
849                                         if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
850                                                 packetloss++;
851                         packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
852                         ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
853                 }
854
855                 if(sv_status_privacy.integer && cmd->source != src_local && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
856                         strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
857                 else
858                         strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
859
860                 frags = client->frags;
861
862                 if(sv_status_show_qcstatus.integer)
863                 {
864                         prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
865                         const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
866                         if(str && *str)
867                         {
868                                 char *p;
869                                 const char *q;
870                                 p = qcstatus;
871                                 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
872                                         if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
873                                                 *p++ = *q;
874                                 *p = 0;
875                                 if(*qcstatus)
876                                         frags = atoi(qcstatus);
877                         }
878                 }
879                 
880                 if (in == 0) // default layout
881                 {
882                         if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
883                         {
884                                 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
885                                 print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
886                                 print ("   %s\n", ip);
887                         }
888                         else
889                         {
890                                 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
891                                 print ("#%-3u %-16.16s %4i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
892                                 print ("   %s\n", ip);
893                         }
894                 }
895                 else if (in == 1) // extended layout
896                 {
897                         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);
898                 }
899                 else if (in == 2) // reduced layout
900                 {
901                         print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
902                 }
903         }
904 }
905
906 void SV_Name(int clientnum)
907 {
908         prvm_prog_t *prog = SVVM_prog;
909         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
910         if (strcmp(host_client->old_name, host_client->name))
911         {
912                 if (host_client->begun)
913                         SV_BroadcastPrintf("\003%s ^7changed name to ^3%s\n", host_client->old_name, host_client->name);
914                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
915                 // send notification to all clients
916                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
917                 MSG_WriteByte (&sv.reliable_datagram, clientnum);
918                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
919                 SV_WriteNetnameIntoDemo(host_client);
920         }       
921 }
922
923 /*
924 ======================
925 SV_Name_f
926 ======================
927 */
928 static void SV_Name_f(cmd_state_t *cmd)
929 {
930         int i, j;
931         qbool valid_colors;
932         const char *newNameSource;
933         char newName[sizeof(host_client->name)];
934
935         if (Cmd_Argc (cmd) == 1)
936                 return;
937
938         if (Cmd_Argc (cmd) == 2)
939                 newNameSource = Cmd_Argv(cmd, 1);
940         else
941                 newNameSource = Cmd_Args(cmd);
942
943         strlcpy(newName, newNameSource, sizeof(newName));
944
945         if (cmd->source == src_local)
946                 return;
947
948         if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
949         {
950                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
951                 return;
952         }
953
954         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
955
956         // point the string back at updateclient->name to keep it safe
957         strlcpy (host_client->name, newName, sizeof (host_client->name));
958
959         for (i = 0, j = 0;host_client->name[i];i++)
960                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
961                         host_client->name[j++] = host_client->name[i];
962         host_client->name[j] = 0;
963
964         if(host_client->name[0] == 1 || host_client->name[0] == 2)
965         // may interfere with chat area, and will needlessly beep; so let's add a ^7
966         {
967                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
968                 host_client->name[sizeof(host_client->name) - 1] = 0;
969                 host_client->name[0] = STRING_COLOR_TAG;
970                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
971         }
972
973         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
974         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
975         {
976                 size_t l;
977                 l = strlen(host_client->name);
978                 if(l < sizeof(host_client->name) - 1)
979                 {
980                         // duplicate the color tag to escape it
981                         host_client->name[i] = STRING_COLOR_TAG;
982                         host_client->name[i+1] = 0;
983                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
984                 }
985                 else
986                 {
987                         // remove the last character to fix the color code
988                         host_client->name[l-1] = 0;
989                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
990                 }
991         }
992
993         // find the last color tag offset and decide if we need to add a reset tag
994         for (i = 0, j = -1;host_client->name[i];i++)
995         {
996                 if (host_client->name[i] == STRING_COLOR_TAG)
997                 {
998                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
999                         {
1000                                 j = i;
1001                                 // if this happens to be a reset  tag then we don't need one
1002                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1003                                         j = -1;
1004                                 i++;
1005                                 continue;
1006                         }
1007                         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]))
1008                         {
1009                                 j = i;
1010                                 i += 4;
1011                                 continue;
1012                         }
1013                         if (host_client->name[i+1] == STRING_COLOR_TAG)
1014                         {
1015                                 i++;
1016                                 continue;
1017                         }
1018                 }
1019         }
1020         // does not end in the default color string, so add it
1021         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1022                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1023
1024         SV_Name(host_client - svs.clients);
1025 }
1026
1027 static void SV_Rate_f(cmd_state_t *cmd)
1028 {
1029         int rate;
1030
1031         rate = atoi(Cmd_Argv(cmd, 1));
1032
1033         if (cmd->source == src_local)
1034                 return;
1035
1036         host_client->rate = rate;
1037 }
1038
1039 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
1040 {
1041         int rate_burstsize;
1042
1043         if (Cmd_Argc(cmd) != 2)
1044                 return;
1045
1046         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1047
1048         host_client->rate_burstsize = rate_burstsize;
1049 }
1050
1051 static void SV_Color_f(cmd_state_t *cmd)
1052 {
1053         prvm_prog_t *prog = SVVM_prog;
1054
1055         int top, bottom, playercolor;
1056
1057         top = atoi(Cmd_Argv(cmd, 1));
1058         bottom = atoi(Cmd_Argv(cmd, 2));
1059
1060         top &= 15;
1061         bottom &= 15;
1062
1063         playercolor = top*16 + bottom;
1064
1065         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1066         {
1067                 Con_DPrint("Calling SV_ChangeTeam\n");
1068                 prog->globals.fp[OFS_PARM0] = playercolor;
1069                 PRVM_serverglobalfloat(time) = sv.time;
1070                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1071                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1072         }
1073         else
1074         {
1075                 if (host_client->edict)
1076                 {
1077                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1078                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1079                 }
1080                 host_client->colors = playercolor;
1081                 if (host_client->old_colors != host_client->colors)
1082                 {
1083                         host_client->old_colors = host_client->colors;
1084                         // send notification to all clients
1085                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1086                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1087                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1088                 }
1089         }
1090 }
1091
1092 /*
1093 ==================
1094 SV_Kick_f
1095
1096 Kicks a user off of the server
1097 ==================
1098 */
1099 static void SV_Kick_f(cmd_state_t *cmd)
1100 {
1101         const char *who;
1102         const char *message = NULL;
1103         client_t *save;
1104         int i;
1105         qbool byNumber = false;
1106
1107         if (!sv.active)
1108                 return;
1109
1110         save = host_client;
1111
1112         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1113         {
1114                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1115                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1116                         return;
1117                 byNumber = true;
1118         }
1119         else
1120         {
1121                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1122                 {
1123                         if (!host_client->active)
1124                                 continue;
1125                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1126                                 break;
1127                 }
1128         }
1129
1130         if (i < svs.maxclients)
1131         {
1132                 if (cmd->source == src_local)
1133                 {
1134                         if(!host_isclient.integer)
1135                                 who = "Console";
1136                         else
1137                                 who = cl_name.string;
1138                 }
1139                 else
1140                         who = save->name;
1141
1142                 // can't kick yourself!
1143                 if (host_client == save)
1144                         return;
1145
1146                 if (Cmd_Argc(cmd) > 2)
1147                 {
1148                         message = Cmd_Args(cmd);
1149                         COM_ParseToken_Simple(&message, false, false, true);
1150                         if (byNumber)
1151                         {
1152                                 message++;                                                      // skip the #
1153                                 while (*message == ' ')                         // skip white space
1154                                         message++;
1155                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
1156                         }
1157                         while (*message && *message == ' ')
1158                                 message++;
1159                 }
1160                 if (message)
1161                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1162                 else
1163                         SV_ClientPrintf("Kicked by %s\n", who);
1164                 SV_DropClient (false); // kicked
1165         }
1166
1167         host_client = save;
1168 }
1169
1170 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1171 {
1172         int n;
1173
1174         if (Cmd_Argc(cmd) != 2)
1175         {
1176                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1177                 return;
1178         }
1179
1180         if (sv.active)
1181         {
1182                 Con_Print("maxplayers can not be changed while a server is running.\n");
1183                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1184         }
1185
1186         n = atoi(Cmd_Argv(cmd, 1));
1187         n = bound(1, n, MAX_SCOREBOARD);
1188         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1189
1190         svs.maxclients_next = n;
1191         if (n == 1)
1192                 Cvar_Set (&cvars_all, "deathmatch", "0");
1193         else
1194                 Cvar_Set (&cvars_all, "deathmatch", "1");
1195 }
1196
1197 /*
1198 ======================
1199 SV_Playermodel_f
1200 ======================
1201 */
1202 // the old playermodel in cl_main has been renamed to __cl_playermodel
1203 static void SV_Playermodel_f(cmd_state_t *cmd)
1204 {
1205         prvm_prog_t *prog = SVVM_prog;
1206         int i, j;
1207         char newPath[sizeof(host_client->playermodel)];
1208
1209         if (Cmd_Argc (cmd) == 1)
1210                 return;
1211
1212         if (Cmd_Argc (cmd) == 2)
1213                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1214         else
1215                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1216
1217         for (i = 0, j = 0;newPath[i];i++)
1218                 if (newPath[i] != '\r' && newPath[i] != '\n')
1219                         newPath[j++] = newPath[i];
1220         newPath[j] = 0;
1221
1222         /*
1223         if (host.realtime < host_client->nametime)
1224         {
1225                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1226                 return;
1227         }
1228
1229         host_client->nametime = host.realtime + 5;
1230         */
1231
1232         // point the string back at updateclient->name to keep it safe
1233         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1234         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1235         if (strcmp(host_client->old_model, host_client->playermodel))
1236         {
1237                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1238                 /*// send notification to all clients
1239                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1240                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1241                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1242         }
1243 }
1244
1245 /*
1246 ======================
1247 SV_Playerskin_f
1248 ======================
1249 */
1250 static void SV_Playerskin_f(cmd_state_t *cmd)
1251 {
1252         prvm_prog_t *prog = SVVM_prog;
1253         int i, j;
1254         char newPath[sizeof(host_client->playerskin)];
1255
1256         if (Cmd_Argc (cmd) == 1)
1257                 return;
1258
1259         if (Cmd_Argc (cmd) == 2)
1260                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1261         else
1262                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1263
1264         for (i = 0, j = 0;newPath[i];i++)
1265                 if (newPath[i] != '\r' && newPath[i] != '\n')
1266                         newPath[j++] = newPath[i];
1267         newPath[j] = 0;
1268
1269         /*
1270         if (host.realtime < host_client->nametime)
1271         {
1272                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1273                 return;
1274         }
1275
1276         host_client->nametime = host.realtime + 5;
1277         */
1278
1279         // point the string back at updateclient->name to keep it safe
1280         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1281         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1282         if (strcmp(host_client->old_skin, host_client->playerskin))
1283         {
1284                 //if (host_client->begun)
1285                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1286                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1287                 /*// send notification to all clients
1288                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1289                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1290                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1291         }
1292 }
1293
1294 /*
1295 ======================
1296 SV_PModel_f
1297 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1298 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1299 ======================
1300 */
1301 static void SV_PModel_f(cmd_state_t *cmd)
1302 {
1303         prvm_prog_t *prog = SVVM_prog;
1304
1305         if (Cmd_Argc (cmd) == 1)
1306                 return;
1307
1308         PRVM_serveredictfloat(host_client->edict, pmodel) = atoi(Cmd_Argv(cmd, 1));
1309 }
1310
1311 /*
1312 ===============================================================================
1313
1314 DEBUGGING TOOLS
1315
1316 ===============================================================================
1317 */
1318
1319 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
1320 {
1321         int             i;
1322         prvm_edict_t    *e;
1323
1324         for (i=0 ; i<prog->num_edicts ; i++)
1325         {
1326                 e = PRVM_EDICT_NUM(i);
1327                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1328                         return e;
1329         }
1330         Con_Print("No viewthing on map\n");
1331         return NULL;
1332 }
1333
1334 /*
1335 ==================
1336 SV_Viewmodel_f
1337 ==================
1338 */
1339 static void SV_Viewmodel_f(cmd_state_t *cmd)
1340 {
1341         prvm_prog_t *prog = SVVM_prog;
1342         prvm_edict_t    *e;
1343         dp_model_t      *m;
1344
1345         if (!sv.active)
1346                 return;
1347
1348         e = FindViewthing(prog);
1349         if (e)
1350         {
1351                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1352                 if (m && m->loaded && m->Draw)
1353                 {
1354                         PRVM_serveredictfloat(e, frame) = 0;
1355                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1356                 }
1357                 else
1358                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1359         }
1360 }
1361
1362 /*
1363 ==================
1364 SV_Viewframe_f
1365 ==================
1366 */
1367 static void SV_Viewframe_f(cmd_state_t *cmd)
1368 {
1369         prvm_prog_t *prog = SVVM_prog;
1370         prvm_edict_t    *e;
1371         int             f;
1372         dp_model_t      *m;
1373
1374         if (!sv.active)
1375                 return;
1376
1377         e = FindViewthing(prog);
1378         if (e)
1379         {
1380                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1381
1382                 f = atoi(Cmd_Argv(cmd, 1));
1383                 if (f >= m->numframes)
1384                         f = m->numframes-1;
1385
1386                 PRVM_serveredictfloat(e, frame) = f;
1387         }
1388 }
1389
1390 static void PrintFrameName (dp_model_t *m, int frame)
1391 {
1392         if (m->animscenes)
1393                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1394         else
1395                 Con_Printf("frame %i\n", frame);
1396 }
1397
1398 /*
1399 ==================
1400 SV_Viewnext_f
1401 ==================
1402 */
1403 static void SV_Viewnext_f(cmd_state_t *cmd)
1404 {
1405         prvm_prog_t *prog = SVVM_prog;
1406         prvm_edict_t    *e;
1407         dp_model_t      *m;
1408
1409         if (!sv.active)
1410                 return;
1411
1412         e = FindViewthing(prog);
1413         if (e)
1414         {
1415                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1416
1417                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1418                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1419                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1420
1421                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1422         }
1423 }
1424
1425 /*
1426 ==================
1427 SV_Viewprev_f
1428 ==================
1429 */
1430 static void SV_Viewprev_f(cmd_state_t *cmd)
1431 {
1432         prvm_prog_t *prog = SVVM_prog;
1433         prvm_edict_t    *e;
1434         dp_model_t      *m;
1435
1436         if (!sv.active)
1437                 return;
1438
1439         e = FindViewthing(prog);
1440         if (e)
1441         {
1442                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1443
1444                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1445                 if (PRVM_serveredictfloat(e, frame) < 0)
1446                         PRVM_serveredictfloat(e, frame) = 0;
1447
1448                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1449         }
1450 }
1451
1452 static void SV_SendCvar_f(cmd_state_t *cmd)
1453 {
1454         int i;  
1455         const char *cvarname;
1456         client_t *old;
1457         
1458         if(Cmd_Argc(cmd) != 2)
1459                 return;
1460
1461         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
1462                 return;
1463
1464         cvarname = Cmd_Argv(cmd, 1);
1465
1466         old = host_client;
1467         if(host_isclient.integer)
1468                 i = 1;
1469         else
1470                 i = 0;
1471         for(;i<svs.maxclients;i++)
1472                 if(svs.clients[i].active && svs.clients[i].netconnection)
1473                 {
1474                         host_client = &svs.clients[i];
1475                         SV_ClientCommands("sendcvar %s\n", cvarname);
1476                 }
1477         host_client = old;
1478 }
1479
1480 static void SV_Ent_Create_f(cmd_state_t *cmd)
1481 {
1482         prvm_prog_t *prog = SVVM_prog;
1483         prvm_edict_t *ed;
1484         mdef_t *key;
1485         int i;
1486         qbool haveorigin;
1487
1488         qbool expectval = false;
1489         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1490
1491         if(!Cmd_Argc(cmd))
1492                 return;
1493
1494         ed = PRVM_ED_Alloc(SVVM_prog);
1495
1496         PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "classname"), Cmd_Argv(cmd, 1), false);
1497
1498         // Spawn where the player is aiming. We need a view matrix first.
1499         if(cmd->source == src_client)
1500         {
1501                 vec3_t org, temp, dest;
1502                 matrix4x4_t view;
1503                 trace_t trace;
1504                 char buf[128];
1505
1506                 SV_GetEntityMatrix(prog, host_client->edict, &view, true);
1507
1508                 Matrix4x4_OriginFromMatrix(&view, org);
1509                 VectorSet(temp, 65536, 0, 0);
1510                 Matrix4x4_Transform(&view, temp, dest);         
1511
1512                 trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value);
1513
1514                 dpsnprintf(buf, sizeof(buf), "%g %g %g", trace.endpos[0], trace.endpos[1], trace.endpos[2]);
1515                 PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "origin"), buf, false);
1516
1517                 haveorigin = true;
1518         }
1519         // Or spawn at a specified origin.
1520         else
1521         {
1522                 print = Con_Printf;
1523                 haveorigin = false;
1524         }
1525
1526         // Allow more than one key/value pair by cycling between expecting either one.
1527         for(i = 2; i < Cmd_Argc(cmd); i++)
1528         {
1529                 if(!expectval)
1530                 {
1531                         if(!(key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, i))))
1532                         {
1533                                 print("Key %s not found!\n", Cmd_Argv(cmd, i));
1534                                 PRVM_ED_Free(prog, ed);
1535                                 return;
1536                         }
1537
1538                         /*
1539                          * This is mostly for dedicated server console, but if the
1540                          * player gave a custom origin, we can ignore the traceline.
1541                          */
1542                         if(!strcmp(Cmd_Argv(cmd, i), "origin"))
1543                                 haveorigin = true;
1544
1545                         expectval = true;
1546                 }
1547                 else
1548                 {
1549                         PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, i), false);
1550                         expectval = false;
1551                 }
1552         }
1553
1554         if(!haveorigin)
1555         {
1556                 print("Missing origin\n");
1557                 PRVM_ED_Free(prog, ed);
1558                 return;
1559         }
1560
1561         // Spawn it
1562         PRVM_ED_CallPrespawnFunction(prog, ed);
1563         
1564         if(!PRVM_ED_CallSpawnFunction(prog, ed, NULL, NULL))
1565         {
1566                 print("Could not spawn a \"%s\". No such entity or it has no spawn function\n", Cmd_Argv(cmd, 1));
1567                 if(cmd->source == src_client)
1568                         Con_Printf("%s tried to spawn a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
1569                 // CallSpawnFunction already freed the edict for us.
1570                 return;
1571         }
1572
1573         PRVM_ED_CallPostspawnFunction(prog, ed);        
1574
1575         // Make it appear in the world
1576         SV_LinkEdict(ed);
1577
1578         if(cmd->source == src_client)
1579                 Con_Printf("%s spawned a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
1580 }
1581
1582 static void SV_Ent_Remove_f(cmd_state_t *cmd)
1583 {
1584         prvm_prog_t *prog = SVVM_prog;
1585         prvm_edict_t *ed;
1586         int i, ednum;
1587         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1588
1589         if(!Cmd_Argc(cmd))
1590                 return;
1591
1592         // Allow specifying edict by number
1593         if(Cmd_Argc(cmd) > 1 && Cmd_Argv(cmd, 1))
1594         {
1595                 ednum = atoi(Cmd_Argv(cmd, 1));
1596                 if(!ednum)
1597                 {
1598                         print("Cannot remove the world\n");
1599                         return;
1600                 }
1601         }
1602         // Or trace a line if it's a client who didn't specify one.
1603         else if(cmd->source == src_client)
1604         {
1605                 vec3_t org, temp, dest;
1606                 matrix4x4_t view;
1607                 trace_t trace;
1608
1609                 SV_GetEntityMatrix(prog, host_client->edict, &view, true);
1610
1611                 Matrix4x4_OriginFromMatrix(&view, org);
1612                 VectorSet(temp, 65536, 0, 0);
1613                 Matrix4x4_Transform(&view, temp, dest);         
1614
1615                 trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, 0, 0, collision_extendmovelength.value);
1616                 
1617                 if(trace.ent)
1618                         ednum = (int)PRVM_EDICT_TO_PROG(trace.ent);
1619                 if(!trace.ent || !ednum)
1620                         // Don't remove the world, but don't annoy players with a print if they miss
1621                         return;
1622         }
1623         else
1624         {
1625                 // Only a dedicated server console should be able to reach this.
1626                 print("No edict given\n");
1627                 return;
1628         }
1629
1630         ed = PRVM_EDICT_NUM(ednum);
1631
1632         if(ed)
1633         {
1634                 // Skip players
1635                 for (i = 0; i < svs.maxclients; i++)
1636                 {
1637                         if(ed == svs.clients[i].edict)
1638                                 return;
1639                 }
1640
1641                 if(!ed->priv.required->free)
1642                 {
1643                         print("Removed a \"%s\"\n", PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)));
1644                         PRVM_ED_ClearEdict(prog, ed);
1645                         PRVM_ED_Free(prog, ed);
1646                 }
1647         }
1648         else
1649         {
1650                 // This should only be reachable if an invalid edict number was given
1651                 print("No such entity\n");
1652                 return;
1653         }
1654 }
1655
1656 static void SV_Ent_Remove_All_f(cmd_state_t *cmd)
1657 {
1658         prvm_prog_t *prog = SVVM_prog;
1659         int i, rmcount;
1660         prvm_edict_t *ed;
1661         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1662
1663         for (i = 0, rmcount = 0, ed = PRVM_EDICT_NUM(i); i < prog->num_edicts; i++, ed = PRVM_NEXT_EDICT(ed))
1664         {
1665                 if(!ed->priv.required->free && !strcmp(PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)), Cmd_Argv(cmd, 1)))
1666                 {
1667                         if(!i)
1668                         {
1669                                 print("Cannot remove the world\n");
1670                                 return;
1671                         }
1672                         PRVM_ED_ClearEdict(prog, ed);
1673                         PRVM_ED_Free(prog, ed);
1674                         rmcount++;
1675                 }
1676         }
1677
1678         if(!rmcount)
1679                 print("No \"%s\" found\n", Cmd_Argv(cmd, 1));
1680         else
1681                 print("Removed %i of \"%s\"\n", rmcount, Cmd_Argv(cmd, 1));
1682 }
1683
1684 void SV_InitOperatorCommands(void)
1685 {
1686         Cvar_RegisterVariable(&sv_cheats);
1687         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1688         Cvar_RegisterVariable(&sv_adminnick);
1689         Cvar_RegisterVariable(&sv_status_privacy);
1690         Cvar_RegisterVariable(&sv_status_show_qcstatus);
1691         Cvar_RegisterVariable(&sv_namechangetimer);
1692         
1693         Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1694         Cmd_AddCommand(CF_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1695         Cmd_AddCommand(CF_SHARED, "restart", SV_Restart_f, "restart current level");
1696         Cmd_AddCommand(CF_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1697         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1698         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1699         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1700         Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1701         Cmd_AddCommand(CF_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1702         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1703         Cmd_AddCommand(CF_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1704         Cmd_AddCommand(CF_SHARED, "save", SV_Savegame_f, "save the game to a file");
1705         Cmd_AddCommand(CF_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1706         Cmd_AddCommand(CF_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1707         Cmd_AddCommand(CF_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1708         Cmd_AddCommand(CF_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1709         Cmd_AddCommand(CF_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1710         Cmd_AddCommand(CF_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1711         Cmd_AddCommand(CF_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1712         Cmd_AddCommand(CF_SERVER, "sendcvar", SV_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
1713
1714         // 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)
1715         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1716         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
1717         Cmd_AddCommand(CF_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)");
1718         Cmd_AddCommand(CF_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)");
1719
1720         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1721         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1722         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1723         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1724         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1725         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1726         
1727         Cmd_AddCommand(CF_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1728         Cmd_AddCommand(CF_USERINFO, "name", SV_Name_f, "change your player name");
1729         Cmd_AddCommand(CF_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1730         Cmd_AddCommand(CF_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
1731         Cmd_AddCommand(CF_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
1732         Cmd_AddCommand(CF_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
1733         Cmd_AddCommand(CF_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");
1734
1735         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_create", SV_Ent_Create_f, "Creates an entity at the specified coordinate, of the specified classname. If executed from a server, origin has to be specified manually.");
1736         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_remove_all", SV_Ent_Remove_All_f, "Removes all entities of the specified classname");
1737         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_remove", SV_Ent_Remove_f, "Removes an entity by number, or the entity you're aiming at");
1738 }