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