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