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