]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_ccmds.c
sv_ccmds: Move what are actually client commands to the client.
[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_Status_f
718 ==================
719 */
720 static void SV_Status_f(cmd_state_t *cmd)
721 {
722         prvm_prog_t *prog = SVVM_prog;
723         char qcstatus[256];
724         client_t *client;
725         int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
726         void (*print) (const char *fmt, ...);
727         char ip[48]; // can contain a full length v6 address with [] and a port
728         int frags;
729         char vabuf[1024];
730
731         if (cmd->source == src_local)
732                 print = Con_Printf;
733         else
734                 print = SV_ClientPrintf;
735
736         if (!sv.active)
737                 return;
738
739         in = 0;
740         if (Cmd_Argc(cmd) == 2)
741         {
742                 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
743                         in = 1;
744                 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
745                         in = 2;
746         }
747
748         for (players = 0, i = 0;i < svs.maxclients;i++)
749                 if (svs.clients[i].active)
750                         players++;
751         print ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CF_SERVER));
752         print ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
753         print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
754         print ("map:      %s\n", sv.name);
755         print ("timing:   %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
756         print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
757
758         if (in == 1)
759                 print ("^2IP                                             %%pl ping  time   frags  no   name\n");
760         else if (in == 2)
761                 print ("^5IP                                              no   name\n");
762
763         for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
764         {
765                 if (!client->active)
766                         continue;
767
768                 ++k;
769
770                 if (in == 0 || in == 1)
771                 {
772                         seconds = (int)(host.realtime - client->connecttime);
773                         minutes = seconds / 60;
774                         if (minutes)
775                         {
776                                 seconds -= (minutes * 60);
777                                 hours = minutes / 60;
778                                 if (hours)
779                                         minutes -= (hours * 60);
780                         }
781                         else
782                                 hours = 0;
783                         
784                         packetloss = 0;
785                         if (client->netconnection)
786                                 for (j = 0;j < NETGRAPH_PACKETS;j++)
787                                         if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
788                                                 packetloss++;
789                         packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
790                         ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
791                 }
792
793                 if(sv_status_privacy.integer && cmd->source != src_local && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
794                         strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
795                 else
796                         strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
797
798                 frags = client->frags;
799
800                 if(sv_status_show_qcstatus.integer)
801                 {
802                         prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
803                         const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
804                         if(str && *str)
805                         {
806                                 char *p;
807                                 const char *q;
808                                 p = qcstatus;
809                                 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
810                                         if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
811                                                 *p++ = *q;
812                                 *p = 0;
813                                 if(*qcstatus)
814                                         frags = atoi(qcstatus);
815                         }
816                 }
817                 
818                 if (in == 0) // default layout
819                 {
820                         if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
821                         {
822                                 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
823                                 print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
824                                 print ("   %s\n", ip);
825                         }
826                         else
827                         {
828                                 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
829                                 print ("#%-3u %-16.16s %4i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
830                                 print ("   %s\n", ip);
831                         }
832                 }
833                 else if (in == 1) // extended layout
834                 {
835                         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);
836                 }
837                 else if (in == 2) // reduced layout
838                 {
839                         print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
840                 }
841         }
842 }
843
844 void SV_Name(int clientnum)
845 {
846         prvm_prog_t *prog = SVVM_prog;
847         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
848         if (strcmp(host_client->old_name, host_client->name))
849         {
850                 if (host_client->begun)
851                         SV_BroadcastPrintf("\003%s ^7changed name to ^3%s\n", host_client->old_name, host_client->name);
852                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
853                 // send notification to all clients
854                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
855                 MSG_WriteByte (&sv.reliable_datagram, clientnum);
856                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
857                 SV_WriteNetnameIntoDemo(host_client);
858         }       
859 }
860
861 /*
862 ======================
863 SV_Name_f
864 ======================
865 */
866 static void SV_Name_f(cmd_state_t *cmd)
867 {
868         int i, j;
869         qbool valid_colors;
870         const char *newNameSource;
871         char newName[sizeof(host_client->name)];
872
873         if (Cmd_Argc (cmd) == 1)
874                 return;
875
876         if (Cmd_Argc (cmd) == 2)
877                 newNameSource = Cmd_Argv(cmd, 1);
878         else
879                 newNameSource = Cmd_Args(cmd);
880
881         strlcpy(newName, newNameSource, sizeof(newName));
882
883         if (cmd->source == src_local)
884                 return;
885
886         if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
887         {
888                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
889                 return;
890         }
891
892         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
893
894         // point the string back at updateclient->name to keep it safe
895         strlcpy (host_client->name, newName, sizeof (host_client->name));
896
897         for (i = 0, j = 0;host_client->name[i];i++)
898                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
899                         host_client->name[j++] = host_client->name[i];
900         host_client->name[j] = 0;
901
902         if(host_client->name[0] == 1 || host_client->name[0] == 2)
903         // may interfere with chat area, and will needlessly beep; so let's add a ^7
904         {
905                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
906                 host_client->name[sizeof(host_client->name) - 1] = 0;
907                 host_client->name[0] = STRING_COLOR_TAG;
908                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
909         }
910
911         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
912         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
913         {
914                 size_t l;
915                 l = strlen(host_client->name);
916                 if(l < sizeof(host_client->name) - 1)
917                 {
918                         // duplicate the color tag to escape it
919                         host_client->name[i] = STRING_COLOR_TAG;
920                         host_client->name[i+1] = 0;
921                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
922                 }
923                 else
924                 {
925                         // remove the last character to fix the color code
926                         host_client->name[l-1] = 0;
927                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
928                 }
929         }
930
931         // find the last color tag offset and decide if we need to add a reset tag
932         for (i = 0, j = -1;host_client->name[i];i++)
933         {
934                 if (host_client->name[i] == STRING_COLOR_TAG)
935                 {
936                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
937                         {
938                                 j = i;
939                                 // if this happens to be a reset  tag then we don't need one
940                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
941                                         j = -1;
942                                 i++;
943                                 continue;
944                         }
945                         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]))
946                         {
947                                 j = i;
948                                 i += 4;
949                                 continue;
950                         }
951                         if (host_client->name[i+1] == STRING_COLOR_TAG)
952                         {
953                                 i++;
954                                 continue;
955                         }
956                 }
957         }
958         // does not end in the default color string, so add it
959         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
960                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
961
962         SV_Name(host_client - svs.clients);
963 }
964
965 static void SV_Rate_f(cmd_state_t *cmd)
966 {
967         int rate;
968
969         rate = atoi(Cmd_Argv(cmd, 1));
970
971         if (cmd->source == src_local)
972                 return;
973
974         host_client->rate = rate;
975 }
976
977 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
978 {
979         int rate_burstsize;
980
981         if (Cmd_Argc(cmd) != 2)
982                 return;
983
984         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
985
986         host_client->rate_burstsize = rate_burstsize;
987 }
988
989 static void SV_Color_f(cmd_state_t *cmd)
990 {
991         prvm_prog_t *prog = SVVM_prog;
992
993         int top, bottom, playercolor;
994
995         top = atoi(Cmd_Argv(cmd, 1));
996         bottom = atoi(Cmd_Argv(cmd, 2));
997
998         top &= 15;
999         bottom &= 15;
1000
1001         playercolor = top*16 + bottom;
1002
1003         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1004         {
1005                 Con_DPrint("Calling SV_ChangeTeam\n");
1006                 prog->globals.fp[OFS_PARM0] = playercolor;
1007                 PRVM_serverglobalfloat(time) = sv.time;
1008                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1009                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1010         }
1011         else
1012         {
1013                 if (host_client->edict)
1014                 {
1015                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1016                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1017                 }
1018                 host_client->colors = playercolor;
1019                 if (host_client->old_colors != host_client->colors)
1020                 {
1021                         host_client->old_colors = host_client->colors;
1022                         // send notification to all clients
1023                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1024                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1025                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1026                 }
1027         }
1028 }
1029
1030 /*
1031 ==================
1032 SV_Kick_f
1033
1034 Kicks a user off of the server
1035 ==================
1036 */
1037 static void SV_Kick_f(cmd_state_t *cmd)
1038 {
1039         const char *who;
1040         const char *message = NULL;
1041         client_t *save;
1042         int i;
1043         qbool byNumber = false;
1044
1045         if (!sv.active)
1046                 return;
1047
1048         save = host_client;
1049
1050         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1051         {
1052                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1053                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1054                         return;
1055                 byNumber = true;
1056         }
1057         else
1058         {
1059                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1060                 {
1061                         if (!host_client->active)
1062                                 continue;
1063                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1064                                 break;
1065                 }
1066         }
1067
1068         if (i < svs.maxclients)
1069         {
1070                 if (cmd->source == src_local)
1071                 {
1072                         if(!host_isclient.integer)
1073                                 who = "Console";
1074                         else
1075                                 who = cl_name.string;
1076                 }
1077                 else
1078                         who = save->name;
1079
1080                 // can't kick yourself!
1081                 if (host_client == save)
1082                         return;
1083
1084                 if (Cmd_Argc(cmd) > 2)
1085                 {
1086                         message = Cmd_Args(cmd);
1087                         COM_ParseToken_Simple(&message, false, false, true);
1088                         if (byNumber)
1089                         {
1090                                 message++;                                                      // skip the #
1091                                 while (*message == ' ')                         // skip white space
1092                                         message++;
1093                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
1094                         }
1095                         while (*message && *message == ' ')
1096                                 message++;
1097                 }
1098                 if (message)
1099                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1100                 else
1101                         SV_ClientPrintf("Kicked by %s\n", who);
1102                 SV_DropClient (false); // kicked
1103         }
1104
1105         host_client = save;
1106 }
1107
1108 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1109 {
1110         int n;
1111
1112         if (Cmd_Argc(cmd) != 2)
1113         {
1114                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1115                 return;
1116         }
1117
1118         if (sv.active)
1119         {
1120                 Con_Print("maxplayers can not be changed while a server is running.\n");
1121                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1122         }
1123
1124         n = atoi(Cmd_Argv(cmd, 1));
1125         n = bound(1, n, MAX_SCOREBOARD);
1126         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1127
1128         svs.maxclients_next = n;
1129         if (n == 1)
1130                 Cvar_Set (&cvars_all, "deathmatch", "0");
1131         else
1132                 Cvar_Set (&cvars_all, "deathmatch", "1");
1133 }
1134
1135 /*
1136 ======================
1137 SV_Playermodel_f
1138 ======================
1139 */
1140 // the old playermodel in cl_main has been renamed to __cl_playermodel
1141 static void SV_Playermodel_f(cmd_state_t *cmd)
1142 {
1143         prvm_prog_t *prog = SVVM_prog;
1144         int i, j;
1145         char newPath[sizeof(host_client->playermodel)];
1146
1147         if (Cmd_Argc (cmd) == 1)
1148                 return;
1149
1150         if (Cmd_Argc (cmd) == 2)
1151                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1152         else
1153                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1154
1155         for (i = 0, j = 0;newPath[i];i++)
1156                 if (newPath[i] != '\r' && newPath[i] != '\n')
1157                         newPath[j++] = newPath[i];
1158         newPath[j] = 0;
1159
1160         /*
1161         if (host.realtime < host_client->nametime)
1162         {
1163                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1164                 return;
1165         }
1166
1167         host_client->nametime = host.realtime + 5;
1168         */
1169
1170         // point the string back at updateclient->name to keep it safe
1171         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1172         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1173         if (strcmp(host_client->old_model, host_client->playermodel))
1174         {
1175                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1176                 /*// send notification to all clients
1177                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1178                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1179                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1180         }
1181 }
1182
1183 /*
1184 ======================
1185 SV_Playerskin_f
1186 ======================
1187 */
1188 static void SV_Playerskin_f(cmd_state_t *cmd)
1189 {
1190         prvm_prog_t *prog = SVVM_prog;
1191         int i, j;
1192         char newPath[sizeof(host_client->playerskin)];
1193
1194         if (Cmd_Argc (cmd) == 1)
1195                 return;
1196
1197         if (Cmd_Argc (cmd) == 2)
1198                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1199         else
1200                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1201
1202         for (i = 0, j = 0;newPath[i];i++)
1203                 if (newPath[i] != '\r' && newPath[i] != '\n')
1204                         newPath[j++] = newPath[i];
1205         newPath[j] = 0;
1206
1207         /*
1208         if (host.realtime < host_client->nametime)
1209         {
1210                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1211                 return;
1212         }
1213
1214         host_client->nametime = host.realtime + 5;
1215         */
1216
1217         // point the string back at updateclient->name to keep it safe
1218         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1219         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1220         if (strcmp(host_client->old_skin, host_client->playerskin))
1221         {
1222                 //if (host_client->begun)
1223                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1224                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1225                 /*// send notification to all clients
1226                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1227                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1228                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1229         }
1230 }
1231
1232 /*
1233 ======================
1234 SV_PModel_f
1235 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1236 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1237 ======================
1238 */
1239 static void SV_PModel_f(cmd_state_t *cmd)
1240 {
1241         prvm_prog_t *prog = SVVM_prog;
1242
1243         if (Cmd_Argc (cmd) == 1)
1244                 return;
1245
1246         PRVM_serveredictfloat(host_client->edict, pmodel) = atoi(Cmd_Argv(cmd, 1));
1247 }
1248
1249 /*
1250 ===============================================================================
1251
1252 DEBUGGING TOOLS
1253
1254 ===============================================================================
1255 */
1256
1257 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
1258 {
1259         int             i;
1260         prvm_edict_t    *e;
1261
1262         for (i=0 ; i<prog->num_edicts ; i++)
1263         {
1264                 e = PRVM_EDICT_NUM(i);
1265                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1266                         return e;
1267         }
1268         Con_Print("No viewthing on map\n");
1269         return NULL;
1270 }
1271
1272 /*
1273 ==================
1274 SV_Viewmodel_f
1275 ==================
1276 */
1277 static void SV_Viewmodel_f(cmd_state_t *cmd)
1278 {
1279         prvm_prog_t *prog = SVVM_prog;
1280         prvm_edict_t    *e;
1281         dp_model_t      *m;
1282
1283         if (!sv.active)
1284                 return;
1285
1286         e = FindViewthing(prog);
1287         if (e)
1288         {
1289                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1290                 if (m && m->loaded && m->Draw)
1291                 {
1292                         PRVM_serveredictfloat(e, frame) = 0;
1293                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1294                 }
1295                 else
1296                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1297         }
1298 }
1299
1300 /*
1301 ==================
1302 SV_Viewframe_f
1303 ==================
1304 */
1305 static void SV_Viewframe_f(cmd_state_t *cmd)
1306 {
1307         prvm_prog_t *prog = SVVM_prog;
1308         prvm_edict_t    *e;
1309         int             f;
1310         dp_model_t      *m;
1311
1312         if (!sv.active)
1313                 return;
1314
1315         e = FindViewthing(prog);
1316         if (e)
1317         {
1318                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1319
1320                 f = atoi(Cmd_Argv(cmd, 1));
1321                 if (f >= m->numframes)
1322                         f = m->numframes-1;
1323
1324                 PRVM_serveredictfloat(e, frame) = f;
1325         }
1326 }
1327
1328 static void PrintFrameName (dp_model_t *m, int frame)
1329 {
1330         if (m->animscenes)
1331                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1332         else
1333                 Con_Printf("frame %i\n", frame);
1334 }
1335
1336 /*
1337 ==================
1338 SV_Viewnext_f
1339 ==================
1340 */
1341 static void SV_Viewnext_f(cmd_state_t *cmd)
1342 {
1343         prvm_prog_t *prog = SVVM_prog;
1344         prvm_edict_t    *e;
1345         dp_model_t      *m;
1346
1347         if (!sv.active)
1348                 return;
1349
1350         e = FindViewthing(prog);
1351         if (e)
1352         {
1353                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1354
1355                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1356                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1357                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1358
1359                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1360         }
1361 }
1362
1363 /*
1364 ==================
1365 SV_Viewprev_f
1366 ==================
1367 */
1368 static void SV_Viewprev_f(cmd_state_t *cmd)
1369 {
1370         prvm_prog_t *prog = SVVM_prog;
1371         prvm_edict_t    *e;
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                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1383                 if (PRVM_serveredictfloat(e, frame) < 0)
1384                         PRVM_serveredictfloat(e, frame) = 0;
1385
1386                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1387         }
1388 }
1389
1390 static void SV_SendCvar_f(cmd_state_t *cmd)
1391 {
1392         int i;  
1393         const char *cvarname;
1394         client_t *old;
1395         
1396         if(Cmd_Argc(cmd) != 2)
1397                 return;
1398
1399         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
1400                 return;
1401
1402         cvarname = Cmd_Argv(cmd, 1);
1403
1404         old = host_client;
1405         if(host_isclient.integer)
1406                 i = 1;
1407         else
1408                 i = 0;
1409         for(;i<svs.maxclients;i++)
1410                 if(svs.clients[i].active && svs.clients[i].netconnection)
1411                 {
1412                         host_client = &svs.clients[i];
1413                         SV_ClientCommands("sendcvar %s\n", cvarname);
1414                 }
1415         host_client = old;
1416 }
1417
1418 static void SV_Ent_Create_f(cmd_state_t *cmd)
1419 {
1420         prvm_prog_t *prog = SVVM_prog;
1421         prvm_edict_t *ed;
1422         mdef_t *key;
1423         int i;
1424         qbool haveorigin;
1425
1426         qbool expectval = false;
1427         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1428
1429         if(!Cmd_Argc(cmd))
1430                 return;
1431
1432         ed = PRVM_ED_Alloc(SVVM_prog);
1433
1434         PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "classname"), Cmd_Argv(cmd, 1), false);
1435
1436         // Spawn where the player is aiming. We need a view matrix first.
1437         if(cmd->source == src_client)
1438         {
1439                 vec3_t org, temp, dest;
1440                 matrix4x4_t view;
1441                 trace_t trace;
1442                 char buf[128];
1443
1444                 SV_GetEntityMatrix(prog, host_client->edict, &view, true);
1445
1446                 Matrix4x4_OriginFromMatrix(&view, org);
1447                 VectorSet(temp, 65536, 0, 0);
1448                 Matrix4x4_Transform(&view, temp, dest);         
1449
1450                 trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value);
1451
1452                 dpsnprintf(buf, sizeof(buf), "%g %g %g", trace.endpos[0], trace.endpos[1], trace.endpos[2]);
1453                 PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "origin"), buf, false);
1454
1455                 haveorigin = true;
1456         }
1457         // Or spawn at a specified origin.
1458         else
1459         {
1460                 print = Con_Printf;
1461                 haveorigin = false;
1462         }
1463
1464         // Allow more than one key/value pair by cycling between expecting either one.
1465         for(i = 2; i < Cmd_Argc(cmd); i++)
1466         {
1467                 if(!expectval)
1468                 {
1469                         if(!(key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, i))))
1470                         {
1471                                 print("Key %s not found!\n", Cmd_Argv(cmd, i));
1472                                 PRVM_ED_Free(prog, ed);
1473                                 return;
1474                         }
1475
1476                         /*
1477                          * This is mostly for dedicated server console, but if the
1478                          * player gave a custom origin, we can ignore the traceline.
1479                          */
1480                         if(!strcmp(Cmd_Argv(cmd, i), "origin"))
1481                                 haveorigin = true;
1482
1483                         expectval = true;
1484                 }
1485                 else
1486                 {
1487                         PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, i), false);
1488                         expectval = false;
1489                 }
1490         }
1491
1492         if(!haveorigin)
1493         {
1494                 print("Missing origin\n");
1495                 PRVM_ED_Free(prog, ed);
1496                 return;
1497         }
1498
1499         // Spawn it
1500         PRVM_ED_CallPrespawnFunction(prog, ed);
1501         
1502         if(!PRVM_ED_CallSpawnFunction(prog, ed, NULL, NULL))
1503         {
1504                 print("Could not spawn a \"%s\". No such entity or it has no spawn function\n", Cmd_Argv(cmd, 1));
1505                 if(cmd->source == src_client)
1506                         Con_Printf("%s tried to spawn a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
1507                 // CallSpawnFunction already freed the edict for us.
1508                 return;
1509         }
1510
1511         PRVM_ED_CallPostspawnFunction(prog, ed);        
1512
1513         // Make it appear in the world
1514         SV_LinkEdict(ed);
1515
1516         if(cmd->source == src_client)
1517                 Con_Printf("%s spawned a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
1518 }
1519
1520 static void SV_Ent_Remove_f(cmd_state_t *cmd)
1521 {
1522         prvm_prog_t *prog = SVVM_prog;
1523         prvm_edict_t *ed;
1524         int i, ednum;
1525         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1526
1527         if(!Cmd_Argc(cmd))
1528                 return;
1529
1530         // Allow specifying edict by number
1531         if(Cmd_Argc(cmd) > 1 && Cmd_Argv(cmd, 1))
1532         {
1533                 ednum = atoi(Cmd_Argv(cmd, 1));
1534                 if(!ednum)
1535                 {
1536                         print("Cannot remove the world\n");
1537                         return;
1538                 }
1539         }
1540         // Or trace a line if it's a client who didn't specify one.
1541         else if(cmd->source == src_client)
1542         {
1543                 vec3_t org, temp, dest;
1544                 matrix4x4_t view;
1545                 trace_t trace;
1546
1547                 SV_GetEntityMatrix(prog, host_client->edict, &view, true);
1548
1549                 Matrix4x4_OriginFromMatrix(&view, org);
1550                 VectorSet(temp, 65536, 0, 0);
1551                 Matrix4x4_Transform(&view, temp, dest);         
1552
1553                 trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, 0, 0, collision_extendmovelength.value);
1554                 
1555                 if(trace.ent)
1556                         ednum = (int)PRVM_EDICT_TO_PROG(trace.ent);
1557                 if(!trace.ent || !ednum)
1558                         // Don't remove the world, but don't annoy players with a print if they miss
1559                         return;
1560         }
1561         else
1562         {
1563                 // Only a dedicated server console should be able to reach this.
1564                 print("No edict given\n");
1565                 return;
1566         }
1567
1568         ed = PRVM_EDICT_NUM(ednum);
1569
1570         if(ed)
1571         {
1572                 // Skip players
1573                 for (i = 0; i < svs.maxclients; i++)
1574                 {
1575                         if(ed == svs.clients[i].edict)
1576                                 return;
1577                 }
1578
1579                 if(!ed->priv.required->free)
1580                 {
1581                         print("Removed a \"%s\"\n", PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)));
1582                         PRVM_ED_ClearEdict(prog, ed);
1583                         PRVM_ED_Free(prog, ed);
1584                 }
1585         }
1586         else
1587         {
1588                 // This should only be reachable if an invalid edict number was given
1589                 print("No such entity\n");
1590                 return;
1591         }
1592 }
1593
1594 static void SV_Ent_Remove_All_f(cmd_state_t *cmd)
1595 {
1596         prvm_prog_t *prog = SVVM_prog;
1597         int i, rmcount;
1598         prvm_edict_t *ed;
1599         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1600
1601         for (i = 0, rmcount = 0, ed = PRVM_EDICT_NUM(i); i < prog->num_edicts; i++, ed = PRVM_NEXT_EDICT(ed))
1602         {
1603                 if(!ed->priv.required->free && !strcmp(PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)), Cmd_Argv(cmd, 1)))
1604                 {
1605                         if(!i)
1606                         {
1607                                 print("Cannot remove the world\n");
1608                                 return;
1609                         }
1610                         PRVM_ED_ClearEdict(prog, ed);
1611                         PRVM_ED_Free(prog, ed);
1612                         rmcount++;
1613                 }
1614         }
1615
1616         if(!rmcount)
1617                 print("No \"%s\" found\n", Cmd_Argv(cmd, 1));
1618         else
1619                 print("Removed %i of \"%s\"\n", rmcount, Cmd_Argv(cmd, 1));
1620 }
1621
1622 void SV_InitOperatorCommands(void)
1623 {
1624         Cvar_RegisterVariable(&sv_cheats);
1625         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1626         Cvar_RegisterVariable(&sv_adminnick);
1627         Cvar_RegisterVariable(&sv_status_privacy);
1628         Cvar_RegisterVariable(&sv_status_show_qcstatus);
1629         Cvar_RegisterVariable(&sv_namechangetimer);
1630         
1631         Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1632         Cmd_AddCommand(CF_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1633         Cmd_AddCommand(CF_SHARED, "restart", SV_Restart_f, "restart current level");
1634         Cmd_AddCommand(CF_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1635         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1636         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1637         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1638         Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1639         Cmd_AddCommand(CF_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1640         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1641         Cmd_AddCommand(CF_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1642         Cmd_AddCommand(CF_SHARED, "save", SV_Savegame_f, "save the game to a file");
1643         Cmd_AddCommand(CF_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1644         Cmd_AddCommand(CF_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1645         Cmd_AddCommand(CF_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1646         Cmd_AddCommand(CF_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1647         Cmd_AddCommand(CF_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1648         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");
1649
1650         // 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)
1651         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1652         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)");
1653         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)");
1654         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)");
1655
1656         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1657         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1658         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1659         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1660         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1661         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1662         
1663         Cmd_AddCommand(CF_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1664         Cmd_AddCommand(CF_USERINFO, "name", SV_Name_f, "change your player name");
1665         Cmd_AddCommand(CF_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1666         Cmd_AddCommand(CF_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
1667         Cmd_AddCommand(CF_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
1668         Cmd_AddCommand(CF_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
1669         Cmd_AddCommand(CF_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");
1670
1671         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.");
1672         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_remove_all", SV_Ent_Remove_All_f, "Removes all entities of the specified classname");
1673         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");
1674 }