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