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