]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_ccmds.c
csqc: Implement builtin #177 "localsound"
[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(false, NULL);
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", SV_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         char reason[512];
1030         client_t *save;
1031         int i;
1032         qbool byNumber = false;
1033
1034         if (!sv.active)
1035                 return;
1036
1037         save = host_client;
1038
1039         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1040         {
1041                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1042                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1043                         return;
1044                 byNumber = true;
1045         }
1046         else
1047         {
1048                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1049                 {
1050                         if (!host_client->active)
1051                                 continue;
1052                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1053                                 break;
1054                 }
1055         }
1056
1057         if (i < svs.maxclients)
1058         {
1059                 if (cmd->source == src_local)
1060                 {
1061                         if(!host_isclient.integer)
1062                                 who = "Console";
1063                         else
1064                                 who = cl_name.string;
1065                 }
1066                 else
1067                         who = save->name;
1068
1069                 // can't kick yourself!
1070                 if (host_client == save)
1071                         return;
1072
1073                 if (Cmd_Argc(cmd) > 2)
1074                 {
1075                         message = Cmd_Args(cmd);
1076                         COM_ParseToken_Simple(&message, false, false, true);
1077                         if (byNumber)
1078                         {
1079                                 message++;                                                      // skip the #
1080                                 while (*message == ' ')                         // skip white space
1081                                         message++;
1082                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
1083                         }
1084                         while (*message && *message == ' ')
1085                                 message++;
1086                 }
1087                 if (message)
1088                         SV_DropClient (false, va(reason, sizeof(reason), "Kicked by %s: %s", who, message)); // kicked
1089                         //SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1090                 else
1091                         //SV_ClientPrintf("Kicked by %s\n", who);
1092                         SV_DropClient (false, va(reason, sizeof(reason), "Kicked by %s", who)); // kicked
1093         }
1094
1095         host_client = save;
1096 }
1097
1098 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1099 {
1100         int n;
1101
1102         if (Cmd_Argc(cmd) != 2)
1103         {
1104                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1105                 return;
1106         }
1107
1108         if (sv.active)
1109         {
1110                 Con_Print("maxplayers can not be changed while a server is running.\n");
1111                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1112         }
1113
1114         n = atoi(Cmd_Argv(cmd, 1));
1115         n = bound(1, n, MAX_SCOREBOARD);
1116         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1117
1118         svs.maxclients_next = n;
1119         if (n == 1)
1120                 Cvar_Set (&cvars_all, "deathmatch", "0");
1121         else
1122                 Cvar_Set (&cvars_all, "deathmatch", "1");
1123 }
1124
1125 /*
1126 ======================
1127 SV_Playermodel_f
1128 ======================
1129 */
1130 // the old playermodel in cl_main has been renamed to __cl_playermodel
1131 static void SV_Playermodel_f(cmd_state_t *cmd)
1132 {
1133         prvm_prog_t *prog = SVVM_prog;
1134         int i, j;
1135         char newPath[sizeof(host_client->playermodel)];
1136
1137         if (Cmd_Argc (cmd) == 1)
1138                 return;
1139
1140         if (Cmd_Argc (cmd) == 2)
1141                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1142         else
1143                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1144
1145         for (i = 0, j = 0;newPath[i];i++)
1146                 if (newPath[i] != '\r' && newPath[i] != '\n')
1147                         newPath[j++] = newPath[i];
1148         newPath[j] = 0;
1149
1150         /*
1151         if (host.realtime < host_client->nametime)
1152         {
1153                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1154                 return;
1155         }
1156
1157         host_client->nametime = host.realtime + 5;
1158         */
1159
1160         // point the string back at updateclient->name to keep it safe
1161         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1162         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1163         if (strcmp(host_client->old_model, host_client->playermodel))
1164         {
1165                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1166                 /*// send notification to all clients
1167                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1168                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1169                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1170         }
1171 }
1172
1173 /*
1174 ======================
1175 SV_Playerskin_f
1176 ======================
1177 */
1178 static void SV_Playerskin_f(cmd_state_t *cmd)
1179 {
1180         prvm_prog_t *prog = SVVM_prog;
1181         int i, j;
1182         char newPath[sizeof(host_client->playerskin)];
1183
1184         if (Cmd_Argc (cmd) == 1)
1185                 return;
1186
1187         if (Cmd_Argc (cmd) == 2)
1188                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1189         else
1190                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1191
1192         for (i = 0, j = 0;newPath[i];i++)
1193                 if (newPath[i] != '\r' && newPath[i] != '\n')
1194                         newPath[j++] = newPath[i];
1195         newPath[j] = 0;
1196
1197         /*
1198         if (host.realtime < host_client->nametime)
1199         {
1200                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1201                 return;
1202         }
1203
1204         host_client->nametime = host.realtime + 5;
1205         */
1206
1207         // point the string back at updateclient->name to keep it safe
1208         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1209         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1210         if (strcmp(host_client->old_skin, host_client->playerskin))
1211         {
1212                 //if (host_client->begun)
1213                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1214                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1215                 /*// send notification to all clients
1216                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1217                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1218                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1219         }
1220 }
1221
1222 /*
1223 ======================
1224 SV_PModel_f
1225 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1226 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1227 ======================
1228 */
1229 static void SV_PModel_f(cmd_state_t *cmd)
1230 {
1231         prvm_prog_t *prog = SVVM_prog;
1232
1233         if (Cmd_Argc (cmd) == 1)
1234                 return;
1235
1236         PRVM_serveredictfloat(host_client->edict, pmodel) = atoi(Cmd_Argv(cmd, 1));
1237 }
1238
1239 /*
1240 ===============================================================================
1241
1242 DEBUGGING TOOLS
1243
1244 ===============================================================================
1245 */
1246
1247 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
1248 {
1249         int             i;
1250         prvm_edict_t    *e;
1251
1252         for (i=0 ; i<prog->num_edicts ; i++)
1253         {
1254                 e = PRVM_EDICT_NUM(i);
1255                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1256                         return e;
1257         }
1258         Con_Print("No viewthing on map\n");
1259         return NULL;
1260 }
1261
1262 /*
1263 ==================
1264 SV_Viewmodel_f
1265 ==================
1266 */
1267 static void SV_Viewmodel_f(cmd_state_t *cmd)
1268 {
1269         prvm_prog_t *prog = SVVM_prog;
1270         prvm_edict_t    *e;
1271         model_t *m;
1272
1273         if (!sv.active)
1274                 return;
1275
1276         e = FindViewthing(prog);
1277         if (e)
1278         {
1279                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1280                 if (m && m->loaded && m->Draw)
1281                 {
1282                         PRVM_serveredictfloat(e, frame) = 0;
1283                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1284                 }
1285                 else
1286                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1287         }
1288 }
1289
1290 /*
1291 ==================
1292 SV_Viewframe_f
1293 ==================
1294 */
1295 static void SV_Viewframe_f(cmd_state_t *cmd)
1296 {
1297         prvm_prog_t *prog = SVVM_prog;
1298         prvm_edict_t    *e;
1299         int             f;
1300         model_t *m;
1301
1302         if (!sv.active)
1303                 return;
1304
1305         e = FindViewthing(prog);
1306         if (e)
1307         {
1308                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1309
1310                 f = atoi(Cmd_Argv(cmd, 1));
1311                 if (f >= m->numframes)
1312                         f = m->numframes-1;
1313
1314                 PRVM_serveredictfloat(e, frame) = f;
1315         }
1316 }
1317
1318 static void PrintFrameName (model_t *m, int frame)
1319 {
1320         if (m->animscenes)
1321                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1322         else
1323                 Con_Printf("frame %i\n", frame);
1324 }
1325
1326 /*
1327 ==================
1328 SV_Viewnext_f
1329 ==================
1330 */
1331 static void SV_Viewnext_f(cmd_state_t *cmd)
1332 {
1333         prvm_prog_t *prog = SVVM_prog;
1334         prvm_edict_t    *e;
1335         model_t *m;
1336
1337         if (!sv.active)
1338                 return;
1339
1340         e = FindViewthing(prog);
1341         if (e)
1342         {
1343                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1344
1345                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1346                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1347                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1348
1349                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1350         }
1351 }
1352
1353 /*
1354 ==================
1355 SV_Viewprev_f
1356 ==================
1357 */
1358 static void SV_Viewprev_f(cmd_state_t *cmd)
1359 {
1360         prvm_prog_t *prog = SVVM_prog;
1361         prvm_edict_t    *e;
1362         model_t *m;
1363
1364         if (!sv.active)
1365                 return;
1366
1367         e = FindViewthing(prog);
1368         if (e)
1369         {
1370                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1371
1372                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1373                 if (PRVM_serveredictfloat(e, frame) < 0)
1374                         PRVM_serveredictfloat(e, frame) = 0;
1375
1376                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1377         }
1378 }
1379
1380 static void SV_SendCvar_f(cmd_state_t *cmd)
1381 {
1382         int i;  
1383         const char *cvarname;
1384         client_t *old;
1385         
1386         if(Cmd_Argc(cmd) != 2)
1387                 return;
1388
1389         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
1390                 return;
1391
1392         cvarname = Cmd_Argv(cmd, 1);
1393
1394         old = host_client;
1395         if(host_isclient.integer)
1396                 i = 1;
1397         else
1398                 i = 0;
1399         for(;i<svs.maxclients;i++)
1400                 if(svs.clients[i].active && svs.clients[i].netconnection)
1401                 {
1402                         host_client = &svs.clients[i];
1403                         SV_ClientCommands("sendcvar %s\n", cvarname);
1404                 }
1405         host_client = old;
1406 }
1407
1408 static void SV_Ent_Create_f(cmd_state_t *cmd)
1409 {
1410         prvm_prog_t *prog = SVVM_prog;
1411         prvm_edict_t *ed;
1412         mdef_t *key;
1413         int i;
1414         qbool haveorigin;
1415
1416         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1417
1418         if(!Cmd_Argc(cmd))
1419                 return;
1420
1421         ed = PRVM_ED_Alloc(SVVM_prog);
1422
1423         PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "classname"), Cmd_Argv(cmd, 1), false);
1424
1425         // Spawn where the player is aiming. We need a view matrix first.
1426         if(cmd->source == src_client)
1427         {
1428                 vec3_t org, temp, dest;
1429                 matrix4x4_t view;
1430                 trace_t trace;
1431                 char buf[128];
1432
1433                 SV_GetEntityMatrix(prog, host_client->edict, &view, true);
1434
1435                 Matrix4x4_OriginFromMatrix(&view, org);
1436                 VectorSet(temp, 65536, 0, 0);
1437                 Matrix4x4_Transform(&view, temp, dest);         
1438
1439                 trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value);
1440
1441                 dpsnprintf(buf, sizeof(buf), "%g %g %g", trace.endpos[0], trace.endpos[1], trace.endpos[2]);
1442                 PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "origin"), buf, false);
1443
1444                 haveorigin = true;
1445         }
1446         // Or spawn at a specified origin.
1447         else
1448         {
1449                 print = Con_Printf;
1450                 haveorigin = false;
1451         }
1452
1453         // Allow more than one key/value pair by cycling between expecting either one.
1454         for(i = 2; i < Cmd_Argc(cmd); i += 2)
1455         {
1456                 if(!(key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, i))))
1457                 {
1458                         print("Key %s not found!\n", Cmd_Argv(cmd, i));
1459                         PRVM_ED_Free(prog, ed);
1460                         return;
1461                 }
1462
1463                 /*
1464                  * This is mostly for dedicated server console, but if the
1465                  * player gave a custom origin, we can ignore the traceline.
1466                  */
1467                 if(!strcmp(Cmd_Argv(cmd, i), "origin"))
1468                         haveorigin = true;
1469
1470                 if (i + 1 < Cmd_Argc(cmd))
1471                         PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, i+1), false);
1472         }
1473
1474         if(!haveorigin)
1475         {
1476                 print("Missing origin\n");
1477                 PRVM_ED_Free(prog, ed);
1478                 return;
1479         }
1480
1481         // Spawn it
1482         PRVM_ED_CallPrespawnFunction(prog, ed);
1483         
1484         if(!PRVM_ED_CallSpawnFunction(prog, ed, NULL, NULL))
1485         {
1486                 print("Could not spawn a \"%s\". No such entity or it has no spawn function\n", Cmd_Argv(cmd, 1));
1487                 if(cmd->source == src_client)
1488                         Con_Printf("%s tried to spawn a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
1489                 // CallSpawnFunction already freed the edict for us.
1490                 return;
1491         }
1492
1493         PRVM_ED_CallPostspawnFunction(prog, ed);        
1494
1495         // Make it appear in the world
1496         SV_LinkEdict(ed);
1497
1498         if(cmd->source == src_client)
1499                 Con_Printf("%s spawned a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
1500 }
1501
1502 static void SV_Ent_Remove_f(cmd_state_t *cmd)
1503 {
1504         prvm_prog_t *prog = SVVM_prog;
1505         prvm_edict_t *ed;
1506         int i, ednum = 0;
1507         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1508
1509         if(!Cmd_Argc(cmd))
1510                 return;
1511
1512         // Allow specifying edict by number
1513         if(Cmd_Argc(cmd) > 1 && Cmd_Argv(cmd, 1))
1514         {
1515                 ednum = atoi(Cmd_Argv(cmd, 1));
1516                 if(!ednum)
1517                 {
1518                         print("Cannot remove the world\n");
1519                         return;
1520                 }
1521         }
1522         // Or trace a line if it's a client who didn't specify one.
1523         else if(cmd->source == src_client)
1524         {
1525                 vec3_t org, temp, dest;
1526                 matrix4x4_t view;
1527                 trace_t trace;
1528
1529                 SV_GetEntityMatrix(prog, host_client->edict, &view, true);
1530
1531                 Matrix4x4_OriginFromMatrix(&view, org);
1532                 VectorSet(temp, 65536, 0, 0);
1533                 Matrix4x4_Transform(&view, temp, dest);         
1534
1535                 trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, 0, 0, collision_extendmovelength.value);
1536                 
1537                 if(trace.ent)
1538                         ednum = (int)PRVM_EDICT_TO_PROG(trace.ent);
1539                 if(!trace.ent || !ednum)
1540                         // Don't remove the world, but don't annoy players with a print if they miss
1541                         return;
1542         }
1543         else
1544         {
1545                 // Only a dedicated server console should be able to reach this.
1546                 print("No edict given\n");
1547                 return;
1548         }
1549
1550         ed = PRVM_EDICT_NUM(ednum);
1551
1552         if(ed)
1553         {
1554                 // Skip players
1555                 for (i = 0; i < svs.maxclients; i++)
1556                 {
1557                         if(ed == svs.clients[i].edict)
1558                                 return;
1559                 }
1560
1561                 if(!ed->free)
1562                 {
1563                         print("Removed a \"%s\"\n", PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)));
1564                         PRVM_ED_ClearEdict(prog, ed);
1565                         PRVM_ED_Free(prog, ed);
1566                 }
1567         }
1568         else
1569         {
1570                 // This should only be reachable if an invalid edict number was given
1571                 print("No such entity\n");
1572                 return;
1573         }
1574 }
1575
1576 static void SV_Ent_Remove_All_f(cmd_state_t *cmd)
1577 {
1578         prvm_prog_t *prog = SVVM_prog;
1579         int i, rmcount;
1580         prvm_edict_t *ed;
1581         void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
1582
1583         for (i = 0, rmcount = 0, ed = PRVM_EDICT_NUM(i); i < prog->num_edicts; i++, ed = PRVM_NEXT_EDICT(ed))
1584         {
1585                 if(!ed->free && !strcmp(PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)), Cmd_Argv(cmd, 1)))
1586                 {
1587                         if(!i)
1588                         {
1589                                 print("Cannot remove the world\n");
1590                                 return;
1591                         }
1592                         PRVM_ED_ClearEdict(prog, ed);
1593                         PRVM_ED_Free(prog, ed);
1594                         rmcount++;
1595                 }
1596         }
1597
1598         if(!rmcount)
1599                 print("No \"%s\" found\n", Cmd_Argv(cmd, 1));
1600         else
1601                 print("Removed %i of \"%s\"\n", rmcount, Cmd_Argv(cmd, 1));
1602 }
1603
1604 void SV_InitOperatorCommands(void)
1605 {
1606         Cvar_RegisterVariable(&sv_cheats);
1607         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1608         Cvar_RegisterVariable(&sv_adminnick);
1609         Cvar_RegisterVariable(&sv_status_privacy);
1610         Cvar_RegisterVariable(&sv_status_show_qcstatus);
1611         Cvar_RegisterVariable(&sv_namechangetimer);
1612         
1613         Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1614         Cmd_AddCommand(CF_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1615         Cmd_AddCommand(CF_SHARED, "restart", SV_Restart_f, "restart current level");
1616         Cmd_AddCommand(CF_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1617         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1618         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1619         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1620         Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1621         Cmd_AddCommand(CF_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1622         Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1623         Cmd_AddCommand(CF_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1624         Cmd_AddCommand(CF_SHARED, "save", SV_Savegame_f, "save the game to a file");
1625         Cmd_AddCommand(CF_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1626         Cmd_AddCommand(CF_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1627         Cmd_AddCommand(CF_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1628         Cmd_AddCommand(CF_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1629         Cmd_AddCommand(CF_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1630         host.hook.SV_SendCvar = SV_SendCvar_f;
1631
1632         // commands that do not have automatic forwarding from cmd_local, 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)
1633         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1634         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)");
1635         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)");
1636         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)");
1637
1638         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1639         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1640         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1641         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1642         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1643         Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1644         
1645         Cmd_AddCommand(CF_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1646         Cmd_AddCommand(CF_USERINFO, "name", SV_Name_f, "change your player name");
1647         Cmd_AddCommand(CF_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1648         Cmd_AddCommand(CF_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
1649         Cmd_AddCommand(CF_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
1650         Cmd_AddCommand(CF_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
1651         Cmd_AddCommand(CF_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");
1652
1653         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.");
1654         Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_remove_all", SV_Ent_Remove_All_f, "Removes all entities of the specified classname");
1655         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");
1656 }