]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/vote.qc
Merge branch 'master' into z411/bai-server
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / vote.qc
1 #include "vote.qh"
2
3 #include <common/command/_mod.qh>
4 #include <common/constants.qh>
5 #include <common/gamemodes/_mod.qh>
6 #include <common/items/inventory.qh>
7 #include <common/mapinfo.qh>
8 #include <common/net_linked.qh>
9 #include <common/notifications/all.qh>
10 #include <common/playerstats.qh>
11 #include <common/stats.qh>
12 #include <common/util.qh>
13 #include <common/weapons/_all.qh>
14 #include <server/client.qh>
15 #include <server/command/banning.qh>
16 #include <server/command/common.qh>
17 #include <server/command/vote.qh>
18 #include <server/damage.qh>
19 #include <server/gamelog.qh>
20 #include <server/intermission.qh>
21 #include <server/mutators/_mod.qh>
22 #include <server/race.qh>
23 #include <server/round_handler.qh>
24 #include <server/scores.qh>
25 #include <server/teamplay.qh>
26 #include <server/weapons/accuracy.qh>
27 #include <server/world.qh>
28
29 // =============================================
30 //  Server side voting code, reworked by Samual
31 //  Last updated: December 27th, 2011
32 // =============================================
33
34 //  Nagger for players to know status of voting
35 bool Nagger_SendEntity(entity this, entity to, float sendflags)
36 {
37         int nags, i, f, b;
38         entity e;
39         WriteHeader(MSG_ENTITY, ENT_CLIENT_NAGGER);
40
41         // bits:
42         //   1 = ready
43         //   2 = player needs to ready up
44         //   4 = vote
45         //   8 = player needs to vote
46         //  16 = warmup
47         // sendflags:
48         //  64 = vote counts
49         // 128 = vote string
50
51         nags = 0;
52         if (readycount)
53         {
54                 nags |= BIT(0);
55                 if (to.ready == 0) nags |= BIT(1);
56         }
57         if (vote_called)
58         {
59                 nags |= BIT(2);
60                 if (to.vote_selection == 0) nags |= BIT(3);
61         }
62         if (warmup_stage) nags |= BIT(4);
63
64         if (sendflags & BIT(6)) nags |= BIT(6);
65
66         if (sendflags & BIT(7)) nags |= BIT(7);
67
68         if (!(nags & 4))  // no vote called? send no string
69                 nags &= ~(BIT(6) | BIT(7));
70
71         WriteByte(MSG_ENTITY, nags);
72
73         if (nags & BIT(6))
74         {
75                 WriteByte(MSG_ENTITY, vote_accept_count);
76                 WriteByte(MSG_ENTITY, vote_reject_count);
77                 WriteByte(MSG_ENTITY, vote_needed_overall);
78                 WriteChar(MSG_ENTITY, to.vote_selection);
79         }
80
81         if (nags & BIT(7)) WriteString(MSG_ENTITY, vote_called_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 = BIT(0); b < BIT(8); b <<= 1, e = nextent(e))
88                                 if (!IS_REAL_CLIENT(e) || 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 = new_pure(nagger), false, 0, Nagger_SendEntity);
100 }
101
102 void Nagger_VoteChanged()
103 {
104         if (nagger) nagger.SendFlags |= BIT(7);
105 }
106
107 void Nagger_VoteCountChanged()
108 {
109         if (nagger) nagger.SendFlags |= BIT(6);
110 }
111
112 void Nagger_ReadyCounted()
113 {
114         if (nagger) nagger.SendFlags |= BIT(0);
115 }
116
117 // If the vote_caller is still here, return their name, otherwise vote_caller_name
118 string OriginalCallerName()
119 {
120         if (IS_REAL_CLIENT(vote_caller)) return playername(vote_caller.netname, vote_caller.team, false);
121         return vote_caller_name;
122 }
123
124 // =======================
125 //  Game logic for voting
126 // =======================
127
128 void VoteReset()
129 {
130         FOREACH_CLIENT(true, { it.vote_selection = 0; });
131
132         if (vote_called)
133         {
134                 strfree(vote_called_command);
135                 strfree(vote_called_display);
136                 strfree(vote_caller_name);
137         }
138
139         vote_called = VOTE_NULL;
140         vote_caller = NULL;
141         vote_endtime = 0;
142
143         vote_parsed_command = string_null;
144         vote_parsed_display = string_null;
145
146         Nagger_VoteChanged();
147 }
148
149 void VoteStop(entity stopper)
150 {
151         bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n");
152         if (autocvar_sv_eventlog)   GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
153         // Don't force them to wait for next vote, this way they can e.g. correct their vote.
154         if ((vote_caller) && (stopper == vote_caller))   vote_caller.vote_waittime = time + autocvar_sv_vote_stop;
155         VoteReset();
156 }
157
158 void VoteAccept()
159 {
160         bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ^1", vote_called_display, "^2 was accepted\n");
161
162         if ((vote_called == VOTE_MASTER) && vote_caller) vote_caller.vote_master = 1;
163         else localcmd(strcat(vote_called_command, "\n"));
164
165         if (vote_caller)   vote_caller.vote_waittime = 0;  // people like your votes, you don't need to wait to vote again
166
167         VoteReset();
168         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_ACCEPT);
169 }
170
171 void VoteReject()
172 {
173         bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 was rejected\n");
174         VoteReset();
175         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
176 }
177
178 void VoteTimeout()
179 {
180         bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 timed out\n");
181         VoteReset();
182         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
183 }
184
185 void VoteSpam(float notvoters, float mincount, string result)
186 {
187         bprint(strcat(
188                 strcat("\{1}^2* vote results: ^1", ftos(vote_accept_count)),
189                 strcat("^2:^1", ftos(vote_reject_count)),
190                 ((mincount >= 0) ? strcat("^2 (^1", ftos(mincount), "^2 needed)") : "^2"),
191                 strcat(", ^1", ftos(vote_abstain_count), "^2 didn't care"),
192                 strcat(", ^1", ftos(notvoters), strcat("^2 didn't ", ((mincount >= 0) ? "" : "have to "), "vote\n"))));
193
194         if (autocvar_sv_eventlog)
195         {
196                 GameLogEcho(strcat(
197                         strcat(":vote:v", result, ":", ftos(vote_accept_count)),
198                         strcat(":", ftos(vote_reject_count)),
199                         strcat(":", ftos(vote_abstain_count)),
200                         strcat(":", ftos(notvoters)),
201                         strcat(":", ftos(mincount))));
202         }
203 }
204
205 #define spectators_allowed (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || intermission_running)))
206
207 void VoteCount(float first_count)
208 {
209         // declarations
210         vote_accept_count = vote_reject_count = vote_abstain_count = 0;
211
212         float vote_player_count = 0, notvoters = 0;
213         float vote_real_player_count = 0, vote_real_accept_count = 0;
214         float vote_real_reject_count = 0, vote_real_abstain_count = 0;
215         float vote_needed_of_voted, final_needed_votes;
216         float vote_factor_overall, vote_factor_of_voted;
217
218         Nagger_VoteCountChanged();
219
220         // add up all the votes from each connected client
221         FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_CLIENT(it), {
222                 // z411
223                 if(vote_target_type == VOTE_TARGET_TEAM && it.team != vote_caller.team) continue;
224                 if(vote_target_type == VOTE_TARGET_SINGLE && it != vote_target) continue;
225                 
226                 ++vote_player_count;
227                 if (IS_PLAYER(it))   ++vote_real_player_count;
228                 switch (it.vote_selection)
229                 {
230                         case VOTE_SELECT_REJECT:
231                         { ++vote_reject_count;
232                           { if (IS_PLAYER(it)) ++vote_real_reject_count; } break;
233                         }
234                         case VOTE_SELECT_ACCEPT:
235                         { ++vote_accept_count;
236                           { if (IS_PLAYER(it)) ++vote_real_accept_count; } break;
237                         }
238                         case VOTE_SELECT_ABSTAIN:
239                         { ++vote_abstain_count;
240                           { if (IS_PLAYER(it)) ++vote_real_abstain_count; } break;
241                         }
242                         default: break;
243                 }
244         });
245
246         // Check to see if there are enough players on the server to allow master voting... otherwise, vote master could be used for evil.
247         if ((vote_called == VOTE_MASTER) && autocvar_sv_vote_master_playerlimit > vote_player_count)
248         {
249                 if (vote_caller)   vote_caller.vote_waittime = 0;
250                 print_to(vote_caller, "^1There are not enough players on this server to allow you to become vote master.");
251                 VoteReset();
252                 return;
253         }
254
255         // if spectators aren't allowed to vote and there are players in a match, then only count the players in the vote and ignore spectators.
256         if (!spectators_allowed && (vote_real_player_count > 0))
257         {
258                 vote_accept_count = vote_real_accept_count;
259                 vote_reject_count = vote_real_reject_count;
260                 vote_abstain_count = vote_real_abstain_count;
261                 vote_player_count = vote_real_player_count;
262         }
263
264         // people who have no opinion in any way :D
265         notvoters = (vote_player_count - vote_accept_count - vote_reject_count - vote_abstain_count);
266
267         // determine the goal for the vote to be passed or rejected normally
268         vote_factor_overall = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
269         vote_needed_overall = floor((vote_player_count - vote_abstain_count) * vote_factor_overall) + 1;
270
271         // if the vote times out, determine the amount of votes needed of the people who actually already voted
272         vote_factor_of_voted = bound(0.5, autocvar_sv_vote_majority_factor_of_voted, 0.999);
273         vote_needed_of_voted = floor((vote_accept_count + vote_reject_count) * vote_factor_of_voted) + 1;
274
275         // are there any players at all on the server? it could be an admin vote
276         if (vote_player_count == 0 && first_count)
277         {
278                 VoteSpam(0, -1, "yes");  // no players at all, just accept it
279                 VoteAccept();
280                 return;
281         }
282
283         // since there ARE players, finally calculate the result of the vote
284         if (vote_accept_count >= vote_needed_overall)
285         {
286                 VoteSpam(notvoters, -1, "yes");  // there is enough acceptions to pass the vote
287                 VoteAccept();
288                 return;
289         }
290
291         if (vote_reject_count > vote_player_count - vote_abstain_count - vote_needed_overall)
292         {
293                 VoteSpam(notvoters, -1, "no");  // there is enough rejections to deny the vote
294                 VoteReject();
295                 return;
296         }
297
298         // there is not enough votes in either direction, now lets just calculate what the voters have said
299         if (time > vote_endtime)
300         {
301                 final_needed_votes = vote_needed_overall;
302
303                 if (autocvar_sv_vote_majority_factor_of_voted)
304                 {
305                         if (vote_accept_count >= vote_needed_of_voted)
306                         {
307                                 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "yes");
308                                 VoteAccept();
309                                 return;
310                         }
311
312                         if (vote_accept_count + vote_reject_count > 0)
313                         {
314                                 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "no");
315                                 VoteReject();
316                                 return;
317                         }
318
319                         final_needed_votes = min(vote_needed_overall, vote_needed_of_voted);
320                 }
321
322                 // it didn't pass or fail, so not enough votes to even make a decision.
323                 VoteSpam(notvoters, final_needed_votes, "timeout");
324                 VoteTimeout();
325         }
326 }
327
328 void VoteThink()
329 {
330         if (vote_endtime > 0)        // a vote was called
331         {
332                 if (time > vote_endtime) // time is up
333                         VoteCount(false);
334         }
335 }
336
337
338 // =======================
339 //  Game logic for warmup
340 // =======================
341
342 // Resets the state of all clients, items, weapons, waypoints, ... of the map.
343 void reset_map(bool dorespawn, bool is_fake_round_start)
344 {
345         if (time <= game_starttime)
346         {
347                 if (game_stopped)
348                         return;
349
350                 if (!is_fake_round_start)
351                 {
352                         Score_ClearAll();
353                         PlayerStats_GameReport_Reset_All();
354                 }
355
356                 if (round_handler_IsActive())
357                         round_handler_Reset(game_starttime);
358         }
359         
360         // for RJZ
361         if (autocvar_rjz_count_shards) {
362                 total_shards = 0;
363                 send_TotalShardsAll();
364         }
365
366         if (shuffleteams_on_reset_map)
367         {
368                 shuffleteams();
369                 shuffleteams_on_reset_map = false;
370         }
371
372         FOREACH_CLIENT(true, {
373                 if (time <= game_starttime)
374                         accuracy_reset(it); // for spectators too because weapon accuracy is persistent
375                 if (!IS_PLAYER(it))
376                         continue;
377                 if (STAT(FROZEN, it))
378                         Unfreeze(it, false);
379                 player_powerups_remove_all(it);
380                 entity store = PS(it);
381                 if (store)
382                 {
383                         Inventory_clear(store.inventory);
384                         Inventory_update(store);
385                 }
386         });
387
388         MUTATOR_CALLHOOK(reset_map_global);
389
390         FOREACH_ENTITY_FLOAT_ORDERED(pure_data, false,
391         {
392                 if(IS_CLIENT(it))
393                         continue;
394                 if (it.reset)
395                 {
396                         it.reset(it);
397                         continue;
398                 }
399                 if (it.team_saved) it.team = it.team_saved;
400                 if (it.flags & FL_PROJECTILE) delete(it);  // remove any projectiles left
401         });
402
403         // Waypoints and assault start come LAST
404         FOREACH_ENTITY_ORDERED(IS_NOT_A_CLIENT(it), {
405                 if (it.reset2) it.reset2(it);
406         });
407
408         // Moving the player reset code here since the player-reset depends
409         // on spawnpoint entities which have to be reset first --blub
410         if (dorespawn)
411         {
412                 if (!MUTATOR_CALLHOOK(reset_map_players))
413                 {
414                         FOREACH_CLIENT(IS_PLAYER(it),
415                         {
416                                 // PlayerScore_Clear(it);
417                                 CS(it).killcount = 0;
418                                 // stop the player from moving so that he stands still once he gets respawned
419                                 it.velocity = '0 0 0';
420                                 it.avelocity = '0 0 0';
421                                 CS(it).movement = '0 0 0';
422                                 PutClientInServer(it);
423                         });
424                 }
425         }
426 }
427
428 // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set)
429 void ReadyRestart_think(entity this)
430 {
431         reset_map(true, false);
432         delete(this);
433 }
434
435 // Forces a restart of the game without actually reloading the map // this is a mess...
436 void ReadyRestart_force(bool is_fake_round_start)
437 {
438         if (time <= game_starttime && game_stopped)
439                 return;
440         if (!is_fake_round_start)
441                 bprint("^1Match is restarting...\n");
442
443         VoteReset();
444
445         // clear overtime, we have to decrease timelimit to its original value again.
446         if (checkrules_overtimesadded > 0 && g_race_qualifying != 2)
447                 cvar_set("timelimit", ftos(autocvar_timelimit - (checkrules_overtimesadded * autocvar_timelimit_overtime)));
448         checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = overtimes = 0;
449
450         if(warmup_stage)
451                 game_starttime = time; // Warmup: No countdown in warmup
452         else
453                 game_starttime = time + RESTART_COUNTDOWN; // Go into match mode
454
455         // clear player attributes
456         FOREACH_CLIENT(IS_PLAYER(it), {
457                 it.alivetime = 0;
458                 CS(it).killcount = 0;
459         });
460
461         // if we're ending the warmup stage call the corresponding hook
462         if(!is_fake_round_start && !warmup_stage)
463                 localcmd("\nsv_hook_warmupend\n");
464
465         // reset the .ready status of all players (also spectators)
466         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
467                 it.ready = false;
468                 Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_MISSING_READY);
469         });
470         readycount = 0;
471         Nagger_ReadyCounted();  // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
472
473         // lock teams with lockonrestart
474         if (autocvar_teamplay_lockonrestart && teamplay)
475                 lockteams = !warmup_stage;
476
477         // initiate the restart-countdown-announcer entity
478         if (!is_fake_round_start && sv_ready_restart_after_countdown && !warmup_stage)
479         {
480                 entity restart_timer = new_pure(restart_timer);
481                 setthink(restart_timer, ReadyRestart_think);
482                 restart_timer.nextthink = game_starttime;
483         }
484
485         // after a restart every players number of allowed timeouts gets reset, too
486         if (autocvar_sv_timeout)
487         {
488                 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { CS(it).allowed_timeouts = autocvar_sv_timeout_number; });
489         }
490
491         round_handler_Activate(!warmup_stage);
492         if (!sv_ready_restart_after_countdown || warmup_stage)
493                 reset_map(true, is_fake_round_start);
494
495         if (autocvar_sv_eventlog) GameLogEcho(":restart");
496 }
497
498 void ReadyRestart(bool forceWarmupEnd)
499 {
500         if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || intermission_running || race_completing) localcmd("restart\n");
501         else localcmd("\nsv_hook_readyrestart\n");
502
503         if(forceWarmupEnd)
504                 warmup_stage = 0; // forcefully end warmup and go to match stage
505         else
506                 warmup_stage = cvar("g_warmup"); // go into warmup if it's enabled, otherwise restart into match stage
507
508         ReadyRestart_force(false);
509 }
510
511 // Count the players who are ready and determine whether or not to restart the match
512 void ReadyCount()
513 {
514         float ready_needed_factor, ready_needed_count;
515         float t_ready = 0, t_players = 0;
516
517         FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME_JOINED(it)), {
518                 ++t_players;
519                 if (it.ready) ++t_ready;
520         });
521
522         readycount = t_ready;
523
524         Nagger_ReadyCounted();
525
526         ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 0.999);
527         ready_needed_count = floor(t_players * ready_needed_factor) + 1;
528
529         if (readycount >= ready_needed_count) ReadyRestart(true);
530 }
531
532
533 // ======================================
534 //  Supporting functions for VoteCommand
535 // ======================================
536
537 float Votecommand_check_assignment(entity caller, float assignment)
538 {
539         float from_server = (!caller);
540
541         if ((assignment == VC_ASGNMNT_BOTH)
542             || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY)
543             || (from_server && assignment == VC_ASGNMNT_SERVERONLY))) return true;
544
545         return false;
546 }
547
548 string VoteCommand_extractcommand(string input, float startpos, int argc)
549 {
550         string output;
551
552         if ((argc - 1) < startpos) output = "";
553         else output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos));
554
555         return output;
556 }
557
558 float VoteCommand_checknasty(string vote_command)
559 {
560         if ((strstrofs(vote_command, ";", 0) >= 0)
561             || (strstrofs(vote_command, "\n", 0) >= 0)
562             || (strstrofs(vote_command, "\r", 0) >= 0)
563             || (strstrofs(vote_command, "$", 0) >= 0)) return false;
564
565         return true;
566 }
567
568 // NOTE: requires input to be surrounded by spaces
569 string VoteCommand_checkreplacements(string input)
570 {
571         string output = input;
572         // allow gotomap replacements
573         output = strreplace(" map ", " gotomap ", output);
574         output = strreplace(" chmap ", " gotomap ", output);
575         return output;
576 }
577
578 float VoteCommand_checkinlist(string vote_command, string list)
579 {
580         string l = VoteCommand_checkreplacements(strcat(" ", list, " "));
581
582         if (strstrofs(l, VoteCommand_checkreplacements(strcat(" ", vote_command, " ")), 0) >= 0) return true;
583
584         return false;
585 }
586
587 string ValidateMap(string validated_map, entity caller)
588 {
589         validated_map = MapInfo_FixName(validated_map);
590
591         if (!validated_map)
592         {
593                 print_to(caller, "This map is not available on this server.");
594                 return string_null;
595         }
596
597         if (!autocvar_sv_vote_override_mostrecent && caller)
598         {
599                 if (Map_IsRecent(validated_map))
600                 {
601                         print_to(caller, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
602                         return string_null;
603                 }
604         }
605
606         if (!MapInfo_CheckMap(validated_map))
607         {
608                 print_to(caller, strcat("^1Invalid mapname, \"^3", validated_map, "^1\" does not support the current game mode."));
609                 return string_null;
610         }
611
612         return validated_map;
613 }
614
615 float VoteCommand_checkargs(float startpos, int argc)
616 {
617         float p, q, check, minargs;
618         string cvarname = strcat("sv_vote_command_restriction_", argv(startpos));
619         string cmdrestriction = "";  // No we don't.
620         string charlist, arg;
621         float checkmate;
622
623         if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
624                 cmdrestriction = cvar_string(cvarname);
625         else
626                 LOG_INFO("NOTE: ", cvarname, " does not exist, no restrictions will be applied.");
627
628         if (cmdrestriction == "") return true;
629
630         ++startpos;  // skip command name
631
632         // check minimum arg count
633
634         // 0 args: argc == startpos
635         // 1 args: argc == startpos + 1
636         // ...
637
638         minargs = stof(cmdrestriction);
639         if (argc - startpos < minargs) return false;
640
641         p = strstrofs(cmdrestriction, ";", 0);  // find first semicolon
642
643         for ( ; ; )
644         {
645                 // we know that at any time, startpos <= argc - minargs
646                 // so this means: argc-minargs >= startpos >= argc, thus
647                 // argc-minargs >= argc, thus minargs <= 0, thus all minargs
648                 // have been seen already
649
650                 if (startpos >= argc) // all args checked? GOOD
651                         break;
652
653                 if (p < 0)            // no more args? FAIL
654                 {
655                         // exception: exactly minargs left, this one included
656                         if (argc - startpos == minargs) break;
657
658                         // otherwise fail
659                         return false;
660                 }
661
662                 // cut to next semicolon
663                 q = strstrofs(cmdrestriction, ";", p + 1);  // find next semicolon
664                 if (q < 0) charlist = substring(cmdrestriction, p + 1, -1);
665                 else charlist = substring(cmdrestriction, p + 1, q - (p + 1));
666
667                 // in case we ever want to allow semicolons in VoteCommand_checknasty
668                 // charlist = strreplace("^^", ";", charlist);
669
670                 if (charlist != "")
671                 {
672                         // verify the arg only contains allowed chars
673                         arg = argv(startpos);
674                         checkmate = strlen(arg);
675                         for (check = 0; check < checkmate; ++check)
676                                 if (strstrofs(charlist, substring(arg, check, 1), 0) < 0) return false;
677                         // not allowed character
678                         // all characters are allowed. FINE.
679                 }
680
681                 ++startpos;
682                 --minargs;
683                 p = q;
684         }
685
686         return true;
687 }
688
689 int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, int argc)
690 {
691         string first_command = argv(startpos);
692         int missing_chars = argv_start_index(startpos);
693
694         if (autocvar_sv_vote_limit > 0 && strlen(vote_command) > autocvar_sv_vote_limit)
695                 return 0;
696
697         if (!VoteCommand_checkinlist(first_command, vote_list)) return 0;
698
699         if (!VoteCommand_checkargs(startpos, argc)) return 0;
700
701         switch (MUTATOR_CALLHOOK(VoteCommand_Parse, caller, first_command, vote_command, startpos, argc))
702         {
703                 case MUT_VOTEPARSE_CONTINUE: { break; }
704                 case MUT_VOTEPARSE_SUCCESS: { return 1; }
705                 case MUT_VOTEPARSE_INVALID: { return -1; }
706                 case MUT_VOTEPARSE_UNACCEPTABLE: { return 0; }
707         }
708
709         vote_target_type = VOTE_TARGET_ALL;
710         
711         switch (first_command) // now go through and parse the proper commands to adjust as needed.
712         {
713                 case "kick":
714                 case "kickban":    // catch all kick/kickban commands
715                 {
716                         entity victim = GetIndexedEntity(argc, (startpos + 1));
717                         float accepted = VerifyClientEntity(victim, true, false);
718
719                         if (accepted > 0)
720                         {
721                                 string reason = "No reason provided";
722                                 if(argc > next_token)
723                                         reason = substring(vote_command, argv_start_index(next_token) - missing_chars, -1);
724
725                                 string command_arguments = reason;
726                                 if (first_command == "kickban")
727                                         command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~");
728
729                                 if (first_command == "kick") // z411 : Use our kick implementation - kind of hacky...
730                                         vote_parsed_command = strcat("defer 2 \"sv_cmd kickkick # ", ftos(etof(victim)), " ", command_arguments, "\"");
731                                 else
732                                         vote_parsed_command = strcat("defer 2 \"", first_command, " # ", ftos(etof(victim)), " ", command_arguments, "\"");
733                                 
734                                 vote_parsed_display = sprintf("^1%s #%d ^7%s^1 %s", first_command, etof(victim), victim.netname, reason);
735                         }
736                         else
737                         {
738                                 print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n"));
739                                 return 0;
740                         }
741
742                         break;
743                 }
744
745                 case "map":
746                 case "chmap":
747                 case "gotomap":  // re-direct all map selection commands to gotomap
748                 {
749                         vote_command = ValidateMap(argv(startpos + 1), caller);
750                         if (!vote_command)  return -1;
751                         vote_parsed_command = strcat("defer 2 \"gotomap ", vote_command, "\"");
752                         vote_parsed_display = strzone(strcat("^1gotomap ", vote_command));
753
754                         break;
755                 }
756                 
757                 // z411 team calls
758                 case "teamname":
759                 {
760                         if (teamplay && Team_IsValidTeam(caller.team)) {
761                                 vote_target_type = VOTE_TARGET_TEAM;
762                         
763                                 string tmname = strtolower(Static_Team_ColorName(caller.team));
764                                 string newname = argv(startpos + 1);
765                                 
766                                 vote_parsed_command = strcat(first_command, " ", tmname, " \"", newname, "\"");
767                                 vote_parsed_display = strzone(strcat("^3(Team) ^1", first_command, " ^2", newname));
768                         } else { print_to(caller, "vcall: Not in a team\n"); return 0; }
769                         
770                         break;
771                 }
772
773                 // TODO: replicate the old behaviour of being able to vote for maps from different modes on multimode servers (possibly support it in gotomap too)
774                 // maybe fallback instead of aborting if map name is invalid?
775                 case "nextmap":
776                 {
777                         vote_command = ValidateMap(argv(startpos + 1), caller);
778                         if (!vote_command)  return -1;
779                         vote_parsed_command = strcat("nextmap ", vote_command);
780                         vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
781
782                         break;
783                 }
784
785                 case "timelimit": // include restrictions on the maximum votable time limit
786                 {
787                         float timelimit_vote = stof(argv(startpos + 1));
788                         if(timelimit_vote > autocvar_timelimit_max || timelimit_vote < autocvar_timelimit_min)
789                         {
790                                 print_to(caller, strcat("Invalid timelimit vote, accepted values are between ", ftos(autocvar_timelimit_min), " and ", ftos(autocvar_timelimit_max), "."));
791                                 return -1;
792                         }
793                         timelimit_vote = bound(autocvar_timelimit_min, timelimit_vote, autocvar_timelimit_max);
794                         vote_parsed_command = strcat("timelimit ", ftos(timelimit_vote));
795                         vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
796
797                         break;
798                 }
799
800                 case "gg":
801                 case "shuffleteams":
802                 case "endmatch":
803                 {
804                         // add a delay so that vote result can be seen and announcer can be heard
805                         // if the vote is accepted
806                         vote_parsed_command = strcat("defer 2 ", vote_command);
807                         vote_parsed_display = strzone(strcat("^1", vote_command));
808                         
809                         break;
810                 }
811
812                 case "reset":
813                 case "restart": // re-direct all match restarting to resetmatch
814                         vote_command = "resetmatch"; // fall-through
815                 case "resetmatch":
816                 {
817                         vote_parsed_command = strcat("defer 2 ", vote_command);
818                         vote_parsed_display = strzone(strcat("^1", vote_command));
819
820                         break;
821                 }
822
823                 case "allready":
824                 {
825                         if(!warmup_stage) {
826                                 print_to(caller, "Game already started. Use the resetmatch command to restart the match.");
827                                 return -1;
828                         }
829
830                         vote_parsed_command = strcat("defer 2 ", vote_command);
831                         vote_parsed_display = strzone(strcat("^1", vote_command));
832                         break;
833                 }
834
835                 default:
836                 {
837                         vote_parsed_command = vote_command;
838                         vote_parsed_display = strzone(strcat("^1", vote_command));
839
840                         break;
841                 }
842         }
843
844         return 1;
845 }
846
847
848 // =======================
849 //  Command Sub-Functions
850 // =======================
851
852 void VoteCommand_abstain(int request, entity caller)  // CLIENT ONLY
853 {
854         switch (request)
855         {
856                 case CMD_REQUEST_COMMAND:
857                 {
858                         if (!vote_called) { print_to(caller, "^1No vote called."); }
859                         else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
860                         {
861                                 print_to(caller, "^1You have already voted.");
862                         }
863
864                         else  // everything went okay, continue changing vote
865                         {
866                                 print_to(caller, "^1You abstained from your vote.");
867                                 caller.vote_selection = VOTE_SELECT_ABSTAIN;
868                                 msg_entity = caller;
869                                 if (!autocvar_sv_vote_singlecount)
870                                         VoteCount(false);
871                         }
872
873                         return;
874                 }
875
876                 default:
877                 case CMD_REQUEST_USAGE:
878                 {
879                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote abstain"));
880                         print_to(caller, "  No arguments required.");
881                         return;
882                 }
883         }
884 }
885
886 void print_available_commands_to(entity caller)
887 {
888         print_to(caller, strcat("You can call a vote for or execute these commands: ^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
889 }
890
891 void VoteCommand_call(int request, entity caller, int argc, string vote_command)  // BOTH
892 {
893         switch (request)
894         {
895                 case CMD_REQUEST_COMMAND:
896                 {
897                         float tmp_playercount = 0;
898                         int parse_error;
899
900                         vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
901
902                         if (!autocvar_sv_vote_call && caller)
903                         {
904                                 print_to(caller, "^1Vote calling is not allowed.");
905                         }
906                         else if (!autocvar_sv_vote_gamestart && time < game_starttime)
907                         {
908                                 print_to(caller, "^1Vote calling is not allowed before the match has started.");
909                         }
910                         else if (vote_called)
911                         {
912                                 print_to(caller, "^1There is already a vote called.");
913                         }
914                         else if (!spectators_allowed && (caller && !IS_PLAYER(caller)))
915                         {
916                                 print_to(caller, "^1Only players can call a vote.");
917                         }
918                         else if (caller && !IS_CLIENT(caller))
919                         {
920                                 print_to(caller, "^1Only connected clients can vote.");
921                         }
922                         else if (timeout_status && vote_command != "timein")
923                         {
924                                 print_to(caller, "^1You can not call a vote while a timeout is active.");
925                         }
926                         else if (caller && (time < caller.vote_waittime))
927                         {
928                                 print_to(caller, strcat("^1You have to wait ^2", ftos(ceil(caller.vote_waittime - time)), "^1 seconds before you can again call a vote."));
929                         }
930                         else if (!VoteCommand_checknasty(vote_command))
931                         {
932                                 print_to(caller, "^1Syntax error in command.");
933                         }
934                         else if ((parse_error = VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) <= 0)
935                         {
936                                 if(parse_error == 0)
937                                 {
938                                         if (vote_called_command == "")
939                                                 VoteCommand_call(CMD_REQUEST_USAGE, caller, argc, vote_command);
940                                         else
941                                                 print_to(caller, "^1This command is not acceptable or not available.");
942                                 }
943                         }
944                         else  // everything went okay, continue with calling the vote
945                         {
946                                 vote_caller = caller;  // remember who called the vote
947                                 vote_caller_name = strzone(GetCallerName(vote_caller));
948                                 vote_called = VOTE_NORMAL;
949                                 vote_called_command = strzone(vote_parsed_command);
950                                 vote_called_display = strzone(vote_parsed_display);
951                                 vote_endtime = time + autocvar_sv_vote_timeout;
952
953                                 if (caller)
954                                 {
955                                         caller.vote_selection = VOTE_SELECT_ACCEPT;
956                                         caller.vote_waittime = time + autocvar_sv_vote_wait;
957                                         msg_entity = caller;
958                                 }
959
960                                 FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
961
962                                 bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
963                                 if (autocvar_sv_eventlog)
964                                         GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
965                                 Nagger_VoteChanged();
966                                 VoteCount(true);  // needed if you are the only one
967
968                                 if (tmp_playercount > 1 && vote_called != VOTE_NULL)
969                                         Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
970                         }
971
972                         return;
973                 }
974
975                 default:
976                 case CMD_REQUEST_USAGE:
977                 {
978                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote call <command>"));
979                         print_to(caller, "  Where <command> is the command to request a vote upon.");
980                         print_to(caller, strcat("Examples: ", GetCommandPrefix(caller), " vote call gotomap dance"));
981                         print_to(caller, strcat("          ", GetCommandPrefix(caller), " vote call endmatch"));
982                         print_available_commands_to(caller);
983                         print_to(caller, "Shortcuts: ^2vcall <command>, vend, vmap, vkick, ...");
984                         return;
985                 }
986         }
987 }
988
989 void VoteCommand_master(int request, entity caller, int argc, string vote_command)  // CLIENT ONLY
990 {
991         switch (request)
992         {
993                 case CMD_REQUEST_COMMAND:
994                 {
995                         if (autocvar_sv_vote_master)
996                         {
997                                 switch (strtolower(argv(2)))
998                                 {
999                                         case "do":
1000                                         {
1001                                                 int parse_error;
1002                                                 vote_command = VoteCommand_extractcommand(vote_command, 3, argc);
1003
1004                                                 if (!caller.vote_master)
1005                                                         print_to(caller, "^1You do not have vote master privileges.");
1006                                                 else if (!VoteCommand_checknasty(vote_command))
1007                                                 {
1008                                                         print_to(caller, "^1Syntax error in command.");
1009                                                 }
1010                                                 else if ((parse_error = VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc)) <= 0)
1011                                                 {
1012                                                         if(parse_error == 0)
1013                                                         {
1014                                                                 if (vote_called_command == "")
1015                                                                         VoteCommand_master(CMD_REQUEST_USAGE, caller, argc, vote_command);
1016                                                                 else
1017                                                                         print_to(caller, "^1This command is not acceptable or not available.");
1018                                                         }
1019                                                 }
1020                                                 else  // everything went okay, proceed with command
1021                                                 {
1022                                                         localcmd(strcat(vote_parsed_command, "\n"));
1023                                                         print_to(caller, strcat("Executing command '", vote_parsed_display, "' on server."));
1024                                                         bprint("\{1}^2* ^3", GetCallerName(caller), "^2 used their ^3master^2 status to do \"^2", vote_parsed_display, "^2\".\n");
1025                                                         if (autocvar_sv_eventlog)
1026                                                                 GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display));
1027                                                 }
1028
1029                                                 return;
1030                                         }
1031
1032                                         case "login":
1033                                         {
1034                                                 if (autocvar_sv_vote_master_password == "") { print_to(caller, "^1Login to vote master is not allowed."); }
1035                                                 else if (caller.vote_master)
1036                                                 {
1037                                                         print_to(caller, "^1You are already logged in as vote master.");
1038                                                 }
1039                                                 else if (autocvar_sv_vote_master_password != argv(3))
1040                                                 {
1041                                                         print_to(caller, strcat("Rejected vote master login from ", GetCallerName(caller)));
1042                                                 }
1043                                                 else  // everything went okay, proceed with giving this player master privilages
1044                                                 {
1045                                                         caller.vote_master = true;
1046                                                         print_to(caller, strcat("Accepted vote master login from ", GetCallerName(caller)));
1047                                                         bprint("\{1}^2* ^3", GetCallerName(caller), "^2 logged in as ^3master^2\n");
1048                                                         if (autocvar_sv_eventlog)
1049                                                                 GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid)));
1050                                                 }
1051
1052                                                 return;
1053                                         }
1054
1055                                         default:  // calling a vote for master
1056                                         {
1057                                                 if (!autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); }
1058                                                 else if (vote_called)
1059                                                 {
1060                                                         print_to(caller, "^1There is already a vote called.");
1061                                                 }
1062                                                 else if (!spectators_allowed && (caller && !IS_PLAYER(caller)))
1063                                                 {
1064                                                         print_to(caller, "^1Only players can call a vote.");
1065                                                 }
1066                                                 else if (timeout_status)
1067                                                 {
1068                                                         print_to(caller, "^1You can not call a vote while a timeout is active.");
1069                                                 }
1070                                                 else  // everything went okay, continue with creating vote
1071                                                 {
1072                                                         vote_caller = caller;
1073                                                         vote_caller_name = strzone(GetCallerName(vote_caller));
1074                                                         vote_called = VOTE_MASTER;
1075                                                         vote_called_command = strzone("XXX");
1076                                                         vote_called_display = strzone("^3master");
1077                                                         vote_endtime = time + autocvar_sv_vote_timeout;
1078
1079                                                         caller.vote_selection = VOTE_SELECT_ACCEPT;
1080                                                         caller.vote_waittime = time + autocvar_sv_vote_wait;
1081
1082                                                         bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote to become ^3master^2.\n");
1083                                                         if (autocvar_sv_eventlog)
1084                                                                 GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
1085                                                         Nagger_VoteChanged();
1086                                                         VoteCount(true);  // needed if you are the only one
1087                                                 }
1088
1089                                                 return;
1090                                         }
1091                                 }
1092                         }
1093                         else { print_to(caller, "^1Master control of voting is not allowed."); }
1094
1095                         return;
1096                 }
1097
1098                 default:
1099                 case CMD_REQUEST_USAGE:
1100                 {
1101                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote master [<action> [<command> | <password>]]"));
1102                         print_to(caller, "  If <action> is left blank, it calls a vote for you to become master.");
1103                         print_to(caller, "  Otherwise it can be either 'do' (to run <command>) or 'login' as master.");
1104                         return;
1105                 }
1106         }
1107 }
1108
1109 void VoteCommand_no(int request, entity caller)  // CLIENT ONLY
1110 {
1111         switch (request)
1112         {
1113                 case CMD_REQUEST_COMMAND:
1114                 {
1115                         if (!vote_called) { print_to(caller, "^1No vote called."); }
1116                         else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
1117                         {
1118                                 print_to(caller, "^1You have already voted.");
1119                         }
1120                         else if (((caller == vote_caller) || caller.vote_master) && autocvar_sv_vote_no_stops_vote)
1121                         {
1122                                 VoteStop(caller);
1123                         }
1124
1125                         else  // everything went okay, continue changing vote
1126                         {
1127                                 print_to(caller, "^1You rejected the vote.");
1128                                 caller.vote_selection = VOTE_SELECT_REJECT;
1129                                 msg_entity = caller;
1130                                 if (!autocvar_sv_vote_singlecount)
1131                                         VoteCount(false);
1132                         }
1133
1134                         return;
1135                 }
1136
1137                 default:
1138                 case CMD_REQUEST_USAGE:
1139                 {
1140                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote no"));
1141                         print_to(caller, "  No arguments required.");
1142                         return;
1143                 }
1144         }
1145 }
1146
1147 void VoteCommand_status(int request, entity caller)  // BOTH
1148 {
1149         switch (request)
1150         {
1151                 case CMD_REQUEST_COMMAND:
1152                 {
1153                         if (vote_called) print_to(caller, strcat("^7Vote for ", vote_called_display, "^7 called by ^7", OriginalCallerName(), "^7."));
1154                         else print_to(caller, "^1No vote called.");
1155
1156                         return;
1157                 }
1158
1159                 default:
1160                 case CMD_REQUEST_USAGE:
1161                 {
1162                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote status"));
1163                         print_to(caller, "  No arguments required.");
1164                         return;
1165                 }
1166         }
1167 }
1168
1169 void VoteCommand_stop(int request, entity caller)  // BOTH
1170 {
1171         switch (request)
1172         {
1173                 case CMD_REQUEST_COMMAND:
1174                 {
1175                         if (!vote_called)   print_to(caller, "^1No vote called.");
1176                         else if ((caller == vote_caller) || !caller || caller.vote_master)   VoteStop(caller);
1177                         else   print_to(caller, "^1You are not allowed to stop that vote.");
1178                         return;
1179                 }
1180
1181                 default:
1182                 case CMD_REQUEST_USAGE:
1183                 {
1184                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote stop"));
1185                         print_to(caller, "  No arguments required.");
1186                         return;
1187                 }
1188         }
1189 }
1190
1191 void VoteCommand_yes(int request, entity caller)  // CLIENT ONLY
1192 {
1193         switch (request)
1194         {
1195                 case CMD_REQUEST_COMMAND:
1196                 {
1197                         if (!vote_called) { print_to(caller, "^1No vote called."); }
1198                         else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
1199                         {
1200                                 print_to(caller, "^1You have already voted.");
1201                         }
1202                         else  // everything went okay, continue changing vote
1203                         {
1204                                 print_to(caller, "^1You accepted the vote.");
1205                                 caller.vote_selection = VOTE_SELECT_ACCEPT;
1206                                 msg_entity = caller;
1207                                 if (!autocvar_sv_vote_singlecount)
1208                                         VoteCount(false);
1209                         }
1210
1211                         return;
1212                 }
1213
1214                 default:
1215                 case CMD_REQUEST_USAGE:
1216                 {
1217                         print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote yes"));
1218                         print_to(caller, "  No arguments required.");
1219                         return;
1220                 }
1221         }
1222 }
1223
1224 /* use this when creating a new command, making sure to place it in alphabetical order... also,
1225 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
1226 void VoteCommand_(int request)
1227 {
1228     switch(request)
1229     {
1230         case CMD_REQUEST_COMMAND:
1231         {
1232
1233             return;
1234         }
1235
1236         default:
1237         case CMD_REQUEST_USAGE:
1238         {
1239             print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote ");
1240             print_to(caller, "  No arguments required.");
1241             return;
1242         }
1243     }
1244 }
1245 */
1246
1247
1248 // ================================
1249 //  Macro system for vote commands
1250 // ================================
1251
1252 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
1253 #define VOTE_COMMANDS(request, caller, arguments, command) \
1254         VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current vote", VC_ASGNMNT_CLIENTONLY) \
1255         VOTE_COMMAND("call", VoteCommand_call(request, caller, arguments, command), "Create a new vote for players to decide on", VC_ASGNMNT_BOTH) \
1256         VOTE_COMMAND("help", VoteCommand_macro_help(caller, arguments), "Shows this information", VC_ASGNMNT_BOTH) \
1257         VOTE_COMMAND("master", VoteCommand_master(request, caller, arguments, command), "Full control over all voting and vote commands", VC_ASGNMNT_CLIENTONLY) \
1258         VOTE_COMMAND("no", VoteCommand_no(request, caller), "Select no in current vote", VC_ASGNMNT_CLIENTONLY) \
1259         VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current vote", VC_ASGNMNT_BOTH) \
1260         VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \
1261         VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \
1262         /* nothing */
1263
1264 void VoteCommand_macro_help(entity caller, int argc)
1265 {
1266         string command_origin = GetCommandPrefix(caller);
1267
1268         if (argc == 2 || argv(2) == "help")  // help display listing all commands
1269         {
1270                 print_to(caller, "\nVoting commands:\n");
1271                 #define VOTE_COMMAND(name, function, description, assignment) \
1272                         { if (Votecommand_check_assignment(caller, assignment)) { print_to(caller, strcat("  ^2", name, "^7: ", description)); } }
1273
1274                 VOTE_COMMANDS(0, caller, 0, "");
1275                 #undef VOTE_COMMAND
1276
1277                 print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote <command>^7, where possible commands are listed above.\n"));
1278                 print_to(caller, strcat("For help about a specific command, type ", command_origin, " vote help <command>"));
1279                 print_available_commands_to(caller);
1280         }
1281         else  // usage for individual command
1282         {
1283                 #define VOTE_COMMAND(name, function, description, assignment) \
1284                         { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(2))) { function; return; } } }
1285
1286                 VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
1287                 #undef VOTE_COMMAND
1288
1289                 string cvarname = strcat("sv_vote_command_help_", argv(2));
1290                 if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
1291                         wordwrap_sprint(caller, cvar_string(cvarname), 1000);
1292                 else if (argv(2) != "")
1293                         print_to(caller, "No documentation exists for this vote");
1294         }
1295 }
1296
1297 float VoteCommand_macro_command(entity caller, int argc, string vote_command)
1298 {
1299         #define VOTE_COMMAND(name, function, description, assignment) \
1300                 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } }
1301
1302         VOTE_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, vote_command);
1303         #undef VOTE_COMMAND
1304
1305         return false;
1306 }
1307
1308
1309 // ======================================
1310 //  Main function handling vote commands
1311 // ======================================
1312
1313 void VoteCommand(int request, entity caller, int argc, string vote_command)
1314 {
1315         // Guide for working with argc arguments by example:
1316         // argc:   1    - 2      - 3     - 4
1317         // argv:   0    - 1      - 2     - 3
1318         // cmd     vote - master - login - password
1319
1320         switch (request)
1321         {
1322                 case CMD_REQUEST_COMMAND:
1323                 {
1324                         if (VoteCommand_macro_command(caller, argc, vote_command)) return;
1325                 }
1326
1327                 default:
1328                         print_to(caller, strcat(((argv(1) != "") ? strcat("Unknown vote command \"", argv(1), "\"") : "No command provided"), ". For a list of supported commands, try ", GetCommandPrefix(caller), " vote help.\n"));
1329                 case CMD_REQUEST_USAGE:
1330                 {
1331                         VoteCommand_macro_help(caller, argc);
1332                         return;
1333                 }
1334         }
1335 }