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