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