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