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