]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/clientcommands.qc
Finish re-write of VoteCount() and ReadyCount() -- now with several new
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / clientcommands.qc
1 // =========================================================
2 //  Server side networked commands code, reworked by Samual
3 //  Last updated: December 6th, 2011
4 // =========================================================
5
6 #define CC_REQUEST_COMMAND 1
7 #define CC_REQUEST_USAGE 2
8
9 .float cmd_floodtime;
10 .float cmd_floodcount;
11 .float checkfail;
12 .float lms_spectate_warning;
13
14 string MapVote_Suggest(string m);
15 //void MapVote_SendPicture(float id)
16
17
18 // ============================
19 //  Misc. Supporting Functions
20 // ============================
21
22 float SV_ParseClientCommand_floodcheck()
23 {
24         if (timeoutStatus != 2) // if the game is not paused... but wait, doesn't that mean it could be dos'd by pausing it? eh? (old code)
25         {
26                 if(time <= (self.cmd_floodtime + autocvar_sv_clientcommand_antispam_time))
27                 {
28                         self.cmd_floodcount += 1;
29                         if(self.cmd_floodcount > autocvar_sv_clientcommand_antispam_count) { return FALSE; } // too much spam, halt
30                 }
31                 else
32                 {
33                         self.cmd_floodtime = time;
34                         self.cmd_floodcount = 1;
35                 }
36         }
37         return TRUE; // continue, as we're not flooding yet
38 }
39
40
41 // =======================
42 //  Command Sub-Functions
43 // =======================
44
45 void ClientCommand_autoswitch(float request, float argc)
46 {
47         switch(request)
48         {
49                 case CC_REQUEST_COMMAND:
50                 {
51                         self.autoswitch = ("0" != argv(1));
52                         sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
53                         return; // never fall through to usage
54                 }
55                         
56                 default:
57                 case CC_REQUEST_USAGE:
58                 {
59                         sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
60                         sprint(self, "  Where 'selection' is 1 or 0 for on or off.\n"); 
61                         return;
62                 }
63         }
64 }
65
66 void ClientCommand_checkfail(float request, string command) // used only by client side code
67 {
68         switch(request)
69         {
70                 case CC_REQUEST_COMMAND:
71                 {
72                         print(sprintf("CHECKFAIL: %s (%s) epically failed check %s\n", self.netname, self.netaddress, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1))));
73                         self.checkfail = 1;
74                         return; // never fall through to usage
75                 }
76                         
77                 default:
78                 case CC_REQUEST_USAGE:
79                 {
80                         sprint(self, "\nUsage:^3 cmd checkfail message\n");
81                         sprint(self, "  Where 'message' is the message reported by client about the fail.\n");
82                         return;
83                 }
84         }
85 }
86
87 void ClientCommand_clientversion(float request, float argc) // used only by client side code
88 {
89         switch(request)
90         {
91                 case CC_REQUEST_COMMAND:
92                 {
93                         if(self.flags & FL_CLIENT)
94                         {
95                                 self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
96                                 
97                                 if(self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
98                                 {
99                                         self.version_mismatch = 1;
100                                         ClientKill_TeamChange(-2); // observe
101                                 } 
102                                 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force) 
103                                 {
104                                         //JoinBestTeam(self, FALSE, TRUE);
105                                 } 
106                                 else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0)) 
107                                 {
108                                         self.classname = "observer"; // really?
109                                         stuffcmd(self, "menu_showteamselect\n");
110                                 }
111                         }
112                         return; // never fall through to usage
113                 }
114                         
115                 default:
116                 case CC_REQUEST_USAGE:
117                 {
118                         sprint(self, "\nUsage:^3 cmd clientversion version\n");
119                         sprint(self, "  Where 'version' is the game version reported by self.\n");
120                         return;
121                 }
122         }
123 }
124
125 void ClientCommand_cvar_changes(float request)
126 {
127         switch(request)
128         {
129                 case CC_REQUEST_COMMAND:
130                 {
131                         sprint(self, cvar_changes);
132                         return; // never fall through to usage
133                 }
134                         
135                 default:
136                 case CC_REQUEST_USAGE:
137                 {
138                         sprint(self, "\nUsage:^3 sv_cmd cvar_changes\n");
139                         sprint(self, "  No arguments required.\n");
140                         //sprint(self, "See also: ^2cvar_purechanges^7\n");
141                         return;
142                 }
143         }
144 }
145
146 void ClientCommand_cvar_purechanges(float request)
147 {
148         switch(request)
149         {
150                 case CC_REQUEST_COMMAND:
151                 {
152                         sprint(self, cvar_purechanges);
153                         return; // never fall through to usage
154                 }
155                         
156                 default:
157                 case CC_REQUEST_USAGE:
158                 {
159                         sprint(self, "\nUsage:^3 sv_cmd cvar_purechanges\n");
160                         sprint(self, "  No arguments required.\n");
161                         //sprint(self, "See also: ^2cvar_changes^7\n");
162                         return;
163                 }
164         }
165 }
166
167 void ClientCommand_getmapvotepic(float request, float argc)
168 {
169         switch(request)
170         {
171                 case CC_REQUEST_COMMAND:
172                 {
173                         if(intermission_running)                                
174                                 MapVote_SendPicture(stof(argv(1)));
175
176                         return; // never fall through to usage
177                 }
178                         
179                 default:
180                 case CC_REQUEST_USAGE:
181                 {
182                         sprint(self, "\nUsage:^3 cmd getmapvotepic mapid\n");
183                         sprint(self, "  Where 'mapid' is the id number of the map to request an image of on the map vote selection menu.\n");
184                         return;
185                 }
186         }
187 }
188
189 void ClientCommand_info(float request, float argc)
190 {       
191         switch(request)
192         {
193                 case CC_REQUEST_COMMAND:
194                 {
195                         string command;
196                         
197                         command = builtin_cvar_string(strcat("sv_info_", argv(1))); 
198                         if(command)
199                                 wordwrap_sprint(command, 1111); // why 1111?
200                         else
201                                 sprint(self, "ERROR: unsupported info command\n");
202                                 
203                         return; // never fall through to usage
204                 }
205                         
206                 default:
207                 case CC_REQUEST_USAGE:
208                 {
209                         sprint(self, "\nUsage:^3 cmd info request\n");
210                         sprint(self, "  Where 'request' is the suffixed string appended onto the request for cvar.\n");
211                         return;
212                 }
213         }
214 }
215
216 void ClientCommand_join(float request)
217 {
218         switch(request)
219         {
220                 case CC_REQUEST_COMMAND:
221                 {
222                         if(self.flags & FL_CLIENT)
223                         {
224                                 if(self.classname != "player" && !lockteams && !g_arena)
225                                 {
226                                         if(nJoinAllowed(1)) 
227                                         {
228                                                 if(g_ca) { self.caplayer = 1; }
229                                                 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
230                                                 
231                                                 self.classname = "player";
232                                                 PlayerScore_Clear(self);
233                                                 bprint ("^4", self.netname, "^4 is playing now\n");
234                                                 PutClientInServer();
235                                         }
236                                         else 
237                                         {
238                                                 //player may not join because of g_maxplayers is set
239                                                 centerprint(self, PREVENT_JOIN_TEXT);
240                                         }
241                                 }
242                         }
243                         return; // never fall through to usage
244                 }
245                         
246                 default:
247                 case CC_REQUEST_USAGE:
248                 {
249                         sprint(self, "\nUsage:^3 cmd join\n");
250                         sprint(self, "  No arguments required.\n");
251                         return;
252                 }
253         }
254 }
255
256 void ClientCommand_ladder(float request)
257 {
258         switch(request)
259         {
260                 case CC_REQUEST_COMMAND:
261                 {
262                         sprint(self, ladder_reply);
263                         return; // never fall through to usage
264                 }
265                         
266                 default:
267                 case CC_REQUEST_USAGE:
268                 {
269                         sprint(self, "\nUsage:^3 cmd ladder\n");
270                         sprint(self, "  No arguments required.\n");
271                         return;
272                 }
273         }
274 }
275
276 void ClientCommand_lsmaps(float request)
277 {
278         switch(request)
279         {
280                 case CC_REQUEST_COMMAND:
281                 {
282                         sprint(self, lsmaps_reply);
283                         return; // never fall through to usage
284                 }
285                         
286                 default:
287                 case CC_REQUEST_USAGE:
288                 {
289                         sprint(self, "\nUsage:^3 cmd lsmaps\n");
290                         sprint(self, "  No arguments required.\n");
291                         return;
292                 }
293         }
294 }
295
296 void ClientCommand_lsnewmaps(float request)
297 {
298         switch(request)
299         {
300                 case CC_REQUEST_COMMAND:
301                 {
302                         sprint(self, lsnewmaps_reply);
303                         return; // never fall through to usage
304                 }
305                         
306                 default:
307                 case CC_REQUEST_USAGE:
308                 {
309                         sprint(self, "\nUsage:^3 cmd lsnewmaps\n");
310                         sprint(self, "  No arguments required.\n");
311                         return;
312                 }
313         }
314 }
315
316 void ClientCommand_maplist(float request)
317 {
318         switch(request)
319         {
320                 case CC_REQUEST_COMMAND:
321                 {
322                         sprint(self, maplist_reply);
323                         return; // never fall through to usage
324                 }
325                         
326                 default:
327                 case CC_REQUEST_USAGE:
328                 {
329                         sprint(self, "\nUsage:^3 cmd maplist\n");
330                         sprint(self, "  No arguments required.\n");
331                         return;
332                 }
333         }
334 }
335
336 void ClientCommand_rankings(float request)
337 {
338         switch(request)
339         {
340                 case CC_REQUEST_COMMAND:
341                 {
342                         sprint(self, rankings_reply);
343                         return; // never fall through to usage
344                 }
345                         
346                 default:
347                 case CC_REQUEST_USAGE:
348                 {
349                         sprint(self, "\nUsage:^3 cmd rankings\n");
350                         sprint(self, "  No arguments required.\n");
351                         return;
352                 }
353         }
354 }
355
356 void ClientCommand_ready(float request) 
357 {
358         switch(request)
359         {
360                 case CC_REQUEST_COMMAND:
361                 {
362                         if(self.flags & FL_CLIENT)
363                         {
364                                 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
365                                 {
366                                         if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
367                                         {
368                                                 if (self.ready) // toggle
369                                                 {
370                                                         self.ready = FALSE;
371                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
372                                                 }
373                                                 else
374                                                 {
375                                                         self.ready = TRUE;
376                                                         bprint(self.netname, "^2 is ready\n");
377                                                 }
378
379                                                 // cannot reset the game while a timeout is active!
380                                                 if(!timeoutStatus)
381                                                         ReadyCount();
382                                         } else {
383                                                 sprint(self, "^1Game has already been restarted\n");
384                                         }
385                                 }
386                         }
387                         return; // never fall through to usage
388                 }
389                         
390                 default:
391                 case CC_REQUEST_USAGE:
392                 {
393                         sprint(self, "\nUsage:^3 cmd ready\n");
394                         sprint(self, "  No arguments required.\n");
395                         return;
396                 }
397         }
398 }
399
400 void ClientCommand_records(float request) // TODO: Isn't this flooding with the sprint messages? Old code, but perhaps bad?
401 {       
402         switch(request)
403         {
404                 case CC_REQUEST_COMMAND:
405                 {
406                         float i;
407                         
408                         for(i = 0; i < 10; ++i)
409                                 sprint(self, records_reply[i]);
410                                 
411                         return; // never fall through to usage
412                 }
413                         
414                 default:
415                 case CC_REQUEST_USAGE:
416                 {
417                         sprint(self, "\nUsage:^3 cmd records\n");
418                         sprint(self, "  No arguments required.\n");
419                         return;
420                 }
421         }
422 }
423
424 void ClientCommand_reportcvar(float request, float argc, string command) // TODO: confirm this works
425 {       
426         switch(request)
427         {
428                 case CC_REQUEST_COMMAND:
429                 {
430                         float tokens;
431                         string s;
432                         
433                         if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
434                         {
435                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
436                                 tokens = tokenize_console(s);
437                         }
438                         GetCvars(1);
439                         return; // never fall through to usage
440                 }
441                         
442                 default:
443                 case CC_REQUEST_USAGE:
444                 {
445                         sprint(self, "\nUsage:^3 cmd reportcvar <cvar>\n");
446                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
447                         return;
448                 }
449         }
450 }
451
452 void ClientCommand_say(float request, float argc, string command)
453 {
454         switch(request)
455         {
456                 case CC_REQUEST_COMMAND:
457                 {
458                         if(argc >= 2) { Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
459                         return; // never fall through to usage
460                 }
461                         
462                 default:
463                 case CC_REQUEST_USAGE:
464                 {
465                         sprint(self, "\nUsage:^3 cmd say <message>\n");
466                         sprint(self, "  Where 'message' is the string of text to say.\n");
467                         return;
468                 }
469         }
470 }
471
472 void ClientCommand_say_team(float request, float argc, string command)
473 {
474         switch(request)
475         {
476                 case CC_REQUEST_COMMAND:
477                 {
478                         if(argc >= 2) { Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
479                         return; // never fall through to usage
480                 }
481                         
482                 default:
483                 case CC_REQUEST_USAGE:
484                 {
485                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
486                         sprint(self, "  Where 'message' is the string of text to say.\n");
487                         return;
488                 }
489         }
490 }
491
492 void ClientCommand_selectteam(float request, float argc) // TODO: Update the messages for this command
493 {
494         switch(request)
495         {
496                 case CC_REQUEST_COMMAND:
497                 {
498                         float selection;
499                         
500                         if (self.flags & FL_CLIENT)
501                         {
502                                 if(teamplay)
503                                         if not(self.team_forced > 0) 
504                                                 if not(lockteams) 
505                                                 {
506                                                         switch(argv(1))
507                                                         {
508                                                                 case "red": selection = COLOR_TEAM1; break;
509                                                                 case "blue": selection = COLOR_TEAM2; break;
510                                                                 case "yellow": selection = COLOR_TEAM3; break;
511                                                                 case "pink": selection = COLOR_TEAM4; break;
512                                                                 case "auto": selection = (-1); break;
513                                                                 
514                                                                 default: break;
515                                                         }
516                                                         
517                                                         if(selection)
518                                                         {
519                                                                 if(self.team != selection || self.deadflag != DEAD_NO)
520                                                                         ClientKill_TeamChange(selection);
521                                                                 else
522                                                                         sprint(self, "^7You already are on that team.\n");
523                                                         }
524                                                 }
525                                                 else
526                                                         sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
527                                         else
528                                                 sprint(self, "^7selectteam can not be used as your team is forced\n");
529                                 else
530                                         sprint(self, "^7selectteam can only be used in teamgames\n");
531                         }
532                         return; // never fall through to usage
533                 }
534
535                 default:
536                 case CC_REQUEST_USAGE:
537                 {
538                         //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
539                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
540                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
541                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
542                         return;
543                 }
544         }
545 }
546
547 void ClientCommand_sentcvar(float request, float argc, string command)
548 {
549         switch(request)
550         {
551                 case CC_REQUEST_COMMAND:
552                 {
553                         float tokens;
554                         string s;
555                         
556                         if(argc == 2) // undefined cvar: use the default value on the server then
557                         {
558                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
559                                 tokens = tokenize_console(s);
560                         }
561                         GetCvars(1);
562                         return; // never fall through to usage
563                 }
564                         
565                 default:
566                 case CC_REQUEST_USAGE:
567                 {
568                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
569                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
570                         return;
571                 }
572         }
573 }
574
575 void ClientCommand_spectate(float request)
576 {
577         switch(request)
578         {
579                 case CC_REQUEST_COMMAND:
580                 {
581                         if(self.flags & FL_CLIENT)
582                         {
583                                 if(g_arena) { return; } 
584                                 if(g_lms)
585                                 {
586                                         if(self.lms_spectate_warning)
587                                         {
588                                                 // mark player as spectator
589                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
590                                         }
591                                         else
592                                         {
593                                                 self.lms_spectate_warning = 1;
594                                                 sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
595                                                 return;
596                                         }
597                                 }
598                                 
599                                 if(self.classname == "player" && autocvar_sv_spectate == 1) 
600                                         ClientKill_TeamChange(-2); // observe
601                                 
602                                 // in CA, allow a dead player to move to spectatators (without that, caplayer!=0 will be moved back to the player list)
603                                 // note: if arena game mode is ever done properly, this needs to be removed.
604                                 if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer"))
605                                 {
606                                         sprint(self, "WARNING: you will spectate in the next round.\n");
607                                         self.caplayer = 0;
608                                 }
609                         }
610                         return; // never fall through to usage
611                 }
612                         
613                 default:
614                 case CC_REQUEST_USAGE:
615                 {
616                         sprint(self, "\nUsage:^3 cmd spectate\n");
617                         sprint(self, "  No arguments required.\n");
618                         return;
619                 }
620         }
621 }
622
623 void ClientCommand_suggestmap(float request, float argc)
624 {
625         switch(request)
626         {
627                 case CC_REQUEST_COMMAND:
628                 {
629                         sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
630                         return; // never fall through to usage
631                 }
632                         
633                 default:
634                 case CC_REQUEST_USAGE:
635                 {
636                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
637                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
638                         return;
639                 }
640         }
641 }
642
643 void ClientCommand_teamstatus(float request)
644 {
645         switch(request)
646         {
647                 case CC_REQUEST_COMMAND:
648                 {
649                         Score_NicePrint(self);
650                         return; // never fall through to usage
651                 }
652                         
653                 default:
654                 case CC_REQUEST_USAGE:
655                 {
656                         sprint(self, "\nUsage:^3 cmd teamstatus\n");
657                         sprint(self, "  No arguments required.\n");
658                         return;
659                 }
660         }
661 }
662
663 void ClientCommand_tell(float request, float argc, string command)
664 {
665         switch(request)
666         {
667                 case CC_REQUEST_COMMAND:
668                 {
669                         entity e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
670                         
671                         if(e && argc > ParseCommandPlayerSlotTarget_firsttoken)
672                         {
673                                 Say(self, FALSE, e, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
674                         }
675                         else
676                         {
677                                 if(argc > ParseCommandPlayerSlotTarget_firsttoken)
678                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
679                         }
680                         return; // never fall through to usage
681                 }
682                         
683                 default:
684                 case CC_REQUEST_USAGE:
685                 {
686                         sprint(self, "\nUsage:^3 cmd tell playerid <message>\n");
687                         sprint(self, "  Where 'playerid' is the entity number of the player to send the 'message' to.\n");
688                         return;
689                 }
690         }
691 }
692
693 void ClientCommand_timein(float request)
694 {
695         switch(request)
696         {
697                 case CC_REQUEST_COMMAND:
698                 {
699                         if(self.flags & FL_CLIENT)
700                         {
701                                 if(autocvar_sv_timeout)
702                                 {
703                                         if (!timeoutStatus)
704                                                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
705                                         if (self != timeoutInitiator)
706                                                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
707                                                 
708                                         if (timeoutStatus == 1) 
709                                         {
710                                                 remainingTimeoutTime = timeoutStatus = 0;
711                                                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
712                                                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
713                                         }
714                                         else if (timeoutStatus == 2) 
715                                         {
716                                                 //only shorten the remainingTimeoutTime if it makes sense
717                                                 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) 
718                                                 {
719                                                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
720                                                         remainingTimeoutTime = autocvar_sv_timeout_resumetime;
721                                                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
722                                                 }
723                                                 else
724                                                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
725                                         }
726                                 }
727                         }
728                         return; // never fall through to usage
729                 }
730                         
731                 default:
732                 case CC_REQUEST_USAGE:
733                 {
734                         sprint(self, "\nUsage:^3 cmd timein\n");
735                         sprint(self, "  No arguments required.\n");
736                         return;
737                 }
738         }
739 }
740
741 void ClientCommand_timeout(float request) // DEAR GOD THIS COMMAND IS TERRIBLE.
742 {
743         switch(request)
744         {
745                 case CC_REQUEST_COMMAND:
746                 {
747                         if(self.flags & FL_CLIENT)
748                         {
749                                 if(autocvar_sv_timeout) 
750                                 {
751                                         if(self.classname == "player") 
752                                         {
753                                                 if(votecalled)
754                                                         sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
755                                                 else
756                                                 {
757                                                         if (inWarmupStage && !g_warmup_allow_timeout)
758                                                                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
759                                                         if (time < game_starttime )
760                                                                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
761                                                                 
762                                                         if (timeoutStatus != 2) {
763                                                                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
764                                                                 if (autocvar_timelimit) {
765                                                                         //a timelimit was used
766                                                                         float myTl;
767                                                                         myTl = autocvar_timelimit;
768
769                                                                         float lastPossibleTimeout;
770                                                                         lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
771
772                                                                         if (lastPossibleTimeout < time - game_starttime)
773                                                                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
774                                                                 }
775                                                         }
776                                                         
777                                                         //player may not call a timeout if he has no calls left
778                                                         if (self.allowedTimeouts < 1)
779                                                                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
780                                                                 
781                                                                 
782                                                         //now all required checks are passed
783                                                         self.allowedTimeouts -= 1;
784                                                         bprint(self.netname, " ^7called a timeout (", ftos(self.allowedTimeouts), " timeouts left)!\n"); //write a bprint who started the timeout (and how many he has left)
785                                                         remainingTimeoutTime = autocvar_sv_timeout_length;
786                                                         remainingLeadTime = autocvar_sv_timeout_leadtime;
787                                                         timeoutInitiator = self;
788                                                         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
789                                                                 timeoutStatus = 1;
790                                                                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
791                                                                 timeoutHandler = spawn();
792                                                                 timeoutHandler.think = timeoutHandler_Think;
793                                                         }
794                                                         timeoutHandler.nextthink = time; //always let the entity think asap
795
796                                                         //inform all connected clients about the timeout call
797                                                         Announce("timeoutcalled");
798                                                 }
799                                         }
800                                         else
801                                                 sprint(self, "^7Error: only players can call a timeout!\n");
802                                 }
803                         }
804                         return; // never fall through to usage
805                 }
806                         
807                 default:
808                 case CC_REQUEST_USAGE:
809                 {
810                         sprint(self, "\nUsage:^3 cmd timeout\n");
811                         sprint(self, "  No arguments required.\n");
812                         return;
813                 }
814         }
815 }
816
817 void ClientCommand_voice(float request, float argc, string command)
818 {
819         switch(request)
820         {
821                 case CC_REQUEST_COMMAND:
822                 {
823                         if(argc >= 3)
824                                 VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
825                         else
826                                 VoiceMessage(argv(1), "");
827                         return; // never fall through to usage
828                 }
829                         
830                 default:
831                 case CC_REQUEST_USAGE:
832                 {
833                         sprint(self, "\nUsage:^3 cmd voice\n");
834                         sprint(self, "  FIXME ARGUMENTS UNKNOWN.\n");
835                         return;
836                 }
837         }
838 }
839
840 string strlimitedlen(string input, float strip_colors, float limit)
841 {
842         if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
843                 return input;
844         else
845                 return strcat(substring(input, 0, (strlen(input) - 3)), "...");
846 }
847
848 void ClientCommand_who(float request)
849 {
850         switch(request)
851         {
852                 case CC_REQUEST_COMMAND:
853                 {
854                         float total_listed_players, tmp_hours, tmp_minutes, tmp_seconds;
855                         entity tmp_player;
856                         string tmp_player_name;
857                         
858                         sprint(self, strcat("List of client information", (autocvar_sv_status_privacy ? " (some data is hidden for privacy)" : string_null), ":\n"));
859                         sprint(self, sprintf(" %-4s %-20s %-5s %-3s %-9s %-16s %s\n", "ent", "nickname", "ping", "pl", "time", "ip", "crypto_id"));
860                         
861                         FOR_EACH_CLIENT(tmp_player)
862                         {
863                                 tmp_player_name = strlimitedlen(tmp_player.netname, TRUE, 20);
864                                 
865                                 tmp_hours = tmp_minutes = tmp_seconds = 0;
866                                 
867                                 tmp_seconds = (time - tmp_player.jointime);
868                                 tmp_minutes = (tmp_seconds / 60);
869                                 
870                                 if(tmp_minutes)
871                                 {
872                                         tmp_seconds -= (tmp_minutes * 60);
873                                         tmp_hours = (tmp_minutes / 60);
874                                         
875                                         if(tmp_hours) { tmp_minutes -= (tmp_hours * 60); }
876                                 }
877                                 
878                                 sprint(self, sprintf(" %-4s %-20s %-5d %-3d %-9s %-16s %s\n", 
879                                         strcat("#", ftos(num_for_edict(tmp_player))), 
880                                         strcat(tmp_player_name, sprintf("%*s", (20 - strlen(strdecolorize(tmp_player_name))), "")),
881                                         tmp_player.ping, tmp_player.ping_packetloss, 
882                                         sprintf("%02d:%02d:%02d", tmp_hours, tmp_minutes, tmp_seconds),
883                                         (autocvar_sv_status_privacy ? "hidden" : tmp_player.netaddress),
884                                         (autocvar_sv_status_privacy ? "hidden" : tmp_player.crypto_idfp)));
885                                         
886                                 ++total_listed_players;
887                         }
888                         
889                         sprint(self, strcat("Finished listing ", ftos(total_listed_players), " client(s). \n"));
890                         
891                         return; // never fall through to usage
892                 }
893                         
894                 default:
895                 case CC_REQUEST_USAGE:
896                 {
897                         sprint(self, "\nUsage:^3 cmd who\n");
898                         sprint(self, "  No arguments required.\n");
899                         return;
900                 }
901         }
902 }
903
904 /* use this when creating a new command, making sure to place it in alphabetical order.
905 void ClientCommand_(float request)
906 {
907         switch(request)
908         {
909                 case CC_REQUEST_COMMAND:
910                 {
911                         
912                         return; // never fall through to usage
913                 }
914                         
915                 default:
916                 case CC_REQUEST_USAGE:
917                 {
918                         sprint(self, "\nUsage:^3 cmd \n");
919                         sprint(self, "  No arguments required.\n");
920                         return;
921                 }
922         }
923 }
924 */
925
926
927 // =====================================
928 //  Macro system for networked commands
929 // =====================================
930
931 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
932 #define CLIENT_COMMANDS(request,arguments,command) \
933         CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(request, arguments), "Whether or not to switch automatically when getting a better weapon") \
934         CLIENT_COMMAND("checkfail", ClientCommand_checkfail(request, command), "Report if a client-side check failed") \
935         CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
936         CLIENT_COMMAND("cvar_changes", ClientCommand_cvar_changes(request), "Prints a list of all changed server cvars") \
937         CLIENT_COMMAND("cvar_purechanges", ClientCommand_cvar_purechanges(request), "Prints a list of all changed gameplay cvars") \
938         CLIENT_COMMAND("getmapvotepic", ClientCommand_getmapvotepic(request, arguments), "Retrieve mapshot picture from the server") \
939         CLIENT_COMMAND("info", ClientCommand_info(request, arguments), "Request for unique server information set up by admin") \
940         CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
941         CLIENT_COMMAND("ladder", ClientCommand_ladder(request), "Get information about top players if supported") \
942         CLIENT_COMMAND("lsmaps", ClientCommand_lsmaps(request), "List maps which can be used with the current game mode") \
943         CLIENT_COMMAND("lsnewmaps", ClientCommand_lsnewmaps(request), "List maps which TODO") \
944         CLIENT_COMMAND("maplist", ClientCommand_maplist(request), "Full server maplist reply") \
945         CLIENT_COMMAND("rankings", ClientCommand_rankings(request), "Print information about rankings") \
946         CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
947         CLIENT_COMMAND("records", ClientCommand_records(request), "List top 10 records for the current map") \
948         CLIENT_COMMAND("reportcvar", ClientCommand_reportcvar(request, arguments, command), "Old system for sending a client cvar to the server") \
949         CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
950         CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
951         CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \
952         CLIENT_COMMAND("sentcvar", ClientCommand_sentcvar(request, arguments, command), "New system for sending a client cvar to the server") \
953         CLIENT_COMMAND("spectate", ClientCommand_spectate(request), "Become an observer") \
954         CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \
955         CLIENT_COMMAND("teamstatus", ClientCommand_teamstatus(request), "Print detailed score information for all players") \
956         CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
957         CLIENT_COMMAND("timein", ClientCommand_timein(request), "Resume the game from being paused with a timeout") \
958         CLIENT_COMMAND("timeout", ClientCommand_timeout(request), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
959         CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
960         CLIENT_COMMAND("vote", VoteCommand(request, self, arguments, command), "Request an action to be voted upon by players") \
961         CLIENT_COMMAND("who", ClientCommand_who(request), "Display detailed client information about all players") \
962         /* nothing */
963         
964 void ClientCommand_macro_help()
965 {
966         #define CLIENT_COMMAND(name,function,description) \
967                 { print("  ^2", name, "^7: ", description, "\n"); }
968                 
969         CLIENT_COMMANDS(0, 0, "")
970         #undef CLIENT_COMMAND
971         
972         return;
973 }
974
975 float ClientCommand_macro_command(float argc, string command)
976 {
977         #define CLIENT_COMMAND(name,function,description) \
978                 { if(name == strtolower(argv(0))) { function; return TRUE; } }
979                 
980         CLIENT_COMMANDS(CC_REQUEST_COMMAND, argc, command)
981         #undef CLIENT_COMMAND
982         
983         return FALSE;
984 }
985
986 float ClientCommand_macro_usage(float argc, string command)
987 {
988         #define CLIENT_COMMAND(name,function,description) \
989                 { if(name == strtolower(argv(1))) { function; return TRUE; } }
990                 
991         CLIENT_COMMANDS(CC_REQUEST_USAGE, argc, command)
992         #undef CLIENT_COMMAND
993         
994         return FALSE;
995 }
996
997
998 // ======================================
999 //  Main Function Called By Engine (cmd)
1000 // ======================================
1001 // If this function exists, server game code parses clientcommand before the engine code gets it.
1002
1003 void SV_ParseClientCommand(string command)
1004 {
1005         float argc = tokenize_console(command);
1006         
1007         // for floodcheck
1008         switch(strtolower(argv(0)))
1009         {
1010                 // exempt commands which are not subject to floodcheck
1011                 case "begin": break; // handled by engine in host_cmd.c
1012                 case "getmapvotepic": break; // handled by server in this file
1013                 case "pause": break; // handled by engine in host_cmd.c
1014                 case "prespawn": break; // handled by engine in host_cmd.c
1015                 case "reportcvar": break; // handled by server in this file
1016                 case "sentcvar": break; // handled by server in this file
1017                 case "spawn": break; // handled by engine in host_cmd.c
1018                 
1019                 default: 
1020                         if(SV_ParseClientCommand_floodcheck())
1021                                 break; // "TRUE": continue, as we're not flooding yet
1022                         else
1023                                 return; // "FALSE": not allowed to continue, halt
1024         }
1025         
1026         /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
1027         if(argv(0) == "help") 
1028         {
1029                 if(argc == 1) 
1030                 {
1031                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
1032                         ClientCommand_macro_help;
1033                         sprint(self, "For help about specific commands, type cmd help COMMAND\n");
1034                         return;
1035                 } 
1036                 else if(ClientCommand_macro_usage(argc, command)) // Instead of trying to call a command, we're going to see detailed information about it
1037                 {
1038                         return;
1039                 }
1040         } 
1041         else*/ if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
1042         {
1043                 return; // handled by a mutator
1044         }
1045         else if(CheatCommand(argc)) 
1046         {
1047                 return; // handled by server/cheats.qc
1048         }
1049         else if(ClientCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
1050         {
1051                 return; // handled by one of the above GameCommand_* functions
1052         }
1053         else
1054                 clientcommand(self, command);
1055 }