]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sv_ccmds.c
(Round 1) Break up host_cmd.c
[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 <server.h>
23
24 int current_skill;
25 cvar_t sv_cheats = {CVAR_SERVER | CVAR_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
26 cvar_t sv_adminnick = {CVAR_SERVER | CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
27 cvar_t sv_status_privacy = {CVAR_SERVER | CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
28 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."};
29 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"};
30
31 /*
32 ===============================================================================
33
34 SERVER TRANSITIONS
35
36 ===============================================================================
37 */
38
39 /*
40 ======================
41 SV_Map_f
42
43 handle a
44 map <servername>
45 command from the console.  Active clients are kicked off.
46 ======================
47 */
48 static void SV_Map_f(cmd_state_t *cmd)
49 {
50         char level[MAX_QPATH];
51
52         if (Cmd_Argc(cmd) != 2)
53         {
54                 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
55                 return;
56         }
57
58         // GAME_DELUXEQUAKE - clear warpmark (used by QC)
59         if (gamemode == GAME_DELUXEQUAKE)
60                 Cvar_Set(&cvars_all, "warpmark", "");
61
62         cls.demonum = -1;               // stop demo loop in case this fails
63
64         CL_Disconnect ();
65         SV_Shutdown();
66
67         if(svs.maxclients != svs.maxclients_next)
68         {
69                 svs.maxclients = svs.maxclients_next;
70                 if (svs.clients)
71                         Mem_Free(svs.clients);
72                 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
73         }
74
75 #ifdef CONFIG_MENU
76         // remove menu
77         if (key_dest == key_menu || key_dest == key_menu_grabbed)
78                 MR_ToggleMenu(0);
79 #endif
80         key_dest = key_game;
81
82         svs.serverflags = 0;                    // haven't completed an episode yet
83         strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
84         SV_SpawnServer(level);
85         if (sv.active && cls.state == ca_disconnected)
86                 CL_EstablishConnection("local:1", -2);
87 }
88
89 /*
90 ==================
91 SV_Changelevel_f
92
93 Goes to a new map, taking all clients along
94 ==================
95 */
96 static void SV_Changelevel_f(cmd_state_t *cmd)
97 {
98         char level[MAX_QPATH];
99
100         if (Cmd_Argc(cmd) != 2)
101         {
102                 Con_Print("changelevel <levelname> : continue game on a new level\n");
103                 return;
104         }
105         // HACKHACKHACK
106         if (!sv.active) {
107                 SV_Map_f(cmd);
108                 return;
109         }
110
111 #ifdef CONFIG_MENU
112         // remove menu
113         if (key_dest == key_menu || key_dest == key_menu_grabbed)
114                 MR_ToggleMenu(0);
115 #endif
116         key_dest = key_game;
117
118         SV_SaveSpawnparms ();
119         strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
120         SV_SpawnServer(level);
121         if (sv.active && cls.state == ca_disconnected)
122                 CL_EstablishConnection("local:1", -2);
123 }
124
125 /*
126 ==================
127 SV_Restart_f
128
129 Restarts the current server for a dead player
130 ==================
131 */
132 static void SV_Restart_f(cmd_state_t *cmd)
133 {
134         char mapname[MAX_QPATH];
135
136         if (Cmd_Argc(cmd) != 1)
137         {
138                 Con_Print("restart : restart current level\n");
139                 return;
140         }
141         if (!sv.active)
142         {
143                 Con_Print("Only the server may restart\n");
144                 return;
145         }
146
147 #ifdef CONFIG_MENU
148         // remove menu
149         if (key_dest == key_menu || key_dest == key_menu_grabbed)
150                 MR_ToggleMenu(0);
151 #endif
152         key_dest = key_game;
153
154         strlcpy(mapname, sv.name, sizeof(mapname));
155         SV_SpawnServer(mapname);
156         if (sv.active && cls.state == ca_disconnected)
157                 CL_EstablishConnection("local:1", -2);
158 }
159
160 //===========================================================================
161
162 // Disable cheats if sv_cheats is turned off
163 static void SV_DisableCheats_c(char *value)
164 {
165         prvm_prog_t *prog = SVVM_prog;
166         int i = 0;
167
168         if (value[0] == '0' && !value[1])
169         {
170                 while (svs.clients[i].edict)
171                 {
172                         if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
173                                 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
174                         if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
175                                 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
176                         if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
177                                 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
178                         {
179                                 noclip_anglehack = false;
180                                 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
181                         }
182                         i++;
183                 }
184         }
185 }
186
187 /*
188 ==================
189 SV_God_f
190
191 Sets client to godmode
192 ==================
193 */
194 static void SV_God_f(cmd_state_t *cmd)
195 {
196         prvm_prog_t *prog = SVVM_prog;
197
198         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
199         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
200                 SV_ClientPrint("godmode OFF\n");
201         else
202                 SV_ClientPrint("godmode ON\n");
203 }
204
205 qboolean noclip_anglehack;
206
207 static void SV_Noclip_f(cmd_state_t *cmd)
208 {
209         prvm_prog_t *prog = SVVM_prog;
210
211         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
212         {
213                 noclip_anglehack = true;
214                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
215                 SV_ClientPrint("noclip ON\n");
216         }
217         else
218         {
219                 noclip_anglehack = false;
220                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
221                 SV_ClientPrint("noclip OFF\n");
222         }
223 }
224
225 /*
226 ==================
227 SV_Give_f
228 ==================
229 */
230 static void SV_Give_f(cmd_state_t *cmd)
231 {
232         prvm_prog_t *prog = SVVM_prog;
233         const char *t;
234         int v;
235
236         t = Cmd_Argv(cmd, 1);
237         v = atoi (Cmd_Argv(cmd, 2));
238
239         switch (t[0])
240         {
241         case '0':
242         case '1':
243         case '2':
244         case '3':
245         case '4':
246         case '5':
247         case '6':
248         case '7':
249         case '8':
250         case '9':
251                 // MED 01/04/97 added hipnotic give stuff
252                 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
253                 {
254                         if (t[0] == '6')
255                         {
256                                 if (t[1] == 'a')
257                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
258                                 else
259                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
260                         }
261                         else if (t[0] == '9')
262                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
263                         else if (t[0] == '0')
264                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
265                         else if (t[0] >= '2')
266                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
267                 }
268                 else
269                 {
270                         if (t[0] >= '2')
271                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
272                 }
273                 break;
274
275         case 's':
276                 if (gamemode == GAME_ROGUE)
277                         PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
278
279                 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
280                 break;
281         case 'n':
282                 if (gamemode == GAME_ROGUE)
283                 {
284                         PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
285                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
286                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
287                 }
288                 else
289                 {
290                         PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
291                 }
292                 break;
293         case 'l':
294                 if (gamemode == GAME_ROGUE)
295                 {
296                         PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
297                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
298                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
299                 }
300                 break;
301         case 'r':
302                 if (gamemode == GAME_ROGUE)
303                 {
304                         PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
305                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
306                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
307                 }
308                 else
309                 {
310                         PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
311                 }
312                 break;
313         case 'm':
314                 if (gamemode == GAME_ROGUE)
315                 {
316                         PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
317                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
318                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
319                 }
320                 break;
321         case 'h':
322                 PRVM_serveredictfloat(host_client->edict, health) = v;
323                 break;
324         case 'c':
325                 if (gamemode == GAME_ROGUE)
326                 {
327                         PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
328                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
329                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
330                 }
331                 else
332                 {
333                         PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
334                 }
335                 break;
336         case 'p':
337                 if (gamemode == GAME_ROGUE)
338                 {
339                         PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
340                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
341                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
342                 }
343                 break;
344         }
345 }
346
347 /*
348 ==================
349 SV_Fly_f
350
351 Sets client to flymode
352 ==================
353 */
354 static void SV_Fly_f(cmd_state_t *cmd)
355 {
356         prvm_prog_t *prog = SVVM_prog;
357
358         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
359         {
360                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
361                 SV_ClientPrint("flymode ON\n");
362         }
363         else
364         {
365                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
366                 SV_ClientPrint("flymode OFF\n");
367         }
368 }
369
370 static void SV_Notarget_f(cmd_state_t *cmd)
371 {
372         prvm_prog_t *prog = SVVM_prog;
373
374         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
375         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
376                 SV_ClientPrint("notarget OFF\n");
377         else
378                 SV_ClientPrint("notarget ON\n");
379 }
380
381 /*
382 ==================
383 SV_Kill_f
384 ==================
385 */
386 static void SV_Kill_f(cmd_state_t *cmd)
387 {
388         prvm_prog_t *prog = SVVM_prog;
389         if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
390         {
391                 SV_ClientPrint("Can't suicide -- already dead!\n");
392                 return;
393         }
394
395         PRVM_serverglobalfloat(time) = sv.time;
396         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
397         prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
398 }
399
400 /*
401 ==================
402 SV_Pause_f
403 ==================
404 */
405 static void SV_Pause_f(cmd_state_t *cmd)
406 {
407         void (*print) (const char *fmt, ...);
408         if (cmd->source == src_command)
409         {
410                 // if running a client, try to send over network so the pause is handled by the server
411                 if (cls.state == ca_connected)
412                 {
413                         Cmd_ForwardToServer_f(cmd);
414                         return;
415                 }
416                 print = Con_Printf;
417         }
418         else
419                 print = SV_ClientPrintf;
420
421         if (!pausable.integer)
422         {
423                 if (cmd->source == src_client)
424                 {
425                         if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
426                         {
427                                 print("Pause not allowed.\n");
428                                 return;
429                         }
430                 }
431         }
432         
433         sv.paused ^= 1;
434         if (cmd->source != src_command)
435                 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
436         else if(*(sv_adminnick.string))
437                 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
438         else
439                 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
440         // send notification to all clients
441         MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
442         MSG_WriteByte(&sv.reliable_datagram, sv.paused);
443 }
444
445 static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
446 {
447         prvm_prog_t *prog = SVVM_prog;
448         client_t *save;
449         int j, quoted;
450         const char *p1;
451         char *p2;
452         // LadyHavoc: long say messages
453         char text[1024];
454         qboolean fromServer = false;
455
456         if (cmd->source == src_command)
457         {
458                 if (cls.state == ca_dedicated)
459                 {
460                         fromServer = true;
461                         teamonly = false;
462                 }
463                 else
464                 {
465                         Cmd_ForwardToServer_f(cmd);
466                         return;
467                 }
468         }
469
470         if (Cmd_Argc (cmd) < 2)
471                 return;
472
473         if (!teamplay.integer)
474                 teamonly = false;
475
476         p1 = Cmd_Args(cmd);
477         quoted = false;
478         if (*p1 == '\"')
479         {
480                 quoted = true;
481                 p1++;
482         }
483         // note this uses the chat prefix \001
484         if (!fromServer && !teamonly)
485                 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
486         else if (!fromServer && teamonly)
487                 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
488         else if(*(sv_adminnick.string))
489                 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
490         else
491                 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
492         p2 = text + strlen(text);
493         while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
494         {
495                 if (p2[-1] == '\"' && quoted)
496                         quoted = false;
497                 p2[-1] = 0;
498                 p2--;
499         }
500         strlcat(text, "\n", sizeof(text));
501
502         // note: save is not a valid edict if fromServer is true
503         save = host_client;
504         for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
505                 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
506                         SV_ClientPrint(text);
507         host_client = save;
508
509         if (cls.state == ca_dedicated)
510                 Con_Print(&text[1]);
511 }
512
513 static void SV_Say_f(cmd_state_t *cmd)
514 {
515         SV_Say(cmd, false);
516 }
517
518 static void SV_Say_Team_f(cmd_state_t *cmd)
519 {
520         SV_Say(cmd, true);
521 }
522
523 static void SV_Tell_f(cmd_state_t *cmd)
524 {
525         const char *playername_start = NULL;
526         size_t playername_length = 0;
527         int playernumber = 0;
528         client_t *save;
529         int j;
530         const char *p1, *p2;
531         char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
532         qboolean fromServer = false;
533
534         if (cmd->source == src_command)
535         {
536                 if (cls.state == ca_dedicated)
537                         fromServer = true;
538                 else
539                 {
540                         Cmd_ForwardToServer_f(cmd);
541                         return;
542                 }
543         }
544
545         if (Cmd_Argc (cmd) < 2)
546                 return;
547
548         // note this uses the chat prefix \001
549         if (!fromServer)
550                 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
551         else if(*(sv_adminnick.string))
552                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
553         else
554                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
555
556         p1 = Cmd_Args(cmd);
557         p2 = p1 + strlen(p1);
558         // remove the target name
559         while (p1 < p2 && *p1 == ' ')
560                 p1++;
561         if(*p1 == '#')
562         {
563                 ++p1;
564                 while (p1 < p2 && *p1 == ' ')
565                         p1++;
566                 while (p1 < p2 && isdigit(*p1))
567                 {
568                         playernumber = playernumber * 10 + (*p1 - '0');
569                         p1++;
570                 }
571                 --playernumber;
572         }
573         else if(*p1 == '"')
574         {
575                 ++p1;
576                 playername_start = p1;
577                 while (p1 < p2 && *p1 != '"')
578                         p1++;
579                 playername_length = p1 - playername_start;
580                 if(p1 < p2)
581                         p1++;
582         }
583         else
584         {
585                 playername_start = p1;
586                 while (p1 < p2 && *p1 != ' ')
587                         p1++;
588                 playername_length = p1 - playername_start;
589         }
590         while (p1 < p2 && *p1 == ' ')
591                 p1++;
592         if(playername_start)
593         {
594                 // set playernumber to the right client
595                 char namebuf[128];
596                 if(playername_length >= sizeof(namebuf))
597                 {
598                         if (fromServer)
599                                 Con_Print("Host_Tell: too long player name/ID\n");
600                         else
601                                 SV_ClientPrint("Host_Tell: too long player name/ID\n");
602                         return;
603                 }
604                 memcpy(namebuf, playername_start, playername_length);
605                 namebuf[playername_length] = 0;
606                 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
607                 {
608                         if (!svs.clients[playernumber].active)
609                                 continue;
610                         if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
611                                 break;
612                 }
613         }
614         if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
615         {
616                 if (fromServer)
617                         Con_Print("Host_Tell: invalid player name/ID\n");
618                 else
619                         SV_ClientPrint("Host_Tell: invalid player name/ID\n");
620                 return;
621         }
622         // remove trailing newlines
623         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
624                 p2--;
625         // remove quotes if present
626         if (*p1 == '"')
627         {
628                 p1++;
629                 if (p2[-1] == '"')
630                         p2--;
631                 else if (fromServer)
632                         Con_Print("Host_Tell: missing end quote\n");
633                 else
634                         SV_ClientPrint("Host_Tell: missing end quote\n");
635         }
636         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
637                 p2--;
638         if(p1 == p2)
639                 return; // empty say
640         for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
641                 text[j++] = *p1++;
642         text[j++] = '\n';
643         text[j++] = 0;
644
645         save = host_client;
646         host_client = svs.clients + playernumber;
647         SV_ClientPrint(text);
648         host_client = save;
649 }
650
651 /*
652 ==================
653 SV_Ping_f
654
655 ==================
656 */
657 static void SV_Ping_f(cmd_state_t *cmd)
658 {
659         int i;
660         client_t *client;
661         void (*print) (const char *fmt, ...);
662
663         if (cmd->source == src_command)
664         {
665                 // if running a client, try to send over network so the client's ping report parser will see the report
666                 if (cls.state == ca_connected)
667                 {
668                         Cmd_ForwardToServer_f(cmd);
669                         return;
670                 }
671                 print = Con_Printf;
672         }
673         else
674                 print = SV_ClientPrintf;
675
676         if (!sv.active)
677                 return;
678
679         print("Client ping times:\n");
680         for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
681         {
682                 if (!client->active)
683                         continue;
684                 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
685         }
686 }
687
688 /*
689 ====================
690 SV_Pings_f
691
692 Send back ping and packet loss update for all current players to this player
693 ====================
694 */
695 static void SV_Pings_f(cmd_state_t *cmd)
696 {
697         int             i, j, ping, packetloss, movementloss;
698         char temp[128];
699
700         if (!host_client->netconnection)
701                 return;
702
703         if (sv.protocol != PROTOCOL_QUAKEWORLD)
704         {
705                 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
706                 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
707         }
708         for (i = 0;i < svs.maxclients;i++)
709         {
710                 packetloss = 0;
711                 movementloss = 0;
712                 if (svs.clients[i].netconnection)
713                 {
714                         for (j = 0;j < NETGRAPH_PACKETS;j++)
715                                 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
716                                         packetloss++;
717                         for (j = 0;j < NETGRAPH_PACKETS;j++)
718                                 if (svs.clients[i].movement_count[j] < 0)
719                                         movementloss++;
720                 }
721                 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
722                 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
723                 ping = (int)floor(svs.clients[i].ping*1000+0.5);
724                 ping = bound(0, ping, 9999);
725                 if (sv.protocol == PROTOCOL_QUAKEWORLD)
726                 {
727                         // send qw_svc_updateping and qw_svc_updatepl messages
728                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
729                         MSG_WriteShort(&host_client->netconnection->message, ping);
730                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
731                         MSG_WriteByte(&host_client->netconnection->message, packetloss);
732                 }
733                 else
734                 {
735                         // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
736                         if(movementloss)
737                                 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
738                         else
739                                 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
740                         MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
741                 }
742         }
743         if (sv.protocol != PROTOCOL_QUAKEWORLD)
744                 MSG_WriteString(&host_client->netconnection->message, "\n");
745 }
746
747 /*
748 ====================
749 SV_User_f
750
751 user <name or userid>
752
753 Dump userdata / masterdata for a user
754 ====================
755 */
756 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
757 {
758         int             uid;
759         int             i;
760
761         if (Cmd_Argc(cmd) != 2)
762         {
763                 Con_Printf ("Usage: user <username / userid>\n");
764                 return;
765         }
766
767         uid = atoi(Cmd_Argv(cmd, 1));
768
769         for (i = 0;i < cl.maxclients;i++)
770         {
771                 if (!cl.scores[i].name[0])
772                         continue;
773                 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
774                 {
775                         InfoString_Print(cl.scores[i].qw_userinfo);
776                         return;
777                 }
778         }
779         Con_Printf ("User not in server.\n");
780 }
781
782 /*
783 ====================
784 SV_Users_f
785
786 Dump userids for all current players
787 ====================
788 */
789 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
790 {
791         int             i;
792         int             c;
793
794         c = 0;
795         Con_Printf ("userid frags name\n");
796         Con_Printf ("------ ----- ----\n");
797         for (i = 0;i < cl.maxclients;i++)
798         {
799                 if (cl.scores[i].name[0])
800                 {
801                         Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
802                         c++;
803                 }
804         }
805
806         Con_Printf ("%i total users\n", c);
807 }
808
809 /*
810 ==================
811 SV_Status_f
812 ==================
813 */
814 static void SV_Status_f(cmd_state_t *cmd)
815 {
816         prvm_prog_t *prog = SVVM_prog;
817         char qcstatus[256];
818         client_t *client;
819         int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
820         void (*print) (const char *fmt, ...);
821         char ip[48]; // can contain a full length v6 address with [] and a port
822         int frags;
823         char vabuf[1024];
824
825         if (cmd->source == src_command)
826         {
827                 // if running a client, try to send over network so the client's status report parser will see the report
828                 if (cls.state == ca_connected)
829                 {
830                         Cmd_ForwardToServer_f(cmd);
831                         return;
832                 }
833                 print = Con_Printf;
834         }
835         else
836                 print = SV_ClientPrintf;
837
838         if (!sv.active)
839                 return;
840
841         in = 0;
842         if (Cmd_Argc(cmd) == 2)
843         {
844                 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
845                         in = 1;
846                 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
847                         in = 2;
848         }
849
850         for (players = 0, i = 0;i < svs.maxclients;i++)
851                 if (svs.clients[i].active)
852                         players++;
853         print ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
854         print ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
855         print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
856         print ("map:      %s\n", sv.name);
857         print ("timing:   %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
858         print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
859
860         if (in == 1)
861                 print ("^2IP                                             %%pl ping  time   frags  no   name\n");
862         else if (in == 2)
863                 print ("^5IP                                              no   name\n");
864
865         for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
866         {
867                 if (!client->active)
868                         continue;
869
870                 ++k;
871
872                 if (in == 0 || in == 1)
873                 {
874                         seconds = (int)(host.realtime - client->connecttime);
875                         minutes = seconds / 60;
876                         if (minutes)
877                         {
878                                 seconds -= (minutes * 60);
879                                 hours = minutes / 60;
880                                 if (hours)
881                                         minutes -= (hours * 60);
882                         }
883                         else
884                                 hours = 0;
885                         
886                         packetloss = 0;
887                         if (client->netconnection)
888                                 for (j = 0;j < NETGRAPH_PACKETS;j++)
889                                         if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
890                                                 packetloss++;
891                         packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
892                         ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
893                 }
894
895                 if(sv_status_privacy.integer && cmd->source != src_command)
896                         strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
897                 else
898                         strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
899
900                 frags = client->frags;
901
902                 if(sv_status_show_qcstatus.integer)
903                 {
904                         prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
905                         const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
906                         if(str && *str)
907                         {
908                                 char *p;
909                                 const char *q;
910                                 p = qcstatus;
911                                 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
912                                         if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
913                                                 *p++ = *q;
914                                 *p = 0;
915                                 if(*qcstatus)
916                                         frags = atoi(qcstatus);
917                         }
918                 }
919                 
920                 if (in == 0) // default layout
921                 {
922                         if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
923                         {
924                                 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
925                                 print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
926                                 print ("   %s\n", ip);
927                         }
928                         else
929                         {
930                                 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
931                                 print ("#%-3u %-16.16s %4i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
932                                 print ("   %s\n", ip);
933                         }
934                 }
935                 else if (in == 1) // extended layout
936                 {
937                         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);
938                 }
939                 else if (in == 2) // reduced layout
940                 {
941                         print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
942                 }
943         }
944 }
945
946 /*
947 ==================
948 SV_Kick_f
949
950 Kicks a user off of the server
951 ==================
952 */
953 static void SV_Kick_f(cmd_state_t *cmd)
954 {
955         const char *who;
956         const char *message = NULL;
957         client_t *save;
958         int i;
959         qboolean byNumber = false;
960
961         if (!sv.active)
962                 return;
963
964         save = host_client;
965
966         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
967         {
968                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
969                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
970                         return;
971                 byNumber = true;
972         }
973         else
974         {
975                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
976                 {
977                         if (!host_client->active)
978                                 continue;
979                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
980                                 break;
981                 }
982         }
983
984         if (i < svs.maxclients)
985         {
986                 if (cmd->source == src_command)
987                 {
988                         if (cls.state == ca_dedicated)
989                                 who = "Console";
990                         else
991                                 who = cl_name.string;
992                 }
993                 else
994                         who = save->name;
995
996                 // can't kick yourself!
997                 if (host_client == save)
998                         return;
999
1000                 if (Cmd_Argc(cmd) > 2)
1001                 {
1002                         message = Cmd_Args(cmd);
1003                         COM_ParseToken_Simple(&message, false, false, true);
1004                         if (byNumber)
1005                         {
1006                                 message++;                                                      // skip the #
1007                                 while (*message == ' ')                         // skip white space
1008                                         message++;
1009                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
1010                         }
1011                         while (*message && *message == ' ')
1012                                 message++;
1013                 }
1014                 if (message)
1015                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1016                 else
1017                         SV_ClientPrintf("Kicked by %s\n", who);
1018                 SV_DropClient (false); // kicked
1019         }
1020
1021         host_client = save;
1022 }
1023
1024 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1025 {
1026         int n;
1027
1028         if (Cmd_Argc(cmd) != 2)
1029         {
1030                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1031                 return;
1032         }
1033
1034         if (sv.active)
1035         {
1036                 Con_Print("maxplayers can not be changed while a server is running.\n");
1037                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1038         }
1039
1040         n = atoi(Cmd_Argv(cmd, 1));
1041         n = bound(1, n, MAX_SCOREBOARD);
1042         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1043
1044         svs.maxclients_next = n;
1045         if (n == 1)
1046                 Cvar_Set (&cvars_all, "deathmatch", "0");
1047         else
1048                 Cvar_Set (&cvars_all, "deathmatch", "1");
1049 }
1050
1051 /*
1052 ===============================================================================
1053
1054 DEBUGGING TOOLS
1055
1056 ===============================================================================
1057 */
1058
1059 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
1060 {
1061         int             i;
1062         prvm_edict_t    *e;
1063
1064         for (i=0 ; i<prog->num_edicts ; i++)
1065         {
1066                 e = PRVM_EDICT_NUM(i);
1067                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1068                         return e;
1069         }
1070         Con_Print("No viewthing on map\n");
1071         return NULL;
1072 }
1073
1074 /*
1075 ==================
1076 SV_Viewmodel_f
1077 ==================
1078 */
1079 static void SV_Viewmodel_f(cmd_state_t *cmd)
1080 {
1081         prvm_prog_t *prog = SVVM_prog;
1082         prvm_edict_t    *e;
1083         dp_model_t      *m;
1084
1085         if (!sv.active)
1086                 return;
1087
1088         e = FindViewthing(prog);
1089         if (e)
1090         {
1091                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1092                 if (m && m->loaded && m->Draw)
1093                 {
1094                         PRVM_serveredictfloat(e, frame) = 0;
1095                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1096                 }
1097                 else
1098                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1099         }
1100 }
1101
1102 /*
1103 ==================
1104 SV_Viewframe_f
1105 ==================
1106 */
1107 static void SV_Viewframe_f(cmd_state_t *cmd)
1108 {
1109         prvm_prog_t *prog = SVVM_prog;
1110         prvm_edict_t    *e;
1111         int             f;
1112         dp_model_t      *m;
1113
1114         if (!sv.active)
1115                 return;
1116
1117         e = FindViewthing(prog);
1118         if (e)
1119         {
1120                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1121
1122                 f = atoi(Cmd_Argv(cmd, 1));
1123                 if (f >= m->numframes)
1124                         f = m->numframes-1;
1125
1126                 PRVM_serveredictfloat(e, frame) = f;
1127         }
1128 }
1129
1130 static void PrintFrameName (dp_model_t *m, int frame)
1131 {
1132         if (m->animscenes)
1133                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1134         else
1135                 Con_Printf("frame %i\n", frame);
1136 }
1137
1138 /*
1139 ==================
1140 SV_Viewnext_f
1141 ==================
1142 */
1143 static void SV_Viewnext_f(cmd_state_t *cmd)
1144 {
1145         prvm_prog_t *prog = SVVM_prog;
1146         prvm_edict_t    *e;
1147         dp_model_t      *m;
1148
1149         if (!sv.active)
1150                 return;
1151
1152         e = FindViewthing(prog);
1153         if (e)
1154         {
1155                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1156
1157                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1158                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1159                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1160
1161                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1162         }
1163 }
1164
1165 /*
1166 ==================
1167 SV_Viewprev_f
1168 ==================
1169 */
1170 static void SV_Viewprev_f(cmd_state_t *cmd)
1171 {
1172         prvm_prog_t *prog = SVVM_prog;
1173         prvm_edict_t    *e;
1174         dp_model_t      *m;
1175
1176         if (!sv.active)
1177                 return;
1178
1179         e = FindViewthing(prog);
1180         if (e)
1181         {
1182                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1183
1184                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1185                 if (PRVM_serveredictfloat(e, frame) < 0)
1186                         PRVM_serveredictfloat(e, frame) = 0;
1187
1188                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1189         }
1190 }
1191
1192 void SV_InitOperatorCommands(void)
1193 {
1194         Cvar_RegisterVariable(&sv_cheats);
1195         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1196         Cvar_RegisterVariable(&sv_adminnick);
1197         Cvar_RegisterVariable(&sv_status_privacy);
1198         Cvar_RegisterVariable(&sv_status_show_qcstatus);
1199         Cvar_RegisterVariable(&sv_namechangetimer);
1200         
1201         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1202         Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "map", SV_Map_f, "kick everyone off the server and start a new level");
1203         Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
1204         Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1205         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1206         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1207         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1208         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1209         Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1210         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1211         Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "load", SV_Loadgame_f, "load a saved game file");
1212         Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
1213         Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1214         Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1215         Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1216         Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1217         Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1218         Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1219         Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1220
1221         // 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)
1222         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1223         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)");
1224         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)");
1225         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)");
1226
1227         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1228         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1229         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1230         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1231         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1232         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1233 }