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