]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/vote.qc
Merge remote branch 'origin/master' into samual/updatecommands
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / vote.qc
1 float Nagger_SendEntity(entity to, float sendflags)
2 {
3         float nags, i, f, b;
4         entity e;
5         WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
6
7         // bits:
8         //   1 = ready
9         //   2 = player needs to ready up
10         //   4 = vote
11         //   8 = player needs to vote
12         //  16 = warmup
13         // sendflags:
14         //  64 = vote counts
15         // 128 = vote string
16
17         nags = 0;
18         if(readycount)
19         {
20                 nags |= 1;
21                 if(to.ready == 0)
22                         nags |= 2;
23         }
24         if(votecalled)
25         {
26                 nags |= 4;
27                 if(to.vote_vote == 0)
28                         nags |= 8;
29         }
30         if(inWarmupStage)
31                 nags |= 16;
32
33         if(sendflags & 64)
34                 nags |= 64;
35
36         if(sendflags & 128)
37                 nags |= 128;
38
39         if(!(nags & 4)) // no vote called? send no string
40                 nags &~= (64 | 128);
41
42         WriteByte(MSG_ENTITY, nags);
43
44         if(nags & 64)
45         {
46                 WriteByte(MSG_ENTITY, vote_yescount);
47                 WriteByte(MSG_ENTITY, vote_nocount);
48                 WriteByte(MSG_ENTITY, vote_needed_absolute);
49                 WriteChar(MSG_ENTITY, to.vote_vote);
50         }
51
52         if(nags & 128)
53                 WriteString(MSG_ENTITY, votecalledvote_display);
54
55         if(nags & 1)
56         {
57                 for(i = 1; i <= maxclients; i += 8)
58                 {
59                         for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
60                                 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
61                                         f |= b;
62                         WriteByte(MSG_ENTITY, f);
63                 }
64         }
65
66         return TRUE;
67 }
68 void Nagger_Init()
69 {
70         Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
71 }
72 void Nagger_VoteChanged()
73 {
74         if(nagger)
75                 nagger.SendFlags |= 128;
76 }
77 void Nagger_VoteCountChanged()
78 {
79         if(nagger)
80                 nagger.SendFlags |= 64;
81 }
82 void Nagger_ReadyCounted()
83 {
84         if(nagger)
85                 nagger.SendFlags |= 1;
86 }
87
88 void ReadyRestartForce()
89 {
90         local entity e;
91
92         bprint("^1Server is restarting...\n");
93
94         VoteReset();
95
96         // clear overtime
97         if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
98                 //we have to decrease timelimit to its original value again!!
99                 float newTL;
100                 newTL = autocvar_timelimit;
101                 newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
102                 cvar_set("timelimit", ftos(newTL));
103         }
104
105         checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
106
107
108         readyrestart_happened = 1;
109         game_starttime = time;
110         if(!g_ca && !g_arena)
111                 game_starttime += RESTART_COUNTDOWN;
112         restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
113
114         inWarmupStage = 0; //once the game is restarted the game is in match stage
115
116         //reset the .ready status of all players (also spectators)
117         FOR_EACH_CLIENTSLOT(e)
118                 e.ready = 0;
119         readycount = 0;
120         Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
121
122         if(autocvar_teamplay_lockonrestart && teamplay) {
123                 lockteams = 1;
124                 bprint("^1The teams are now locked.\n");
125         }
126
127         //initiate the restart-countdown-announcer entity
128         if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
129         {
130                 restartTimer = spawn();
131                 restartTimer.think = restartTimer_Think;
132                 restartTimer.nextthink = game_starttime;
133         }
134
135         //after a restart every players number of allowed timeouts gets reset, too
136         if(autocvar_sv_timeout)
137         {
138                 FOR_EACH_REALPLAYER(e)
139                         e.allowedTimeouts = autocvar_sv_timeout_number;
140         }
141
142         //reset map immediately if this cvar is not set
143         if (!autocvar_sv_ready_restart_after_countdown)
144                 reset_map(TRUE);
145
146         if(autocvar_sv_eventlog)
147                 GameLogEcho(":restart");
148 }
149
150 void ReadyRestart()
151 {
152         // no arena, assault support yet...
153         if(g_arena | g_assault | gameover | intermission_running | race_completing)
154                 localcmd("restart\n");
155         else
156                 localcmd("\nsv_hook_gamerestart\n");
157
158         ReadyRestartForce();
159
160         // reset ALL scores, but only do that at the beginning
161         //of the countdown if sv_ready_restart_after_countdown is off!
162         //Otherwise scores could be manipulated during the countdown!
163         if (!autocvar_sv_ready_restart_after_countdown)
164                 Score_ClearAll();
165 }
166
167 /**
168  * Counts how many players are ready. If not enough players are ready, the function
169  * does nothing. If all players are ready, the timelimit will be extended and the
170  * restart_countdown variable is set to allow other functions like PlayerPostThink
171  * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
172  * is not set the map will be resetted.
173  *
174  * Function is called after the server receives a 'ready' sign from a player.
175  */
176 void ReadyCount()
177 {
178         local entity e;
179         local float r, p;
180
181         r = p = 0;
182
183         FOR_EACH_REALPLAYER(e)
184         {
185                 p += 1;
186                 if(e.ready)
187                         r += 1;
188         }
189
190         readycount = r;
191
192         Nagger_ReadyCounted();
193
194         if(r) // at least one is ready
195         if(r == p) // and, everyone is ready
196                 ReadyRestart();
197 }
198
199 /**
200  * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
201  * is set)
202  */
203 void restartTimer_Think() {
204         restart_mapalreadyrestarted = 1;
205         reset_map(TRUE);
206         Score_ClearAll();
207         remove(self);
208         return;
209 }
210
211 float VoteCheckNasty(string cmd)
212 {
213         if(strstrofs(cmd, ";", 0) >= 0)
214                 return TRUE;
215         if(strstrofs(cmd, "\n", 0) >= 0)
216                 return TRUE;
217         if(strstrofs(cmd, "\r", 0) >= 0)
218                 return TRUE;
219         if(strstrofs(cmd, "$", 0) >= 0)
220                 return TRUE;
221         return FALSE;
222 }
223
224 string GetKickVoteVictim_newcommand;
225 string GetKickVoteVictim_reason;
226
227 entity GetKickVoteVictim(string vote, string cmd, entity caller)
228 {
229         float tokens;
230         string ns;
231         entity e;
232         string reason;
233
234         tokens = tokenize_console(vote);
235         ns = "";
236
237         e = GetCommandPlayerSlotTargetFromTokenizedCommand(tokens, 1);
238         if(e)
239         {
240                 if(ParseCommandPlayerSlotTarget_firsttoken < tokens)
241                         GetKickVoteVictim_reason = substring(vote, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken));
242                 else
243                         GetKickVoteVictim_reason = "";
244
245                 reason = "";
246                 if(cmd != "vdo" || GetKickVoteVictim_reason == "")
247                         reason = "~"; // by convention, ~ prefixes a "unverified" kickban which will not be networked
248
249                 if(substring(GetKickVoteVictim_reason, 0, 1) == "~")
250                 {
251                         reason = "~";
252                         GetKickVoteVictim_reason = substring(GetKickVoteVictim_reason, 1, strlen(GetKickVoteVictim_reason) - 1);
253                 }
254
255                 if(caller)
256                         reason = strcat(reason, "player ", strdecolorize(caller.netname));
257                 else
258                         reason = strcat(reason, "console vote");
259                 if(GetKickVoteVictim_reason != "")
260                         reason = strcat(reason, ": ", strdecolorize(GetKickVoteVictim_reason));
261
262                 if not(cvar_value_issafe(reason))
263                         reason = uri_escape(reason);
264
265                 GetKickVoteVictim_newcommand = strcat(argv(0), " # ", ftos(num_for_edict(e)));
266                 if(argv(0) == "kickban")
267                 {
268                         GetKickVoteVictim_newcommand = strcat(GetKickVoteVictim_newcommand, " ", ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ", reason);
269                 }
270                 else if(argv(0) == "kick")
271                 {
272                         GetKickVoteVictim_newcommand = strcat(GetKickVoteVictim_newcommand, " ", reason);
273                 }
274                 return e;
275         }
276
277         print_to(caller, strcat("Usage: ", cmd, " ", argv(0), " #playernumber (as in \"status\")\n"));
278         return world;
279 }
280
281 string RemapVote_display;
282 string RemapVote_vote;
283 float RemapVote(string vote, string cmd, entity e)
284 {
285         float vote_argc;
286         entity victim;
287         vote_argc = tokenize_console(vote);
288
289         if(!VoteAllowed(argv(0), cmd))
290                 return FALSE;
291
292         // VoteAllowed tokenizes!
293         vote_argc = tokenize_console(vote);
294
295         // remap chmap to gotomap (forces intermission)
296         if(vote_argc < 2)
297                 if(argv(0) == "chmap" || argv(0) == "gotomap" || argv(0) == "kick" || argv(0) == "kickban") // won't work without arguments
298                         return FALSE;
299         if(argv(0) == "chmap")
300         {
301                 vote = strcat("gotomap ", substring(vote, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
302                 vote_argc = tokenize_console(vote);
303         }
304         if(argv(0) == "gotomap")
305         {
306                 if(!(vote = ValidateMap(substring(vote, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), e)))
307                         return FALSE;
308                 vote = strcat("gotomap ", vote);
309                 vote_argc = tokenize_console(vote); // ValidateMap may have done some stuff to it
310         }
311
312         // make kick and kickban votes a bit nicer (and reject them if formatted badly)
313         if(argv(0) == "kick" || argv(0) == "kickban")
314         {
315                 if(!(victim = GetKickVoteVictim(vote, cmd, e)))
316                         return FALSE;
317                 RemapVote_vote = GetKickVoteVictim_newcommand;
318                 RemapVote_display = strcat("^1", vote, " (^7", victim.netname, "^1): ", GetKickVoteVictim_reason);
319         }
320         else
321         {
322                 RemapVote_vote = vote;
323                 RemapVote_display = strzone(strcat("^1", vote));
324         }
325
326         return TRUE;
327 }
328
329 float GameCommand_Vote(string s, entity e) 
330 {
331         float playercount;
332         float argc;
333         argc = tokenize_console(s);
334         if(argv(0) == "help") {
335                 print_to(e, "  vote COMMANDS ARGUMENTS. See 'vhelp' for more info.");
336                 return TRUE;
337         } else if(argv(0) == "vote") {
338                 if(argv(1) == "") {
339                         print_to(e, "^1You have to supply a vote command. See 'vhelp' for more info.");
340                 } else if(argv(1) == "help") {
341                         VoteHelp(e);
342                 } else if(argv(1) == "status") {
343                         if(votecalled) {
344                                 print_to(e, strcat("^7Vote for ", votecalledvote_display, "^7 called by ^7", VoteNetname(votecaller), "^7."));
345                         } else {
346                                 print_to(e, "^1No vote called.");
347                         }
348                 } else if(argv(1) == "call") {
349                         if(!e || autocvar_sv_vote_call) {
350                                 if(autocvar_sv_vote_nospectators && e && e.classname != "player") {
351                                         print_to(e, "^1Error: Only players can call a vote."); // TODO invent a cvar name for allowing votes by spectators during warmup anyway
352                                 }
353                                 else if(timeoutStatus) { //don't allow a vote call during a timeout
354                                         print_to(e, "^1Error: You can not call a vote while a timeout is active.");
355                                 }
356                                 else if(votecalled) {
357                                         print_to(e, "^1There is already a vote called.");
358                                 } else {
359                                         string vote;
360                                         vote = VoteParse(s, argc);
361                                         if(vote == "") {
362                                                 print_to(e, "^1Your vote is empty. See 'vhelp' for more info.");
363                                         } else if(e
364                                                         && time < e.vote_next) {
365                                                 print_to(e, strcat("^1You have to wait ^2", ftos(ceil(e.vote_next - time)), "^1 seconds before you can again call a vote."));
366                                         } else if(VoteCheckNasty(vote)) {
367                                                 print_to(e, "Syntax error in command. See 'vhelp' for more info.");
368                                         } else if(RemapVote(vote, "vcall", e)) {
369                                                 votecalledvote = strzone(RemapVote_vote);
370                                                 votecalledvote_display = strzone(RemapVote_display);
371                                                 votecalled = TRUE;
372                                                 votecalledmaster = FALSE;
373                                                 votefinished = time + autocvar_sv_vote_timeout;
374                                                 votecaller = e; // remember who called the vote
375                                                 if(e) {
376                                                         e.vote_vote = 1; // of course you vote yes
377                                                         e.vote_next = time + autocvar_sv_vote_wait;
378                                                 }
379                                                 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote for ", votecalledvote_display, "\n");
380                                                 if(autocvar_sv_eventlog)
381                                                         GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
382                                                 Nagger_VoteChanged();
383                                                 VoteCount(); // needed if you are the only one
384                                                 msg_entity = e;
385
386                                                 entity player;
387                                                 FOR_EACH_REALCLIENT(player)
388                                                 {
389                                                         ++playercount;
390                                                 }
391                                                 if(playercount > 1) // don't announce a "vote now" sound if player is alone
392                                                         Announce("votecall");
393                                         } else {
394                                                 print_to(e, "^1This vote is not ok. See 'vhelp' for more info.");
395                                         }
396                                 }
397                         } else {
398                                 print_to(e, "^1Vote calling is NOT allowed.");
399                         }
400                 } else if(argv(1) == "stop") {
401                         if(!votecalled) {
402                                 print_to(e, "^1No vote called.");
403                         } else if(e == votecaller) { // the votecaller can stop a vote
404                                 VoteStop(e);
405                         } else if(!e) { // server admin / console can too
406                                 VoteStop(e);
407                         } else if(e.vote_master) { // masters can too
408                                 VoteStop(e);
409                         } else {
410                                 print_to(e, "^1You are not allowed to stop that Vote.");
411                         }
412                 } else if(argv(1) == "master") {
413                         if(autocvar_sv_vote_master) {
414                                 if(votecalled) {
415                                         print_to(e, "^1There is already a vote called.");
416                                 } else {
417                                         votecalled = TRUE;
418                                         votecalledmaster = TRUE;
419                                         votecalledvote = strzone("XXX");
420                                         votecalledvote_display = strzone("^3master");
421                                         votefinished = time + autocvar_sv_vote_timeout;
422                                         votecaller = e; // remember who called the vote
423                                         if(e) {
424                                                 e.vote_vote = 1; // of course you vote yes
425                                                 e.vote_next = time + autocvar_sv_vote_wait;
426                                         }
427                                         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote to become ^3master^2.\n");
428                                         if(autocvar_sv_eventlog)
429                                                 GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
430                                         Nagger_VoteChanged();
431                                         VoteCount(); // needed if you are the only one
432                                 }
433                         } else {
434                                 print_to(e, "^1Vote to become master is NOT allowed.");
435                         }
436                 } else if(argv(1) == "do") {
437                         if(!e || e.vote_master) {
438                                 string dovote;
439                                 dovote = VoteParse(s, argc);
440                                 if(dovote == "") {
441                                         print_to(e, "^1Your command was empty. See 'vhelp' for more info.");
442                                 } else if(VoteCheckNasty(dovote)) {
443                                         print_to(e, "Syntax error in command. See 'vhelp' for more info.");
444                                 } else if(RemapVote(dovote, "vdo", e)) { // strcat seems to be necessary
445                                         bprint("\{1}^2* ^3", VoteNetname(e), "^2 used their ^3master^2 status to do \"^2", RemapVote_display, "^2\".\n");
446                                         if(autocvar_sv_eventlog)
447                                                 GameLogEcho(strcat(":vote:vdo:", ftos(e.playerid), ":", RemapVote_display));
448                                         localcmd(strcat(RemapVote_vote, "\n"));
449                                 } else {
450                                         print_to(e, "^1This command is not ok. See 'vhelp' for more info.");
451                                 }
452                         } else {
453                                 print_to(e, "^1You are NOT a master.  You might need to login or vote to become master first. See 'vhelp' for more info.");
454                         }
455                 } else if(argv(1) == "login") {
456                         string masterpwd;
457                         masterpwd = autocvar_sv_vote_master_password;
458                         if(masterpwd != "") {
459                                 float granted;
460                                 granted = (masterpwd == argv(2));
461                                 if (e)
462                                         e.vote_master = granted;
463                                 if(granted) {
464                                         print("Accepted master login from ", VoteNetname(e), "\n");
465                                         bprint("\{1}^2* ^3", VoteNetname(e), "^2 logged in as ^3master^2\n");
466                                         if(autocvar_sv_eventlog)
467                                                 GameLogEcho(strcat(":vote:vlogin:", ftos(e.playerid)));
468                                 }
469                                 else
470                                         print("REJECTED master login from ", VoteNetname(e), "\n");
471                         }
472                         else
473                                 print_to(e, "^1Login to become master is NOT allowed.");
474                 } else if(argv(1) == "yes") {
475                         if(!votecalled) {
476                                 print_to(e, "^1No vote called.");
477                         } else if (!e) {
478                                 print_to(e, "^1You can't vote from the server console.");
479                         } else if(e.vote_vote == 0
480                                   || autocvar_sv_vote_change) {
481                                 msg_entity = e;
482                                 print_to(e, "^1You accepted the vote.");
483                                 e.vote_vote = 1;
484                                 if(!autocvar_sv_vote_singlecount) {
485                                         VoteCount();
486                                 }
487                         } else {
488                                 print_to(e, "^1You have already voted.");
489                         }
490                 } else if(argv(1) == "no") {
491                         if(!votecalled) {
492                                 print_to(e, "^1No vote called.");
493                         } else if (!e) {
494                                 print_to(e, "^1You can't vote from the server console.");
495                         } else if(e.vote_vote == 0
496                                   || autocvar_sv_vote_change) {
497                                 msg_entity = e;
498                                 print_to(e, "^1You rejected the vote.");
499                                 e.vote_vote = -1;
500                                 if(!autocvar_sv_vote_singlecount) {
501                                         VoteCount();
502                                 }
503                         } else {
504                                 print_to(e, "^1You have already voted.");
505                         }
506                 } else if(argv(1) == "abstain" || argv(1) == "dontcare") {
507                         if(!votecalled) {
508                                 print_to(e, "^1No vote called.");
509                         } else if (!e) {
510                                 print_to(e, "^1You can't vote from the server console.");
511                         } else if(e.vote_vote == 0
512                                   || autocvar_sv_vote_change) {
513                                 msg_entity = e;
514                                 print_to(e, "^1You abstained from your vote.");
515                                 e.vote_vote = -2;
516                                 if(!autocvar_sv_vote_singlecount) {
517                                         VoteCount();
518                                 }
519                         } else {
520                                 print_to(e, "^1You have already voted.");
521                         }
522                 } else {
523                         // ignore this?
524                         print_to(e, "^1Unknown vote command.");
525                 }
526                 return TRUE;
527         }
528         return FALSE;
529 }
530
531 void VoteHelp(entity e) {
532         string vmasterdis;
533         if(!autocvar_sv_vote_master) {
534                 vmasterdis = " ^1(disabled)";
535         }
536
537         string vlogindis;
538         if("" == autocvar_sv_vote_master_password) {
539                 vlogindis = " ^1(disabled)";
540         }
541
542         string vcalldis;
543         if(!autocvar_sv_vote_call) {
544                 vcalldis = " ^1(disabled)";
545         }
546
547         print_to(e, "^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 login^7\" \"^2cmd vote do ^3COMMAND ARGUMENTS^7\" \"^2cmd vote yes^7\" \"^2cmd vote no^7\" \"^2cmd vote abstain^7\" \"^2cmd vote dontcare^7\".");
548         print_to(e, "^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\" \"^2vlogin^7\" \"^2vdo ^3COMMAND ARGUMENTS^7\" \"^2vyes^7\" \"^2vno^7\" \"^2abstain^7\" \"^2vdontcare^7\".");
549         print_to(e, "^7\"^2help^7\" shows this info.");
550         print_to(e, "^7\"^2status^7\" shows if there is a vote called and who called it.");
551         print_to(e, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7"));
552         print_to(e, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.");
553         print_to(e, strcat("^7\"^2master^7\" call a vote to become master who can execute commands without a vote", vmasterdis, "^7"));
554         print_to(e, strcat("^7\"^2login^7\" login to become master who can execute commands without a vote.", vlogindis, "^7"));
555         print_to(e, "^7\"^2do^7\" executes a command if you are a master. See the list of allowed commands.");
556         print_to(e, "^7\"^2yes^7\", \"^2no^7\", \"^2abstain^7\" and \"^2dontcare^7\" to make your vote.");
557         print_to(e, "^7If enough of the players vote yes the vote is accepted.");
558         print_to(e, "^7If enough of the players vote no the vote is rejected.");
559         print_to(e, strcat("^7If neither the vote will timeout after ", ftos(autocvar_sv_vote_timeout), "^7 seconds."));
560         print_to(e, "^7You can call a vote for or execute these commands:");
561         print_to(e, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
562 }
563
564 string VoteNetname(entity e)
565 {
566         if(e) {
567                 return e.netname;
568         } else {
569                 if(autocvar_sv_adminnick != "") {
570                         return autocvar_sv_adminnick;
571                 } else {
572                         return autocvar_hostname;
573                 }
574         }
575 }
576
577 string ValidateMap(string m, entity e)
578 {
579         m = MapInfo_FixName(m);
580         if(!m)
581         {
582                 print_to(e, "This map is not available on this server.");
583                 return string_null;
584         }
585         if(!autocvar_sv_vote_override_mostrecent)
586                 if(Map_IsRecent(m))
587                 {
588                         print_to(e, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
589                         return string_null;
590                 }
591         if(!MapInfo_CheckMap(m))
592         {
593                 print_to(e, strcat("^1Invalid mapname, \"^3", m, "^1\" does not support the current game mode."));
594                 return string_null;
595         }
596
597         return m;
598 }
599
600
601 void VoteThink() {
602         if(votefinished > 0) // a vote was called
603         if(time > votefinished) // time is up
604         {
605                 VoteCount();
606         }
607 }
608
609 string VoteParse(string all, float argc) {
610         if(argc < 3)
611                 return "";
612         return substring(all, argv_start_index(2), argv_end_index(-1) - argv_start_index(2));
613 }
614
615 float VoteCommandInList(string votecommand, string list)
616 {
617         string l;
618         l = strcat(" ", list, " ");
619         
620         if(strstrofs(l, strcat(" ", votecommand, " "), 0) >= 0)
621                 return TRUE;
622         
623         // if gotomap is allowed, chmap is too, and vice versa
624         if(votecommand == "gotomap")
625                 if(strstrofs(l, " chmap ", 0) >= 0)
626                         return TRUE;
627         if(votecommand == "chmap")
628                 if(strstrofs(l, " gotomap ", 0) >= 0)
629                         return TRUE;
630         
631         return FALSE;
632 }
633
634 float VoteAllowed(string votecommand, string cmd) {
635         if(VoteCommandInList(votecommand, autocvar_sv_vote_commands))
636                 return TRUE;
637
638         if(cmd == "vdo")
639         {
640                 if(VoteCommandInList(votecommand, autocvar_sv_vote_master_commands))
641                         return TRUE;
642         }
643         else
644         {
645                 if(VoteCommandInList(votecommand, autocvar_sv_vote_only_commands))
646                         return TRUE;
647         }
648
649         return FALSE;
650 }
651
652 void VoteReset() {
653         entity player;
654
655         FOR_EACH_CLIENT(player)
656         {
657                 player.vote_vote = 0;
658         }
659
660         if(votecalled)
661         {
662                 strunzone(votecalledvote);
663                 strunzone(votecalledvote_display);
664         }
665
666         votecalled = FALSE;
667         votecalledmaster = FALSE;
668         votefinished = 0;
669         votecalledvote = string_null;
670         votecalledvote_display = string_null;
671
672         Nagger_VoteChanged();
673 }
674
675 void VoteAccept() {
676         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
677         if(votecalledmaster)
678         {
679                 if(votecaller) {
680                         votecaller.vote_master = 1;
681                 }
682         } else {
683                 localcmd(strcat(votecalledvote, "\n"));
684         }
685         if(votecaller) {
686                 votecaller.vote_next = 0; // people like your votes,
687                                           // no wait for next vote
688         }
689         VoteReset();
690         Announce("voteaccept");
691 }
692
693 void VoteReject() {
694         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
695         VoteReset();
696         Announce("votefail");
697 }
698
699 void VoteTimeout() {
700         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
701         VoteReset();
702         Announce("votefail");
703 }
704
705 void VoteStop(entity stopper) {
706         bprint("\{1}^2* ^3", VoteNetname(stopper), "^2 stopped ^3", VoteNetname(votecaller), "^2's vote\n");
707         if(autocvar_sv_eventlog)
708                 GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
709         if(stopper == votecaller) {
710                 // no wait for next vote so you can correct your vote
711                 if(votecaller) {
712                         votecaller.vote_next = time + autocvar_sv_vote_stop;
713                 }
714         }
715         VoteReset();
716 }
717
718 void VoteSpam(float notvoters, float mincount, string result)
719 {
720         string s;
721         if(mincount >= 0)
722         {
723                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
724                 s = strcat(s, ftos(vote_nocount), "^2 (^1");
725                 s = strcat(s, ftos(mincount), "^2 needed), ^1");
726                 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
727                 s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
728         }
729         else
730         {
731                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
732                 s = strcat(s, ftos(vote_nocount), "^2, ^1");
733                 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
734                 s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
735         }
736         bprint(s);
737         if(autocvar_sv_eventlog)
738         {
739                 s = strcat(":vote:v", result, ":", ftos(vote_yescount));
740                 s = strcat(s, ":", ftos(vote_nocount));
741                 s = strcat(s, ":", ftos(vote_abstaincount));
742                 s = strcat(s, ":", ftos(notvoters));
743                 s = strcat(s, ":", ftos(mincount));
744                 GameLogEcho(s);
745         }
746 }
747
748 void VoteCount() {
749         float playercount;
750         playercount = 0;
751         vote_yescount = 0;
752         vote_nocount = 0;
753         vote_abstaincount = 0;
754         entity player;
755         //same for real players
756         float realplayercount;
757         float realplayeryescount;
758         float realplayernocount;
759         float realplayerabstaincount;
760         realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
761
762         Nagger_VoteCountChanged();
763
764         FOR_EACH_REALCLIENT(player)
765         {
766                 if(player.vote_vote == -1) {
767                         ++vote_nocount;
768                 } else if(player.vote_vote == 1) {
769                         ++vote_yescount;
770                 } else if(player.vote_vote == -2) {
771                         ++vote_abstaincount;
772                 }
773                 ++playercount;
774                 //do the same for real players
775                 if(player.classname == "player") {
776                         if(player.vote_vote == -1) {
777                                 ++realplayernocount;
778                         } else if(player.vote_vote == 1) {
779                                 ++realplayeryescount;
780                         } else if(player.vote_vote == -2) {
781                                 ++realplayerabstaincount;
782                         }
783                         ++realplayercount;
784                 }
785         }
786
787         //in tournament mode, if we have at least one player then don't make the vote dependent on spectators (so specs don't have to press F1)
788         if(autocvar_sv_vote_nospectators)
789         if(realplayercount > 0) {
790                 vote_yescount = realplayeryescount;
791                 vote_nocount = realplayernocount;
792                 vote_abstaincount = realplayerabstaincount;
793                 playercount = realplayercount;
794         }
795
796         float votefactor, simplevotefactor;
797         votefactor = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
798         simplevotefactor = autocvar_sv_vote_simple_majority_factor;
799
800         // FIXME this number is a guess
801         vote_needed_absolute = floor((playercount - vote_abstaincount) * votefactor) + 1;
802         if(simplevotefactor)
803         {
804                 simplevotefactor = bound(votefactor, simplevotefactor, 0.999);
805                 vote_needed_simple = floor((vote_yescount + vote_nocount) * simplevotefactor) + 1;
806         }
807         else
808                 vote_needed_simple = 0;
809
810         if(votecalledmaster
811            && playercount == 1) {
812                 // if only one player is on the server becoming vote
813                 // master is not allowed.  This could be used for
814                 // trolling or worse. 'self' is the user who has
815                 // called the vote because this function is called
816                 // by SV_ParseClientCommand. Maybe all voting should
817                 // be disabled for a single player?
818                 print_to(votecaller, "^1You are the only player on this server so you can not become vote master.");
819                 if(votecaller) {
820                         votecaller.vote_next = 0;
821                 }
822                 VoteReset();
823         } else {
824                 if(vote_yescount >= vote_needed_absolute)
825                 {
826                         VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "yes");
827                         VoteAccept();
828                 }
829                 else if(vote_nocount > playercount - vote_abstaincount - vote_needed_absolute) // that means, vote_yescount cannot reach vote_needed_absolute any more
830                 {
831                         VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "no");
832                         VoteReject();
833                 }
834                 else if(time > votefinished)
835                 {
836                         if(simplevotefactor)
837                         {
838                                 string result;
839                                 if(vote_yescount >= vote_needed_simple)
840                                         result = "yes";
841                                 else if(vote_yescount + vote_nocount > 0)
842                                         result = "no";
843                                 else
844                                         result = "timeout";
845                                 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, min(vote_needed_absolute, vote_needed_simple), result);
846                                 if(result == "yes")
847                                         VoteAccept();
848                                 else if(result == "no")
849                                         VoteReject();
850                                 else
851                                         VoteTimeout();
852                         }
853                         else
854                         {
855                                 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, vote_needed_absolute, "timeout");
856                                 VoteTimeout();
857                         }
858                 }
859         }
860 }