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