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