]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/common.qc
Merge MR 'Various Q3 and QL map entity features and fixes'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / common.qc
1 #include "common.qh"
2
3 #include <server/client.qh>
4 #include <common/weapons/_all.qh>
5 #include <common/stats.qh>
6 #include <server/world.qh>
7 #include <server/miscfunctions.qh>
8
9 #include <common/command/_mod.qh>
10 #include "common.qh"
11
12 #include "../scores.qh"
13
14 #include <common/monsters/_mod.qh>
15 #include <common/notifications/all.qh>
16 #include <lib/warpzone/common.qh>
17
18
19 // ====================================================
20 //  Shared code for server commands, written by Samual
21 //  Last updated: December 27th, 2011
22 // ====================================================
23
24 // select the proper prefix for usage and other messages
25 string GetCommandPrefix(entity caller)
26 {
27         if (caller) return "cmd";
28         else return "sv_cmd";
29 }
30
31 // if client return player nickname, or if server return admin nickname
32 string GetCallerName(entity caller)
33 {
34         if (caller) return playername(caller, false);
35         else return ((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : "SERVER ADMIN"); // autocvar_hostname
36 }
37
38 // verify that the client provided is acceptable for kicking
39 float VerifyKickableEntity(entity client)
40 {
41         if (!IS_REAL_CLIENT(client)) return CLIENT_NOT_REAL;
42         return CLIENT_ACCEPTABLE;
43 }
44
45 // verify that the client provided is acceptable for use
46 float VerifyClientEntity(entity client, float must_be_real, float must_be_bots)
47 {
48         if (!IS_CLIENT(client)) return CLIENT_DOESNT_EXIST;
49         else if (must_be_real && !IS_REAL_CLIENT(client)) return CLIENT_NOT_REAL;
50         else if (must_be_bots && !IS_BOT_CLIENT(client)) return CLIENT_NOT_BOT;
51
52         return CLIENT_ACCEPTABLE;
53 }
54
55 // if the client is not acceptable, return a string to be used for error messages
56 string GetClientErrorString_color(float clienterror, string original_input, string col)
57 {
58         switch (clienterror)
59         {
60                 case CLIENT_DOESNT_EXIST:
61                 { return strcat(col, "Client '", original_input, col, "' doesn't exist");
62                 }
63                 case CLIENT_NOT_REAL:
64                 { return strcat(col, "Client '", original_input, col, "' is not real");
65                 }
66                 case CLIENT_NOT_BOT:
67                 { return strcat(col, "Client '", original_input, col, "' is not a bot");
68                 }
69                 default:
70                 { return "Incorrect usage of GetClientErrorString";
71                 }
72         }
73 }
74
75 // is this entity number even in the possible range of entities?
76 float VerifyClientNumber(float tmp_number)
77 {
78         if ((tmp_number < 1) || (tmp_number > maxclients)) return false;
79         else return true;
80 }
81
82 entity GetIndexedEntity(int argc, float start_index)
83 {
84         entity selection;
85         float tmp_number, index;
86         string tmp_string;
87
88         next_token = -1;
89         index = start_index;
90         selection = NULL;
91
92         if (argc > start_index)
93         {
94                 if (substring(argv(index), 0, 1) == "#")
95                 {
96                         tmp_string = substring(argv(index), 1, -1);
97                         ++index;
98
99                         if (tmp_string != "")  // is it all one token? like #1
100                         {
101                                 tmp_number = stof(tmp_string);
102                         }
103                         else if (argc > index)  // no, it's two tokens? # 1
104                         {
105                                 tmp_number = stof(argv(index));
106                                 ++index;
107                         }
108                         else
109                         {
110                                 tmp_number = 0;
111                         }
112                 }
113                 else  // maybe it's ONLY a number?
114                 {
115                         tmp_number = stof(argv(index));
116                         ++index;
117                 }
118
119                 if (VerifyClientNumber(tmp_number))
120                 {
121                         selection = edict_num(tmp_number);  // yes, it was a number
122                 }
123                 else  // no, maybe it's a name?
124                 {
125                         FOREACH_CLIENT(true, {
126                                 if(strdecolorize(it.netname) == strdecolorize(argv(start_index)))
127                                 {
128                                         selection = it;
129                                         break; // no reason to keep looking
130                                 }
131                         });
132
133                         index = (start_index + 1);
134                 }
135         }
136
137         next_token = index;
138         // print(strcat("start_index: ", ftos(start_index), ", next_token: ", ftos(next_token), ", edict: ", ftos(num_for_edict(selection)), ".\n"));
139         return selection;
140 }
141
142 // find a player which matches the input string, and return their entity
143 entity GetFilteredEntity(string input)
144 {
145         entity selection;
146         float tmp_number;
147
148         if (substring(input, 0, 1) == "#") tmp_number = stof(substring(input, 1, -1));
149         else tmp_number = stof(input);
150
151         if (VerifyClientNumber(tmp_number))
152         {
153                 selection = edict_num(tmp_number);
154         }
155         else
156         {
157                 selection = NULL;
158                 FOREACH_CLIENT(true, {
159                         if(strdecolorize(it.netname) == strdecolorize(input))
160                         {
161                                 selection = it;
162                                 break; // no reason to keep looking
163                         }
164                 });
165         }
166
167         return selection;
168 }
169
170 // same thing, but instead return their edict number
171 float GetFilteredNumber(string input)
172 {
173         entity selection = GetFilteredEntity(input);
174         float output;
175
176         output = etof(selection);
177
178         return output;
179 }
180
181 // switch between sprint and print depending on whether the receiver is the server or a player
182 void print_to(entity to, string input)
183 {
184         if (to) sprint(to, strcat(input, "\n"));
185         else print(input, "\n");
186 }
187
188 // ==========================================
189 //  Supporting functions for common commands
190 // ==========================================
191
192 // used by CommonCommand_timeout() and CommonCommand_timein() to handle game pausing and messaging and such.
193 void timeout_handler_reset(entity this)
194 {
195         timeout_caller = NULL;
196         timeout_time = 0;
197         timeout_leadtime = 0;
198
199         delete(this);
200 }
201
202 void timeout_handler_think(entity this)
203 {
204         switch (timeout_status)
205         {
206                 case TIMEOUT_ACTIVE:
207                 {
208                         if (timeout_time > 0)  // countdown is still going
209                         {
210                                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TIMEOUT_ENDING, timeout_time);
211
212                                 if (timeout_time == autocvar_sv_timeout_resumetime) // play a warning sound when only <sv_timeout_resumetime> seconds are left
213                                         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_PREPARE);
214
215                                 this.nextthink = time + TIMEOUT_SLOWMO_VALUE;       // think again in one second
216                                 timeout_time -= 1;                                  // decrease the time counter
217                         }
218                         else  // time to end the timeout
219                         {
220                                 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_TIMEIN);
221                                 timeout_status = TIMEOUT_INACTIVE;
222
223                                 // reset the slowmo value back to normal
224                                 cvar_set("slowmo", ftos(orig_slowmo));
225
226                                 // unlock the view for players so they can move around again
227                                 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
228                                         it.fixangle = false;
229                                 });
230
231                                 timeout_handler_reset(this);
232                         }
233
234                         return;
235                 }
236
237                 case TIMEOUT_LEADTIME:
238                 {
239                         if (timeout_leadtime > 0)  // countdown is still going
240                         {
241                                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TIMEOUT_BEGINNING, timeout_leadtime);
242
243                                 this.nextthink = time + 1; // think again in one second
244                                 timeout_leadtime -= 1;     // decrease the time counter
245                         }
246                         else  // time to begin the timeout
247                         {
248                                 timeout_status = TIMEOUT_ACTIVE;
249
250                                 // set the slowmo value to the timeout default slowmo value
251                                 cvar_set("slowmo", ftos(TIMEOUT_SLOWMO_VALUE));
252
253                                 // reset all the flood variables
254                                 FOREACH_CLIENT(true, {
255                                         it.nickspamcount = it.nickspamtime = it.floodcontrol_chat =
256                                                 it.floodcontrol_chatteam = it.floodcontrol_chattell =
257                                                         it.floodcontrol_voice = it.floodcontrol_voiceteam = 0;
258                                 });
259
260                                 // copy .v_angle to .lastV_angle for every player in order to fix their view during pause (see PlayerPreThink)
261                                 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
262                                         it.lastV_angle = it.v_angle;
263                                 });
264
265                                 this.nextthink = time;  // think again next frame to handle it under TIMEOUT_ACTIVE code
266                         }
267
268                         return;
269                 }
270
271
272                 case TIMEOUT_INACTIVE:
273                 default:
274                 {
275                         timeout_handler_reset(this);
276                         return;
277                 }
278         }
279 }
280
281
282 // ===================================================
283 //  Common commands used in both sv_cmd.qc and cmd.qc
284 // ===================================================
285
286 void CommonCommand_cvar_changes(int request, entity caller)
287 {
288         switch (request)
289         {
290                 case CMD_REQUEST_COMMAND:
291                 {
292                         print_to(caller, cvar_changes);
293                         return;  // never fall through to usage
294                 }
295
296                 default:
297                 case CMD_REQUEST_USAGE:
298                 {
299                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_changes"));
300                         print_to(caller, "  No arguments required.");
301                         print_to(caller, "See also: ^2cvar_purechanges^7");
302                         return;
303                 }
304         }
305 }
306
307 void CommonCommand_cvar_purechanges(int request, entity caller)
308 {
309         switch (request)
310         {
311                 case CMD_REQUEST_COMMAND:
312                 {
313                         print_to(caller, cvar_purechanges);
314                         return;  // never fall through to usage
315                 }
316
317                 default:
318                 case CMD_REQUEST_USAGE:
319                 {
320                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_purechanges"));
321                         print_to(caller, "  No arguments required.");
322                         print_to(caller, "See also: ^2cvar_changes^7");
323                         return;
324                 }
325         }
326 }
327
328 void CommonCommand_editmob(int request, entity caller, int argc)
329 {
330         switch (request)
331         {
332                 case CMD_REQUEST_COMMAND:
333                 {
334                         if (autocvar_g_campaign) { print_to(caller, "Monster editing is disabled in singleplayer"); return; }
335                         // no checks for g_monsters here, as it may be toggled mid match which existing monsters
336
337                         if (caller)
338                         {
339                                 makevectors(caller.v_angle);
340                                 WarpZone_TraceLine(caller.origin + caller.view_ofs, caller.origin + caller.view_ofs + v_forward * 100, MOVE_NORMAL, caller);
341                         }
342
343                         entity mon = trace_ent;
344                         bool is_visible = IS_MONSTER(mon);
345                         string argument = argv(2);
346
347                         switch (argv(1))
348                         {
349                                 case "name":
350                                 {
351                                         if (!caller) { print_to(caller, "Only players can edit monsters"); return; }
352                                         if (!argument)   break;  // escape to usage
353                                         if (!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
354                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
355                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
356
357                                         string mon_oldname = mon.monster_name;
358
359                                         mon.monster_name = argument;
360                                         if (mon.sprite)   WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null);
361                                         print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name));
362                                         return;
363                                 }
364                                 case "spawn":
365                                 {
366                                         if (!caller) { print_to(caller, "Only players can spawn monsters"); return; }
367                                         if (!argv(2))   break;  // escape to usage
368
369                                         int moveflag, tmp_moncount = 0;
370                                         string arg_lower = strtolower(argument);
371                                         moveflag = (argv(3)) ? stof(argv(3)) : 1;  // follow owner if not defined
372
373                                         if (arg_lower == "list") { print_to(caller, monsterlist_reply); return; }
374
375                                         IL_EACH(g_monsters, it.realowner == caller,
376                                         {
377                                                 ++tmp_moncount;
378                                         });
379
380                                         if (!autocvar_g_monsters) { print_to(caller, "Monsters are disabled"); return; }
381                                         if (autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { print_to(caller, "Monster spawning is disabled"); return; }
382                                         if (!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; }
383                                         if (MUTATOR_CALLHOOK(AllowMobSpawning, caller)) { print_to(caller, M_ARGV(1, string)); return; }
384                                         if (caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; }
385                                         if (STAT(FROZEN, caller)) { print_to(caller, "You can't spawn monsters while frozen"); return; }
386                                         if (IS_DEAD(caller)) { print_to(caller, "You can't spawn monsters while dead"); return; }
387                                         if (tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; }
388                                         if (tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; }
389
390                                         bool found = false;
391                                         FOREACH(Monsters, it != MON_Null && it.netname == arg_lower,
392                                         {
393                                                 found = true;
394                                                 break;
395                                         });
396
397                                         if (!found && arg_lower != "random" && arg_lower != "anyrandom") { print_to(caller, "Invalid monster"); return; }
398
399                                         totalspawned += 1;
400                                         WarpZone_TraceBox(CENTER_OR_VIEWOFS(caller), caller.mins, caller.maxs, CENTER_OR_VIEWOFS(caller) + v_forward * 150, true, caller);
401                                         mon = spawnmonster(spawn(), arg_lower, MON_Null, caller, caller, trace_endpos, false, false, moveflag);
402                                         print_to(caller, strcat("Spawned ", mon.monster_name));
403                                         return;
404                                 }
405                                 case "kill":
406                                 {
407                                         if (!caller) { print_to(caller, "Only players can kill monsters"); return; }
408                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
409                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
410
411                                         Damage(mon, NULL, NULL, GetResource(mon, RES_HEALTH) + mon.max_health + 200, DEATH_KILL.m_id, DMG_NOWEP, mon.origin, '0 0 0');
412                                         print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated"));
413                                         return;
414                                 }
415                                 case "skin":
416                                 {
417                                         if (!caller) { print_to(caller, "Only players can edit monsters"); return; }
418                                         if (!argument)   break;  // escape to usage
419                                         if (!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
420                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
421                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
422                                         if (mon.monsterdef == MON_MAGE) { print_to(caller, "Mage skins can't be changed"); return; }  // TODO
423
424                                         mon.skin = stof(argument);
425                                         print_to(caller, strcat("Monster skin successfully changed to ", ftos(mon.skin)));
426                                         return;
427                                 }
428                                 case "movetarget":
429                                 {
430                                         if (!caller) { print_to(caller, "Only players can edit monsters"); return; }
431                                         if (!argument)   break;  // escape to usage
432                                         if (!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
433                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
434                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
435
436                                         mon.monster_moveflags = stof(argument);
437                                         print_to(caller, strcat("Monster move target successfully changed to ", ftos(mon.monster_moveflags)));
438                                         return;
439                                 }
440                                 case "butcher":
441                                 {
442                                         if (caller) { print_to(caller, "This command is not available to players"); return; }
443                                         if (MUTATOR_CALLHOOK(AllowMobButcher)) { LOG_INFO(M_ARGV(0, string)); return; }
444
445                                         int tmp_remcount = 0;
446
447                                         IL_EACH(g_monsters, true,
448                                         {
449                                                 Monster_Remove(it);
450                                                 ++tmp_remcount;
451                                         });
452                                         IL_CLEAR(g_monsters);
453
454                                         monsters_total = monsters_killed = totalspawned = 0;
455
456                                         print_to(caller, (tmp_remcount) ? sprintf("Killed %d monster%s", tmp_remcount, (tmp_remcount == 1) ? "" : "s") : "No monsters to kill");
457                                         return;
458                                 }
459                         }
460                 }
461
462                 default:
463                 case CMD_REQUEST_USAGE:
464                 {
465                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]"));
466                         print_to(caller, "  Where 'command' can be butcher spawn skin movetarget kill name");
467                         print_to(caller, "  spawn, skin, movetarget and name require 'arguments'");
468                         print_to(caller, "  spawn also takes arguments list and random");
469                         print_to(caller, "  Monster will follow owner if third argument of spawn command is not defined");
470                         return;
471                 }
472         }
473 }
474
475 void CommonCommand_info(int request, entity caller, int argc)
476 {
477         switch (request)
478         {
479                 case CMD_REQUEST_COMMAND:
480                 {
481                         string command = cvar_string(strcat("sv_info_", argv(1)));
482
483                         if (command) wordwrap_sprint(caller, command, 1000);
484                         else print_to(caller, "ERROR: unsupported info command");
485
486                         return;  // never fall through to usage
487                 }
488
489                 default:
490                 case CMD_REQUEST_USAGE:
491                 {
492                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info request"));
493                         print_to(caller, "  Where 'request' is the suffixed string appended onto the request for cvar.");
494                         return;
495                 }
496         }
497 }
498
499 void CommonCommand_ladder(int request, entity caller)
500 {
501         switch (request)
502         {
503                 case CMD_REQUEST_COMMAND:
504                 {
505                         print_to(caller, ladder_reply);
506                         return;  // never fall through to usage
507                 }
508
509                 default:
510                 case CMD_REQUEST_USAGE:
511                 {
512                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " ladder"));
513                         print_to(caller, "  No arguments required.");
514                         return;
515                 }
516         }
517 }
518
519 void CommonCommand_lsmaps(int request, entity caller)
520 {
521         switch (request)
522         {
523                 case CMD_REQUEST_COMMAND:
524                 {
525                         print_to(caller, lsmaps_reply);
526                         return;  // never fall through to usage
527                 }
528
529                 default:
530                 case CMD_REQUEST_USAGE:
531                 {
532                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " lsmaps"));
533                         print_to(caller, "  No arguments required.");
534                         return;
535                 }
536         }
537 }
538
539 void CommonCommand_printmaplist(int request, entity caller)
540 {
541         switch (request)
542         {
543                 case CMD_REQUEST_COMMAND:
544                 {
545                         print_to(caller, maplist_reply);
546                         return;  // never fall through to usage
547                 }
548
549                 default:
550                 case CMD_REQUEST_USAGE:
551                 {
552                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " printmaplist"));
553                         print_to(caller, "  No arguments required.");
554                         return;
555                 }
556         }
557 }
558
559 void CommonCommand_rankings(int request, entity caller)
560 {
561         switch (request)
562         {
563                 case CMD_REQUEST_COMMAND:
564                 {
565                         print_to(caller, rankings_reply);
566                         return;  // never fall through to usage
567                 }
568
569                 default:
570                 case CMD_REQUEST_USAGE:
571                 {
572                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " rankings"));
573                         print_to(caller, "  No arguments required.");
574                         return;
575                 }
576         }
577 }
578
579 void CommonCommand_records(int request, entity caller)
580 {
581         switch (request)
582         {
583                 case CMD_REQUEST_COMMAND:
584                 {
585                         int num = stoi(argv(1));
586                         if(num > 0 && num <= 10 && records_reply[num - 1] != "")
587                                 print_to(caller, records_reply[num - 1]);
588                         else
589                         {
590                                 for (int i = 0; i < 10; ++i)
591                                         if (records_reply[i] != "") print_to(caller, records_reply[i]);
592                         }
593
594                         return;  // never fall through to usage
595                 }
596
597                 default:
598                 case CMD_REQUEST_USAGE:
599                 {
600                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records"));
601                         print_to(caller, "  No arguments required.");
602                         return;
603                 }
604         }
605 }
606
607 void CommonCommand_teamstatus(int request, entity caller)
608 {
609         switch (request)
610         {
611                 case CMD_REQUEST_COMMAND:
612                 {
613                         Score_NicePrint(caller);
614                         return;  // never fall through to usage
615                 }
616
617                 default:
618                 case CMD_REQUEST_USAGE:
619                 {
620                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " teamstatus"));
621                         print_to(caller, "  No arguments required.");
622                         return;
623                 }
624         }
625 }
626
627 void CommonCommand_time(int request, entity caller)
628 {
629         switch (request)
630         {
631                 case CMD_REQUEST_COMMAND:
632                 {
633                         print_to(caller, strcat("time = ", ftos(time)));
634                         print_to(caller, strcat("frame start = ", ftos(gettime(GETTIME_FRAMESTART))));
635                         print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
636                         print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
637                         print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
638                         print_to(caller, strcat("localtime = ", strftime(true, "%a %b %d %H:%M:%S %Z %Y")));
639                         print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %d %H:%M:%S %Z %Y")));
640                         return;
641                 }
642
643                 default:
644                 case CMD_REQUEST_USAGE:
645                 {
646                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " time"));
647                         print_to(caller, "  No arguments required.");
648                         return;
649                 }
650         }
651 }
652
653 void CommonCommand_timein(int request, entity caller)
654 {
655         switch (request)
656         {
657                 case CMD_REQUEST_COMMAND:
658                 {
659                         if (!caller || autocvar_sv_timeout)
660                         {
661                                 if (!timeout_status) { print_to(caller, "^7Error: There is no active timeout called."); }
662                                 else if (caller && (caller != timeout_caller))
663                                 {
664                                         print_to(caller, "^7Error: You are not allowed to stop the active timeout.");
665                                 }
666
667                                 else  // everything should be okay, continue aborting timeout
668                                 {
669                                         switch (timeout_status)
670                                         {
671                                                 case TIMEOUT_LEADTIME:
672                                                 {
673                                                         timeout_status = TIMEOUT_INACTIVE;
674                                                         timeout_time = 0;
675                                                         timeout_handler.nextthink = time;  // timeout_handler has to take care of it immediately
676                                                         bprint(strcat("^7The timeout was aborted by ", GetCallerName(caller), " !\n"));
677                                                         return;
678                                                 }
679
680                                                 case TIMEOUT_ACTIVE:
681                                                 {
682                                                         timeout_time = autocvar_sv_timeout_resumetime;
683                                                         timeout_handler.nextthink = time;  // timeout_handler has to take care of it immediately
684                                                         bprint(strcat("^1Attention: ^7", GetCallerName(caller), " resumed the game! Prepare for battle!\n"));
685                                                         return;
686                                                 }
687
688                                                 default: LOG_TRACE("timeout status was inactive, but this code was executed anyway?");
689                                                         return;
690                                         }
691                                 }
692                         }
693                         else { print_to(caller, "^1Timeins are not allowed to be called, enable them with sv_timeout 1.\n"); }
694
695                         return;  // never fall through to usage
696                 }
697
698                 default:
699                 case CMD_REQUEST_USAGE:
700                 {
701                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timein"));
702                         print_to(caller, "  No arguments required.");
703                         return;
704                 }
705         }
706 }
707
708 void CommonCommand_timeout(int request, entity caller)  // DEAR GOD THIS COMMAND IS TERRIBLE.
709 {
710         switch (request)
711         {
712                 case CMD_REQUEST_COMMAND:
713                 {
714                         if (!caller || autocvar_sv_timeout)
715                         {
716                                 float last_possible_timeout = ((autocvar_timelimit * 60) - autocvar_sv_timeout_leadtime - 1);
717
718                                 if (timeout_status) { print_to(caller, "^7Error: A timeout is already active."); }
719                                 else if (vote_called)
720                                 {
721                                         print_to(caller, "^7Error: You can not call a timeout while a vote is active.");
722                                 }
723                                 else if (warmup_stage && !g_warmup_allow_timeout)
724                                 {
725                                         print_to(caller, "^7Error: You can not call a timeout in warmup-stage.");
726                                 }
727                                 else if (time < game_starttime)
728                                 {
729                                         print_to(caller, "^7Error: You can not call a timeout while the map is being restarted.");
730                                 }
731                                 else if (caller && (CS(caller).allowed_timeouts < 1))
732                                 {
733                                         print_to(caller, "^7Error: You already used all your timeout calls for this map.");
734                                 }
735                                 else if (caller && !IS_PLAYER(caller))
736                                 {
737                                         print_to(caller, "^7Error: You must be a player to call a timeout.");
738                                 }
739                                 else if ((autocvar_timelimit) && (last_possible_timeout < time - game_starttime))
740                                 {
741                                         print_to(caller, "^7Error: It is too late to call a timeout now!");
742                                 }
743
744                                 else  // everything should be okay, proceed with starting the timeout
745                                 {
746                                         if (caller)   CS(caller).allowed_timeouts -= 1;
747                                         // write a bprint who started the timeout (and how many they have left)
748                                         bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(CS(caller).allowed_timeouts), " timeout(s) left)") : ""), "!\n");
749
750                                         timeout_status = TIMEOUT_LEADTIME;
751                                         timeout_caller = caller;
752                                         timeout_time = autocvar_sv_timeout_length;
753                                         timeout_leadtime = autocvar_sv_timeout_leadtime;
754
755                                         timeout_handler = spawn();
756                                         setthink(timeout_handler, timeout_handler_think);
757                                         timeout_handler.nextthink = time;  // always let the entity think asap
758
759                                         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_TIMEOUT);
760                                 }
761                         }
762                         else { print_to(caller, "^1Timeouts are not allowed to be called, enable them with sv_timeout 1.\n"); }
763
764                         return;  // never fall through to usage
765                 }
766
767                 default:
768                 case CMD_REQUEST_USAGE:
769                 {
770                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timeout"));
771                         print_to(caller, "  No arguments required.");
772                         return;
773                 }
774         }
775 }
776
777 void CommonCommand_who(int request, entity caller, int argc)
778 {
779         switch (request)
780         {
781                 case CMD_REQUEST_COMMAND:
782                 {
783                         float total_listed_players, is_bot;
784
785                         float privacy = (caller && autocvar_sv_status_privacy);
786                         string separator = strreplace("%", " ", strcat((argv(1) ? argv(1) : " "), "^7"));
787                         string tmp_netaddress, tmp_crypto_idfp;
788
789                         print_to(caller, strcat("List of client information", (privacy ? " (some data is hidden for privacy)" : ""), ":"));
790                         print_to(caller, sprintf(strreplace(" ", separator, " %-4s %-20s %-5s %-3s %-9s %-16s %s "),
791                                 "ent", "nickname", "ping", "pl", "time", "ip", "crypto_id"));
792
793                         total_listed_players = 0;
794                         FOREACH_CLIENT(true, {
795                                 is_bot = (IS_BOT_CLIENT(it));
796
797                                 if (is_bot)
798                                 {
799                                         tmp_netaddress = "null/botclient";
800                                         tmp_crypto_idfp = "null/botclient";
801                                 }
802                                 else if (privacy)
803                                 {
804                                         tmp_netaddress = "hidden";
805                                         tmp_crypto_idfp = "hidden";
806                                 }
807                                 else
808                                 {
809                                         tmp_netaddress = it.netaddress;
810                                         tmp_crypto_idfp = it.crypto_idfp;
811                                 }
812
813                                 print_to(caller, sprintf(strreplace(" ", separator, " #%-3d %-20.20s %-5d %-3d %-9s %-16s %s "),
814                                         etof(it),
815                                         it.netname,
816                                         CS(it).ping,
817                                         CS(it).ping_packetloss,
818                                         process_time(1, time - CS(it).jointime),
819                                         tmp_netaddress,
820                                         tmp_crypto_idfp));
821
822                                 ++total_listed_players;
823                         });
824
825                         print_to(caller, strcat("Finished listing ", ftos(total_listed_players), " client(s) out of ", ftos(maxclients), " slots."));
826
827                         return;  // never fall through to usage
828                 }
829
830                 default:
831                 case CMD_REQUEST_USAGE:
832                 {
833                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " who [separator]"));
834                         print_to(caller, "  Where 'separator' is the optional string to separate the values with, default is a space.");
835                         return;
836                 }
837         }
838 }
839
840 /* use this when creating a new command, making sure to place it in alphabetical order... also,
841 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
842 void CommonCommand_(int request, entity caller)
843 {
844     switch(request)
845     {
846         case CMD_REQUEST_COMMAND:
847         {
848
849             return; // never fall through to usage
850         }
851
852         default:
853         case CMD_REQUEST_USAGE:
854         {
855             print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " "));
856             print_to(caller, "  No arguments required.");
857             return;
858         }
859     }
860 }
861 */