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