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