3 #include <server/defs.qh>
4 #include <server/miscfunctions.qh>
6 #include <common/command/_mod.qh>
11 #include "../g_damage.qh"
12 #include "../g_world.qh"
14 #include "../round_handler.qh"
15 #include "../scores.qh"
17 #include "../mutators/_mod.qh"
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>
26 // =============================================
27 // Server side voting code, reworked by Samual
28 // Last updated: December 27th, 2011
29 // =============================================
31 // Nagger for players to know status of voting
32 bool Nagger_SendEntity(entity this, entity to, float sendflags)
36 WriteHeader(MSG_ENTITY, ENT_CLIENT_NAGGER);
40 // 2 = player needs to ready up
42 // 8 = player needs to vote
52 if (to.ready == 0) nags |= BIT(1);
57 if (to.vote_selection == 0) nags |= BIT(3);
59 if (warmup_stage) nags |= BIT(4);
61 if (sendflags & BIT(6)) nags |= BIT(6);
63 if (sendflags & BIT(7)) nags |= BIT(7);
65 if (!(nags & 4)) // no vote called? send no string
66 nags &= ~(BIT(6) | BIT(7));
68 WriteByte(MSG_ENTITY, nags);
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);
78 if (nags & BIT(7)) WriteString(MSG_ENTITY, vote_called_display);
82 for (i = 1; i <= maxclients; i += 8)
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)
87 WriteByte(MSG_ENTITY, f);
96 Net_LinkEntity(nagger = new_pure(nagger), false, 0, Nagger_SendEntity);
99 void Nagger_VoteChanged()
101 if (nagger) nagger.SendFlags |= BIT(7);
104 void Nagger_VoteCountChanged()
106 if (nagger) nagger.SendFlags |= BIT(6);
109 void Nagger_ReadyCounted()
111 if (nagger) nagger.SendFlags |= BIT(0);
114 // If the vote_caller is still here, return their name, otherwise vote_caller_name
115 string OriginalCallerName()
117 if (IS_REAL_CLIENT(vote_caller)) return playername(vote_caller, false);
118 return vote_caller_name;
121 // =======================
122 // Game logic for voting
123 // =======================
127 FOREACH_CLIENT(true, { it.vote_selection = 0; });
131 strfree(vote_called_command);
132 strfree(vote_called_display);
133 strfree(vote_caller_name);
136 vote_called = VOTE_NULL;
140 vote_parsed_command = string_null;
141 vote_parsed_display = string_null;
143 Nagger_VoteChanged();
146 void VoteStop(entity stopper)
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;
157 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ^1", vote_called_display, "^2 was accepted\n");
159 if ((vote_called == VOTE_MASTER) && vote_caller) vote_caller.vote_master = 1;
160 else localcmd(strcat(vote_called_command, "\n"));
162 if (vote_caller) vote_caller.vote_waittime = 0; // people like your votes, you don't need to wait to vote again
165 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_ACCEPT);
170 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 was rejected\n");
172 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
177 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 timed out\n");
179 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
182 void VoteSpam(float notvoters, float mincount, string result)
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"))));
191 if (autocvar_sv_eventlog)
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))));
202 #define spectators_allowed (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || intermission_running)))
204 void VoteCount(float first_count)
207 vote_accept_count = vote_reject_count = vote_abstain_count = 0;
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;
215 Nagger_VoteCountChanged();
217 // add up all the votes from each connected client
218 FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_CLIENT(it), {
220 if (IS_PLAYER(it)) ++vote_real_player_count;
221 switch (it.vote_selection)
223 case VOTE_SELECT_REJECT:
224 { ++vote_reject_count;
225 { if (IS_PLAYER(it)) ++vote_real_reject_count; } break;
227 case VOTE_SELECT_ACCEPT:
228 { ++vote_accept_count;
229 { if (IS_PLAYER(it)) ++vote_real_accept_count; } break;
231 case VOTE_SELECT_ABSTAIN:
232 { ++vote_abstain_count;
233 { if (IS_PLAYER(it)) ++vote_real_abstain_count; } break;
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)
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.");
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))
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;
257 // people who have no opinion in any way :D
258 notvoters = (vote_player_count - vote_accept_count - vote_reject_count - vote_abstain_count);
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;
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;
268 // are there any players at all on the server? it could be an admin vote
269 if (vote_player_count == 0 && first_count)
271 VoteSpam(0, -1, "yes"); // no players at all, just accept it
276 // since there ARE players, finally calculate the result of the vote
277 if (vote_accept_count >= vote_needed_overall)
279 VoteSpam(notvoters, -1, "yes"); // there is enough acceptions to pass the vote
284 if (vote_reject_count > vote_player_count - vote_abstain_count - vote_needed_overall)
286 VoteSpam(notvoters, -1, "no"); // there is enough rejections to deny the vote
291 // there is not enough votes in either direction, now lets just calculate what the voters have said
292 if (time > vote_endtime)
294 final_needed_votes = vote_needed_overall;
296 if (autocvar_sv_vote_majority_factor_of_voted)
298 if (vote_accept_count >= vote_needed_of_voted)
300 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "yes");
305 if (vote_accept_count + vote_reject_count > 0)
307 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "no");
312 final_needed_votes = min(vote_needed_overall, vote_needed_of_voted);
315 // it didn't pass or fail, so not enough votes to even make a decision.
316 VoteSpam(notvoters, final_needed_votes, "timeout");
323 if (vote_endtime > 0) // a vote was called
325 if (time > vote_endtime) // time is up
331 // =======================
332 // Game logic for warmup
333 // =======================
335 // Resets the state of all clients, items, weapons, waypoints, ... of the map.
336 void reset_map(bool dorespawn)
338 if (time <= game_starttime)
342 if (round_handler_IsActive())
343 round_handler_Reset(game_starttime);
346 MUTATOR_CALLHOOK(reset_map_global);
348 FOREACH_ENTITY_FLOAT_ORDERED(pure_data, false,
357 if (it.team_saved) it.team = it.team_saved;
358 if (it.flags & FL_PROJECTILE) delete(it); // remove any projectiles left
361 // Waypoints and assault start come LAST
362 FOREACH_ENTITY_ORDERED(IS_NOT_A_CLIENT(it), {
363 if (it.reset2) it.reset2(it);
366 FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it); });
368 // Moving the player reset code here since the player-reset depends
369 // on spawnpoint entities which have to be reset first --blub
372 if (!MUTATOR_CALLHOOK(reset_map_players))
374 if (restart_mapalreadyrestarted || (time < game_starttime))
376 FOREACH_CLIENT(IS_PLAYER(it),
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
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);
398 // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set)
399 void ReadyRestart_think(entity this)
401 restart_mapalreadyrestarted = true;
407 // Forces a restart of the game without actually reloading the map // this is a mess...
408 void ReadyRestart_force()
410 if (time <= game_starttime && game_stopped)
413 bprint("^1Server is restarting...\n");
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;
422 readyrestart_happened = true;
423 game_starttime = time + RESTART_COUNTDOWN;
425 // clear player attributes
426 FOREACH_CLIENT(IS_PLAYER(it), {
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);
433 restart_mapalreadyrestarted = false; // reset this var, needed when cvar sv_ready_restart_repeatable is in use
435 // disable the warmup global for the server
436 warmup_stage = 0; // once the game is restarted the game is in match stage
438 // reset the .ready status of all players (also spectators)
439 FOREACH_CLIENT(IS_REAL_CLIENT(it), { it.ready = false; });
441 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
443 // lock teams with lockonrestart
444 if (autocvar_teamplay_lockonrestart && teamplay)
447 bprint("^1The teams are now locked.\n");
450 // initiate the restart-countdown-announcer entity
451 if (sv_ready_restart_after_countdown)
453 entity restart_timer = new_pure(restart_timer);
454 setthink(restart_timer, ReadyRestart_think);
455 restart_timer.nextthink = game_starttime;
458 // after a restart every players number of allowed timeouts gets reset, too
459 if (autocvar_sv_timeout)
461 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { CS(it).allowed_timeouts = autocvar_sv_timeout_number; });
464 if (!sv_ready_restart_after_countdown) reset_map(true);
465 if (autocvar_sv_eventlog) GameLogEcho(":restart");
470 if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || game_stopped || race_completing) localcmd("restart\n");
471 else localcmd("\nsv_hook_gamerestart\n");
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();
479 // Count the players who are ready and determine whether or not to restart the match
482 float ready_needed_factor, ready_needed_count;
483 float t_ready = 0, t_players = 0;
485 FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || it.caplayer == 1), {
487 if (it.ready) ++t_ready;
490 readycount = t_ready;
492 Nagger_ReadyCounted();
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;
497 if (readycount >= ready_needed_count) ReadyRestart();
501 // ======================================
502 // Supporting functions for VoteCommand
503 // ======================================
505 float Votecommand_check_assignment(entity caller, float assignment)
507 float from_server = (!caller);
509 if ((assignment == VC_ASGNMNT_BOTH)
510 || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY)
511 || (from_server && assignment == VC_ASGNMNT_SERVERONLY))) return true;
516 string VoteCommand_extractcommand(string input, float startpos, float argc)
520 if ((argc - 1) < startpos) output = "";
521 else output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos));
526 float VoteCommand_checknasty(string vote_command)
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;
536 float VoteCommand_checkinlist(string vote_command, string list)
538 string l = strcat(" ", list, " ");
540 if (strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0) return true;
545 string ValidateMap(string validated_map, entity caller)
547 validated_map = MapInfo_FixName(validated_map);
551 print_to(caller, "This map is not available on this server.");
555 if (!autocvar_sv_vote_override_mostrecent && caller)
557 if (Map_IsRecent(validated_map))
559 print_to(caller, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
564 if (!MapInfo_CheckMap(validated_map))
566 print_to(caller, strcat("^1Invalid mapname, \"^3", validated_map, "^1\" does not support the current game mode."));
570 return validated_map;
573 float VoteCommand_checkargs(float startpos, float argc)
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;
581 if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
582 cmdrestriction = cvar_string(cvarname);
584 LOG_INFO("NOTE: ", cvarname, " does not exist, no restrictions will be applied.");
586 if (cmdrestriction == "") return true;
588 ++startpos; // skip command name
590 // check minimum arg count
592 // 0 args: argc == startpos
593 // 1 args: argc == startpos + 1
596 minargs = stof(cmdrestriction);
597 if (argc - startpos < minargs) return false;
599 p = strstrofs(cmdrestriction, ";", 0); // find first semicolon
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
608 if (startpos >= argc) // all args checked? GOOD
611 if (p < 0) // no more args? FAIL
613 // exception: exactly minargs left, this one included
614 if (argc - startpos == minargs) break;
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));
625 // in case we ever want to allow semicolons in VoteCommand_checknasty
626 // charlist = strreplace("^^", ";", charlist);
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.
647 int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
649 string first_command = argv(startpos);
650 int missing_chars = argv_start_index(startpos);
652 if (autocvar_sv_vote_limit > 0 && strlen(vote_command) > autocvar_sv_vote_limit)
655 if (!VoteCommand_checkinlist(first_command, vote_list)) return 0;
657 if (!VoteCommand_checkargs(startpos, argc)) return 0;
659 switch (MUTATOR_CALLHOOK(VoteCommand_Parse, caller, first_command, vote_command, startpos, argc))
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; }
667 switch (first_command) // now go through and parse the proper commands to adjust as needed.
670 case "kickban": // catch all kick/kickban commands
672 entity victim = GetIndexedEntity(argc, (startpos + 1));
673 float accepted = VerifyClientEntity(victim, true, false);
677 string reason = "No reason provided";
678 if(argc > next_token)
679 reason = substring(vote_command, argv_start_index(next_token) - missing_chars, -1);
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), " ~");
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);
688 else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return 0; }
695 case "gotomap": // re-direct all map selection commands to gotomap
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));
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?
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));
717 vote_parsed_command = vote_command;
718 vote_parsed_display = strzone(strcat("^1", vote_command));
728 // =======================
729 // Command Sub-Functions
730 // =======================
732 void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY
736 case CMD_REQUEST_COMMAND:
738 if (!vote_called) { print_to(caller, "^1No vote called."); }
739 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
741 print_to(caller, "^1You have already voted.");
744 else // everything went okay, continue changing vote
746 print_to(caller, "^1You abstained from your vote.");
747 caller.vote_selection = VOTE_SELECT_ABSTAIN;
749 if (!autocvar_sv_vote_singlecount) VoteCount(false); }
755 case CMD_REQUEST_USAGE:
757 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote abstain"));
758 print_to(caller, " No arguments required.");
764 void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH
768 case CMD_REQUEST_COMMAND:
770 float tmp_playercount = 0;
773 vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
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)
778 print_to(caller, "^1Vote calling is not allowed before the match has started.");
780 else if (vote_called)
782 print_to(caller, "^1There is already a vote called.");
784 else if (!spectators_allowed && (caller && !IS_PLAYER(caller)))
786 print_to(caller, "^1Only players can call a vote.");
788 else if (caller && !IS_CLIENT(caller))
790 print_to(caller, "^1Only connected clients can vote.");
792 else if (timeout_status)
794 print_to(caller, "^1You can not call a vote while a timeout is active.");
796 else if (caller && (time < caller.vote_waittime))
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."));
800 else if (!VoteCommand_checknasty(vote_command))
802 print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
804 else if ((parse_error = VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) <= 0)
807 print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
809 else // everything went okay, continue with calling the vote
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;
820 caller.vote_selection = VOTE_SELECT_ACCEPT;
821 caller.vote_waittime = time + autocvar_sv_vote_wait;
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);
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
840 case CMD_REQUEST_USAGE:
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"));
851 void VoteCommand_master(float request, entity caller, float argc, string vote_command) // CLIENT ONLY
855 case CMD_REQUEST_COMMAND:
857 if (autocvar_sv_vote_master)
859 switch (strtolower(argv(2)))
864 vote_command = VoteCommand_extractcommand(vote_command, 3, argc);
866 if (!caller.vote_master)
867 print_to(caller, "^1You do not have vote master privileges.");
868 else if (!VoteCommand_checknasty(vote_command))
870 print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
872 else if ((parse_error = VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc)) <= 0)
875 print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
877 else // everything went okay, proceed with command
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));
891 if (autocvar_sv_vote_master_password == "") { print_to(caller, "^1Login to vote master is not allowed."); }
892 else if (caller.vote_master)
894 print_to(caller, "^1You are already logged in as vote master.");
896 else if (autocvar_sv_vote_master_password != argv(3))
898 print_to(caller, strcat("Rejected vote master login from ", GetCallerName(caller)));
900 else // everything went okay, proceed with giving this player master privilages
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)));
912 default: // calling a vote for master
914 if (!autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); }
915 else if (vote_called)
917 print_to(caller, "^1There is already a vote called.");
919 else if (!spectators_allowed && (caller && !IS_PLAYER(caller)))
921 print_to(caller, "^1Only players can call a vote.");
923 else if (timeout_status)
925 print_to(caller, "^1You can not call a vote while a timeout is active.");
927 else // everything went okay, continue with creating vote
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;
936 caller.vote_selection = VOTE_SELECT_ACCEPT;
937 caller.vote_waittime = time + autocvar_sv_vote_wait;
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
950 else { print_to(caller, "^1Master control of voting is not allowed."); }
956 case CMD_REQUEST_USAGE:
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.");
966 void VoteCommand_no(float request, entity caller) // CLIENT ONLY
970 case CMD_REQUEST_COMMAND:
972 if (!vote_called) { print_to(caller, "^1No vote called."); }
973 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
975 print_to(caller, "^1You have already voted.");
977 else if (((caller == vote_caller) || caller.vote_master) && autocvar_sv_vote_no_stops_vote)
982 else // everything went okay, continue changing vote
984 print_to(caller, "^1You rejected the vote.");
985 caller.vote_selection = VOTE_SELECT_REJECT;
987 if (!autocvar_sv_vote_singlecount)
995 case CMD_REQUEST_USAGE:
997 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote no"));
998 print_to(caller, " No arguments required.");
1004 void VoteCommand_status(float request, entity caller) // BOTH
1008 case CMD_REQUEST_COMMAND:
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.");
1017 case CMD_REQUEST_USAGE:
1019 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote status"));
1020 print_to(caller, " No arguments required.");
1026 void VoteCommand_stop(float request, entity caller) // BOTH
1030 case CMD_REQUEST_COMMAND:
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.");
1039 case CMD_REQUEST_USAGE:
1041 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote stop"));
1042 print_to(caller, " No arguments required.");
1048 void VoteCommand_yes(float request, entity caller) // CLIENT ONLY
1052 case CMD_REQUEST_COMMAND:
1054 if (!vote_called) { print_to(caller, "^1No vote called."); }
1055 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
1057 print_to(caller, "^1You have already voted.");
1059 else // everything went okay, continue changing vote
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)
1072 case CMD_REQUEST_USAGE:
1074 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote yes"));
1075 print_to(caller, " No arguments required.");
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)
1087 case CMD_REQUEST_COMMAND:
1094 case CMD_REQUEST_USAGE:
1096 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote ");
1097 print_to(caller, " No arguments required.");
1105 // ================================
1106 // Macro system for vote commands
1107 // ================================
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) \
1121 void VoteCommand_macro_help(entity caller, float argc)
1123 string command_origin = GetCommandPrefix(caller);
1125 if (argc == 2 || argv(2) == "help") // help display listing all commands
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)); } }
1131 VOTE_COMMANDS(0, caller, 0, "");
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"));
1138 else // usage for individual command
1140 #define VOTE_COMMAND(name, function, description, assignment) \
1141 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(2))) { function; return; } } }
1143 VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
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);
1150 print_to(caller, "No documentation exists for this vote");
1154 float VoteCommand_macro_command(entity caller, float argc, string vote_command)
1156 #define VOTE_COMMAND(name, function, description, assignment) \
1157 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } }
1159 VOTE_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, vote_command);
1166 // ======================================
1167 // Main function handling vote commands
1168 // ======================================
1170 void VoteCommand(float request, entity caller, float argc, string vote_command)
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
1179 case CMD_REQUEST_COMMAND:
1181 if (VoteCommand_macro_command(caller, argc, vote_command)) return;
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:
1188 VoteCommand_macro_help(caller, argc);