]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/common.qc
Merge remote branch 'origin/master' into samual/updatecommands
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / common.qc
1 // ====================================================
2 //  Shared code for server commands, written by Samual
3 //  Last updated: December 25th, 2011
4 // ====================================================
5
6 // select the proper prefix for usage and other messages
7 string GetCommandPrefix(entity caller)
8 {
9         if(caller)
10                 return "cmd";
11         else
12                 return "sv_cmd";
13 }
14
15 // if client return player nickname, or if server return admin nickname
16 string GetCallerName(entity caller)
17 {
18         if(caller)
19                 return caller.netname;
20         else
21                 return admin_name(); //((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
22 }
23
24 // verify that the client provided is acceptable for use
25 float VerifyClientEntity(entity client, float must_be_real, float must_be_bots)
26 {
27         if not(client.flags & FL_CLIENT)
28                 return CLIENT_DOESNT_EXIST;
29         else if(must_be_real && (clienttype(client) != CLIENTTYPE_REAL))
30                 return CLIENT_NOT_REAL;
31         else if(must_be_bots && (clienttype(client) != CLIENTTYPE_BOT))
32                 return CLIENT_NOT_BOT;
33                 
34         return CLIENT_ACCEPTABLE;
35 }
36
37 // if the client is not acceptable, return a string to be used for error messages
38 string GetClientErrorString(float clienterror, string original_input)
39 {
40         switch(clienterror)
41         {
42                 case CLIENT_DOESNT_EXIST: { return strcat("Client '", original_input, "' doesn't exist"); }
43                 case CLIENT_NOT_REAL: { return strcat("Client '", original_input, "' is not real"); }
44                 case CLIENT_NOT_BOT: { return strcat("Client '", original_input, "' is not a bot"); }
45                 default: { return "Incorrect usage of GetClientErrorString"; }
46         }
47 }
48
49 // is this entity number even in the possible range of entities?
50 float VerifyClientNumber(float tmp_number)
51 {
52         if((tmp_number < 1) || (tmp_number > maxclients))
53                 return FALSE;
54         else
55                 return TRUE;
56 }
57
58 // find a player which matches the input string, and return their entity
59 entity GetFilteredEntity(string input)
60 {
61         entity tmp_player, selection;
62         float tmp_number;
63         
64         if(substring(input, 0, 1) == "#")
65                 tmp_number = stof(substring(input, 1, -1));
66         else
67                 tmp_number = stof(input);
68         
69         if(VerifyClientNumber(tmp_number))
70         {
71                 selection = edict_num(tmp_number);
72         }
73         else
74         {
75                 FOR_EACH_CLIENT(tmp_player)
76                         if (strdecolorize(tmp_player.netname) == strdecolorize(input))
77                                 selection = tmp_player;
78         }
79         
80         return selection;
81 }
82
83 // same thing, but instead return their edict number
84 float GetFilteredNumber(string input)
85 {
86         entity selection = GetFilteredEntity(input);
87         float output;
88         
89         if(selection) { output = num_for_edict(selection); }
90
91         return output;
92 }
93
94 // switch between sprint and print depending on whether the reciever is the server or a player
95 void print_to(entity to, string input)
96 {
97     if(to)
98         sprint(to, strcat(input, "\n"));
99     else
100         print(input, "\n");
101 }
102
103
104 // ===================================================
105 //  Common commands used in both sv_cmd.qc and cmd.qc
106 // ===================================================
107
108 void CommonCommand_cvar_changes(float request, entity caller)
109 {
110         switch(request)
111         {
112                 case CMD_REQUEST_COMMAND:
113                 {
114                         print_to(caller, cvar_changes);
115                         return; // never fall through to usage
116                 }
117                         
118                 default:
119                 case CMD_REQUEST_USAGE:
120                 {
121                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_changes"));
122                         print_to(caller, "  No arguments required.");
123                         print_to(caller, "See also: ^2cvar_purechanges^7");
124                         return;
125                 }
126         }
127 }
128
129 void CommonCommand_cvar_purechanges(float request, entity caller)
130 {
131         switch(request)
132         {
133                 case CMD_REQUEST_COMMAND:
134                 {
135                         print_to(caller, cvar_purechanges);
136                         return; // never fall through to usage
137                 }
138                         
139                 default:
140                 case CMD_REQUEST_USAGE:
141                 {
142                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_purechanges"));
143                         print_to(caller, "  No arguments required.");
144                         print_to(caller, "See also: ^2cvar_changes^7");
145                         return;
146                 }
147         }
148 }
149
150 void CommonCommand_info(float request, entity caller, float argc) // legacy
151 {       
152         switch(request)
153         {
154                 case CMD_REQUEST_COMMAND:
155                 {
156                         string command = builtin_cvar_string(strcat("sv_info_", argv(1))); 
157                         
158                         if(command)
159                                 wordwrap_sprint(command, 1000); 
160                         else
161                                 print_to(caller, "ERROR: unsupported info command");
162                                 
163                         return; // never fall through to usage
164                 }
165                         
166                 default:
167                 case CMD_REQUEST_USAGE:
168                 {
169                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info request"));
170                         print_to(caller, "  Where 'request' is the suffixed string appended onto the request for cvar.");
171                         return;
172                 }
173         }
174 }
175
176 void CommonCommand_ladder(float request, entity caller)
177 {
178         switch(request)
179         {
180                 case CMD_REQUEST_COMMAND:
181                 {
182                         print_to(caller, ladder_reply);
183                         return; // never fall through to usage
184                 }
185                         
186                 default:
187                 case CMD_REQUEST_USAGE:
188                 {
189                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " ladder"));
190                         print_to(caller, "  No arguments required.");
191                         return;
192                 }
193         }
194 }
195
196 void CommonCommand_lsmaps(float request, entity caller)
197 {
198         switch(request)
199         {
200                 case CMD_REQUEST_COMMAND:
201                 {
202                         print_to(caller, lsmaps_reply);
203                         return; // never fall through to usage
204                 }
205                         
206                 default:
207                 case CMD_REQUEST_USAGE:
208                 {
209                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " lsmaps"));
210                         print_to(caller, "  No arguments required.");
211                         return;
212                 }
213         }
214 }
215
216 void CommonCommand_lsnewmaps(float request, entity caller)
217 {
218         switch(request)
219         {
220                 case CMD_REQUEST_COMMAND:
221                 {
222                         print_to(caller, lsnewmaps_reply);
223                         return; // never fall through to usage
224                 }
225                         
226                 default:
227                 case CMD_REQUEST_USAGE:
228                 {
229                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " lsnewmaps"));
230                         print_to(caller, "  No arguments required.");
231                         return;
232                 }
233         }
234 }
235
236 void CommonCommand_maplist(float request, entity caller)
237 {
238         switch(request)
239         {
240                 case CMD_REQUEST_COMMAND:
241                 {
242                         print_to(caller, maplist_reply);
243                         return; // never fall through to usage
244                 }
245                         
246                 default:
247                 case CMD_REQUEST_USAGE:
248                 {
249                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " maplist"));
250                         print_to(caller, "  No arguments required.");
251                         return;
252                 }
253         }
254 }
255
256 /*
257 void GameCommand_rankings(float request) // this is OLD.... jeez.
258 {
259         switch(request)
260         {
261                 case CMD_REQUEST_COMMAND:
262                 {
263                         strunzone(rankings_reply);
264                         rankings_reply = strzone(getrankings());
265                         print(rankings_reply);
266                         return;
267                 }
268                         
269                 default:
270                 case CMD_REQUEST_USAGE:
271                 {
272                         print("\nUsage:^3 sv_cmd rankings");
273                         print("  No arguments required.");
274                         return;
275                 }
276         }
277 }
278 */
279
280 void CommonCommand_rankings(float request, entity caller)
281 {
282         switch(request)
283         {
284                 case CMD_REQUEST_COMMAND:
285                 {
286                         print_to(caller, rankings_reply);
287                         return; // never fall through to usage
288                 }
289                         
290                 default:
291                 case CMD_REQUEST_USAGE:
292                 {
293                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " rankings"));
294                         print_to(caller, "  No arguments required.");
295                         return;
296                 }
297         }
298 }
299
300 void CommonCommand_records(float request, entity caller)
301 {       
302         switch(request)
303         {
304                 case CMD_REQUEST_COMMAND:
305                 {
306                         float i;
307                         
308                         for(i = 0; i < 10; ++i)
309                                 print_to(caller, records_reply[i]);
310                                 
311                         return; // never fall through to usage
312                 }
313                         
314                 default:
315                 case CMD_REQUEST_USAGE:
316                 {
317                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records"));
318                         print_to(caller, "  No arguments required.");
319                         return;
320                 }
321         }
322 }
323
324 void CommonCommand_teamstatus(float request, entity caller)
325 {
326         switch(request)
327         {
328                 case CMD_REQUEST_COMMAND:
329                 {
330                         Score_NicePrint(caller);
331                         return; // never fall through to usage
332                 }
333                         
334                 default:
335                 case CMD_REQUEST_USAGE:
336                 {
337                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " teamstatus"));
338                         print_to(caller, "  No arguments required.");
339                         return;
340                 }
341         }
342 }
343
344 void CommonCommand_time(float request, entity caller)
345 {
346         switch(request)
347         {
348                 case CMD_REQUEST_COMMAND:
349                 {
350                         print_to(caller, strcat("time = ", ftos(time)));
351                         print_to(caller, strcat("frame start = ", ftos(gettime(GETTIME_FRAMESTART))));
352                         print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
353                         print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
354                         print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
355                         print_to(caller, strcat("localtime = ", strftime(TRUE, "%a %b %e %H:%M:%S %Z %Y")));
356                         print_to(caller, strcat("gmtime = ", strftime(FALSE, "%a %b %e %H:%M:%S %Z %Y")));
357                         return;
358                 }
359                         
360                 default:
361                 case CMD_REQUEST_USAGE:
362                 {
363                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " time"));
364                         print_to(caller, "  No arguments required.");
365                         return;
366                 }
367         }
368 }
369
370 void CommonCommand_timein(float request, entity caller) // todo entirely re-write this
371 {
372         switch(request)
373         {
374                 case CMD_REQUEST_COMMAND:
375                 {
376                         if(caller.flags & FL_CLIENT)
377                         {
378                                 if(autocvar_sv_timeout)
379                                 {
380                                         if (!timeoutStatus)
381                                                 return print_to(caller, "^7Error: There is no active timeout which could be aborted!");
382                                         if (caller != timeoutInitiator)
383                                                 return print_to(caller, "^7Error: You may not abort the active timeout. Only the player who called it can do that!");
384                                                 
385                                         if (timeoutStatus == 1) 
386                                         {
387                                                 remainingTimeoutTime = timeoutStatus = 0;
388                                                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
389                                                 bprint(strcat("^7The timeout was aborted by ", caller.netname, " !\n"));
390                                         }
391                                         else if (timeoutStatus == 2) 
392                                         {
393                                                 //only shorten the remainingTimeoutTime if it makes sense
394                                                 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) 
395                                                 {
396                                                         bprint(strcat("^1Attention: ^7", caller.netname, " resumed the game! Prepare for battle!\n"));
397                                                         remainingTimeoutTime = autocvar_sv_timeout_resumetime;
398                                                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
399                                                 }
400                                                 else
401                                                         print_to(caller, "^7Error: Your resumegame call was discarded!");
402                                         }
403                                 }
404                         }
405                         return; // never fall through to usage
406                 }
407                         
408                 default:
409                 case CMD_REQUEST_USAGE:
410                 {
411                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timein"));
412                         print_to(caller, "  No arguments required.");
413                         return;
414                 }
415         }
416 }
417
418 void CommonCommand_timeout(float request, entity caller) // DEAR GOD THIS COMMAND IS TERRIBLE.
419 {
420         switch(request)
421         {
422                 case CMD_REQUEST_COMMAND:
423                 {
424                         if(autocvar_sv_timeout) 
425                         {
426                                 if(vote_called) { print_to(caller, "^7Error: You can not call a timeout while a vote is active."); }
427                                 else if(inWarmupStage && !g_warmup_allow_timeout) { print_to(caller, "^7Error: You can not call a timeout in warmup-stage."); }
428                                 else if(time < game_starttime) { print_to(caller, "^7Error: You can not call a timeout while the map is being restarted."); }
429                                 else if(caller && (caller.allowedTimeouts < 1)) { print_to(caller, "^7Error: You already used all your timeout calls for this map."); }
430                                 else if(caller && (caller.classname != "player")) { print_to(caller, "^7Error: You must be a player to call a timeout."); }
431                                 
432                                 else // everything should be okay, proceed with starting the timeout
433                                 {
434                                         if (timeoutStatus != 2) {
435                                                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
436                                                 if (autocvar_timelimit) {
437                                                         //a timelimit was used
438                                                         float myTl;
439                                                         myTl = autocvar_timelimit;
440
441                                                         float lastPossibleTimeout;
442                                                         lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
443
444                                                         if (lastPossibleTimeout < time - game_starttime)
445                                                                 return print_to(caller, "^7Error: It is too late to call a timeout now!");
446                                                 }
447                                         }
448                                                 
449                                         if(caller) { caller.allowedTimeouts -= 1; }
450                                         
451                                         bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowedTimeouts), " timeouts left)") : string_null), "!\n"); // write a bprint who started the timeout (and how many they have left)
452                                         
453                                         remainingTimeoutTime = autocvar_sv_timeout_length;
454                                         remainingLeadTime = autocvar_sv_timeout_leadtime;
455                                         
456                                         timeoutInitiator = caller;
457                                         
458                                         // if another timeout was already active, don't change its status (which was 1 or 2) to 1
459                                         if (timeoutStatus == 0) 
460                                         {
461                                                 timeoutStatus = 1;
462                                                 timeoutHandler = spawn();
463                                                 timeoutHandler.think = timeoutHandler_Think;
464                                         }
465                                         
466                                         timeoutHandler.nextthink = time; //always let the entity think asap
467
468                                         Announce("timeoutcalled");
469                                 }
470                         }
471                         return; // never fall through to usage
472                 }
473                         
474                 default:
475                 case CMD_REQUEST_USAGE:
476                 {
477                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timeout"));
478                         print_to(caller, "  No arguments required.");
479                         return;
480                 }
481         }
482 }
483
484 void CommonCommand_who(float request, entity caller, float argc)
485 {
486         switch(request)
487         {
488                 case CMD_REQUEST_COMMAND:
489                 {
490                         float total_listed_players, tmp_hours, tmp_minutes, tmp_seconds, is_bot;
491                         entity tmp_player;
492                         
493                         float privacy = (caller && autocvar_sv_status_privacy);
494                         string separator = strreplace("%", " ", strcat((argv(1) ? argv(1) : " "), "^7"));
495                         string tmp_netaddress, tmp_crypto_idfp;
496                         
497                         print_to(caller, strcat("List of client information", (privacy ? " (some data is hidden for privacy)" : string_null), ":"));
498                         print_to(caller, sprintf(strreplace(" ", separator, " %-4s %-20s %-5s %-3s %-9s %-16s %s "), 
499                                 "ent", "nickname", "ping", "pl", "time", "ip", "crypto_id"));
500                         
501                         FOR_EACH_CLIENT(tmp_player)
502                         {
503                                 is_bot = (clienttype(tmp_player) == CLIENTTYPE_BOT);
504                                 
505                                 if(is_bot)
506                                 {
507                                         tmp_netaddress = "null/botclient";
508                                         tmp_crypto_idfp = "null/botclient";
509                                 }
510                                 else if(privacy)
511                                 {
512                                         tmp_netaddress = "hidden";
513                                         tmp_crypto_idfp = "hidden";
514                                 }
515                                 else
516                                 {
517                                         tmp_netaddress = tmp_player.netaddress;
518                                         tmp_crypto_idfp = tmp_player.crypto_idfp;
519                                 }
520                                 
521                                 tmp_hours = tmp_minutes = tmp_seconds = 0;
522                                 
523                                 tmp_seconds = floor(time - tmp_player.jointime);
524                                 tmp_minutes = floor(tmp_seconds / 60);
525                                 tmp_hours = floor(tmp_minutes / 60);
526
527                                 if(tmp_minutes) { tmp_seconds -= (tmp_minutes * 60); }                          
528                                 if(tmp_hours) { tmp_minutes -= (tmp_hours * 60); }
529
530                                 print_to(caller, sprintf(strreplace(" ", separator, " #%-3d %-20.20s %-5d %-3d %-9s %-16s %s "), 
531                                         num_for_edict(tmp_player), 
532                                         tmp_player.netname,
533                                         tmp_player.ping, 
534                                         tmp_player.ping_packetloss, 
535                                         sprintf("%02d:%02d:%02d", tmp_hours, tmp_minutes, tmp_seconds),
536                                         tmp_netaddress,
537                                         tmp_crypto_idfp));
538                                 
539                                 ++total_listed_players;
540                         }
541                         
542                         print_to(caller, strcat("Finished listing ", ftos(total_listed_players), " client(s) out of ", ftos(maxclients), " slots."));
543                         
544                         return; // never fall through to usage
545                 }
546                         
547                 default:
548                 case CMD_REQUEST_USAGE:
549                 {
550                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " who [separator]"));
551                         print_to(caller, "  Where 'separator' is the optional string to separate the values with, default is a space.");
552                         return;
553                 }
554         }
555 }
556
557 /* use this when creating a new command, making sure to place it in alphabetical order... also,
558 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
559 void CommonCommand_(float request, entity caller)
560 {
561         switch(request)
562         {
563                 case CMD_REQUEST_COMMAND:
564                 {
565                         
566                         return; // never fall through to usage
567                 }
568                         
569                 default:
570                 case CMD_REQUEST_USAGE:
571                 {
572                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " "));
573                         print_to(caller, "  No arguments required.");
574                         return;
575                 }
576         }
577 }
578 */