]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/vote.qc
Move all of the command files (clientcommands.qc, gamecommand.qc, command_common...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / vote.qc
1 // =============================================
2 //  Server side voting code, reworked by Samual
3 //  Last updated: December 10th, 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 #define VOTE_SELECT_ABSTAIN -2
14 #define VOTE_SELECT_REJECT -1
15 #define VOTE_SELECT_NULL 0
16 #define VOTE_SELECT_ACCEPT 1
17
18 string vote_parsed_command;
19 string vote_parsed_display;
20
21
22 // =============================================
23 //  Nagger for players to know status of voting
24 // =============================================
25
26 float Nagger_SendEntity(entity to, float sendflags)
27 {
28         float nags, i, f, b;
29         entity e;
30         WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
31
32         // bits:
33         //   1 = ready
34         //   2 = player needs to ready up
35         //   4 = vote
36         //   8 = player needs to vote
37         //  16 = warmup
38         // sendflags:
39         //  64 = vote counts
40         // 128 = vote string
41
42         nags = 0;
43         if(readycount)
44         {
45                 nags |= 1;
46                 if(to.ready == 0)
47                         nags |= 2;
48         }
49         if(votecalled)
50         {
51                 nags |= 4;
52                 if(to.vote_selection == 0)
53                         nags |= 8;
54         }
55         if(inWarmupStage)
56                 nags |= 16;
57
58         if(sendflags & 64)
59                 nags |= 64;
60
61         if(sendflags & 128)
62                 nags |= 128;
63
64         if(!(nags & 4)) // no vote called? send no string
65                 nags &~= (64 | 128);
66
67         WriteByte(MSG_ENTITY, nags);
68
69         if(nags & 64)
70         {
71                 WriteByte(MSG_ENTITY, vote_accept_count);
72                 WriteByte(MSG_ENTITY, vote_reject_count);
73                 WriteByte(MSG_ENTITY, vote_needed_overall);
74                 WriteChar(MSG_ENTITY, to.vote_selection);
75         }
76
77         if(nags & 128)
78                 WriteString(MSG_ENTITY, votecalledvote_display);
79
80         if(nags & 1)
81         {
82                 for(i = 1; i <= maxclients; i += 8)
83                 {
84                         for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
85                                 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
86                                         f |= b;
87                         WriteByte(MSG_ENTITY, f);
88                 }
89         }
90
91         return TRUE;
92 }
93
94 void Nagger_Init()
95 {
96         Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
97 }
98
99 void Nagger_VoteChanged()
100 {
101         if(nagger)
102                 nagger.SendFlags |= 128;
103 }
104
105 void Nagger_VoteCountChanged()
106 {
107         if(nagger)
108                 nagger.SendFlags |= 64;
109 }
110
111 void Nagger_ReadyCounted()
112 {
113         if(nagger)
114                 nagger.SendFlags |= 1;
115 }
116
117
118 // =======================
119 //  Game logic for voting
120 // =======================
121
122 void VoteReset() 
123 {
124         entity tmp_player;
125
126         FOR_EACH_CLIENT(tmp_player) { tmp_player.vote_selection = 0; }
127
128         if(votecalled)
129         {
130                 strunzone(votecalledvote);
131                 strunzone(votecalledvote_display);
132         }
133
134         votecalled = FALSE;
135         votecalledmaster = FALSE;
136         votefinished = 0;
137         votecalledvote = string_null;
138         votecalledvote_display = string_null;
139         
140         vote_parsed_command = string_null;
141         vote_parsed_display = string_null;
142
143         Nagger_VoteChanged();
144 }
145
146 void VoteStop(entity stopper) 
147 {
148         bprint("\{1}^2* ^3", VoteCommand_getname(stopper), "^2 stopped ^3", VoteCommand_getname(votecaller), "^2's vote\n");
149         if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid))); }
150         
151         // Don't force them to wait for next vote, this way they can e.g. correct their vote.
152         if((votecaller) && (stopper == votecaller)) { votecaller.vote_next = time + autocvar_sv_vote_stop; }
153
154         VoteReset();
155 }
156
157 void VoteThink() 
158 {
159         if(votefinished > 0) // a vote was called
160         if(time > votefinished) // time is up
161         {
162                 VoteCount();
163         }
164         
165         return;
166 }
167
168 void VoteAccept() 
169 {
170         bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
171         
172         if(votecalledmaster && votecaller)
173                 votecaller.vote_master = 1;
174         else
175                 localcmd(strcat(votecalledvote, "\n"));
176         
177         if(votecaller) { votecaller.vote_next = 0; } // people like your votes, you don't need to wait to vote again // todo separate anti-spam even for succeeded votes
178
179         VoteReset();
180         Announce("voteaccept");
181 }
182
183 void VoteReject() 
184 {
185         bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
186         VoteReset();
187         Announce("votefail");
188 }
189
190 void VoteTimeout() 
191 {
192         bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
193         VoteReset();
194         Announce("votefail");
195 }
196
197 void VoteSpam(float notvoters, float mincount, string result)
198 {
199         string s;
200         if(mincount >= 0)
201         {
202                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_accept_count), "^2:^1");
203                 s = strcat(s, ftos(vote_reject_count), "^2 (^1");
204                 s = strcat(s, ftos(mincount), "^2 needed), ^1");
205                 s = strcat(s, ftos(vote_abstain_count), "^2 didn't care, ^1");
206                 s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
207         }
208         else
209         {
210                 s = strcat("\{1}^2* vote results: ^1", ftos(vote_accept_count), "^2:^1");
211                 s = strcat(s, ftos(vote_reject_count), "^2, ^1");
212                 s = strcat(s, ftos(vote_abstain_count), "^2 didn't care, ^1");
213                 s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
214         }
215         
216         bprint(s);
217         
218         if(autocvar_sv_eventlog)
219         {
220                 s = strcat(":vote:v", result, ":", ftos(vote_accept_count));
221                 s = strcat(s, ":", ftos(vote_reject_count));
222                 s = strcat(s, ":", ftos(vote_abstain_count));
223                 s = strcat(s, ":", ftos(notvoters));
224                 s = strcat(s, ":", ftos(mincount));
225                 GameLogEcho(s);
226         }
227 }
228
229 void VoteCount() 
230 {
231         // declarations
232         vote_accept_count = vote_reject_count = vote_abstain_count = 0;
233         
234         float spectators_allowed = ((autocvar_sv_vote_nospectators != 2) 
235                                 || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage) 
236                                 || (autocvar_sv_vote_nospectators == 0));
237                                 
238         float vote_player_count, is_player, notvoters;
239         float vote_real_player_count, vote_real_accept_count;
240         float vote_real_reject_count, vote_real_abstain_count;
241         float vote_needed_of_voted, final_needed_votes;
242         float vote_factor_overall, vote_factor_of_voted;
243         
244         entity tmp_player;
245
246         Nagger_VoteCountChanged();
247         
248         // add up all the votes from each connected client
249         FOR_EACH_REALCLIENT(tmp_player)
250         {
251                 is_player = (tmp_player.classname == "player");
252                 
253                 ++vote_player_count;
254                 if(is_player) { ++vote_real_player_count; }
255                 
256                 switch(tmp_player.vote_selection)
257                 {
258                         case VOTE_SELECT_REJECT: { ++vote_reject_count; { if(is_player) ++vote_real_reject_count; } break; }
259                         case VOTE_SELECT_ACCEPT: { ++vote_accept_count; { if(is_player) ++vote_real_reject_count; } break; }
260                         case VOTE_SELECT_ABSTAIN: { ++vote_abstain_count; { if(is_player) ++vote_real_abstain_count; } break; }
261                         default: break;
262                 }
263         }
264         
265         // Check to see if there are enough players on the server to allow master voting... otherwise, vote master could be used for evil.
266         if(votecalledmaster && autocvar_sv_vote_master_playerlimit > vote_player_count) 
267         {
268                 if(votecaller) { votecaller.vote_next = 0; }
269                 print_to(votecaller, "^1There are not enough players on this server to allow you to become vote master.");
270                 VoteReset();
271                 return;
272         }
273         
274         // if spectators aren't allowed to vote and there are players in a match, then only count the players in the vote and ignore spectators. 
275         if(!spectators_allowed && (vote_real_player_count > 0))
276         {
277                 vote_accept_count = vote_real_accept_count;
278                 vote_reject_count = vote_real_reject_count;
279                 vote_abstain_count = vote_real_abstain_count;
280                 vote_player_count = vote_real_player_count;
281         }
282         
283         // people who have no opinion in any way :D
284         notvoters = (vote_player_count - vote_accept_count - vote_reject_count - vote_abstain_count);
285
286         // determine the goal for the vote to be passed or rejected normally
287         vote_factor_overall = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
288         vote_needed_overall = floor((vote_player_count - vote_abstain_count) * vote_factor_overall) + 1;
289         
290         // if the vote times out, determine the amount of votes needed of the people who actually already voted
291         vote_factor_of_voted = bound(0.5, autocvar_sv_vote_majority_factor_of_voted, 0.999);
292         vote_needed_of_voted = floor((vote_accept_count + vote_reject_count) * vote_factor_of_voted) + 1;
293         
294         
295         // finally calculate the result of the vote     
296         if(vote_accept_count >= vote_needed_overall)
297         {
298                 VoteSpam(notvoters, -1, "yes"); // there is enough acceptions to pass the vote
299                 VoteAccept();
300                 return;
301         }
302         
303         if(vote_reject_count > vote_player_count - vote_abstain_count - vote_needed_overall)
304         {
305                 VoteSpam(notvoters, -1, "no"); // there is enough rejections to deny the vote
306                 VoteReject();
307                 return;
308         }
309         
310         // there is not enough votes in either direction, now lets just calculate what the voters have said
311         if(time > votefinished)
312         {
313                 final_needed_votes = vote_needed_overall;
314                 
315                 if(autocvar_sv_vote_majority_factor_of_voted)
316                 {
317                         if(vote_accept_count >= vote_needed_of_voted)
318                         {
319                                 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "yes");
320                                 VoteAccept();
321                                 return;
322                         }
323                         
324                         if(vote_accept_count + vote_reject_count > 0)
325                         {
326                                 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "no");
327                                 VoteReject();
328                                 return;
329                         }
330                         
331                         final_needed_votes = min(vote_needed_overall, vote_needed_of_voted);
332                 }
333
334                 // it didn't pass or fail, so not enough votes to even make a decision. 
335                 VoteSpam(notvoters, final_needed_votes, "timeout");
336                 VoteTimeout();
337         }
338 }
339
340
341 // =======================
342 //  Game logic for warmup
343 // =======================
344
345 // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set)
346 void ReadyRestart_think() 
347 {
348         restart_mapalreadyrestarted = 1;
349         reset_map(TRUE);
350         Score_ClearAll();
351         remove(self);
352         
353         return;
354 }
355
356 // Forces a restart of the game without actually reloading the map // this is a mess...
357 void ReadyRestart_force()
358 {
359         entity tmp_player, restart_timer;
360
361         bprint("^1Server is restarting...\n");
362
363         VoteReset();
364
365         // clear overtime, we have to decrease timelimit to its original value again.
366         if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) { cvar_set("timelimit", ftos(autocvar_timelimit - (checkrules_overtimesadded * autocvar_timelimit_overtime))); }
367
368         checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
369
370         readyrestart_happened = 1;
371         game_starttime = time;
372         if(!g_ca && !g_arena) { game_starttime += RESTART_COUNTDOWN; }
373                 
374         restart_mapalreadyrestarted = 0; // reset this var, needed when cvar sv_ready_restart_repeatable is in use
375
376         // disable the warmup global for the server
377         inWarmupStage = 0; // once the game is restarted the game is in match stage
378
379         // reset the .ready status of all players (also spectators)
380         FOR_EACH_CLIENTSLOT(tmp_player) { tmp_player.ready = 0; }
381         readycount = 0;
382         Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
383
384         // lock teams with lockonrestart
385         if(autocvar_teamplay_lockonrestart && teamplay) 
386         {
387                 lockteams = 1;
388                 bprint("^1The teams are now locked.\n");
389         }
390
391         //initiate the restart-countdown-announcer entity
392         if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
393         {
394                 restart_timer = spawn();
395                 restart_timer.think = ReadyRestart_think;
396                 restart_timer.nextthink = game_starttime;
397         }
398
399         // after a restart every players number of allowed timeouts gets reset, too
400         if(autocvar_sv_timeout) { FOR_EACH_REALPLAYER(tmp_player) { tmp_player.allowedTimeouts = autocvar_sv_timeout_number; } }
401
402         //reset map immediately if this cvar is not set
403         if not(autocvar_sv_ready_restart_after_countdown) { reset_map(TRUE); }
404
405         if(autocvar_sv_eventlog) { GameLogEcho(":restart"); }
406 }
407
408 void ReadyRestart()
409 {
410         // no arena, assault support yet...
411         if(g_arena | g_assault | gameover | intermission_running | race_completing)
412                 localcmd("restart\n");
413         else
414                 localcmd("\nsv_hook_gamerestart\n");
415
416         // Reset ALL scores, but only do that at the beginning of the countdown if sv_ready_restart_after_countdown is off!
417         // Otherwise scores could be manipulated during the countdown.
418         if not(autocvar_sv_ready_restart_after_countdown) { Score_ClearAll(); }
419
420         ReadyRestart_force();
421         
422         return;
423 }
424
425 // Count the players who are ready and determine whether or not to restart the match // todo: add percentage ready support
426 void ReadyCount()
427 {
428         entity tmp_player;
429         float ready_needed_factor, ready_needed_count;
430         float t_ready, t_players;
431
432         FOR_EACH_REALPLAYER(tmp_player)
433         {
434                 ++t_players;
435                 if(tmp_player.ready) { ++t_ready; }
436         }
437
438         readycount = t_ready;
439
440         Nagger_ReadyCounted();
441
442         ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 0.999);
443         ready_needed_count = floor(t_players * ready_needed_factor) + 1;
444         
445         if(readycount >= ready_needed_count)
446         {
447                 ReadyRestart();
448         }
449                 
450         return;
451 }
452
453
454 // ======================================
455 //  Supporting functions for VoteCommand
456 // ======================================
457
458 float Votecommand_check_assignment(entity caller, float assignment)
459 {
460         float from_server = (!caller);
461         
462         if((assignment == VC_ASGNMNT_BOTH) 
463                 || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY) 
464                 || (from_server && assignment == VC_ASGNMNT_SERVERONLY)))
465         {
466                 return TRUE;
467         }
468
469         return FALSE;
470 }
471
472 string VoteCommand_getprefix(entity caller)
473 {
474         if(caller)
475                 return "cmd";
476         else
477                 return "sv_cmd";
478 }
479
480 string VoteCommand_getname(entity caller)
481 {
482         if(caller)
483                 return caller.netname;
484         else
485                 return ((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
486 }
487
488 string VoteCommand_extractcommand(string input, float startpos, float argc) 
489 {
490         string output;
491         
492         if((argc - 1) < startpos)
493                 output = "";
494         else
495                 output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos));
496                 
497         print("VoteCommand_parse: '", output, "'. \n");
498         return output;
499 }
500
501 float VoteCommand_checknasty(string vote_command)
502 {
503         if((strstrofs(vote_command, ";", 0) >= 0)
504                 || (strstrofs(vote_command, "\n", 0) >= 0)
505                 || (strstrofs(vote_command, "\r", 0) >= 0)
506                 || (strstrofs(vote_command, "$", 0) >= 0))
507                 return FALSE;
508                 
509         return TRUE;
510 }
511
512 float VoteCommand_checkinlist(string vote_command, string list)
513 {
514         string l = strcat(" ", list, " ");
515         
516         if(strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0)
517                 return TRUE;
518         
519         // if gotomap is allowed, chmap is too, and vice versa
520         if(vote_command == "gotomap")
521                 if(strstrofs(l, " chmap ", 0) >= 0)
522                         return TRUE;
523                         
524         if(vote_command == "chmap")
525                 if(strstrofs(l, " gotomap ", 0) >= 0)
526                         return TRUE;
527         
528         return FALSE;
529 }
530
531 string ValidateMap(string validated_map, entity caller)
532 {
533         validated_map = MapInfo_FixName(validated_map);
534         
535         if(!validated_map)
536         {
537                 print_to(caller, "This map is not available on this server.");
538                 return string_null;
539         }
540         
541         if(!autocvar_sv_vote_override_mostrecent && caller)
542         {
543                 if(Map_IsRecent(validated_map))
544                 {
545                         print_to(caller, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
546                         return string_null;
547                 }
548         }
549         
550         if(!MapInfo_CheckMap(validated_map))
551         {
552                 print_to(caller, strcat("^1Invalid mapname, \"^3", validated_map, "^1\" does not support the current game mode."));
553                 return string_null;
554         }
555
556         return validated_map;
557 }
558
559 float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
560 {
561         string first_command;
562         entity victim;
563         
564         first_command = argv(startpos);
565
566         if not(VoteCommand_checkinlist(vote_command, vote_list))
567                 return FALSE;
568
569         if((argc - 1) < startpos) // These commands won't work without arguments
570         {
571                 switch(first_command)
572                 {
573                         case "map":
574                         case "chmap":
575                         case "gotomap":
576                         case "kick":
577                         case "kickban":
578                                 return FALSE;
579                                 
580                         default: { break; }
581                 }
582         }
583         
584         switch(first_command) // now go through and parse the proper commands to adjust as needed.
585         {
586                 case "kick":
587                 case "kickban": // catch all kick/kickban commands
588                 {
589                         victim = edict_num(GetFilteredNumber(substring(vote_command, argv_start_index(startpos + 1), argv_end_index(-1) - argv_start_index(startpos + 1))));
590                         if not(victim) { return FALSE; }
591                         // TODO: figure out how kick/kickban/ban commands work and re-write this to fit around them
592                         vote_parsed_command = vote_command;
593                         vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", "todo");
594                         
595                         break;
596                 }
597                 
598                 case "map":
599                 case "chmap":
600                 case "gotomap": // re-direct all map selection commands to gotomap
601                 {
602                         vote_command = ValidateMap(substring(vote_command, argv_start_index(startpos + 1), argv_end_index(-1) - argv_start_index(startpos + 1)), caller);
603                         if not(vote_command) { return FALSE; }
604                         vote_parsed_command = strcat("gotomap ", vote_command);
605                         vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
606                         
607                         break;
608                 }
609                 
610                 default: 
611                 { 
612                         vote_parsed_command = vote_command;
613                         vote_parsed_display = strzone(strcat("^1", vote_command));
614                         
615                         break; 
616                 }
617         }
618
619         return TRUE;
620 }
621
622
623 // =======================
624 //  Command Sub-Functions
625 // =======================
626
627 void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY
628 {
629         switch(request)
630         {
631                 case VC_REQUEST_COMMAND:
632                 {
633                         if not(votecalled) { print_to(caller, "^1No vote called."); }
634                         else if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { print_to(caller, "^1You have already voted."); }
635                         
636                         else // everything went okay, continue changing vote
637                         {
638                                 print_to(caller, "^1You abstained from your vote.");
639                                 caller.vote_selection = VOTE_SELECT_ABSTAIN;
640                                 msg_entity = caller;
641                                 if(!autocvar_sv_vote_singlecount) { VoteCount(); }
642                         }
643                         
644                         return;
645                 }
646                         
647                 default:
648                 case VC_REQUEST_USAGE:
649                 {
650                         print("\nUsage:^3 vote abstain\n");
651                         print("  No arguments required.\n");
652                         return;
653                 }
654         }
655 }
656
657 void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH
658 {
659         switch(request)
660         {
661                 case VC_REQUEST_COMMAND:
662                 {
663                         float spectators_allowed = ((autocvar_sv_vote_nospectators != 2) 
664                                 || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage) 
665                                 || (autocvar_sv_vote_nospectators == 0));
666                                 
667                         float tmp_playercount;
668                         entity tmp_player;
669                         
670                         vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
671                         
672                         if not(autocvar_sv_vote_call || !caller) { print_to(caller, "^1Vote calling is not allowed."); }
673                         else if(votecalled) { print_to(caller, "^1There is already a vote called."); }
674                         else if(!spectators_allowed && (caller && (caller.classname != "player"))) { print_to(caller, "^1Only players can call a vote."); }
675                         else if(timeoutStatus) { print_to(caller, "^1You can not call a vote while a timeout is active."); }
676                         else if(caller && (time < caller.vote_next)) { print_to(caller, strcat("^1You have to wait ^2", ftos(ceil(caller.vote_next - time)), "^1 seconds before you can again call a vote.")); }
677                         else if not(VoteCommand_checknasty(vote_command)) { print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); }
678                         else if not(VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) { print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); }
679
680                         else // everything went okay, continue with calling the vote // TODO: fixes to make this more compatible with sv_cmd
681                         {
682                                 votecalled = TRUE;
683                                 votecalledmaster = FALSE;
684                                 votecalledvote = strzone(vote_parsed_command);
685                                 votecalledvote_display = strzone(vote_parsed_display);
686                                 votefinished = time + autocvar_sv_vote_timeout;
687                                 votecaller = caller; // remember who called the vote
688                                 
689                                 if(caller)
690                                 {
691                                         caller.vote_selection = VOTE_SELECT_ACCEPT;
692                                         caller.vote_next = time + autocvar_sv_vote_wait;
693                                         msg_entity = caller; // todo: what is this for?
694                                 }
695                                 
696                                 FOR_EACH_REALCLIENT(tmp_player) { ++tmp_playercount; }
697                                 if(tmp_playercount > 1) { Announce("votecall"); } // don't announce a "vote now" sound if player is alone
698                                 
699                                 bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2 calls a vote for ", votecalledvote_display, "\n");
700                                 if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display)); }
701                                 Nagger_VoteChanged();
702                                 VoteCount(); // needed if you are the only one
703                         }
704                         
705                         return;
706                 }
707                         
708                 default:
709                 case VC_REQUEST_USAGE:
710                 {
711                         print("\nUsage:^3 vote call\n");
712                         print("  TODO.\n");
713                         return;
714                 }
715         }
716 }
717
718 void VoteCommand_master(float request, entity caller, float argc, string vote_command) // CLIENT ONLY
719 {
720         switch(request)
721         {
722                 case VC_REQUEST_COMMAND:
723                 {
724                         if(autocvar_sv_vote_master)
725                         {
726                                 switch(strtolower(argv(2)))
727                                 {
728                                         case "do":
729                                         {
730                                                 vote_command = VoteCommand_extractcommand(vote_command, 3, argc);
731                                                 
732                                                 if not(caller.vote_master) { print_to(caller, "^1You do not have vote master privelages."); }
733                                                 else if not(VoteCommand_checknasty(vote_command)) { print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); }
734                                                 else if not(VoteCommand_parse(caller, vote_command, autocvar_sv_vote_master_commands, 3, argc)) { print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); }
735                                                 
736                                                 else // everything went okay, proceed with command
737                                                 {
738                                                         localcmd(strcat(vote_parsed_command, "\n"));
739                                                         print_to(caller, strcat("Executing command '", vote_parsed_display, "' on server."));
740                                                         bprint("\{1}^2* ^3", VoteCommand_getname(caller), "^2 used their ^3master^2 status to do \"^2", vote_parsed_display, "^2\".\n");
741                                                         if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display)); }
742                                                 }
743                                                 
744                                                 return;
745                                         }
746                                         
747                                         case "login":
748                                         {
749                                                 if not(autocvar_sv_vote_master_password != "") { print_to(caller, "^1Login to vote master is not allowed."); }
750                                                 else if(caller.vote_master) { print_to(caller, "^1You are already logged in as vote master."); }
751                                                 else if not(autocvar_sv_vote_master_password == argv(3)) { print_to(caller, strcat("Rejected vote master login from ", VoteCommand_getname(caller))); }
752
753                                                 else // everything went okay, proceed with giving this player master privilages
754                                                 {
755                                                         caller.vote_master = TRUE;
756                                                         print_to(caller, strcat("Accepted vote master login from ", VoteCommand_getname(caller)));
757                                                         bprint("\{1}^2* ^3", VoteCommand_getname(caller), "^2 logged in as ^3master^2\n");
758                                                         if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid))); }
759                                                 }
760                                                 
761                                                 return;
762                                         }
763                                         
764                                         default: // calling a vote for master
765                                         {
766                                                 if not(autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); }
767                                                 else if(votecalled) { print_to(caller, "^1There is already a vote called."); }
768                                                 
769                                                 else // everything went okay, continue with creating vote
770                                                 {
771                                                         votecalled = TRUE;
772                                                         votecalledmaster = TRUE;
773                                                         votecalledvote = strzone("XXX");
774                                                         votecalledvote_display = strzone("^3master");
775                                                         votefinished = time + autocvar_sv_vote_timeout;
776                                                         votecaller = caller;
777                                                         
778                                                         caller.vote_selection = VOTE_SELECT_ACCEPT;
779                                                         caller.vote_next = time + autocvar_sv_vote_wait;
780                                                         
781                                                         bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2 calls a vote to become ^3master^2.\n");
782                                                         if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display)); }
783                                                         Nagger_VoteChanged();
784                                                         VoteCount(); // needed if you are the only one
785                                                 }
786                                                 
787                                                 return;
788                                         }
789                                 }
790                         }
791                         else { print_to(caller, "^1Master control of voting is not allowed."); }
792                         
793                         return;
794                 }
795                         
796                 default:
797                 case VC_REQUEST_USAGE:
798                 {
799                         print("\nUsage:^3 vote master action [arguments]\n");
800                         print("  TODO.\n");
801                         return;
802                 }
803         }
804 }
805
806 void VoteCommand_no(float request, entity caller) // CLIENT ONLY
807 {
808         switch(request)
809         {
810                 case VC_REQUEST_COMMAND:
811                 {
812                         if not(votecalled) { print_to(caller, "^1No vote called."); }
813                         else if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { print_to(caller, "^1You have already voted."); }
814                         
815                         else // everything went okay, continue changing vote
816                         {
817                                 print_to(caller, "^1You rejected the vote.");
818                                 caller.vote_selection = VOTE_SELECT_REJECT;
819                                 msg_entity = caller;
820                                 if(!autocvar_sv_vote_singlecount) { VoteCount(); }
821                         }
822                         
823                         return;
824                 }
825                         
826                 default:
827                 case VC_REQUEST_USAGE:
828                 {
829                         print("\nUsage:^3 vote no\n");
830                         print("  No arguments required.\n");
831                         return;
832                 }
833         }
834 }
835
836 void VoteCommand_status(float request, entity caller) // BOTH
837 {
838         switch(request)
839         {
840                 case VC_REQUEST_COMMAND:
841                 {
842                         if(votecalled)
843                                 print_to(caller, strcat("^7Vote for ", votecalledvote_display, "^7 called by ^7", VoteCommand_getname(votecaller), "^7."));
844                         else
845                                 print_to(caller, "^1No vote called.");
846                                 
847                         return;
848                 }
849                         
850                 default:
851                 case VC_REQUEST_USAGE:
852                 {
853                         print("\nUsage:^3 vote status\n");
854                         print("  No arguments required.\n");
855                         return;
856                 }
857         }
858 }
859
860 void VoteCommand_stop(float request, entity caller) // BOTH
861 {
862         switch(request)
863         {
864                 case VC_REQUEST_COMMAND:
865                 {
866                         if not(votecalled) { print_to(caller, "^1No vote called."); }
867                         else if((caller == votecaller) || !caller || caller.vote_master) { VoteStop(caller); }
868                         else { print_to(caller, "^1You are not allowed to stop that vote."); }
869                         
870                         return;
871                 }
872                         
873                 default:
874                 case VC_REQUEST_USAGE:
875                 {
876                         print("\nUsage:^3 vote stop\n");
877                         print("  No arguments required.\n");
878                         return;
879                 }
880         }
881 }
882
883 void VoteCommand_yes(float request, entity caller) // CLIENT ONLY
884 {
885         switch(request)
886         {
887                 case VC_REQUEST_COMMAND:
888                 {
889                         if not(votecalled) { print_to(caller, "^1No vote called."); }
890                         if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { print_to(caller, "^1You have already voted."); }
891                         
892                         else // everything went okay, continue changing vote
893                         {
894                                 print_to(caller, "^1You accepted the vote.");
895                                 caller.vote_selection = VOTE_SELECT_ACCEPT;
896                                 msg_entity = caller;
897                                 if(!autocvar_sv_vote_singlecount) { VoteCount(); }
898                         }
899                         
900                         return;
901                 }
902                         
903                 default:
904                 case VC_REQUEST_USAGE:
905                 {
906                         print("\nUsage:^3 vote yes\n");
907                         print("  No arguments required.\n");
908                         return;
909                 }
910         }
911 }
912
913 /* use this when creating a new command, making sure to place it in alphabetical order.
914 void VoteCommand_(float request)
915 {
916         switch(request)
917         {
918                 case VC_REQUEST_COMMAND:
919                 {
920                         
921                         return;
922                 }
923                         
924                 default:
925                 case VC_REQUEST_USAGE:
926                 {
927                         print("\nUsage:^3 vote \n");
928                         print("  No arguments required.\n");
929                         return;
930                 }
931         }
932 }
933 */
934
935
936 // ================================
937 //  Macro system for vote commands
938 // ================================
939
940 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
941 #define VOTE_COMMANDS(request,caller,arguments,command) \
942         VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current vote", VC_ASGNMNT_CLIENTONLY) \
943         VOTE_COMMAND("call", VoteCommand_call(request, caller, arguments, command), "Create a new vote for players to decide on", VC_ASGNMNT_BOTH) \
944         VOTE_COMMAND("help", VoteCommand_macro_help(caller, arguments), "Shows this information", VC_ASGNMNT_BOTH) \
945         VOTE_COMMAND("master", VoteCommand_master(request, caller, arguments, command), "", VC_ASGNMNT_CLIENTONLY) \
946         VOTE_COMMAND("no", VoteCommand_no(request, caller), "Select no in current vote", VC_ASGNMNT_CLIENTONLY) \
947         VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current vote", VC_ASGNMNT_BOTH) \
948         VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \
949         VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \
950         /* nothing */
951
952 void VoteCommand_macro_help(entity caller, float argc)
953 {
954         string command_origin = VoteCommand_getprefix(caller);
955         
956         if(argc == 2) // help display listing all commands
957         {
958                 print("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are:\n");
959                 
960                 #define VOTE_COMMAND(name,function,description,assignment) \
961                         { if(Votecommand_check_assignment(caller, assignment)) { print("  ^2", name, "^7: ", description, "\n"); } }
962                         
963                 VOTE_COMMANDS(0, caller, 0, "")
964                 #undef VOTE_COMMAND
965                 
966                 print("For help about specific commands, type ", command_origin, " vote help COMMAND\n");
967         }
968         else // usage for individual command
969         {
970                 #define VOTE_COMMAND(name,function,description,assignment) \
971                         { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(2))) { function; return; } } }
972                         
973                 VOTE_COMMANDS(VC_REQUEST_USAGE, caller, argc, "")
974                 #undef VOTE_COMMAND
975         }
976         
977         return;
978 }
979
980 float VoteCommand_macro_command(entity caller, float argc, string vote_command)
981 {
982         #define VOTE_COMMAND(name,function,description,assignment) \
983                 { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(1))) { function; return TRUE; } } }
984                 
985         VOTE_COMMANDS(VC_REQUEST_COMMAND, caller, argc, vote_command)
986         #undef VOTE_COMMAND
987         
988         return FALSE;
989 }
990
991
992 // ======================================
993 //  Main function handling vote commands
994 // ======================================
995
996 void VoteCommand(float request, entity caller, float argc, string vote_command) 
997 {
998         // Guide for working with argc arguments by example:
999         // argc:   1    - 2      - 3     - 4
1000         // argv:   0    - 1      - 2     - 3 
1001         // cmd     vote - master - login - password
1002         
1003         switch(request)
1004         {
1005                 case VC_REQUEST_COMMAND:
1006                 {
1007                         if(VoteCommand_macro_command(caller, argc, vote_command))
1008                                 return;
1009                 }
1010                         
1011                 default:
1012                         print_to(caller, strcat("Unknown vote command", ((argv(1) != "") ? strcat(" \"", argv(1), "\"") : ""), ". For a list of supported commands, try ", VoteCommand_getprefix(caller), " help.\n"));
1013                 case VC_REQUEST_USAGE:
1014                 {
1015                         VoteCommand_macro_help(caller, argc);
1016                         return;
1017                 }
1018         }
1019 }
1020
1021 // =======================
1022 //  Game logic for voting
1023 // =======================
1024
1025 void VoteHelp(entity e) {
1026         string vmasterdis;
1027         if(!autocvar_sv_vote_master) {
1028                 vmasterdis = " ^1(disabled)";
1029         }
1030
1031         string vlogindis;
1032         if("" == autocvar_sv_vote_master_password) {
1033                 vlogindis = " ^1(disabled)";
1034         }
1035
1036         string vcalldis;
1037         if(!autocvar_sv_vote_call) {
1038                 vcalldis = " ^1(disabled)";
1039         }
1040
1041         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\".");
1042         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\".");
1043         print_to(e, "^7\"^2help^7\" shows this info.");
1044         print_to(e, "^7\"^2status^7\" shows if there is a vote called and who called it.");
1045         print_to(e, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7"));
1046         print_to(e, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.");
1047         print_to(e, strcat("^7\"^2master^7\" call a vote to become master who can execute commands without a vote", vmasterdis, "^7"));
1048         print_to(e, strcat("^7\"^2login^7\" login to become master who can execute commands without a vote.", vlogindis, "^7"));
1049         print_to(e, "^7\"^2do^7\" executes a command if you are a master. See the list of allowed commands.");
1050         print_to(e, "^7\"^2yes^7\", \"^2no^7\", \"^2abstain^7\" and \"^2dontcare^7\" to make your vote.");
1051         print_to(e, "^7If enough of the players vote yes the vote is accepted.");
1052         print_to(e, "^7If enough of the players vote no the vote is rejected.");
1053         print_to(e, strcat("^7If neither the vote will timeout after ", ftos(autocvar_sv_vote_timeout), "^7 seconds."));
1054         print_to(e, "^7You can call a vote for or execute these commands:");
1055         print_to(e, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
1056 }