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