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