]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/vote.qc
List some new commands (their functions still need added)
[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 poll", VC_ASGNMNT_CLIENTONLY) \
449         VOTE_COMMAND("force", VoteCommand_force(request, caller), "Force a result of a vote", VC_ASGNMNT_SERVERONLY) \
450         VOTE_COMMAND("help", VoteCommand_macro_help(caller), "Shows this information", VC_ASGNMNT_BOTH) \
451         VOTE_COMMAND("no", VoteCommand_no(request, caller), "Vote no in current poll", VC_ASGNMNT_CLIENTONLY) \
452         VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current poll", VC_ASGNMNT_BOTH) \
453         VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \
454         VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Vote yes in current poll", VC_ASGNMNT_CLIENTONLY) \
455         //VOTE_COMMAND("", VoteCommand_(request, caller, arguments), "", ) \
456         /* nothing */
457
458 void VoteCommand_macro_help(entity caller)
459 {
460         print("\nUsage:^3 ", VoteCommand_getprefix(caller), " vote COMMAND...^7, where possible commands are:\n");
461         
462         #define VOTE_COMMAND(name,function,description,assignment) \
463                 { if(Votecommand_check_assignment(caller, assignment)) { print("  ^2", name, "^7: ", description, "\n"); } }
464                 
465         VOTE_COMMANDS(0, caller, 0)
466         #undef VOTE_COMMAND
467         
468         return;
469 }
470
471 float VoteCommand_macro_command(entity caller, float argc)
472 {
473         #define VOTE_COMMAND(name,function,description,assignment) \
474                 { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(0))) { function; return TRUE; } } }
475                 
476         VOTE_COMMANDS(VC_REQUEST_COMMAND, caller, argc)
477         #undef VOTE_COMMAND
478         
479         return FALSE;
480 }
481
482 float VoteCommand_macro_usage(entity caller, float argc)
483 {
484         #define VOTE_COMMAND(name,function,description,assignment) \
485                 { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(1))) { function; return TRUE; } } }
486                 
487         VOTE_COMMANDS(VC_REQUEST_USAGE, caller, argc)
488         #undef VOTE_COMMAND
489         
490         return FALSE;
491 }
492
493
494 // ======================================
495 //  Main function handling vote commands
496 // ======================================
497
498 void VoteCommand(entity caller, float argc) 
499 {
500         if(strtolower(argv(0)) == "help") 
501         {
502                 if(argc == 1)
503                 {
504                         VoteCommand_macro_help(caller);
505                         return;
506                 }
507                 else if(VoteCommand_macro_usage(caller, argc))
508                 {
509                         return;
510                 }
511         }
512         else if(VoteCommand_macro_command(caller, argc))
513         {
514                 return;
515         }
516         
517         // nothing above caught the command, must be invalid
518         //print("Unknown server command", ((command != "") ? strcat(" \"", command, "\"") : ""), ". For a list of supported commands, try sv_cmd help.\n");
519 }
520
521 void VoteHelp(entity e) {
522         string vmasterdis;
523         if(!autocvar_sv_vote_master) {
524                 vmasterdis = " ^1(disabled)";
525         }
526
527         string vlogindis;
528         if("" == autocvar_sv_vote_master_password) {
529                 vlogindis = " ^1(disabled)";
530         }
531
532         string vcalldis;
533         if(!autocvar_sv_vote_call) {
534                 vcalldis = " ^1(disabled)";
535         }
536
537         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\".");
538         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\".");
539         print_to(e, "^7\"^2help^7\" shows this info.");
540         print_to(e, "^7\"^2status^7\" shows if there is a vote called and who called it.");
541         print_to(e, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7"));
542         print_to(e, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.");
543         print_to(e, strcat("^7\"^2master^7\" call a vote to become master who can execute commands without a vote", vmasterdis, "^7"));
544         print_to(e, strcat("^7\"^2login^7\" login to become master who can execute commands without a vote.", vlogindis, "^7"));
545         print_to(e, "^7\"^2do^7\" executes a command if you are a master. See the list of allowed commands.");
546         print_to(e, "^7\"^2yes^7\", \"^2no^7\", \"^2abstain^7\" and \"^2dontcare^7\" to make your vote.");
547         print_to(e, "^7If enough of the players vote yes the vote is accepted.");
548         print_to(e, "^7If enough of the players vote no the vote is rejected.");
549         print_to(e, strcat("^7If neither the vote will timeout after ", ftos(autocvar_sv_vote_timeout), "^7 seconds."));
550         print_to(e, "^7You can call a vote for or execute these commands:");
551         print_to(e, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
552 }
553
554 string VoteNetname(entity e)
555 {
556         if(e) {
557                 return e.netname;
558         } else {
559                 if(autocvar_sv_adminnick != "") {
560                         return autocvar_sv_adminnick;
561                 } else {
562                         return autocvar_hostname;
563                 }
564         }
565 }
566
567 string ValidateMap(string m, entity e)
568 {
569         m = MapInfo_FixName(m);
570         if(!m)
571         {
572                 print_to(e, "This map is not available on this server.");
573                 return string_null;
574         }
575         if(!autocvar_sv_vote_override_mostrecent)
576                 if(Map_IsRecent(m))
577                 {
578                         print_to(e, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
579                         return string_null;
580                 }
581         if(!MapInfo_CheckMap(m))
582         {
583                 print_to(e, strcat("^1Invalid mapname, \"^3", m, "^1\" does not support the current game mode."));
584                 return string_null;
585         }
586
587         return m;
588 }
589
590
591 void VoteThink() {
592         if(votefinished > 0) // a vote was called
593         if(time > votefinished) // time is up
594         {
595                 VoteCount();
596         }
597 }
598
599 string VoteParse(string all, float argc) {
600         if(argc < 3)
601                 return "";
602         return substring(all, argv_start_index(2), argv_end_index(-1) - argv_start_index(2));
603 }
604
605 float VoteCommandInList(string votecommand, string list)
606 {
607         string l;
608         l = strcat(" ", list, " ");
609         
610         if(strstrofs(l, strcat(" ", votecommand, " "), 0) >= 0)
611                 return TRUE;
612         
613         // if gotomap is allowed, chmap is too, and vice versa
614         if(votecommand == "gotomap")
615                 if(strstrofs(l, " chmap ", 0) >= 0)
616                         return TRUE;
617         if(votecommand == "chmap")
618                 if(strstrofs(l, " gotomap ", 0) >= 0)
619                         return TRUE;
620         
621         return FALSE;
622 }
623
624 float VoteAllowed(string votecommand, string cmd) {
625         if(VoteCommandInList(votecommand, autocvar_sv_vote_commands))
626                 return TRUE;
627
628         if(cmd == "vdo")
629         {
630                 if(VoteCommandInList(votecommand, autocvar_sv_vote_master_commands))
631                         return TRUE;
632         }
633         else
634         {
635                 if(VoteCommandInList(votecommand, autocvar_sv_vote_only_commands))
636                         return TRUE;
637         }
638
639         return FALSE;
640 }
641
642 void VoteReset() {
643         entity player;
644
645         FOR_EACH_CLIENT(player)
646         {
647                 player.vote_vote = 0;
648         }
649
650         if(votecalled)
651         {
652                 strunzone(votecalledvote);
653                 strunzone(votecalledvote_display);
654         }
655
656         votecalled = FALSE;
657         votecalledmaster = FALSE;
658         votefinished = 0;
659         votecalledvote = string_null;
660         votecalledvote_display = string_null;
661
662         Nagger_VoteChanged();
663 }
664
665 void VoteAccept() {
666         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
667         if(votecalledmaster)
668         {
669                 if(votecaller) {
670                         votecaller.vote_master = 1;
671                 }
672         } else {
673                 localcmd(strcat(votecalledvote, "\n"));
674         }
675         if(votecaller) {
676                 votecaller.vote_next = 0; // people like your votes,
677                                           // no wait for next vote
678         }
679         VoteReset();
680         Announce("voteaccept");
681 }
682
683 void VoteReject() {
684         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
685         VoteReset();
686         Announce("votefail");
687 }
688
689 void VoteTimeout() {
690         bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
691         VoteReset();
692         Announce("votefail");
693 }
694
695 void VoteStop(entity stopper) {
696         bprint("\{1}^2* ^3", VoteNetname(stopper), "^2 stopped ^3", VoteNetname(votecaller), "^2's vote\n");
697         if(autocvar_sv_eventlog)
698                 GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
699         if(stopper == votecaller) {
700                 // no wait for next vote so you can correct your vote
701                 if(votecaller) {
702                         votecaller.vote_next = time + autocvar_sv_vote_stop;
703                 }
704         }
705         VoteReset();
706 }
707
708 void VoteSpam(float notvoters, float mincount, string result)
709 {
710         string s;
711         if(mincount >= 0)
712         {
713                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
714                 s = strcat(s, ftos(vote_nocount), "^2 (^1");
715                 s = strcat(s, ftos(mincount), "^2 needed), ^1");
716                 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
717                 s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
718         }
719         else
720         {
721                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
722                 s = strcat(s, ftos(vote_nocount), "^2, ^1");
723                 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
724                 s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
725         }
726         bprint(s);
727         if(autocvar_sv_eventlog)
728         {
729                 s = strcat(":vote:v", result, ":", ftos(vote_yescount));
730                 s = strcat(s, ":", ftos(vote_nocount));
731                 s = strcat(s, ":", ftos(vote_abstaincount));
732                 s = strcat(s, ":", ftos(notvoters));
733                 s = strcat(s, ":", ftos(mincount));
734                 GameLogEcho(s);
735         }
736 }
737
738 void VoteCount() {
739         float playercount;
740         playercount = 0;
741         vote_yescount = 0;
742         vote_nocount = 0;
743         vote_abstaincount = 0;
744         entity player;
745         //same for real players
746         float realplayercount;
747         float realplayeryescount;
748         float realplayernocount;
749         float realplayerabstaincount;
750         realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
751
752         Nagger_VoteCountChanged();
753
754         FOR_EACH_REALCLIENT(player)
755         {
756                 if(player.vote_vote == -1) {
757                         ++vote_nocount;
758                 } else if(player.vote_vote == 1) {
759                         ++vote_yescount;
760                 } else if(player.vote_vote == -2) {
761                         ++vote_abstaincount;
762                 }
763                 ++playercount;
764                 //do the same for real players
765                 if(player.classname == "player") {
766                         if(player.vote_vote == -1) {
767                                 ++realplayernocount;
768                         } else if(player.vote_vote == 1) {
769                                 ++realplayeryescount;
770                         } else if(player.vote_vote == -2) {
771                                 ++realplayerabstaincount;
772                         }
773                         ++realplayercount;
774                 }
775         }
776
777         //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)
778         if(autocvar_sv_vote_nospectators)
779         if(realplayercount > 0) {
780                 vote_yescount = realplayeryescount;
781                 vote_nocount = realplayernocount;
782                 vote_abstaincount = realplayerabstaincount;
783                 playercount = realplayercount;
784         }
785
786         float votefactor, simplevotefactor;
787         votefactor = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
788         simplevotefactor = autocvar_sv_vote_simple_majority_factor;
789
790         // FIXME this number is a guess
791         vote_needed_absolute = floor((playercount - vote_abstaincount) * votefactor) + 1;
792         if(simplevotefactor)
793         {
794                 simplevotefactor = bound(votefactor, simplevotefactor, 0.999);
795                 vote_needed_simple = floor((vote_yescount + vote_nocount) * simplevotefactor) + 1;
796         }
797         else
798                 vote_needed_simple = 0;
799
800         if(votecalledmaster
801            && playercount == 1) {
802                 // if only one player is on the server becoming vote
803                 // master is not allowed.  This could be used for
804                 // trolling or worse. 'self' is the user who has
805                 // called the vote because this function is called
806                 // by SV_ParseClientCommand. Maybe all voting should
807                 // be disabled for a single player?
808                 print_to(votecaller, "^1You are the only player on this server so you can not become vote master.");
809                 if(votecaller) {
810                         votecaller.vote_next = 0;
811                 }
812                 VoteReset();
813         } else {
814                 if(vote_yescount >= vote_needed_absolute)
815                 {
816                         VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "yes");
817                         VoteAccept();
818                 }
819                 else if(vote_nocount > playercount - vote_abstaincount - vote_needed_absolute) // that means, vote_yescount cannot reach vote_needed_absolute any more
820                 {
821                         VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "no");
822                         VoteReject();
823                 }
824                 else if(time > votefinished)
825                 {
826                         if(simplevotefactor)
827                         {
828                                 string result;
829                                 if(vote_yescount >= vote_needed_simple)
830                                         result = "yes";
831                                 else if(vote_yescount + vote_nocount > 0)
832                                         result = "no";
833                                 else
834                                         result = "timeout";
835                                 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, min(vote_needed_absolute, vote_needed_simple), result);
836                                 if(result == "yes")
837                                         VoteAccept();
838                                 else if(result == "no")
839                                         VoteReject();
840                                 else
841                                         VoteTimeout();
842                         }
843                         else
844                         {
845                                 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, vote_needed_absolute, "timeout");
846                                 VoteTimeout();
847                         }
848                 }
849         }
850 }