]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/clientcommands.qc
added command "gotomap" which does the same as chmap, but forces the
[divverent/nexuiz.git] / data / qcsrc / server / clientcommands.qc
1 void ReadyCount();
2 float ValidateMap(string vote);
3
4 void SV_ParseClientCommand(string s) {
5         local float index;
6
7         tokenize(s);
8
9         if(argv(0) == "clogin") {
10                 if(cvar("sv_clientcommands")) {
11                         if(self.adminstatus < -5) {
12                                 sprint(self, "Too many unsuccessful tries.\n");
13                         } else if(argv(1) == cvar_string("sv_clientcommands_password")) {
14                                 self.adminstatus = 1;
15                                 sprint(self, "You now have remote admin status.\n");
16                                 ServerConsoleEcho(strcat("ClientCommands: ", self.netname, " received admin status"), TRUE);
17                         } else {
18                                 sprint(self, "Wrong password.\n");
19                                 // use of -- produces compiler warning in the if() line???
20                                 self.adminstatus = self.adminstatus - 1;
21                                 if(self.adminstatus == 0)
22                                 {
23                                         sprint(self, "You lost remote admin status.\n");
24                                         ServerConsoleEcho(strcat("ClientCommands: ", self.netname, " lost admin status"), TRUE);
25                                 }
26                         }
27                 } else {
28                         sprint(self, "Clientside commands NOT allowed.\n");
29                 }
30         } else if(argv(0) == "ccmd") {
31                 if(cvar("sv_clientcommands")) {
32                         if(self.adminstatus > 0) {
33                                 local string command;
34                                 command = argv(1);
35                                 index = 2;
36                                 while(argv(index) != "") {
37                                         command = strcat(command, " ", argv(index));
38                                         index++;
39                                 }
40                                 command = strzone(command);
41                                 ServerConsoleEcho(strcat("ClientCommands: ", self.netname, " issued command '", command, "'"), TRUE);
42                                 localcmd(strcat(command, "\n"));
43                                 strunzone(command);
44                         } else
45                                 sprint(self, "You don't have remote admin status.\n");
46                 } else {
47                         sprint(self, "Clientside commands NOT allowed.\n");
48                 }
49         } else if(argv(0) == "vote") {
50                 if(argv(1) == "help") {
51                         local string vmasterdis;
52                         if(!cvar("sv_vote_master")) {
53                                 vmasterdis = " ^1(disabled)";
54                         }
55                         local string vcalldis;
56                         if(!cvar("sv_vote_call")) {
57                                 vcalldis = " ^1(disabled)";
58                         }
59                         sprint(self, "^7You can use voting with \"^2cmd vote help^7\" \"^2cmd vote status^7\" \"^2cmd vote call ^3COMMAND ARGUMENTS^7\" \"^2cmd vote stop^7\" \"^2cmd vote master^7\" \"^2cmd vote do ^3COMMAND ARGUMENTS^7\" \"^2cmd vote yes^7\" \"^2cmd vote no^7\".\n");
60                         sprint(self, "^7Or if your version is up to date you can use these aliases \"^2vhelp^7\" \"^2vstatus^7\" \"^2vcall ^3COMMAND ARGUMENTS^7\" \"^2vstop^7\" \"^2vmaster^7\" \"^2vdo ^3COMMAND ARGUMENTS^7\" \"^2vyes^7\" \"^2vno^7\".\n");
61                         sprint(self, "^7\"^2help^7\" shows this info.\n");
62                         sprint(self, "^7\"^2status^7\" shows if there is a vote called and who called it.\n");
63                         sprint(self, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7\n"));
64                         sprint(self, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.\n");
65                         sprint(self, strcat("^7\"^2master^7\" is used to call a vote to become a master.", vmasterdis, "^7\n"));
66                         sprint(self, "^7\"^2do^7\" If you are a master you can execute a command without a vote. See the list of allowed commands.\n");
67                         sprint(self, "^7\"^2yes^7\" and \"^2no^7\" to make your vote.\n");
68                         sprint(self, "^7If more then 50% of the players vote yes the vote is accepted.\n");
69                         sprint(self, "^7If more then 50% of the players vote no the vote is rejected.\n");
70                         sprint(self, strcat("^7The vote will end after ", cvar_string("sv_vote_timeout"), "^7 seconds.\n"));
71                         sprint(self, "^7You can call a vote for or execute these commands:\n");
72                         sprint(self, strcat("^3", cvar_string("sv_vote_commands"), "^7 and maybe further ^3arguments^7\n"));
73                 } else if(argv(1) == "status") {
74                         if(votecalled) {
75                                 sprint(self, strcat("^7Vote for \"^1", votecalledvote, "^7\" called by \"^7", votecaller.netname, "^7\".\n"));
76                         } else {
77                                 sprint(self, "^1No vote called.\n");
78                         }
79                 } else if(argv(1) == "call") {
80                         if(cvar("sv_vote_call")) {
81                                 if(votecalled) {
82                                         sprint(self, "^1There is already a vote called.\n");
83                                 } else {
84                                         local string vote;
85                                         vote = VoteParse();
86                                         if(vote == "") {
87                                                 sprint(self, "^1Your vote is empty. See help for more info.\n");
88                                         } else if(time < self.vote_next) {
89                                                 sprint(self, strcat("^1You have to wait ^2", ftos(self.vote_next - time), "^1 seconds before you can again call a vote.\n"));
90                                         } else if(VoteAllowed(strcat(argv(2)))) { // strcat seems to be necessary
91                                                 if(!ValidateMap(vote))
92                                                         return;
93                                                 votecalled = TRUE;
94                                                 votecalledmaster = FALSE;
95                                                 // remap chmap to gomap (forces intermission)
96                                                 if(strlen(vote) >= 6)
97                                                         if(substring(vote, 0, 6) == "chmap ")
98                                                                 vote = strcat("gotomap ", substring(vote, 6, strlen(vote) - 6));
99                                                 votecalledvote = strzone(vote);
100                                                 votecaller = self; // remember who called the vote
101                                                 votefinished = time + cvar("sv_vote_timeout");
102                                                 votecaller.vote_vote = 1; // of course you vote yes
103                                                 votecaller.vote_next = time + cvar("sv_vote_wait");
104                                                 bprint(strcat("^3Vote for \"^1", votecalledvote, "^3\" called by \"^7", votecaller.netname, "^3\".\n"));
105                                                 VoteCount(); // needed if you are the only one
106                                         } else {
107                                                 sprint(self, "^1This vote is not ok. See help for more info.\n");
108                                         }
109                                 }
110                         } else {
111                                 sprint(self, "^1Vote calling is NOT allowed.\n");
112                         }
113                 } else if(argv(1) == "stop") {
114                         if(!votecalled) {
115                                 sprint(self, "^1No vote called.\n");
116                         } else if(self == votecaller
117                                   || self.adminstatus > 0) { // the votecaller and admins can stop a vote
118                                 VoteStop(self);
119                         } else {
120                                 sprint(self, "^1You are not allowed to stop that Vote.\n");
121                         }
122                 } else if(argv(1) == "master") {
123                         if(cvar("sv_vote_master")) {
124                                 if(votecalled) {
125                                         sprint(self, "^1There is already a vote called.\n");
126                                 } else {
127                                         votecalled = TRUE;
128                                         votecalledmaster = TRUE;
129                                         votecalledvote = strzone("^3master");
130                                         votecaller = self; // remember who called the vote
131                                         votefinished = time + cvar("sv_vote_timeout");
132                                         votecaller.vote_vote = 1; // of course you vote yes
133                                         votecaller.vote_next = time + cvar("sv_vote_wait");
134                                         bprint(strcat("\"^3", votecaller.netname, "^3\" called a vote to become ^3master^3.\n"));
135                                         VoteCount(); // needed if you are the only one
136                                 }
137                         } else {
138                                 sprint(self, "^1Vote to become master is NOT allowed.\n");
139                         }
140                 } else if(argv(1) == "do") {
141                         if(self.vote_master) {
142                                 local string dovote;
143                                 dovote = VoteParse();
144                                 if(dovote == "") {
145                                         sprint(self, "^1Your command was empty. See help for more info.\n");
146                                 } else if(VoteAllowed(strcat(argv(2)))) { // strcat seems to be necessary
147                                         bprint("\"^7", strcat(self.netname, "^2 used his ^3master^2 status to do \"^2", dovote, "^2\".\n"));
148                                         localcmd(strcat(dovote, "\n"));
149                                 } else {
150                                         sprint(self, "^1This command is not ok. See help for more info.\n");
151                                 }
152                         } else {
153                                 sprint(self, "^1You are NOT a master.\n");
154                         }
155                 } else if(argv(1) == "yes") {
156                         if(!votecalled) {
157                                 sprint(self, "^1No vote called.\n");
158                         } else if(self.vote_vote == 0
159                                   || cvar("sv_vote_change")) {
160                                 sprint(self, "^1You accepted the vote.\n");
161                                 self.vote_vote = 1;
162                                 if(!cvar("sv_vote_singlecount")) {
163                                         VoteCount();
164                                 }
165                         } else {
166                                 sprint(self, "^1You have already voted.\n");
167                         }
168                 } else if(argv(1) == "no") {
169                         if(!votecalled) {
170                                 sprint(self, "^1No vote called.\n");
171                         } else if(self.vote_vote == 0
172                                   || cvar("sv_vote_change")) {
173                                 sprint(self, "^1You rejected the vote.\n");
174                                 self.vote_vote = -1;
175                                 if(!cvar("sv_vote_singlecount")) {
176                                         VoteCount();
177                                 }
178                         } else {
179                                 sprint(self, "^1You have already voted.\n");
180                         }
181                 } else {
182                         // ignore this?
183                         sprint(self, "^1Unknown vote command.\n");
184                 }
185         } else if(argv(0) == "autoswitch") {
186                 // be backwards compatible with older clients (enabled)
187                 self.autoswitch = ("0" != argv(1));
188                 local string autoswitchmsg;
189                 if (self.autoswitch) {
190                         autoswitchmsg = "on";
191                 } else {
192                         autoswitchmsg = "off";
193                 }
194                 sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
195         } else if(argv(0) == "clientversion") {
196                 if (argv(1) == "$gameversion") {
197                         //versionmsg = "^1client is too old to get versioninfo.\nUPDATE!!! (http://www.nexuiz.com)^8";
198                         // either that or someone wants to be funny
199                         self.version = 1;
200                 } else {
201                         self.version = stof(argv(1));
202                 }
203                 if(self.version != cvar("gameversion")) 
204                 {
205                         self.classname = "observer";
206                         self.frags = -2;
207                         PutClientInServer();
208                 } else if(cvar("g_campaign") || cvar("g_balance_teams")) {
209                         //JoinBestTeam(self, 0);
210                 } else if(cvar("teamplay") && !cvar("sv_spectate")) {
211                         self.classname = "observer";
212                         stuffcmd(self,"menu_showteamselect\n");
213                 }
214         } else if(argv(0) == "reportcvar") {
215                 GetCvars(1);
216         } else if(argv(0) == "spectate") {
217                 if(cvar("g_lms") || cvar("g_arena"))
218                         return; // don't allow spectating in lms, unless player runs out of lives
219                 if(self.classname == "player" && cvar("sv_spectate") == 1) {
220                         self.classname = "observer";
221                         PutClientInServer();
222                 }
223         } else if(argv(0) == "join") {
224                 if(!cvar("g_arena"))
225                 if (self.classname != "player")
226                 {
227                         self.classname = "player";
228                         self.frags = 0;
229                         bprint (strcat("^4", self.netname, "^4 is playing now\n"));
230                         PutClientInServer();
231                 }
232         } else if( argv(0) == "selectteam" ) {
233                 if( !cvar("teamplay") ) {
234                         sprint( self, "selecteam can only be used in teamgames\n");
235                 } else if(cvar("g_campaign")) {
236                         //JoinBestTeam(self, 0);
237                 } else if( argv(1) == "none" ) {
238                         SV_ChangeTeam( 0 );
239                 } else if( argv(1) == "red" ) {
240                         SV_ChangeTeam( COLOR_TEAM1 - 1 );
241                 } else if( argv(1) == "blue" ) {
242                         SV_ChangeTeam( COLOR_TEAM2 - 1 );
243                 } else if( argv(1) == "pink" ) {
244                         SV_ChangeTeam( COLOR_TEAM3 - 1 );
245                 } else if( argv(1) == "yellow" ) {
246                         SV_ChangeTeam( COLOR_TEAM4 - 1 );
247                 } else if( argv(1) == "auto" ) {
248                         self.team = -1;
249                         JoinBestTeam( self, 0 );
250                 } else {
251                         sprint( self, strcat( "selectteam none/red/blue/pink/yellow/auto - \"", argv(1), "\" not recognised\n" ) );
252                 }
253         } else if(argv(0) == "ready") {
254                 if(cvar("sv_ready_restart"))
255                 {
256                         self.ready = TRUE;
257                         bprint(self.netname, "^2 is ready\n");
258                         ReadyCount();
259                 }
260         } else if(argv(0) == "say") {
261                 clientcommand(self, formatmessage(s));
262         } else if(argv(0) == "say_team") {
263                 clientcommand(self, formatmessage(s));
264         } else {
265                 clientcommand(self,s);
266         }
267 }
268
269 float ValidateMap(string vote)
270 {
271         string ext;
272         
273         tokenize(vote);
274         if(argv(0) == "map" || argv(0) == "changelevel")
275                 ext = ".bsp";
276         else if(argv(0) == "chmap")
277                 ext = ".mapcfg";
278         else if(argv(0) == "gotomap")
279                 ext = ".mapcfg";
280         else
281                 return TRUE;
282
283         if(!TryFile(strcat("maps/", argv(1), ext)))
284         {
285                 sprint(self, strcat("^1Invalid mapname, \"^3", argv(1), "^1\" does not exist on this server.\n"));
286                 return FALSE;
287         }
288         return TRUE;
289 }
290
291
292 void VoteThink() {
293         if(votefinished > 0 // a vote was called
294             && time > votefinished) // time is up
295         {
296                 VoteCount();
297         }
298 }
299
300 string VoteParse() {
301         local float index;
302         index = 3;
303         local string vote;
304         vote = argv(2);
305         while(argv(index) != "") {
306                 vote = strcat(vote, " ", argv(index));
307                 index++;
308         }
309
310         // necessary for some of the string operations
311         vote = strzone(vote);
312
313         // now we remove some things that could be misused
314         index = 0;
315         local float found;
316         found = FALSE;
317         local float votelength;
318         votelength = strlen(vote);
319         while(!found && index < votelength)
320         {
321                 local string badchar;
322                 badchar = substring(vote, index, 1);
323                 if(badchar == ";"
324                    || badchar == "\r"
325                    || badchar == "\n")
326                 {
327                         found = TRUE;
328                 } else {
329                         index++;
330                 }
331         }
332         return substring(vote, 0, index);
333 }
334
335 float VoteAllowed(string votecommand) {
336         tokenize(cvar_string("sv_vote_commands"));
337         local float index;
338         index = 0;
339         while(argv(index) != "") {
340                 local string allowed;
341                 allowed = argv(index);
342                 if(votecommand == allowed) {
343                         return TRUE;
344                 }
345                 index++;
346         }
347         return FALSE;
348 }
349
350 void VoteReset() {
351         local string searchclass;
352         searchclass = "player";
353
354         while (TRUE)
355         {
356                 local entity player;
357                 player = find(player, classname, searchclass);
358                 while(player)
359                 {
360                         player.vote_vote = 0;
361                         player = find(player, classname, searchclass);
362                 }
363
364                 if("player" == searchclass) {
365                         searchclass = "observer";
366                 } else if("observer" == searchclass) {
367                         searchclass = "spectator";
368                 } else {
369                         break;
370                 }
371         }
372
373         votecalled = FALSE;
374         votecalledmaster = FALSE;
375         votefinished = 0;
376 }
377
378 void VoteAccept() {
379         bprint(strcat("^2The vote for \"^1", votecalledvote, "^2\" from \"^7", votecaller.netname, "^2\" was accepted.\n"));
380         if(votecalledmaster)
381         {
382                 votecaller.vote_master = 1;
383         } else {
384                 localcmd(strcat(votecalledvote, "\n"));
385         }
386         votecaller.vote_next = 0; // people like your votes, no wait for next vote
387         VoteReset();
388 }
389
390 void VoteReject() {
391         bprint(strcat("^2The vote for \"^1", votecalledvote, "^2\" from \"^7", votecaller.netname, "^2\" was rejected.\n"));
392         VoteReset();
393 }
394
395 void VoteTimeout() {
396         bprint(strcat("^5The vote for \"^1", votecalledvote, "^5\" from \"^7", votecaller.netname, "^5\" did timeout.\n"));
397         VoteReset();
398 }
399
400 void VoteStop(entity stopper) {
401         bprint(strcat("^5The vote for \"^1", votecalledvote, "^5\" from \"^7", votecaller.netname, "^5\" was stopped by \"^5", stopper.netname, "^5\".\n"));
402         if(stopper == votecaller) {
403                 // no wait for next vote so you can correct your vote
404                 votecaller.vote_next = 0;
405         }
406         VoteReset();
407 }
408
409 void VoteCount() {
410         local float playercount;
411         playercount = 0;
412         local float yescount;
413         yescount = 0;
414         local float nocount;
415         nocount = 0;
416         local string searchclass;
417         searchclass = "player";
418
419         while (TRUE)
420         {
421                 local entity player;
422                 player = find(player, classname, searchclass);
423
424                 while(player)
425                 {
426                         if(clienttype(player) != CLIENTTYPE_BOT) {
427                                 if(player.vote_vote < 0) {
428                                         nocount++;
429                                 } else if(player.vote_vote > 0) {
430                                         yescount++;
431                                 }
432                                 playercount++;
433                         }
434                         player = find(player, classname, searchclass);
435                 }
436
437                 if("player" == searchclass) {
438                         searchclass = "observer";
439                 } else if("observer" == searchclass) {
440                         searchclass = "specator";
441                 } else {
442                         break;
443                 }
444         }
445
446         if((playercount == 1) && votecalledmaster) {
447                 // if only one player is on the server becoming vote
448                 // master is not allowed.  This could be used for
449                 // trolling or worse. 'self' is the user who has
450                 // called the vote because this function is called
451                 // by SV_ParseClientCommand. Maybe all voting should
452                 // be disabled for a single player?
453                 sprint(self, "^1You are the only player on this server so you can not become vote master.\n");
454                 votecaller.vote_next = 0;
455                 VoteReset();
456         } else if((playercount / 2) < yescount) { // vote accepted
457                 VoteAccept();
458         } else if((playercount / 2) < nocount) { // vote rejected
459                 VoteReject();
460         } else if(time > votefinished) { // vote timedout
461                 VoteTimeout();
462         } // else still running
463 }
464
465
466 void ReadyCount()
467 {
468         local entity e;
469         local float r, p;
470
471         e = find(world, classname, "player");
472
473         while(e)
474         {
475                 if(clienttype(e) == CLIENTTYPE_REAL)
476                 {
477                         p += 1;
478                         if(e.ready) r += 1;
479                 }
480                 e = find(e, classname, "player");
481         }
482
483         if(p && r == p)
484         {
485                 bprint("^1Server is restarting...\n");
486                 localcmd("restart\n");
487         }
488 }