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