Merge branch 'master' into terencehill/string_prefixes_cleanup
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / common.qc
1 #include "common.qh"
2
3 #include "../../common/counting.qh"
4
5
6 // ====================================================
7 //  Shared code for server commands, written by Samual
8 //  Last updated: December 27th, 2011
9 // ====================================================
10
11 // select the proper prefix for usage and other messages
12 string GetCommandPrefix(entity caller)
13 {
14         if(caller)
15                 return "cmd";
16         else
17                 return "sv_cmd";
18 }
19
20 // if client return player nickname, or if server return admin nickname
21 string GetCallerName(entity caller)
22 {
23         if(caller)
24                 return caller.netname;
25         else
26                 return admin_name(); //((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
27 }
28
29 // verify that the client provided is acceptable for kicking
30 float VerifyKickableEntity(entity client)
31 {
32         if (!IS_REAL_CLIENT(client))
33                 return CLIENT_NOT_REAL;
34         return CLIENT_ACCEPTABLE;
35 }
36
37 // verify that the client provided is acceptable for use
38 float VerifyClientEntity(entity client, float must_be_real, float must_be_bots)
39 {
40         if (!IS_CLIENT(client))
41                 return CLIENT_DOESNT_EXIST;
42         else if(must_be_real && !IS_REAL_CLIENT(client))
43                 return CLIENT_NOT_REAL;
44         else if(must_be_bots && !IS_BOT_CLIENT(client))
45                 return CLIENT_NOT_BOT;
46
47         return CLIENT_ACCEPTABLE;
48 }
49
50 // if the client is not acceptable, return a string to be used for error messages
51 string GetClientErrorString(float clienterror, string original_input)
52 {
53         switch(clienterror)
54         {
55                 case CLIENT_DOESNT_EXIST: { return strcat("Client '", original_input, "' doesn't exist"); }
56                 case CLIENT_NOT_REAL: { return strcat("Client '", original_input, "' is not real"); }
57                 case CLIENT_NOT_BOT: { return strcat("Client '", original_input, "' is not a bot"); }
58                 default: { return "Incorrect usage of GetClientErrorString"; }
59         }
60 }
61
62 // is this entity number even in the possible range of entities?
63 float VerifyClientNumber(float tmp_number)
64 {
65         if((tmp_number < 1) || (tmp_number > maxclients))
66                 return false;
67         else
68                 return true;
69 }
70
71 entity GetIndexedEntity(float argc, float start_index)
72 {
73         entity tmp_player, selection;
74         float tmp_number, index;
75         string tmp_string;
76
77         next_token = -1;
78         index = start_index;
79         selection = world;
80
81         if(argc > start_index)
82         {
83                 if(substring(argv(index), 0, 1) == "#")
84                 {
85                         tmp_string = substring(argv(index), 1, -1);
86                         ++index;
87
88                         if(tmp_string != "") // is it all one token? like #1
89                         {
90                                 tmp_number = stof(tmp_string);
91                         }
92                         else if(argc > index) // no, it's two tokens? # 1
93                         {
94                                 tmp_number = stof(argv(index));
95                                 ++index;
96                         }
97                         else
98                                 tmp_number = 0;
99                 }
100                 else // maybe it's ONLY a number?
101                 {
102                         tmp_number = stof(argv(index));
103                         ++index;
104                 }
105
106                 if(VerifyClientNumber(tmp_number))
107                 {
108                         selection = edict_num(tmp_number); // yes, it was a number
109                 }
110                 else // no, maybe it's a name?
111                 {
112                         FOR_EACH_CLIENT(tmp_player)
113                                 if (strdecolorize(tmp_player.netname) == strdecolorize(argv(start_index)))
114                                         selection = tmp_player;
115
116                         index = (start_index + 1);
117                 }
118         }
119
120         next_token = index;
121         //print(strcat("start_index: ", ftos(start_index), ", next_token: ", ftos(next_token), ", edict: ", ftos(num_for_edict(selection)), ".\n"));
122         return selection;
123 }
124
125 // find a player which matches the input string, and return their entity
126 entity GetFilteredEntity(string input)
127 {
128         entity tmp_player, selection;
129         float tmp_number;
130
131         if(substring(input, 0, 1) == "#")
132                 tmp_number = stof(substring(input, 1, -1));
133         else
134                 tmp_number = stof(input);
135
136         if(VerifyClientNumber(tmp_number))
137         {
138                 selection = edict_num(tmp_number);
139         }
140         else
141         {
142                 selection = world;
143                 FOR_EACH_CLIENT(tmp_player)
144                         if (strdecolorize(tmp_player.netname) == strdecolorize(input))
145                                 selection = tmp_player;
146         }
147
148         return selection;
149 }
150
151 // same thing, but instead return their edict number
152 float GetFilteredNumber(string input)
153 {
154         entity selection = GetFilteredEntity(input);
155         float output;
156
157         output = num_for_edict(selection);
158
159         return output;
160 }
161
162 // switch between sprint and print depending on whether the receiver is the server or a player
163 void print_to(entity to, string input)
164 {
165     if(to)
166         sprint(to, strcat(input, "\n"));
167     else
168         print(input, "\n");
169 }
170
171 // ==========================================
172 //  Supporting functions for common commands
173 // ==========================================
174
175 // used by CommonCommand_timeout() and CommonCommand_timein() to handle game pausing and messaging and such.
176 void timeout_handler_reset()
177 {
178         timeout_caller = world;
179         timeout_time = 0;
180         timeout_leadtime = 0;
181
182         remove(self);
183 }
184
185 void timeout_handler_think()
186 {
187         entity tmp_player;
188
189         switch(timeout_status)
190         {
191                 case TIMEOUT_ACTIVE:
192                 {
193                         if(timeout_time > 0) // countdown is still going
194                         {
195                                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TIMEOUT_ENDING, timeout_time);
196
197                                 if(timeout_time == autocvar_sv_timeout_resumetime) // play a warning sound when only <sv_timeout_resumetime> seconds are left
198                                         Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_PREPARE);
199
200                                 self.nextthink = time + TIMEOUT_SLOWMO_VALUE; // think again in one second
201                                 timeout_time -= 1; // decrease the time counter
202                         }
203                         else // time to end the timeout
204                         {
205                                 timeout_status = TIMEOUT_INACTIVE;
206
207                                 // reset the slowmo value back to normal
208                                 cvar_set("slowmo", ftos(orig_slowmo));
209
210                                 // unlock the view for players so they can move around again
211                                 FOR_EACH_REALPLAYER(tmp_player)
212                                         tmp_player.fixangle = false;
213
214                                 timeout_handler_reset();
215                         }
216
217                         return;
218                 }
219
220                 case TIMEOUT_LEADTIME:
221                 {
222                         if(timeout_leadtime > 0) // countdown is still going
223                         {
224                                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TIMEOUT_BEGINNING, timeout_leadtime);
225
226                                 self.nextthink = time + 1; // think again in one second
227                                 timeout_leadtime -= 1; // decrease the time counter
228                         }
229                         else // time to begin the timeout
230                         {
231                                 timeout_status = TIMEOUT_ACTIVE;
232
233                                 // set the slowmo value to the timeout default slowmo value
234                                 cvar_set("slowmo", ftos(TIMEOUT_SLOWMO_VALUE));
235
236                                 // reset all the flood variables
237                                 FOR_EACH_CLIENT(tmp_player)
238                                         tmp_player.nickspamcount = tmp_player.nickspamtime = tmp_player.floodcontrol_chat =
239                                         tmp_player.floodcontrol_chatteam = tmp_player.floodcontrol_chattell =
240                                         tmp_player.floodcontrol_voice = tmp_player.floodcontrol_voiceteam = 0;
241
242                                 // copy .v_angle to .lastV_angle for every player in order to fix their view during pause (see PlayerPreThink)
243                                 FOR_EACH_REALPLAYER(tmp_player)
244                                         tmp_player.lastV_angle = tmp_player.v_angle;
245
246                                 self.nextthink = time; // think again next frame to handle it under TIMEOUT_ACTIVE code
247                         }
248
249                         return;
250                 }
251
252
253                 case TIMEOUT_INACTIVE:
254                 default:
255                 {
256                         timeout_handler_reset();
257                         return;
258                 }
259         }
260 }
261
262
263
264 // ===================================================
265 //  Common commands used in both sv_cmd.qc and cmd.qc
266 // ===================================================
267
268 void CommonCommand_cvar_changes(float request, entity caller)
269 {
270         switch(request)
271         {
272                 case CMD_REQUEST_COMMAND:
273                 {
274                         print_to(caller, cvar_changes);
275                         return; // never fall through to usage
276                 }
277
278                 default:
279                 case CMD_REQUEST_USAGE:
280                 {
281                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_changes"));
282                         print_to(caller, "  No arguments required.");
283                         print_to(caller, "See also: ^2cvar_purechanges^7");
284                         return;
285                 }
286         }
287 }
288
289 void CommonCommand_cvar_purechanges(float request, entity caller)
290 {
291         switch(request)
292         {
293                 case CMD_REQUEST_COMMAND:
294                 {
295                         print_to(caller, cvar_purechanges);
296                         return; // never fall through to usage
297                 }
298
299                 default:
300                 case CMD_REQUEST_USAGE:
301                 {
302                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_purechanges"));
303                         print_to(caller, "  No arguments required.");
304                         print_to(caller, "See also: ^2cvar_changes^7");
305                         return;
306                 }
307         }
308 }
309
310 void CommonCommand_info(float request, entity caller, float argc)
311 {
312         switch(request)
313         {
314                 case CMD_REQUEST_COMMAND:
315                 {
316                         string command = builtin_cvar_string(strcat("sv_info_", argv(1)));
317
318                         if(command)
319                                 wordwrap_sprint(command, 1000);
320                         else
321                                 print_to(caller, "ERROR: unsupported info command");
322
323                         return; // never fall through to usage
324                 }
325
326                 default:
327                 case CMD_REQUEST_USAGE:
328                 {
329                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info request"));
330                         print_to(caller, "  Where 'request' is the suffixed string appended onto the request for cvar.");
331                         return;
332                 }
333         }
334 }
335
336 void CommonCommand_ladder(float request, entity caller)
337 {
338         switch(request)
339         {
340                 case CMD_REQUEST_COMMAND:
341                 {
342                         print_to(caller, ladder_reply);
343                         return; // never fall through to usage
344                 }
345
346                 default:
347                 case CMD_REQUEST_USAGE:
348                 {
349                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " ladder"));
350                         print_to(caller, "  No arguments required.");
351                         return;
352                 }
353         }
354 }
355
356 void CommonCommand_lsmaps(float request, entity caller)
357 {
358         switch(request)
359         {
360                 case CMD_REQUEST_COMMAND:
361                 {
362                         print_to(caller, lsmaps_reply);
363                         return; // never fall through to usage
364                 }
365
366                 default:
367                 case CMD_REQUEST_USAGE:
368                 {
369                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " lsmaps"));
370                         print_to(caller, "  No arguments required.");
371                         return;
372                 }
373         }
374 }
375
376 void CommonCommand_printmaplist(float request, entity caller)
377 {
378         switch(request)
379         {
380                 case CMD_REQUEST_COMMAND:
381                 {
382                         print_to(caller, maplist_reply);
383                         return; // never fall through to usage
384                 }
385
386                 default:
387                 case CMD_REQUEST_USAGE:
388                 {
389                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " printmaplist"));
390                         print_to(caller, "  No arguments required.");
391                         return;
392                 }
393         }
394 }
395
396 void CommonCommand_rankings(float request, entity caller)
397 {
398         switch(request)
399         {
400                 case CMD_REQUEST_COMMAND:
401                 {
402                         print_to(caller, rankings_reply);
403                         return; // never fall through to usage
404                 }
405
406                 default:
407                 case CMD_REQUEST_USAGE:
408                 {
409                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " rankings"));
410                         print_to(caller, "  No arguments required.");
411                         return;
412                 }
413         }
414 }
415
416 void CommonCommand_records(float request, entity caller)
417 {
418         switch(request)
419         {
420                 case CMD_REQUEST_COMMAND:
421                 {
422                         for(int i = 0; i < 10; ++i)
423                                 if(records_reply[i] != "")
424                                         print_to(caller, records_reply[i]);
425
426                         return; // never fall through to usage
427                 }
428
429                 default:
430                 case CMD_REQUEST_USAGE:
431                 {
432                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records"));
433                         print_to(caller, "  No arguments required.");
434                         return;
435                 }
436         }
437 }
438
439 void CommonCommand_teamstatus(float request, entity caller)
440 {
441         switch(request)
442         {
443                 case CMD_REQUEST_COMMAND:
444                 {
445                         Score_NicePrint(caller);
446                         return; // never fall through to usage
447                 }
448
449                 default:
450                 case CMD_REQUEST_USAGE:
451                 {
452                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " teamstatus"));
453                         print_to(caller, "  No arguments required.");
454                         return;
455                 }
456         }
457 }
458
459 void CommonCommand_time(float request, entity caller)
460 {
461         switch(request)
462         {
463                 case CMD_REQUEST_COMMAND:
464                 {
465                         print_to(caller, strcat("time = ", ftos(time)));
466                         print_to(caller, strcat("frame start = ", ftos(gettime(GETTIME_FRAMESTART))));
467                         print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
468                         print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
469                         print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
470                         print_to(caller, strcat("localtime = ", strftime(true, "%a %b %e %H:%M:%S %Z %Y")));
471                         print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %e %H:%M:%S %Z %Y")));
472                         return;
473                 }
474
475                 default:
476                 case CMD_REQUEST_USAGE:
477                 {
478                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " time"));
479                         print_to(caller, "  No arguments required.");
480                         return;
481                 }
482         }
483 }
484
485 void CommonCommand_timein(float request, entity caller)
486 {
487         switch(request)
488         {
489                 case CMD_REQUEST_COMMAND:
490                 {
491                         if(!caller || autocvar_sv_timeout)
492                         {
493                                 if (!timeout_status) { print_to(caller, "^7Error: There is no active timeout called."); }
494                                 else if(caller && (caller != timeout_caller)) { print_to(caller, "^7Error: You are not allowed to stop the active timeout."); }
495
496                                 else // everything should be okay, continue aborting timeout
497                                 {
498                                         switch(timeout_status)
499                                         {
500                                                 case TIMEOUT_LEADTIME:
501                                                 {
502                                                         timeout_status = TIMEOUT_INACTIVE;
503                                                         timeout_time = 0;
504                                                         timeout_handler.nextthink = time; // timeout_handler has to take care of it immediately
505                                                         bprint(strcat("^7The timeout was aborted by ", GetCallerName(caller), " !\n"));
506                                                         return;
507                                                 }
508
509                                                 case TIMEOUT_ACTIVE:
510                                                 {
511                                                         timeout_time = autocvar_sv_timeout_resumetime;
512                                                         timeout_handler.nextthink = time; // timeout_handler has to take care of it immediately
513                                                         bprint(strcat("^1Attention: ^7", GetCallerName(caller), " resumed the game! Prepare for battle!\n"));
514                                                         return;
515                                                 }
516
517                                                 default: dprint("timeout status was inactive, but this code was executed anyway?"); return;
518                                         }
519                                 }
520                         }
521                         else { print_to(caller, "^1Timeins are not allowed to be called, enable them with sv_timeout 1.\n"); }
522
523                         return; // never fall through to usage
524                 }
525
526                 default:
527                 case CMD_REQUEST_USAGE:
528                 {
529                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timein"));
530                         print_to(caller, "  No arguments required.");
531                         return;
532                 }
533         }
534 }
535
536 void CommonCommand_timeout(float request, entity caller) // DEAR GOD THIS COMMAND IS TERRIBLE.
537 {
538         switch(request)
539         {
540                 case CMD_REQUEST_COMMAND:
541                 {
542                         if(!caller || autocvar_sv_timeout)
543                         {
544                                 float last_possible_timeout = ((autocvar_timelimit * 60) - autocvar_sv_timeout_leadtime - 1);
545
546                                 if(timeout_status) { print_to(caller, "^7Error: A timeout is already active."); }
547                                 else if(vote_called) { print_to(caller, "^7Error: You can not call a timeout while a vote is active."); }
548                                 else if(warmup_stage && !g_warmup_allow_timeout) { print_to(caller, "^7Error: You can not call a timeout in warmup-stage."); }
549                                 else if(time < game_starttime) { print_to(caller, "^7Error: You can not call a timeout while the map is being restarted."); }
550                                 else if(caller && (caller.allowed_timeouts < 1)) { print_to(caller, "^7Error: You already used all your timeout calls for this map."); }
551                                 else if(caller && !IS_PLAYER(caller)) { print_to(caller, "^7Error: You must be a player to call a timeout."); }
552                                 else if((autocvar_timelimit) && (last_possible_timeout < time - game_starttime)) { print_to(caller, "^7Error: It is too late to call a timeout now!"); }
553
554                                 else // everything should be okay, proceed with starting the timeout
555                                 {
556                                         if(caller) { caller.allowed_timeouts -= 1; }
557
558                                         bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); // write a bprint who started the timeout (and how many they have left)
559
560                                         timeout_status = TIMEOUT_LEADTIME;
561                                         timeout_caller = caller;
562                                         timeout_time = autocvar_sv_timeout_length;
563                                         timeout_leadtime = autocvar_sv_timeout_leadtime;
564
565                                         timeout_handler = spawn();
566                                         timeout_handler.think = timeout_handler_think;
567                                         timeout_handler.nextthink = time; // always let the entity think asap
568
569                                         Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_TIMEOUT);
570                                 }
571                         }
572                         else { print_to(caller, "^1Timeouts are not allowed to be called, enable them with sv_timeout 1.\n"); }
573
574                         return; // never fall through to usage
575                 }
576
577                 default:
578                 case CMD_REQUEST_USAGE:
579                 {
580                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timeout"));
581                         print_to(caller, "  No arguments required.");
582                         return;
583                 }
584         }
585 }
586
587 void CommonCommand_who(float request, entity caller, float argc)
588 {
589         switch(request)
590         {
591                 case CMD_REQUEST_COMMAND:
592                 {
593                         float total_listed_players, is_bot;
594                         entity tmp_player;
595
596                         float privacy = (caller && autocvar_sv_status_privacy);
597                         string separator = strreplace("%", " ", strcat((argv(1) ? argv(1) : " "), "^7"));
598                         string tmp_netaddress, tmp_crypto_idfp;
599
600                         print_to(caller, strcat("List of client information", (privacy ? " (some data is hidden for privacy)" : ""), ":"));
601                         print_to(caller, sprintf(strreplace(" ", separator, " %-4s %-20s %-5s %-3s %-9s %-16s %s "),
602                                 "ent", "nickname", "ping", "pl", "time", "ip", "crypto_id"));
603
604                         total_listed_players = 0;
605                         FOR_EACH_CLIENT(tmp_player)
606                         {
607                                 is_bot = (IS_BOT_CLIENT(tmp_player));
608
609                                 if(is_bot)
610                                 {
611                                         tmp_netaddress = "null/botclient";
612                                         tmp_crypto_idfp = "null/botclient";
613                                 }
614                                 else if(privacy)
615                                 {
616                                         tmp_netaddress = "hidden";
617                                         tmp_crypto_idfp = "hidden";
618                                 }
619                                 else
620                                 {
621                                         tmp_netaddress = tmp_player.netaddress;
622                                         tmp_crypto_idfp = tmp_player.crypto_idfp;
623                                 }
624
625                                 print_to(caller, sprintf(strreplace(" ", separator, " #%-3d %-20.20s %-5d %-3d %-9s %-16s %s "),
626                                         num_for_edict(tmp_player),
627                                         tmp_player.netname,
628                                         tmp_player.ping,
629                                         tmp_player.ping_packetloss,
630                                         process_time(1, time - tmp_player.jointime),
631                                         tmp_netaddress,
632                                         tmp_crypto_idfp));
633
634                                 ++total_listed_players;
635                         }
636
637                         print_to(caller, strcat("Finished listing ", ftos(total_listed_players), " client(s) out of ", ftos(maxclients), " slots."));
638
639                         return; // never fall through to usage
640                 }
641
642                 default:
643                 case CMD_REQUEST_USAGE:
644                 {
645                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " who [separator]"));
646                         print_to(caller, "  Where 'separator' is the optional string to separate the values with, default is a space.");
647                         return;
648                 }
649         }
650 }
651
652 /* use this when creating a new command, making sure to place it in alphabetical order... also,
653 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
654 void CommonCommand_(float request, entity caller)
655 {
656         switch(request)
657         {
658                 case CMD_REQUEST_COMMAND:
659                 {
660
661                         return; // never fall through to usage
662                 }
663
664                 default:
665                 case CMD_REQUEST_USAGE:
666                 {
667                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " "));
668                         print_to(caller, "  No arguments required.");
669                         return;
670                 }
671         }
672 }
673 */