]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/common.qc
Fixed sound disabling and medal screening
[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                                 this.nextthink = time + 1;
217                                 timeout_time -= 1;                                  // decrease the time counter
218                         }
219                         else if (timeout_time == -1)  // infinite timer
220                         {
221                                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TIMEOUT_ONGOING);
222                                 this.nextthink = time + TIMEOUT_SLOWMO_VALUE;
223                         }
224                         else  // time to end the timeout
225                         {
226                                 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_TIMEIN);
227                                 timeout_status = TIMEOUT_INACTIVE;
228                                 float total_time = time - timeout_last;
229
230                                 // reset the slowmo value back to normal
231                                 // z411 TODO
232                                 //cvar_set("slowmo", ftos(orig_slowmo));
233                                 
234                                 // Disable timeout and fix times
235                                 game_timeout = false;
236                                 timeout_total_time += total_time;
237                                 game_starttime += total_time;
238                                 if(round_handler && round_handler_GetEndTime() > 0)
239                                         round_handler.round_endtime += total_time;
240                                         
241                                 LOG_INFOF("Timeout lasted %d secs", total_time);
242
243                                 // unlock the view for players so they can move around again
244                                 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
245                                         it.fixangle = false;
246                                 });
247
248                                 timeout_handler_reset(this);
249                         }
250
251                         return;
252                 }
253
254                 case TIMEOUT_LEADTIME:
255                 {
256                         if (timeout_leadtime > 0)  // countdown is still going
257                         {
258                                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TIMEOUT_BEGINNING, timeout_leadtime);
259
260                                 this.nextthink = time + 1; // think again in one second
261                                 timeout_leadtime -= 1;     // decrease the time counter
262                         }
263                         else  // time to begin the timeout
264                         {
265                                 timeout_status = TIMEOUT_ACTIVE;
266
267                                 // set the slowmo value to the timeout default slowmo value
268                                 //cvar_set("slowmo", ftos(TIMEOUT_SLOWMO_VALUE));
269                                 game_timeout = true;
270                                 timeout_last = time;
271                                 
272                                 // play timeout sound
273                                 sound(NULL, CH_INFO, SND_TIMEOUT, VOL_BASE, ATTN_NONE);
274
275                                 // reset all the flood variables
276                                 FOREACH_CLIENT(true, {
277                                         it.nickspamcount = it.nickspamtime = it.floodcontrol_chat =
278                                                 it.floodcontrol_chatteam = it.floodcontrol_chattell =
279                                                         it.floodcontrol_voice = it.floodcontrol_voiceteam = 0;
280                                 });
281
282                                 // copy .v_angle to .lastV_angle for every player in order to fix their view during pause (see PlayerPreThink)
283                                 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
284                                         it.lastV_angle = it.v_angle;
285                                 });
286
287                                 this.nextthink = time;  // think again next frame to handle it under TIMEOUT_ACTIVE code
288                         }
289
290                         return;
291                 }
292
293
294                 case TIMEOUT_INACTIVE:
295                 default:
296                 {
297                         timeout_handler_reset(this);
298                         return;
299                 }
300         }
301 }
302
303
304 // ===================================================
305 //  Common commands used in both sv_cmd.qc and cmd.qc
306 // ===================================================
307
308 void CommonCommand_cvar_changes(int request, entity caller)
309 {
310         switch (request)
311         {
312                 case CMD_REQUEST_COMMAND:
313                 {
314                         print_to(caller, cvar_changes);
315                         return;  // never fall through to usage
316                 }
317
318                 default:
319                 case CMD_REQUEST_USAGE:
320                 {
321                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_changes"));
322                         print_to(caller, "  No arguments required.");
323                         print_to(caller, "See also: ^2cvar_purechanges^7");
324                         return;
325                 }
326         }
327 }
328
329 void CommonCommand_cvar_purechanges(int request, entity caller)
330 {
331         switch (request)
332         {
333                 case CMD_REQUEST_COMMAND:
334                 {
335                         print_to(caller, cvar_purechanges);
336                         return;  // never fall through to usage
337                 }
338
339                 default:
340                 case CMD_REQUEST_USAGE:
341                 {
342                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_purechanges"));
343                         print_to(caller, "  No arguments required.");
344                         print_to(caller, "See also: ^2cvar_changes^7");
345                         return;
346                 }
347         }
348 }
349
350 void CommonCommand_editmob(int request, entity caller, int argc)
351 {
352         switch (request)
353         {
354                 case CMD_REQUEST_COMMAND:
355                 {
356                         if (autocvar_g_campaign) { print_to(caller, "Monster editing is disabled in singleplayer"); return; }
357                         // no checks for g_monsters here, as it may be toggled mid match which existing monsters
358
359                         if (caller)
360                         {
361                                 makevectors(caller.v_angle);
362                                 WarpZone_TraceLine(caller.origin + caller.view_ofs, caller.origin + caller.view_ofs + v_forward * 100, MOVE_NORMAL, caller);
363                         }
364
365                         entity mon = trace_ent;
366                         bool is_visible = IS_MONSTER(mon);
367                         string argument = argv(2);
368
369                         switch (argv(1))
370                         {
371                                 case "name":
372                                 {
373                                         if (!caller) { print_to(caller, "Only players can edit monsters"); return; }
374                                         if (!argument)   break;  // escape to usage
375                                         if (!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
376                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
377                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
378
379                                         string mon_oldname = mon.monster_name;
380
381                                         mon.monster_name = argument;
382                                         if (mon.sprite)   WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null);
383                                         print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name));
384                                         return;
385                                 }
386                                 case "spawn":
387                                 {
388                                         if (!caller) { print_to(caller, "Only players can spawn monsters"); return; }
389                                         if (!argv(2))   break;  // escape to usage
390
391                                         int moveflag, tmp_moncount = 0;
392                                         string arg_lower = strtolower(argument);
393                                         moveflag = (argv(3)) ? stof(argv(3)) : 1;  // follow owner if not defined
394
395                                         if (arg_lower == "list") { print_to(caller, monsterlist_reply); return; }
396
397                                         IL_EACH(g_monsters, it.realowner == caller,
398                                         {
399                                                 ++tmp_moncount;
400                                         });
401
402                                         if (!autocvar_g_monsters) { print_to(caller, "Monsters are disabled"); return; }
403                                         if (autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { print_to(caller, "Monster spawning is disabled"); return; }
404                                         if (!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; }
405                                         if (MUTATOR_CALLHOOK(AllowMobSpawning, caller)) { print_to(caller, M_ARGV(1, string)); return; }
406                                         if (caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; }
407                                         if (STAT(FROZEN, caller)) { print_to(caller, "You can't spawn monsters while frozen"); return; }
408                                         if (IS_DEAD(caller)) { print_to(caller, "You can't spawn monsters while dead"); return; }
409                                         if (tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; }
410                                         if (tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; }
411
412                                         bool found = false;
413                                         FOREACH(Monsters, it != MON_Null && it.netname == arg_lower,
414                                         {
415                                                 found = true;
416                                                 break;
417                                         });
418
419                                         if (!found && arg_lower != "random" && arg_lower != "anyrandom") { print_to(caller, "Invalid monster"); return; }
420
421                                         totalspawned += 1;
422                                         WarpZone_TraceBox(CENTER_OR_VIEWOFS(caller), caller.mins, caller.maxs, CENTER_OR_VIEWOFS(caller) + v_forward * 150, true, caller);
423                                         mon = spawnmonster(spawn(), arg_lower, MON_Null, caller, caller, trace_endpos, false, false, moveflag);
424                                         print_to(caller, strcat("Spawned ", mon.monster_name));
425                                         return;
426                                 }
427                                 case "kill":
428                                 {
429                                         if (!caller) { print_to(caller, "Only players can kill monsters"); return; }
430                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
431                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
432
433                                         Damage(mon, NULL, NULL, GetResource(mon, RES_HEALTH) + mon.max_health + 200, DEATH_KILL.m_id, DMG_NOWEP, mon.origin, '0 0 0');
434                                         print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated"));
435                                         return;
436                                 }
437                                 case "skin":
438                                 {
439                                         if (!caller) { print_to(caller, "Only players can edit monsters"); return; }
440                                         if (!argument)   break;  // escape to usage
441                                         if (!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
442                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
443                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
444                                         if (mon.monsterdef == MON_MAGE) { print_to(caller, "Mage skins can't be changed"); return; }  // TODO
445
446                                         mon.skin = stof(argument);
447                                         print_to(caller, strcat("Monster skin successfully changed to ", ftos(mon.skin)));
448                                         return;
449                                 }
450                                 case "movetarget":
451                                 {
452                                         if (!caller) { print_to(caller, "Only players can edit monsters"); return; }
453                                         if (!argument)   break;  // escape to usage
454                                         if (!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
455                                         if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
456                                         if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
457
458                                         mon.monster_moveflags = stof(argument);
459                                         print_to(caller, strcat("Monster move target successfully changed to ", ftos(mon.monster_moveflags)));
460                                         return;
461                                 }
462                                 case "butcher":
463                                 {
464                                         if (caller) { print_to(caller, "This command is not available to players"); return; }
465                                         if (MUTATOR_CALLHOOK(AllowMobButcher)) { LOG_INFO(M_ARGV(0, string)); return; }
466
467                                         int tmp_remcount = 0;
468
469                                         IL_EACH(g_monsters, true,
470                                         {
471                                                 Monster_Remove(it);
472                                                 ++tmp_remcount;
473                                         });
474                                         IL_CLEAR(g_monsters);
475
476                                         monsters_total = monsters_killed = totalspawned = 0;
477
478                                         print_to(caller, (tmp_remcount) ? sprintf("Killed %d monster%s", tmp_remcount, (tmp_remcount == 1) ? "" : "s") : "No monsters to kill");
479                                         return;
480                                 }
481                         }
482                 }
483
484                 default:
485                 case CMD_REQUEST_USAGE:
486                 {
487                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]"));
488                         print_to(caller, "  Where 'command' can be butcher spawn skin movetarget kill name");
489                         print_to(caller, "  spawn, skin, movetarget and name require 'arguments'");
490                         print_to(caller, "  spawn also takes arguments list and random");
491                         print_to(caller, "  Monster will follow owner if third argument of spawn command is not defined");
492                         return;
493                 }
494         }
495 }
496
497 void CommonCommand_info(int request, entity caller, int argc)
498 {
499         switch (request)
500         {
501                 case CMD_REQUEST_COMMAND:
502                 {
503                         string command = cvar_string(strcat("sv_info_", argv(1)));
504
505                         if (command) wordwrap_sprint(caller, command, 1000);
506                         else print_to(caller, "ERROR: unsupported info command");
507
508                         return;  // never fall through to usage
509                 }
510
511                 default:
512                 case CMD_REQUEST_USAGE:
513                 {
514                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info request"));
515                         print_to(caller, "  Where 'request' is the suffixed string appended onto the request for cvar.");
516                         return;
517                 }
518         }
519 }
520
521 void CommonCommand_ladder(int request, entity caller)
522 {
523         switch (request)
524         {
525                 case CMD_REQUEST_COMMAND:
526                 {
527                         print_to(caller, ladder_reply);
528                         return;  // never fall through to usage
529                 }
530
531                 default:
532                 case CMD_REQUEST_USAGE:
533                 {
534                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " ladder"));
535                         print_to(caller, "  No arguments required.");
536                         return;
537                 }
538         }
539 }
540
541 void CommonCommand_lsmaps(int request, entity caller)
542 {
543         switch (request)
544         {
545                 case CMD_REQUEST_COMMAND:
546                 {
547                         print_to(caller, lsmaps_reply);
548                         return;  // never fall through to usage
549                 }
550
551                 default:
552                 case CMD_REQUEST_USAGE:
553                 {
554                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " lsmaps"));
555                         print_to(caller, "  No arguments required.");
556                         return;
557                 }
558         }
559 }
560
561 void CommonCommand_printmaplist(int request, entity caller)
562 {
563         switch (request)
564         {
565                 case CMD_REQUEST_COMMAND:
566                 {
567                         print_to(caller, maplist_reply);
568                         return;  // never fall through to usage
569                 }
570
571                 default:
572                 case CMD_REQUEST_USAGE:
573                 {
574                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " printmaplist"));
575                         print_to(caller, "  No arguments required.");
576                         return;
577                 }
578         }
579 }
580
581 void CommonCommand_rankings(int request, entity caller)
582 {
583         switch (request)
584         {
585                 case CMD_REQUEST_COMMAND:
586                 {
587                         print_to(caller, rankings_reply);
588                         return;  // never fall through to usage
589                 }
590
591                 default:
592                 case CMD_REQUEST_USAGE:
593                 {
594                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " rankings"));
595                         print_to(caller, "  No arguments required.");
596                         return;
597                 }
598         }
599 }
600
601 void CommonCommand_records(int request, entity caller)
602 {
603         switch (request)
604         {
605                 case CMD_REQUEST_COMMAND:
606                 {
607                         int num = stoi(argv(1));
608                         if(num > 0 && num <= 10 && records_reply[num - 1] != "")
609                                 print_to(caller, records_reply[num - 1]);
610                         else
611                         {
612                                 for (int i = 0; i < 10; ++i)
613                                         if (records_reply[i] != "") print_to(caller, records_reply[i]);
614                         }
615
616                         return;  // never fall through to usage
617                 }
618
619                 default:
620                 case CMD_REQUEST_USAGE:
621                 {
622                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records"));
623                         print_to(caller, "  No arguments required.");
624                         return;
625                 }
626         }
627 }
628
629 void CommonCommand_teamstatus(int request, entity caller)
630 {
631         switch (request)
632         {
633                 case CMD_REQUEST_COMMAND:
634                 {
635                         Score_NicePrint(caller);
636                         return;  // never fall through to usage
637                 }
638
639                 default:
640                 case CMD_REQUEST_USAGE:
641                 {
642                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " teamstatus"));
643                         print_to(caller, "  No arguments required.");
644                         return;
645                 }
646         }
647 }
648
649 void CommonCommand_time(int request, entity caller)
650 {
651         switch (request)
652         {
653                 case CMD_REQUEST_COMMAND:
654                 {
655                         print_to(caller, strcat("time = ", ftos(time)));
656                         print_to(caller, strcat("frame start = ", ftos(gettime(GETTIME_FRAMESTART))));
657                         print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
658                         print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
659                         print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
660                         print_to(caller, strcat("localtime = ", strftime(true, "%a %b %d %H:%M:%S %Z %Y")));
661                         print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %d %H:%M:%S %Z %Y")));
662                         return;
663                 }
664
665                 default:
666                 case CMD_REQUEST_USAGE:
667                 {
668                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " time"));
669                         print_to(caller, "  No arguments required.");
670                         return;
671                 }
672         }
673 }
674
675 void CommonCommand_timein(int request, entity caller)
676 {
677         switch (request)
678         {
679                 case CMD_REQUEST_COMMAND:
680                 {
681                         if (!caller || autocvar_sv_timeout)
682                         {
683                                 if (!timeout_status) { print_to(caller, "^7Error: There is no active timeout called."); }
684                                 else if (caller && (caller != timeout_caller))
685                                 {
686                                         print_to(caller, "^7Error: You are not allowed to stop the active timeout.");
687                                 }
688
689                                 else  // everything should be okay, continue aborting timeout
690                                 {
691                                         switch (timeout_status)
692                                         {
693                                                 case TIMEOUT_LEADTIME:
694                                                 {
695                                                         timeout_status = TIMEOUT_INACTIVE;
696                                                         timeout_time = 0;
697                                                         timeout_handler.nextthink = time;  // timeout_handler has to take care of it immediately
698                                                         bprint(strcat("^7The timeout was aborted by ", GetCallerName(caller), " !\n"));
699                                                         return;
700                                                 }
701
702                                                 case TIMEOUT_ACTIVE:
703                                                 {
704                                                         timeout_time = autocvar_sv_timeout_resumetime;
705                                                         timeout_handler.nextthink = time;  // timeout_handler has to take care of it immediately
706                                                         bprint(strcat("\{1}^1Attention: ^7", GetCallerName(caller), " resumed the game! Prepare for battle!\n"));
707                                                         return;
708                                                 }
709
710                                                 default: LOG_TRACE("timeout status was inactive, but this code was executed anyway?");
711                                                         return;
712                                         }
713                                 }
714                         }
715                         else { print_to(caller, "^1Timeins are not allowed to be called, enable them with sv_timeout 1.\n"); }
716
717                         return;  // never fall through to usage
718                 }
719
720                 default:
721                 case CMD_REQUEST_USAGE:
722                 {
723                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timein"));
724                         print_to(caller, "  No arguments required.");
725                         return;
726                 }
727         }
728 }
729
730 void CommonCommand_timeout(int request, entity caller)  // DEAR GOD THIS COMMAND IS TERRIBLE.
731 {
732         switch (request)
733         {
734                 case CMD_REQUEST_COMMAND:
735                 {
736                         if (!caller || autocvar_sv_timeout)
737                         {
738                                 float last_possible_timeout = ((autocvar_timelimit * 60) - autocvar_sv_timeout_leadtime - 1);
739
740                                 if (timeout_status) { print_to(caller, "^7Error: A timeout is already active."); }
741                                 else if (vote_called)
742                                 {
743                                         print_to(caller, "^7Error: You can not call a timeout while a vote is active.");
744                                 }
745                                 else if (warmup_stage && !g_warmup_allow_timeout)
746                                 {
747                                         print_to(caller, "^7Error: You can not call a timeout in warmup-stage.");
748                                 }
749                                 else if (time < game_starttime)
750                                 {
751                                         print_to(caller, "^7Error: You can not call a timeout while the map is being restarted.");
752                                 }
753                                 else if (caller && (CS(caller).allowed_timeouts < 1))
754                                 {
755                                         print_to(caller, "^7Error: You already used all your timeout calls for this map.");
756                                 }
757                                 else if (caller && !IS_PLAYER(caller))
758                                 {
759                                         print_to(caller, "^7Error: You must be a player to call a timeout.");
760                                 }
761                                 else if ((autocvar_timelimit) && (last_possible_timeout < time - game_starttime))
762                                 {
763                                         print_to(caller, "^7Error: It is too late to call a timeout now!");
764                                 }
765
766                                 else  // everything should be okay, proceed with starting the timeout
767                                 {
768                                         if (caller)   CS(caller).allowed_timeouts -= 1;
769                                         // write a bprint who started the timeout (and how many they have left)
770                                         bprint("\{1}", GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(CS(caller).allowed_timeouts), " timeout(s) left)") : ""), "!\n");
771
772                                         timeout_status = TIMEOUT_LEADTIME;
773                                         timeout_caller = caller;
774                                         timeout_time = autocvar_sv_timeout_length;
775                                         timeout_leadtime = autocvar_sv_timeout_leadtime;
776
777                                         timeout_handler = spawn();
778                                         setthink(timeout_handler, timeout_handler_think);
779                                         timeout_handler.nextthink = time;  // always let the entity think asap
780                                         
781                                         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_TIMEOUT);
782                                 }
783                         }
784                         else { print_to(caller, "^1Timeouts are not allowed to be called, enable them with sv_timeout 1.\n"); }
785
786                         return;  // never fall through to usage
787                 }
788
789                 default:
790                 case CMD_REQUEST_USAGE:
791                 {
792                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timeout"));
793                         print_to(caller, "  No arguments required.");
794                         return;
795                 }
796         }
797 }
798
799 void CommonCommand_who(int request, entity caller, int argc)
800 {
801         switch (request)
802         {
803                 case CMD_REQUEST_COMMAND:
804                 {
805                         float total_listed_players, is_bot;
806
807                         float privacy = (caller && autocvar_sv_status_privacy);
808                         string separator = strreplace("%", " ", strcat((argv(1) ? argv(1) : " "), "^7"));
809                         string tmp_netaddress, tmp_crypto_idfp;
810
811                         print_to(caller, strcat("List of client information", (privacy ? " (some data is hidden for privacy)" : ""), ":"));
812                         print_to(caller, sprintf(strreplace(" ", separator, " %-4s %-20s %-5s %-3s %-9s %-16s %s "),
813                                 "ent", "nickname", "ping", "pl", "time", "ip", "crypto_id"));
814
815                         total_listed_players = 0;
816                         FOREACH_CLIENT(true, {
817                                 is_bot = (IS_BOT_CLIENT(it));
818
819                                 if (is_bot)
820                                 {
821                                         tmp_netaddress = "null/botclient";
822                                         tmp_crypto_idfp = "null/botclient";
823                                 }
824                                 else if (privacy)
825                                 {
826                                         tmp_netaddress = "hidden";
827                                         tmp_crypto_idfp = "hidden";
828                                 }
829                                 else
830                                 {
831                                         tmp_netaddress = it.netaddress;
832                                         tmp_crypto_idfp = it.crypto_idfp;
833                                 }
834
835                                 print_to(caller, sprintf(strreplace(" ", separator, " #%-3d %-20.20s %-5d %-3d %-9s %-16s %s "),
836                                         etof(it),
837                                         it.netname,
838                                         CS(it).ping,
839                                         CS(it).ping_packetloss,
840                                         process_time(1, time - CS(it).jointime),
841                                         tmp_netaddress,
842                                         tmp_crypto_idfp));
843
844                                 ++total_listed_players;
845                         });
846
847                         print_to(caller, strcat("Finished listing ", ftos(total_listed_players), " client(s) out of ", ftos(maxclients), " slots."));
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), " who [separator]"));
856                         print_to(caller, "  Where 'separator' is the optional string to separate the values with, default is a space.");
857                         return;
858                 }
859         }
860 }
861
862 /* use this when creating a new command, making sure to place it in alphabetical order... also,
863 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
864 void CommonCommand_(int request, entity caller)
865 {
866     switch(request)
867     {
868         case CMD_REQUEST_COMMAND:
869         {
870
871             return; // never fall through to usage
872         }
873
874         default:
875         case CMD_REQUEST_USAGE:
876         {
877             print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " "));
878             print_to(caller, "  No arguments required.");
879             return;
880         }
881     }
882 }
883 */