]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/clientcommands.qc
voice flood protection!!!\
[divverent/nexuiz.git] / data / qcsrc / server / clientcommands.qc
1 entity nagger;
2 float readycount;
3 float Nagger_SendEntity(entity to, float sendflags)
4 {
5         float nags;
6         WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
7
8         nags = 0;
9         if(readycount)
10         {
11                 nags |= 1;
12                 if(to.ready == 0)
13                         nags |= 2;
14         }
15         if(votecalled)
16         {
17                 nags |= 4;
18                 if(to.vote_vote == 0)
19                         nags |= 8;
20         }
21
22         if(sendflags & 128)
23         {
24                 WriteByte(MSG_ENTITY, nags | 128);
25                 if(votecalled)
26                         WriteString(MSG_ENTITY, votecalledvote_display);
27                 else
28                         WriteString(MSG_ENTITY, "");
29         }
30         else
31                 WriteByte(MSG_ENTITY, nags);
32
33         return TRUE;
34 }
35 void Nagger_Init()
36 {
37         nagger = spawn();
38         Net_LinkEntity(nagger);
39         nagger.SendFlags = 128;
40         nagger.SendEntity = Nagger_SendEntity;
41 }
42 void Nagger_VoteChanged()
43 {
44         if(nagger)
45                 nagger.SendFlags = 128;
46 }
47 void Nagger_VoteCountChanged()
48 {
49         if(nagger)
50                 nagger.SendFlags = 1;
51 }
52 void Nagger_ReadyCounted()
53 {
54         if(nagger)
55                 nagger.SendFlags = 1;
56 }
57
58 void ReadyCount();
59 string MapVote_Suggest(string m);
60
61 entity GetPlayer(string name)
62 {
63         float num;
64         entity e;
65         string ns;
66
67         if(substring(name, 0, 1) == "#") {
68                 num = stof(substring(name, 1, 999));
69                 if(num >= 1 && num <= maxclients) {
70                         for((e = world); num > 0; --num, (e = nextent(e)))
71                                 ;
72                         //if(clienttype(e) == CLIENTTYPE_REAL)
73                         if(e.classname == "player")
74                                 return e;
75                 }
76         } else {
77                 ns = strdecolorize(name);
78                 FOR_EACH_REALPLAYER(e) {
79                         if(!strcasecmp(strdecolorize(e.netname), ns)) {
80                                 return e;
81                         }
82                 }
83         }
84         return world;
85 }
86
87 //float ctf_clientcommand();
88 float readyrestart_happened;
89 void SV_ParseClientCommand(string s) {
90         local string cmd;
91         local float tokens, f, effectnum;
92         local vector start, end;
93
94         tokens = tokenize_sane(s);
95
96         if(GameCommand_Vote(s, self)) {
97                 return;
98         } else if(GameCommand_MapVote(argv(0))) {
99                 return;
100         } else if(argv(0) == "autoswitch") {
101                 // be backwards compatible with older clients (enabled)
102                 self.autoswitch = ("0" != argv(1));
103                 local string autoswitchmsg;
104                 if (self.autoswitch) {
105                         autoswitchmsg = "on";
106                 } else {
107                         autoswitchmsg = "off";
108                 }
109                 sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
110         } else if(argv(0) == "clientversion") {
111                 if not(self.flags & FL_CLIENT)
112                         return;
113                 if (argv(1) == "$gameversion") {
114                         //versionmsg = "^1client is too old to get versioninfo.\nUPDATE!!! (http://www.nexuiz.com)^8";
115                         // either that or someone wants to be funny
116                         self.version = 1;
117                 } else {
118                         self.version = stof(argv(1));
119                 }
120                 if(self.version != cvar("gameversion"))
121                 {
122                         self.classname = "observer";
123                         self.version_mismatch = 1;
124                         PutClientInServer();
125                 } else if(cvar("g_campaign") || cvar("g_balance_teams") || cvar("g_balance_teams_force")) {
126                         //JoinBestTeam(self, FALSE, TRUE);
127                 } else if(cvar("teamplay") && !cvar("sv_spectate")) {
128                         self.classname = "observer";
129                         stuffcmd(self,"menu_showteamselect\n");
130                 }
131         } else if(argv(0) == "reportcvar") { // old system
132                 if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
133                 {
134                         s = strcat(substring(s, argv_start_index(0), argv_end_index(1)), " \"", cvar_defstring(argv(1)), "\"");
135                         tokens = tokenize_sane(s);
136                 }
137                 GetCvars(1);
138         } else if(argv(0) == "sentcvar") { // new system
139                 if(tokens == 2) // undefined cvar: use the default value on the server then
140                 {
141                         s = strcat(substring(s, argv_start_index(0), argv_end_index(1)), " \"", cvar_defstring(argv(1)), "\"");
142                         tokens = tokenize_sane(s);
143                 }
144                 GetCvars(1);
145         } else if(argv(0) == "spectate") {
146                 if not(self.flags & FL_CLIENT)
147                         return;
148                 if(g_lms || g_arena)
149                         return; // don't allow spectating in lms, unless player runs out of lives
150                 if(self.classname == "player" && cvar("sv_spectate") == 1) {
151                         if(self.flagcarried)
152                                 DropFlag(self.flagcarried, world, world);
153                         kh_Key_DropAll(self, TRUE);
154                         WaypointSprite_PlayerDead();
155                         self.classname = "observer";
156                         if(blockSpectators)
157                                 sprint(self, strcat("^7You have to become a player within the next ", ftos(cvar("g_maxplayers_spectator_blocktime")), " seconds, otherwise you will be kicked, because spectators aren't allowed at this time!\n"));
158                         PutClientInServer();
159                 }
160         } else if(argv(0) == "join") {
161                 if not(self.flags & FL_CLIENT)
162                         return;
163                 if(!g_arena)
164                 if (self.classname != "player" && !lockteams)
165                 {
166                         if(isJoinAllowed()) {
167                                 self.classname = "player";
168                                 PlayerScore_Clear(self);
169                                 bprint ("^4", self.netname, "^4 is playing now\n");
170                                 PutClientInServer();
171                         }
172                         else {
173                                 //player may not join because of g_maxplayers is set
174                                 centerprint_atprio(self, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
175                         }
176                 }
177         } else if( argv(0) == "selectteam" ) {
178                 if not(self.flags & FL_CLIENT)
179                         return;
180                 if( !cvar("teamplay") ) {
181                         sprint( self, "selecteam can only be used in teamgames\n");
182                 } else if(cvar("g_campaign")) {
183                         //JoinBestTeam(self, 0);
184                 } else if(lockteams) {
185                         sprint( self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
186                 } else if( argv(1) == "red" ) {
187                         DoTeamChange(COLOR_TEAM1);
188                 } else if( argv(1) == "blue" ) {
189                         DoTeamChange(COLOR_TEAM2);
190                 } else if( argv(1) == "yellow" ) {
191                         DoTeamChange(COLOR_TEAM3);
192                 } else if( argv(1) == "pink" ) {
193                         DoTeamChange(COLOR_TEAM4);
194                 } else if( argv(1) == "auto" ) {
195                         DoTeamChange(-1);
196                 } else {
197                         sprint( self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
198                 }
199         } else if(argv(0) == "ready") {
200                 if not(self.flags & FL_CLIENT)
201                         return;
202                 if((inWarmupStage && 0 <= g_warmup_limit) // with unlimited warmup players have to be able to restart
203                    || cvar("sv_ready_restart"))
204                 {
205                         if(timeoutStatus) {
206                                 return sprint(self, "^1You cannot reset the game while a timeout is active!\n");
207                         }
208                         
209                         //if(!restartAnnouncer)
210                         {
211                                 if(!readyrestart_happened || cvar("sv_ready_restart_repeatable"))
212                                 {
213                                         self.ready = TRUE;
214                                         bprint(self.netname, "^2 is ready\n");
215                                         ReadyCount();
216                                 } else {
217                                         sprint(self, "^1game has already been restarted\n");
218                                 }
219                         }
220                 }
221         } else if(argv(0) == "maplist") {
222                 sprint(self, maplist_reply);
223         } else if(argv(0) == "lsmaps") {
224                 sprint(self, lsmaps_reply);
225         } else if(argv(0) == "records") {
226                 sprint(self, records_reply);
227         } else if(argv(0) == "voice") {
228                 VoiceMessage(argv(1), substring(s, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
229         } else if(argv(0) == "say") {
230                 if(tokens >= 2)
231                         Say(self, FALSE, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
232                 //clientcommand(self, formatmessage(s));
233         } else if(argv(0) == "say_team") {
234                 if(tokens >= 2)
235                         Say(self, TRUE, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
236                 //clientcommand(self, formatmessage(s));
237         } else if(argv(0) == "info") {
238                 cmd = cvar_string(strcat("sv_info_", argv(1)));
239                 if(cmd == "")
240                         sprint(self, "ERROR: unsupported info command\n");
241                 else
242                         wordwrap_sprint(cmd, 1111);
243         } else if(argv(0) == "suggestmap") {
244                 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
245         } else if(argv(0) == "calltimeout") {
246                 if not(self.flags & FL_CLIENT)
247                         return;
248                 if(cvar("sv_timeout")) {
249                         if(self.classname == "player") {
250                                 if(votecalled)
251                                         sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
252                                 else
253                                         evaluateTimeoutCall();
254                         }
255                         else
256                                 sprint(self, "^7Error: only players can call a timeout!\n");
257                 }
258         } else if(argv(0) == "resumegame") {
259                 if not(self.flags & FL_CLIENT)
260                         return;
261                 if(cvar("sv_timeout")) {
262                         evaluateResumeGame();
263                 }
264         } else if(argv(0) == "teamstatus") {
265                 Score_NicePrint(self);
266         } else if(argv(0) == "cvar_changes") {
267                 sprint(self, cvar_changes);
268         } else if(argv(0) == "pointparticles") {
269                 if(sv_cheats && tokens == 5)
270                 {
271                         // arguments:
272                         //   effectname
273                         //   origin (0..1, on crosshair line)
274                         //   velocity
275                         //   howmany
276                         effectnum = particleeffectnum(argv(1));
277                         f = stof(argv(2));
278                         start = (1-f) * self.origin + f * self.cursor_trace_endpos;
279                         end = stov(argv(3));
280                         f = stof(argv(4));
281                         pointparticles(effectnum, start, end, f);
282                 }
283                 else
284                         print("Usage: sv_cheats 1; restart; cmd pointparticles effectname position(0..1) velocityvector multiplier\n");
285         } else if(argv(0) == "trailparticles") {
286                 if(sv_cheats && tokens == 3)
287                 {
288                         // arguments:
289                         //   effectname
290                         //   shot origin vector
291                         effectnum = particleeffectnum(argv(1));
292                         W_SetupShot(self, stov(argv(2)), FALSE, FALSE, "");
293                         traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
294                         trailparticles(self, effectnum, w_shotorg, trace_endpos);
295                 }
296                 else
297                         print("Usage: sv_cheats 1; restart; cmd trailparticles effectname shotorigin\n");
298         } else {
299                 //if(ctf_clientcommand())
300                 //      return;
301                 cmd = argv(0);
302                 // grep for Cmd_AddCommand_WithClientCommand to find them all
303                 if(cmd != "status")
304                 if(cmd != "max")
305                 if(cmd != "monster")
306                 if(cmd != "scrag")
307                 if(cmd != "wraith")
308                 if(cmd != "gimme")
309                 if(cmd != "god")
310                 if(cmd != "notarget")
311                 if(cmd != "fly")
312                 if(cmd != "noclip")
313                 if(cmd != "give")
314                 //if(cmd != "say") // handled above
315                 //if(cmd != "say_team") // handled above
316                 if(cmd != "tell")
317                 if(cmd != "kill")
318                 if(cmd != "pause")
319                 if(cmd != "ping")
320                 if(cmd != "name")
321                 if(cmd != "color")
322                 if(cmd != "rate")
323                 if(cmd != "pmodel")
324                 if(cmd != "playermodel")
325                 if(cmd != "playerskin")
326                 if(cmd != "prespawn")
327                 if(cmd != "spawn")
328                 if(cmd != "begin")
329                 if(cmd != "pings")
330                 if(cmd != "sv_startdownload")
331                 if(cmd != "download")
332                 {
333                         print("WARNING: Invalid clientcommand by ", self.netname, ": ", s, "\n");
334                         return;
335                 }
336                 clientcommand(self,s);
337         }
338 }
339
340 void ReadyRestartForce()
341 {
342         local entity e;
343
344         bprint("^1Server is restarting...\n");
345
346         VoteReset();
347
348         // clear overtime
349         if(checkrules_overtimeend)
350                 checkrules_overtimeend = 0;
351
352         readyrestart_happened = 1;
353         game_starttime = time + RESTART_COUNTDOWN;
354         restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
355
356         inWarmupStage = 0; //once the game is restarted the game is in match stage
357
358         //reset the .ready status of all players (also spectators)
359         FOR_EACH_CLIENTSLOT(e)
360                 e.ready = 0;
361         readycount = 0;
362         Nagger_ReadyCounted();
363         FOR_EACH_REALCLIENT(e)
364         {
365                 msg_entity = e;
366                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
367                 WriteByte(MSG_ONE, TE_CSQC_WARMUP);
368                 WriteByte(MSG_ONE, 0);
369         }
370
371         if(cvar("teamplay_lockonrestart") && teams_matter) {
372                 lockteams = 1;
373                 bprint("^1The teams are now locked.\n");
374         }
375         
376         //initiate the restart-countdown-announcer entity
377         restartAnnouncer = spawn();
378         restartAnnouncer.think = restartAnnouncer_Think;
379         restartAnnouncer.nextthink = time;
380         restartAnnouncer.spawnflags = !!cvar("sv_ready_restart_after_countdown");
381         
382         //after a restart every players number of allowed timeouts gets reset, too
383         if(cvar("sv_timeout"))
384         {
385                 FOR_EACH_REALPLAYER(e)
386                         e.allowedTimeouts = cvar("sv_timeout_number");
387         }
388
389         //play the prepareforbattle sound to everyone
390         play2all("announcer/robotic/prepareforbattle.wav");
391
392         //reset map immediately if this cvar is not set
393         if (!cvar("sv_ready_restart_after_countdown"))
394                 reset_map();
395         
396         if(cvar("sv_eventlog"))
397                 GameLogEcho(":restart");
398 }
399
400 void ReadyRestart()
401 {
402         // no arena, assault support yet...
403         if(g_arena | g_assault | gameover | intermission_running | race_completing)
404                 localcmd("restart\n");
405
406         // reset ALL scores
407         Score_ClearAll();
408
409         ReadyRestartForce();
410 }
411
412 /**
413  * Counts how many players are ready. If not enough players are ready, the function
414  * does nothing. If all players are ready, the timelimit will be extended and the
415  * restart_countdown variable is set to allow other functions like PlayerPostThink
416  * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
417  * is not set the map will be resetted.
418  * 
419  * Function is called after the server receives a 'ready' sign from a player.
420  */
421 void ReadyCount()
422 {
423         local entity e;
424         local float r, p;
425
426         r = p = 0;
427
428         FOR_EACH_REALPLAYER(e)
429         {
430                 p += 1;
431                 if(e.ready)
432                         r += 1;
433         }
434
435         readycount = r;
436
437         Nagger_ReadyCounted();
438
439         if(r) // at least one is ready
440         if(r == p) // and, everyone is ready
441                 ReadyRestart();
442 }
443
444 /**
445  * Shows the restart countdown for all players.
446  * Plays the countdown sounds for the seconds 3, 2 1, begin for everyone.
447  * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
448  * is set to 1).
449  */
450 void restartAnnouncer_Think() {
451         local entity plr;
452         local string s;
453         float f, c;
454         c = game_starttime - time;
455         f = floor(0.5 + c);
456         if(c <= 0) { //show the "Begin" message and
457                 if(self.spawnflags & 1)
458                 {
459                         restart_mapalreadyrestarted = 1;
460                         reset_map();
461                 }
462
463                 FOR_EACH_REALCLIENT(plr) {
464                         if(plr.classname == "player") {
465                                 s = strcat(NEWLINES, "^1Begin!");
466                                 centerprint(plr, s);
467                         }
468                 }
469                 play2all("announcer/robotic/begin.wav");
470
471                 remove(self);
472                 return;
473         }
474         else {
475                 FOR_EACH_REALCLIENT(plr) {
476                         if(plr.classname == "player") {
477                                 s = strcat(NEWLINES, "^1Game starts in ", ftos(f), " seconds");
478                                 centerprint(plr, s);
479                         }
480                 }
481
482                 if(f <= 3) {
483                         play2all(strcat("announcer/robotic/", ftos(f), ".wav"));
484                 }
485                 self.nextthink = game_starttime - (f - 1);
486         }
487 }
488
489 /**
490  * Checks whether the player who calls the timeout is allowed to do so.
491  * If so, it initializes the timeout countdown. It also checks whether another
492  * timeout was already running at this time and reacts correspondingly.
493  *
494  * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
495  *                          timeoutInitiator, timeoutStatus, timeoutHandler
496  *
497  * This function is called when a player issues the calltimeout command.
498  */
499 void evaluateTimeoutCall() {
500         if (inWarmupStage && !g_warmup_allow_timeout)
501                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
502         if (time < game_starttime )
503                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
504         if (timeoutStatus != 2) {
505                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
506                 if (cvar("timelimit")) {
507                         //a timelimit was used
508                         local float myTl;
509                         myTl = cvar("timelimit");
510
511                         local float lastPossibleTimeout;
512                         lastPossibleTimeout = (myTl*60) - cvar("sv_timeout_leadtime") - 1;
513
514                         if (lastPossibleTimeout < time - game_starttime)
515                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
516                 }
517         }
518         //player may not call a timeout if he has no calls left
519         if (self.allowedTimeouts < 1)
520                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
521         //now all required checks are passed
522         self.allowedTimeouts -= 1;
523         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)
524         remainingTimeoutTime = cvar("sv_timeout_length");
525         remainingLeadTime = cvar("sv_timeout_leadtime");
526         timeoutInitiator = self;
527         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
528                 timeoutStatus = 1;
529                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
530                 timeoutHandler = spawn();
531                 timeoutHandler.think = timeoutHandler_Think;
532         }
533         timeoutHandler.nextthink = time; //always let the entity think asap
534
535         //inform all connected clients about the timeout call
536         play2all("announcer/robotic/timeoutcalled.wav");
537 }
538
539 /**
540  * Checks whether a player is allowed to resume the game. If he is allowed to do it,
541  * and the lead time for the timeout is still active, this countdown just will be aborted (the
542  * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
543  * value of the cvar sv_timeout_resumetime.
544  *
545  * This function is called when a player issues the resumegame command.
546  */
547 void evaluateResumeGame() {
548         if (!timeoutStatus)
549                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
550         if (self != timeoutInitiator)
551                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
552         if (timeoutStatus == 1) {
553                 remainingTimeoutTime = timeoutStatus = 0;
554                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
555                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
556         }
557         else if (timeoutStatus == 2) {
558                 //only shorten the remainingTimeoutTime if it makes sense
559                 if( remainingTimeoutTime > (cvar("sv_timeout_resumetime") + 1) ) {
560                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
561                         remainingTimeoutTime = cvar("sv_timeout_resumetime");
562                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
563                 }
564                 else
565                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
566
567         }
568 }