]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/vote.qc
Move some stuff out of clientcommands.qc (it wasn't appropriate being there in the...
[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         float playercount;
331         float argc;
332         argc = tokenize_console(s);
333         if(argv(0) == "help") {
334                 print_to(e, "  vote COMMANDS ARGUMENTS. See 'vhelp' for more info.");
335                 return TRUE;
336         } else if(argv(0) == "vote") {
337                 if(argv(1) == "") {
338                         print_to(e, "^1You have to supply a vote command. See 'vhelp' for more info.");
339                 } else if(argv(1) == "help") {
340                         VoteHelp(e);
341                 } else if(argv(1) == "status") {
342                         if(votecalled) {
343                                 print_to(e, strcat("^7Vote for ", votecalledvote_display, "^7 called by ^7", VoteNetname(votecaller), "^7."));
344                         } else {
345                                 print_to(e, "^1No vote called.");
346                         }
347                 } else if(argv(1) == "call") {
348                         if(!e || autocvar_sv_vote_call) {
349                                 if(autocvar_sv_vote_nospectators && e && e.classname != "player") {
350                                         print_to(e, "^1Error: Only players can call a vote."); // TODO invent a cvar name for allowing votes by spectators during warmup anyway
351                                 }
352                                 else if(timeoutStatus) { //don't allow a vote call during a timeout
353                                         print_to(e, "^1Error: You can not call a vote while a timeout is active.");
354                                 }
355                                 else if(votecalled) {
356                                         print_to(e, "^1There is already a vote called.");
357                                 } else {
358                                         string vote;
359                                         vote = VoteParse(s, argc);
360                                         if(vote == "") {
361                                                 print_to(e, "^1Your vote is empty. See 'vhelp' for more info.");
362                                         } else if(e
363                                                         && time < e.vote_next) {
364                                                 print_to(e, strcat("^1You have to wait ^2", ftos(ceil(e.vote_next - time)), "^1 seconds before you can again call a vote."));
365                                         } else if(VoteCheckNasty(vote)) {
366                                                 print_to(e, "Syntax error in command. See 'vhelp' for more info.");
367                                         } else if(RemapVote(vote, "vcall", e)) {
368                                                 votecalledvote = strzone(RemapVote_vote);
369                                                 votecalledvote_display = strzone(RemapVote_display);
370                                                 votecalled = TRUE;
371                                                 votecalledmaster = FALSE;
372                                                 votefinished = time + autocvar_sv_vote_timeout;
373                                                 votecaller = e; // remember who called the vote
374                                                 if(e) {
375                                                         e.vote_vote = 1; // of course you vote yes
376                                                         e.vote_next = time + autocvar_sv_vote_wait;
377                                                 }
378                                                 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote for ", votecalledvote_display, "\n");
379                                                 if(autocvar_sv_eventlog)
380                                                         GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
381                                                 Nagger_VoteChanged();
382                                                 VoteCount(); // needed if you are the only one
383                                                 msg_entity = e;
384
385                                                 entity player;
386                                                 FOR_EACH_REALCLIENT(player)
387                                                 {
388                                                         ++playercount;
389                                                 }
390                                                 if(playercount > 1) // don't announce a "vote now" sound if player is alone
391                                                         Announce("votecall");
392                                         } else {
393                                                 print_to(e, "^1This vote is not ok. See 'vhelp' for more info.");
394                                         }
395                                 }
396                         } else {
397                                 print_to(e, "^1Vote calling is NOT allowed.");
398                         }
399                 } else if(argv(1) == "stop") {
400                         if(!votecalled) {
401                                 print_to(e, "^1No vote called.");
402                         } else if(e == votecaller) { // the votecaller can stop a vote
403                                 VoteStop(e);
404                         } else if(!e) { // server admin / console can too
405                                 VoteStop(e);
406                         } else if(e.vote_master) { // masters can too
407                                 VoteStop(e);
408                         } else {
409                                 print_to(e, "^1You are not allowed to stop that Vote.");
410                         }
411                 } else if(argv(1) == "master") {
412                         if(autocvar_sv_vote_master) {
413                                 if(votecalled) {
414                                         print_to(e, "^1There is already a vote called.");
415                                 } else {
416                                         votecalled = TRUE;
417                                         votecalledmaster = TRUE;
418                                         votecalledvote = strzone("XXX");
419                                         votecalledvote_display = strzone("^3master");
420                                         votefinished = time + autocvar_sv_vote_timeout;
421                                         votecaller = e; // remember who called the vote
422                                         if(e) {
423                                                 e.vote_vote = 1; // of course you vote yes
424                                                 e.vote_next = time + autocvar_sv_vote_wait;
425                                         }
426                                         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote to become ^3master^2.\n");
427                                         if(autocvar_sv_eventlog)
428                                                 GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
429                                         Nagger_VoteChanged();
430                                         VoteCount(); // needed if you are the only one
431                                 }
432                         } else {
433                                 print_to(e, "^1Vote to become master is NOT allowed.");
434                         }
435                 } else if(argv(1) == "do") {
436                         if(!e || e.vote_master) {
437                                 string dovote;
438                                 dovote = VoteParse(s, argc);
439                                 if(dovote == "") {
440                                         print_to(e, "^1Your command was empty. See 'vhelp' for more info.");
441                                 } else if(VoteCheckNasty(dovote)) {
442                                         print_to(e, "Syntax error in command. See 'vhelp' for more info.");
443                                 } else if(RemapVote(dovote, "vdo", e)) { // strcat seems to be necessary
444                                         bprint("\{1}^2* ^3", VoteNetname(e), "^2 used their ^3master^2 status to do \"^2", RemapVote_display, "^2\".\n");
445                                         if(autocvar_sv_eventlog)
446                                                 GameLogEcho(strcat(":vote:vdo:", ftos(e.playerid), ":", RemapVote_display));
447                                         localcmd(strcat(RemapVote_vote, "\n"));
448                                 } else {
449                                         print_to(e, "^1This command is not ok. See 'vhelp' for more info.");
450                                 }
451                         } else {
452                                 print_to(e, "^1You are NOT a master.  You might need to login or vote to become master first. See 'vhelp' for more info.");
453                         }
454                 } else if(argv(1) == "login") {
455                         string masterpwd;
456                         masterpwd = autocvar_sv_vote_master_password;
457                         if(masterpwd != "") {
458                                 float granted;
459                                 granted = (masterpwd == argv(2));
460                                 if (e)
461                                         e.vote_master = granted;
462                                 if(granted) {
463                                         print("Accepted master login from ", VoteNetname(e), "\n");
464                                         bprint("\{1}^2* ^3", VoteNetname(e), "^2 logged in as ^3master^2\n");
465                                         if(autocvar_sv_eventlog)
466                                                 GameLogEcho(strcat(":vote:vlogin:", ftos(e.playerid)));
467                                 }
468                                 else
469                                         print("REJECTED master login from ", VoteNetname(e), "\n");
470                         }
471                         else
472                                 print_to(e, "^1Login to become master is NOT allowed.");
473                 } else if(argv(1) == "yes") {
474                         if(!votecalled) {
475                                 print_to(e, "^1No vote called.");
476                         } else if (!e) {
477                                 print_to(e, "^1You can't vote from the server console.");
478                         } else if(e.vote_vote == 0
479                                   || autocvar_sv_vote_change) {
480                                 msg_entity = e;
481                                 print_to(e, "^1You accepted the vote.");
482                                 e.vote_vote = 1;
483                                 if(!autocvar_sv_vote_singlecount) {
484                                         VoteCount();
485                                 }
486                         } else {
487                                 print_to(e, "^1You have already voted.");
488                         }
489                 } else if(argv(1) == "no") {
490                         if(!votecalled) {
491                                 print_to(e, "^1No vote called.");
492                         } else if (!e) {
493                                 print_to(e, "^1You can't vote from the server console.");
494                         } else if(e.vote_vote == 0
495                                   || autocvar_sv_vote_change) {
496                                 msg_entity = e;
497                                 print_to(e, "^1You rejected the vote.");
498                                 e.vote_vote = -1;
499                                 if(!autocvar_sv_vote_singlecount) {
500                                         VoteCount();
501                                 }
502                         } else {
503                                 print_to(e, "^1You have already voted.");
504                         }
505                 } else if(argv(1) == "abstain" || argv(1) == "dontcare") {
506                         if(!votecalled) {
507                                 print_to(e, "^1No vote called.");
508                         } else if (!e) {
509                                 print_to(e, "^1You can't vote from the server console.");
510                         } else if(e.vote_vote == 0
511                                   || autocvar_sv_vote_change) {
512                                 msg_entity = e;
513                                 print_to(e, "^1You abstained from your vote.");
514                                 e.vote_vote = -2;
515                                 if(!autocvar_sv_vote_singlecount) {
516                                         VoteCount();
517                                 }
518                         } else {
519                                 print_to(e, "^1You have already voted.");
520                         }
521                 } else {
522                         // ignore this?
523                         print_to(e, "^1Unknown vote command.");
524                 }
525                 return TRUE;
526         }
527         return FALSE;
528 }
529
530 void VoteHelp(entity e) {
531         string vmasterdis;
532         if(!autocvar_sv_vote_master) {
533                 vmasterdis = " ^1(disabled)";
534         }
535
536         string vlogindis;
537         if("" == autocvar_sv_vote_master_password) {
538                 vlogindis = " ^1(disabled)";
539         }
540
541         string vcalldis;
542         if(!autocvar_sv_vote_call) {
543                 vcalldis = " ^1(disabled)";
544         }
545
546         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\".");
547         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\".");
548         print_to(e, "^7\"^2help^7\" shows this info.");
549         print_to(e, "^7\"^2status^7\" shows if there is a vote called and who called it.");
550         print_to(e, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7"));
551         print_to(e, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.");
552         print_to(e, strcat("^7\"^2master^7\" call a vote to become master who can execute commands without a vote", vmasterdis, "^7"));
553         print_to(e, strcat("^7\"^2login^7\" login to become master who can execute commands without a vote.", vlogindis, "^7"));
554         print_to(e, "^7\"^2do^7\" executes a command if you are a master. See the list of allowed commands.");
555         print_to(e, "^7\"^2yes^7\", \"^2no^7\", \"^2abstain^7\" and \"^2dontcare^7\" to make your vote.");
556         print_to(e, "^7If enough of the players vote yes the vote is accepted.");
557         print_to(e, "^7If enough of the players vote no the vote is rejected.");
558         print_to(e, strcat("^7If neither the vote will timeout after ", ftos(autocvar_sv_vote_timeout), "^7 seconds."));
559         print_to(e, "^7You can call a vote for or execute these commands:");
560         print_to(e, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
561 }
562
563 string VoteNetname(entity e)
564 {
565         if(e) {
566                 return e.netname;
567         } else {
568                 if(autocvar_sv_adminnick != "") {
569                         return autocvar_sv_adminnick;
570                 } else {
571                         return autocvar_hostname;
572                 }
573         }
574 }
575
576 string ValidateMap(string m, entity e)
577 {
578         m = MapInfo_FixName(m);
579         if(!m)
580         {
581                 print_to(e, "This map is not available on this server.");
582                 return string_null;
583         }
584         if(!autocvar_sv_vote_override_mostrecent)
585                 if(Map_IsRecent(m))
586                 {
587                         print_to(e, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
588                         return string_null;
589                 }
590         if(!MapInfo_CheckMap(m))
591         {
592                 print_to(e, strcat("^1Invalid mapname, \"^3", m, "^1\" does not support the current game mode."));
593                 return string_null;
594         }
595
596         return m;
597 }
598
599
600 void VoteThink() {
601         if(votefinished > 0) // a vote was called
602         if(time > votefinished) // time is up
603         {
604                 VoteCount();
605         }
606 }
607
608 string VoteParse(string all, float argc) {
609         if(argc < 3)
610                 return "";
611         return substring(all, argv_start_index(2), argv_end_index(-1) - argv_start_index(2));
612 }
613
614 float VoteCommandInList(string votecommand, string list)
615 {
616         string l;
617         l = strcat(" ", list, " ");
618         
619         if(strstrofs(l, strcat(" ", votecommand, " "), 0) >= 0)
620                 return TRUE;
621         
622         // if gotomap is allowed, chmap is too, and vice versa
623         if(votecommand == "gotomap")
624                 if(strstrofs(l, " chmap ", 0) >= 0)
625                         return TRUE;
626         if(votecommand == "chmap")
627                 if(strstrofs(l, " gotomap ", 0) >= 0)
628                         return TRUE;
629         
630         return FALSE;
631 }
632
633 float VoteAllowed(string votecommand, string cmd) {
634         if(VoteCommandInList(votecommand, autocvar_sv_vote_commands))
635                 return TRUE;
636
637         if(cmd == "vdo")
638         {
639                 if(VoteCommandInList(votecommand, autocvar_sv_vote_master_commands))
640                         return TRUE;
641         }
642         else
643         {
644                 if(VoteCommandInList(votecommand, autocvar_sv_vote_only_commands))
645                         return TRUE;
646         }
647
648         return FALSE;
649 }
650
651 void VoteReset() {
652         entity player;
653
654         FOR_EACH_CLIENT(player)
655         {
656                 player.vote_vote = 0;
657         }
658
659         if(votecalled)
660         {
661                 strunzone(votecalledvote);
662                 strunzone(votecalledvote_display);
663         }
664
665         votecalled = FALSE;
666         votecalledmaster = FALSE;
667         votefinished = 0;
668         votecalledvote = string_null;
669         votecalledvote_display = string_null;
670
671         Nagger_VoteChanged();
672 }
673
674 void VoteAccept() {
675         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
676         if(votecalledmaster)
677         {
678                 if(votecaller) {
679                         votecaller.vote_master = 1;
680                 }
681         } else {
682                 localcmd(strcat(votecalledvote, "\n"));
683         }
684         if(votecaller) {
685                 votecaller.vote_next = 0; // people like your votes,
686                                           // no wait for next vote
687         }
688         VoteReset();
689         Announce("voteaccept");
690 }
691
692 void VoteReject() {
693         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
694         VoteReset();
695         Announce("votefail");
696 }
697
698 void VoteTimeout() {
699         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
700         VoteReset();
701         Announce("votefail");
702 }
703
704 void VoteStop(entity stopper) {
705         bprint("\{1}^2* ^3", VoteNetname(stopper), "^2 stopped ^3", VoteNetname(votecaller), "^2's vote\n");
706         if(autocvar_sv_eventlog)
707                 GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
708         if(stopper == votecaller) {
709                 // no wait for next vote so you can correct your vote
710                 if(votecaller) {
711                         votecaller.vote_next = time + autocvar_sv_vote_stop;
712                 }
713         }
714         VoteReset();
715 }
716
717 void VoteSpam(float notvoters, float mincount, string result)
718 {
719         string s;
720         if(mincount >= 0)
721         {
722                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
723                 s = strcat(s, ftos(vote_nocount), "^2 (^1");
724                 s = strcat(s, ftos(mincount), "^2 needed), ^1");
725                 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
726                 s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
727         }
728         else
729         {
730                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
731                 s = strcat(s, ftos(vote_nocount), "^2, ^1");
732                 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
733                 s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
734         }
735         bprint(s);
736         if(autocvar_sv_eventlog)
737         {
738                 s = strcat(":vote:v", result, ":", ftos(vote_yescount));
739                 s = strcat(s, ":", ftos(vote_nocount));
740                 s = strcat(s, ":", ftos(vote_abstaincount));
741                 s = strcat(s, ":", ftos(notvoters));
742                 s = strcat(s, ":", ftos(mincount));
743                 GameLogEcho(s);
744         }
745 }
746
747 void VoteCount() {
748         float playercount;
749         playercount = 0;
750         vote_yescount = 0;
751         vote_nocount = 0;
752         vote_abstaincount = 0;
753         entity player;
754         //same for real players
755         float realplayercount;
756         float realplayeryescount;
757         float realplayernocount;
758         float realplayerabstaincount;
759         realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
760
761         Nagger_VoteCountChanged();
762
763         FOR_EACH_REALCLIENT(player)
764         {
765                 if(player.vote_vote == -1) {
766                         ++vote_nocount;
767                 } else if(player.vote_vote == 1) {
768                         ++vote_yescount;
769                 } else if(player.vote_vote == -2) {
770                         ++vote_abstaincount;
771                 }
772                 ++playercount;
773                 //do the same for real players
774                 if(player.classname == "player") {
775                         if(player.vote_vote == -1) {
776                                 ++realplayernocount;
777                         } else if(player.vote_vote == 1) {
778                                 ++realplayeryescount;
779                         } else if(player.vote_vote == -2) {
780                                 ++realplayerabstaincount;
781                         }
782                         ++realplayercount;
783                 }
784         }
785
786         //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)
787         if(autocvar_sv_vote_nospectators)
788         if(realplayercount > 0) {
789                 vote_yescount = realplayeryescount;
790                 vote_nocount = realplayernocount;
791                 vote_abstaincount = realplayerabstaincount;
792                 playercount = realplayercount;
793         }
794
795         float votefactor, simplevotefactor;
796         votefactor = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
797         simplevotefactor = autocvar_sv_vote_simple_majority_factor;
798
799         // FIXME this number is a guess
800         vote_needed_absolute = floor((playercount - vote_abstaincount) * votefactor) + 1;
801         if(simplevotefactor)
802         {
803                 simplevotefactor = bound(votefactor, simplevotefactor, 0.999);
804                 vote_needed_simple = floor((vote_yescount + vote_nocount) * simplevotefactor) + 1;
805         }
806         else
807                 vote_needed_simple = 0;
808
809         if(votecalledmaster
810            && playercount == 1) {
811                 // if only one player is on the server becoming vote
812                 // master is not allowed.  This could be used for
813                 // trolling or worse. 'self' is the user who has
814                 // called the vote because this function is called
815                 // by SV_ParseClientCommand. Maybe all voting should
816                 // be disabled for a single player?
817                 print_to(votecaller, "^1You are the only player on this server so you can not become vote master.");
818                 if(votecaller) {
819                         votecaller.vote_next = 0;
820                 }
821                 VoteReset();
822         } else {
823                 if(vote_yescount >= vote_needed_absolute)
824                 {
825                         VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "yes");
826                         VoteAccept();
827                 }
828                 else if(vote_nocount > playercount - vote_abstaincount - vote_needed_absolute) // that means, vote_yescount cannot reach vote_needed_absolute any more
829                 {
830                         VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "no");
831                         VoteReject();
832                 }
833                 else if(time > votefinished)
834                 {
835                         if(simplevotefactor)
836                         {
837                                 string result;
838                                 if(vote_yescount >= vote_needed_simple)
839                                         result = "yes";
840                                 else if(vote_yescount + vote_nocount > 0)
841                                         result = "no";
842                                 else
843                                         result = "timeout";
844                                 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, min(vote_needed_absolute, vote_needed_simple), result);
845                                 if(result == "yes")
846                                         VoteAccept();
847                                 else if(result == "no")
848                                         VoteReject();
849                                 else
850                                         VoteTimeout();
851                         }
852                         else
853                         {
854                                 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, vote_needed_absolute, "timeout");
855                                 VoteTimeout();
856                         }
857                 }
858         }
859 }