]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/clientcommands.qc
- projectile style is now 2 (Newtonian with aimfix), fixes RL when movingh fast
[divverent/nexuiz.git] / data / qcsrc / server / clientcommands.qc
1 void ReadyCount();
2 float ValidateMap(string vote);
3 void(entity e) DropFlag;
4 string MapVote_Suggest(string m);
5
6 void Say(entity source, float teamsay, string msgin)
7 {
8         string msgstr, colorstr, cmsgstr;
9         entity head;
10
11         msgin = formatmessage(msgin);
12
13         if(msgin == "")
14                 return;
15
16         colorstr = Team_ColorCode(source.team);
17
18         if(!teams_matter)
19                 teamsay = FALSE;
20
21         if(intermission_running)
22                 teamsay = FALSE;
23
24         if(source.classname != "player") // observers can't
25                 teamsay = FALSE;
26
27         // how can we prevent the message from appearing in a listen server?
28         // for now, just give "say" back and only handle say_team
29         if(!teamsay)
30         {
31                 clientcommand(self, strcat("say ", msgin));
32                 return;
33         }
34
35         if(teamsay)
36         {
37                 msgstr = strzone(strcat("\{1}\{13}", colorstr, "(^3", source.netname, colorstr, ") ^7", msgin, "\n"));
38                 cmsgstr = strcat(colorstr, "(^3", source.netname, colorstr, ")\n^7", wordwrap(msgin, 50));
39         }
40         else
41                 msgstr = strzone(strcat("\{1}^3", source.netname, "^7: ", msgin, "\n"));
42
43         head = find(world, classname, "player");
44         while(head)
45         {
46                 if(clienttype(head) == CLIENTTYPE_REAL)
47                         if(!teamsay || (head.team == source.team))
48                         {
49                                 sprint(head, msgstr);
50                                 if(teamsay)
51                                         centerprint(head, cmsgstr);
52                                 //stuffcmd(head, "play2 misc/talk.wav\n");
53                         }
54                 head = find(head, classname, "player");
55         }
56
57         if(!teamsay)
58         {
59                 head = find(world, classname, "observer");
60                 while(head)
61                 {
62                         if(clienttype(head) == CLIENTTYPE_REAL)
63                         {
64                                 sprint(head, msgstr);
65                                 //stuffcmd(head, "play2 misc/talk.wav\n");
66                         }
67                         head = find(head, classname, "observer");
68                 }
69                 head = find(world, classname, "spectator");
70                 while(head)
71                 {
72                         if(clienttype(head) == CLIENTTYPE_REAL)
73                         {
74                                 sprint(head, msgstr);
75                                 //stuffcmd(head, "play2 misc/talk.wav\n");
76                         }
77                         head = find(head, classname, "spectator");
78                 }
79                 ServerConsoleEcho(substring(msgstr, 1, strlen(msgstr) - 2), TRUE);
80         }
81
82         strunzone(msgstr);
83 }
84
85 void SV_ParseClientCommand(string s) {
86         local string cmd;
87
88         tokenize(s);
89
90         if(argv(0) == "vote") {
91                 if(argv(1) == "help") {
92                         local string vmasterdis;
93                         if(!cvar("sv_vote_master")) {
94                                 vmasterdis = " ^1(disabled)";
95                         }
96                         local string vcalldis;
97                         if(!cvar("sv_vote_call")) {
98                                 vcalldis = " ^1(disabled)";
99                         }
100                         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");
101                         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");
102                         sprint(self, "^7\"^2help^7\" shows this info.\n");
103                         sprint(self, "^7\"^2status^7\" shows if there is a vote called and who called it.\n");
104                         sprint(self, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7\n"));
105                         sprint(self, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.\n");
106                         sprint(self, strcat("^7\"^2master^7\" is used to call a vote to become a master.", vmasterdis, "^7\n"));
107                         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");
108                         sprint(self, "^7\"^2yes^7\" and \"^2no^7\" to make your vote.\n");
109                         sprint(self, "^7If more then 50% of the players vote yes the vote is accepted.\n");
110                         sprint(self, "^7If more then 50% of the players vote no the vote is rejected.\n");
111                         sprint(self, strcat("^7The vote will end after ", cvar_string("sv_vote_timeout"), "^7 seconds.\n"));
112                         sprint(self, "^7You can call a vote for or execute these commands:\n");
113                         sprint(self, strcat("^3", cvar_string("sv_vote_commands"), "^7 and maybe further ^3arguments^7\n"));
114                 } else if(argv(1) == "status") {
115                         if(votecalled) {
116                                 sprint(self, strcat("^7Vote for \"^1", votecalledvote, "^7\" called by \"^7", votecaller.netname, "^7\".\n"));
117                         } else {
118                                 sprint(self, "^1No vote called.\n");
119                         }
120                 } else if(argv(1) == "call") {
121                         if(cvar("sv_vote_call")) {
122                                 if(votecalled) {
123                                         sprint(self, "^1There is already a vote called.\n");
124                                 } else {
125                                         local string vote;
126                                         vote = VoteParse();
127                                         if(vote == "") {
128                                                 sprint(self, "^1Your vote is empty. See help for more info.\n");
129                                         } else if(time < self.vote_next) {
130                                                 sprint(self, strcat("^1You have to wait ^2", ftos(self.vote_next - time), "^1 seconds before you can again call a vote.\n"));
131                                         } else if(VoteAllowed(strcat(argv(2)))) { // strcat seems to be necessary
132                                                 if(!ValidateMap(vote))
133                                                         return;
134                                                 votecalled = TRUE;
135                                                 votecalledmaster = FALSE;
136                                                 // remap chmap to gotomap (forces intermission)
137                                                 if(strlen(vote) >= 6)
138                                                         if(substring(vote, 0, 6) == "chmap ")
139                                                                 vote = strcat("gotomap ", substring(vote, 6, strlen(vote) - 6));
140                                                 votecalledvote = strzone(vote);
141                                                 votecaller = self; // remember who called the vote
142                                                 votefinished = time + cvar("sv_vote_timeout");
143                                                 votecaller.vote_vote = 1; // of course you vote yes
144                                                 votecaller.vote_next = time + cvar("sv_vote_wait");
145                                                 bprint(strcat("\{1}^2* ^3", votecaller.netname, "^2 calls a vote for ^1", votecalledvote, "\n"));
146                                                 VoteCount(); // needed if you are the only one
147                                         } else {
148                                                 sprint(self, "^1This vote is not ok. See help for more info.\n");
149                                         }
150                                 }
151                         } else {
152                                 sprint(self, "^1Vote calling is NOT allowed.\n");
153                         }
154                 } else if(argv(1) == "stop") {
155                         if(!votecalled) {
156                                 sprint(self, "^1No vote called.\n");
157                         } else if(self == votecaller) { // the votecaller can stop a vote
158                                 VoteStop(self);
159                         } else {
160                                 sprint(self, "^1You are not allowed to stop that Vote.\n");
161                         }
162                 } else if(argv(1) == "master") {
163                         if(cvar("sv_vote_master")) {
164                                 if(votecalled) {
165                                         sprint(self, "^1There is already a vote called.\n");
166                                 } else {
167                                         votecalled = TRUE;
168                                         votecalledmaster = TRUE;
169                                         votecalledvote = strzone("^3master");
170                                         votecaller = self; // remember who called the vote
171                                         votefinished = time + cvar("sv_vote_timeout");
172                                         votecaller.vote_vote = 1; // of course you vote yes
173                                         votecaller.vote_next = time + cvar("sv_vote_wait");
174                                         bprint(strcat("\{1}^2* ^3", votecaller.netname, "^2 calls a vote to become ^3master^2.\n"));
175                                         VoteCount(); // needed if you are the only one
176                                 }
177                         } else {
178                                 sprint(self, "^1Vote to become master is NOT allowed.\n");
179                         }
180                 } else if(argv(1) == "do") {
181                         if(argv(2) == "login") {
182                                 local string masterpwd;
183                                 masterpwd = cvar_string("sv_vote_master_password");
184                                 if(masterpwd != "") {
185                                         self.vote_master = (masterpwd == argv(3));
186                                         if(self.vote_master) {
187                                                 ServerConsoleEcho(strcat("Accepted master login from ", self.netname), TRUE);
188                                                 bprint(strcat("\{1}^2* ^3", self.netname, "^2 logged in as ^3master^2\n"));
189                                         }
190                                         else
191                                                 ServerConsoleEcho(strcat("REJECTED master login from ", self.netname), TRUE);
192                                 }
193                                 else
194                                         sprint(self, "^1You are NOT a master.\n");
195                         } else if(self.vote_master) {
196                                 local string dovote;
197                                 dovote = VoteParse();
198                                 if(dovote == "") {
199                                         sprint(self, "^1Your command was empty. See help for more info.\n");
200                                 } else if(VoteAllowed(strcat(argv(2)))) { // strcat seems to be necessary
201                                         if(!ValidateMap(dovote))
202                                                 return;
203                                         // remap chmap to gotomap (forces intermission)
204                                         if(strlen(dovote) >= 6)
205                                                 if(substring(dovote, 0, 6) == "chmap ")
206                                                         vote = strcat("gotomap ", substring(dovote, 6, strlen(dovote) - 6));
207                                         bprint("\{1}^2* ^3", strcat(self.netname, "^2 used his ^3master^2 status to do \"^2", dovote, "^2\".\n"));
208                                         localcmd(strcat(dovote, "\n"));
209                                 } else {
210                                         sprint(self, "^1This command is not ok. See help for more info.\n");
211                                 }
212                         } else {
213                                 sprint(self, "^1You are NOT a master.\n");
214                         }
215                 } else if(argv(1) == "yes") {
216                         if(!votecalled) {
217                                 sprint(self, "^1No vote called.\n");
218                         } else if(self.vote_vote == 0
219                                   || cvar("sv_vote_change")) {
220                                 sprint(self, "^1You accepted the vote.\n");
221                                 self.vote_vote = 1;
222                                 if(!cvar("sv_vote_singlecount")) {
223                                         VoteCount();
224                                 }
225                         } else {
226                                 sprint(self, "^1You have already voted.\n");
227                         }
228                 } else if(argv(1) == "no") {
229                         if(!votecalled) {
230                                 sprint(self, "^1No vote called.\n");
231                         } else if(self.vote_vote == 0
232                                   || cvar("sv_vote_change")) {
233                                 sprint(self, "^1You rejected the vote.\n");
234                                 self.vote_vote = -1;
235                                 if(!cvar("sv_vote_singlecount")) {
236                                         VoteCount();
237                                 }
238                         } else {
239                                 sprint(self, "^1You have already voted.\n");
240                         }
241                 } else {
242                         // ignore this?
243                         sprint(self, "^1Unknown vote command.\n");
244                 }
245         } else if(argv(0) == "autoswitch") {
246                 // be backwards compatible with older clients (enabled)
247                 self.autoswitch = ("0" != argv(1));
248                 local string autoswitchmsg;
249                 if (self.autoswitch) {
250                         autoswitchmsg = "on";
251                 } else {
252                         autoswitchmsg = "off";
253                 }
254                 sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
255         } else if(argv(0) == "clientversion") {
256                 if (argv(1) == "$gameversion") {
257                         //versionmsg = "^1client is too old to get versioninfo.\nUPDATE!!! (http://www.nexuiz.com)^8";
258                         // either that or someone wants to be funny
259                         self.version = 1;
260                 } else {
261                         self.version = stof(argv(1));
262                 }
263                 if(self.version != cvar("gameversion")) 
264                 {
265                         self.classname = "observer";
266                         self.frags = -2;
267                         PutClientInServer();
268                 } else if(cvar("g_campaign") || cvar("g_balance_teams")) {
269                         //JoinBestTeam(self, 0);
270                 } else if(cvar("teamplay") && !cvar("sv_spectate")) {
271                         self.classname = "observer";
272                         stuffcmd(self,"menu_showteamselect\n");
273                 }
274         } else if(argv(0) == "reportcvar") {
275                 GetCvars(1);
276         } else if(argv(0) == "spectate") {
277                 if(cvar("g_lms") || cvar("g_arena"))
278                         return; // don't allow spectating in lms, unless player runs out of lives
279                 if(self.classname == "player" && cvar("sv_spectate") == 1) {
280                         if(self.flagcarried)
281                                 DropFlag(self.flagcarried);
282                         DistributeFragsAmongTeam(self, self.team, 1.0);
283                         self.classname = "observer";
284                         PutClientInServer();
285                 }
286         } else if(argv(0) == "join") {
287                 if(!cvar("g_arena"))
288                 if (self.classname != "player")
289                 {
290                         self.classname = "player";
291                         self.frags = 0;
292                         bprint (strcat("^4", self.netname, "^4 is playing now\n"));
293                         PutClientInServer();
294                 }
295         } else if( argv(0) == "selectteam" ) {
296                 if( !cvar("teamplay") ) {
297                         sprint( self, "selecteam can only be used in teamgames\n");
298                 } else if(cvar("g_campaign")) {
299                         //JoinBestTeam(self, 0);
300                 } else if( argv(1) == "none" ) {
301                         SV_ChangeTeam( 0 );
302                 } else if( argv(1) == "red" ) {
303                         SV_ChangeTeam( COLOR_TEAM1 - 1 );
304                 } else if( argv(1) == "blue" ) {
305                         SV_ChangeTeam( COLOR_TEAM2 - 1 );
306                 } else if( argv(1) == "pink" ) {
307                         SV_ChangeTeam( COLOR_TEAM3 - 1 );
308                 } else if( argv(1) == "yellow" ) {
309                         SV_ChangeTeam( COLOR_TEAM4 - 1 );
310                 } else if( argv(1) == "auto" ) {
311                         self.team = -1;
312                         JoinBestTeam( self, 0 );
313                 } else {
314                         sprint( self, strcat( "selectteam none/red/blue/pink/yellow/auto - \"", argv(1), "\" not recognised\n" ) );
315                 }
316         } else if(argv(0) == "ready") {
317                 if(cvar("sv_ready_restart"))
318                 {
319                         self.ready = TRUE;
320                         bprint(self.netname, "^2 is ready\n");
321                         ReadyCount();
322                 }
323         } else if(argv(0) == "maplist") {
324                 local float i, n;
325                 local string col;
326                 n = tokenize(cvar_string("g_maplist"));
327                 sprint(self, "^7Maps in list: ");
328                 for(i = 0; i < n; ++i)
329                 {
330                         if(math_mod(i, 2))
331                                 col = "^2";
332                         else
333                                 col = "^3";
334                         sprint(self, strcat(col, argv(i), " "));
335                 }
336                 sprint(self, "\n");
337         } else if(argv(0) == "teamstatus") {
338                 PrintScoreboard(self);
339         } else if(argv(0) == "say") {
340                 Say(self, FALSE, substring(s, 4, strlen(s) - 4));
341                 //clientcommand(self, formatmessage(s));
342         } else if(argv(0) == "say_team") {
343                 Say(self, TRUE, substring(s, 9, strlen(s) - 9));
344                 //clientcommand(self, formatmessage(s));
345         } else if(argv(0) == "info") {
346                 cmd = cvar_string(strcat("sv_info_", argv(1)));
347                 if(cmd == "")
348                         sprint(self, "ERROR: unsupported info command\n");
349                 else
350                         wordwrap_sprint(cmd, 1111);
351         } else if(argv(0) == "suggestmap") {
352                 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
353         } else {
354                 cmd = argv(0);
355                 /* checks not needed any more since DP has separated clientcommands and regular commands
356                 if(cmd != "status")
357                 if(cmd != "name")
358                 //if(cmd != "say") // handled above
359                 //if(cmd != "say_team") // handled above
360                 if(cmd != "tell")
361                 if(cmd != "color")
362                 if(cmd != "kill")
363                 if(cmd != "pause")
364                 if(cmd != "kick")
365                 if(cmd != "ping")
366                 if(cmd != "pings")
367                 if(cmd != "ban")
368                 if(cmd != "pmodel")
369                 if(cmd != "rate")
370                 if(cmd != "playermodel")
371                 if(cmd != "playerskin")
372                 if(cmd != "god") if(cmd != "notarget") if(cmd != "fly") if(cmd != "give") if(cmd != "noclip")
373                 {
374                         ServerConsoleEcho(strcat("WARNING: Invalid clientcommand by ", self.netname, ": ", s), TRUE);
375                         return;
376                 }
377                 */
378                 clientcommand(self,s);
379         }
380 }
381
382 float ValidateMap(string vote)
383 {
384         string ext;
385         
386         tokenize(vote);
387         if(argv(0) == "map" || argv(0) == "changelevel")
388                 ext = ".bsp";
389         else if(argv(0) == "chmap")
390                 ext = ".mapcfg";
391         else if(argv(0) == "gotomap")
392                 ext = ".mapcfg";
393         else
394                 return TRUE;
395
396         if(!TryFile(strcat("maps/", argv(1), ext)))
397         {
398                 sprint(self, strcat("^1Invalid mapname, \"^3", argv(1), "^1\" does not exist on this server.\n"));
399                 return FALSE;
400         }
401         return TRUE;
402 }
403
404
405 void VoteThink() {
406         if(votefinished > 0 // a vote was called
407             && time > votefinished) // time is up
408         {
409                 VoteCount();
410         }
411 }
412
413 string VoteParse() {
414         local float index;
415         index = 3;
416         local string vote;
417         vote = argv(2);
418         while(argv(index) != "") {
419                 vote = strcat(vote, " ", argv(index));
420                 index++;
421         }
422
423         // necessary for some of the string operations
424         vote = strzone(vote);
425
426         // now we remove some things that could be misused
427         index = 0;
428         local float found;
429         found = FALSE;
430         local float votelength;
431         votelength = strlen(vote);
432         while(!found && index < votelength)
433         {
434                 local string badchar;
435                 badchar = substring(vote, index, 1);
436                 if(badchar == ";"
437                    || badchar == "\r"
438                    || badchar == "\n")
439                 {
440                         found = TRUE;
441                 } else {
442                         index++;
443                 }
444         }
445         return substring(vote, 0, index);
446 }
447
448 float VoteAllowed(string votecommand) {
449         tokenize(cvar_string("sv_vote_commands"));
450         local float index;
451         index = 0;
452         while(argv(index) != "") {
453                 local string allowed;
454                 allowed = argv(index);
455                 if(votecommand == allowed) {
456                         return TRUE;
457                 }
458                 index++;
459         }
460         return FALSE;
461 }
462
463 void VoteReset() {
464         local string searchclass;
465         searchclass = "player";
466
467         while (TRUE)
468         {
469                 local entity player;
470                 player = find(player, classname, searchclass);
471                 while(player)
472                 {
473                         player.vote_vote = 0;
474                         player = find(player, classname, searchclass);
475                 }
476
477                 if("player" == searchclass) {
478                         searchclass = "observer";
479                 } else if("observer" == searchclass) {
480                         searchclass = "spectator";
481                 } else {
482                         break;
483                 }
484         }
485
486         votecalled = FALSE;
487         votecalledmaster = FALSE;
488         votefinished = 0;
489 }
490
491 void VoteAccept() {
492         bprint(strcat("\{1}^2* ^3", votecaller.netname, "^2's vote for ^1", votecalledvote, "^2 was accepted\n"));
493         if(votecalledmaster)
494         {
495                 votecaller.vote_master = 1;
496         } else {
497                 localcmd(strcat(votecalledvote, "\n"));
498         }
499         votecaller.vote_next = 0; // people like your votes, no wait for next vote
500         VoteReset();
501 }
502
503 void VoteReject() {
504         bprint(strcat("\{1}^2* ^3", votecaller.netname, "^2's vote for ^1", votecalledvote, "^2 was rejected\n"));
505         VoteReset();
506 }
507
508 void VoteTimeout() {
509         bprint(strcat("\{1}^2* ^3", votecaller.netname, "^2's vote for ^1", votecalledvote, "^2 timed out\n"));
510         VoteReset();
511 }
512
513 void VoteStop(entity stopper) {
514         bprint(strcat("\{1}^2* ^3", stopper.netname, "^2 stopped ^3", votecaller.netname, "^2's vote\n"));
515         if(stopper == votecaller) {
516                 // no wait for next vote so you can correct your vote
517                 votecaller.vote_next = 0;
518         }
519         VoteReset();
520 }
521
522 void VoteCount() {
523         local float playercount;
524         playercount = 0;
525         local float yescount;
526         yescount = 0;
527         local float nocount;
528         nocount = 0;
529         local string searchclass;
530         searchclass = "player";
531
532         while (TRUE)
533         {
534                 local entity player;
535                 player = find(player, classname, searchclass);
536
537                 while(player)
538                 {
539                         if(clienttype(player) != CLIENTTYPE_BOT) {
540                                 if(player.vote_vote < 0) {
541                                         nocount++;
542                                 } else if(player.vote_vote > 0) {
543                                         yescount++;
544                                 }
545                                 playercount++;
546                         }
547                         player = find(player, classname, searchclass);
548                 }
549
550                 if("player" == searchclass) {
551                         searchclass = "observer";
552                 } else if("observer" == searchclass) {
553                         searchclass = "specator";
554                 } else {
555                         break;
556                 }
557         }
558
559         if((playercount == 1) && votecalledmaster) {
560                 // if only one player is on the server becoming vote
561                 // master is not allowed.  This could be used for
562                 // trolling or worse. 'self' is the user who has
563                 // called the vote because this function is called
564                 // by SV_ParseClientCommand. Maybe all voting should
565                 // be disabled for a single player?
566                 sprint(self, "^1You are the only player on this server so you can not become vote master.\n");
567                 votecaller.vote_next = 0;
568                 VoteReset();
569         } else if((playercount / 2) < yescount) { // vote accepted
570                 VoteAccept();
571         } else if((playercount / 2) < nocount) { // vote rejected
572                 VoteReject();
573         } else if(time > votefinished) { // vote timedout
574                 VoteTimeout();
575         } // else still running
576 }
577
578
579 void ReadyCount()
580 {
581         local entity e;
582         local float r, p;
583
584         e = find(world, classname, "player");
585
586         while(e)
587         {
588                 if(clienttype(e) == CLIENTTYPE_REAL)
589                 {
590                         p += 1;
591                         if(e.ready) r += 1;
592                 }
593                 e = find(e, classname, "player");
594         }
595
596         if(p && r == p)
597         {
598                 bprint("^1Server is restarting...\n");
599                 localcmd("restart\n");
600         }
601 }