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"
18 #include <common/gamemodes/_mod.qh>
20 #include <common/constants.qh>
21 #include <common/net_linked.qh>
22 #include <common/mapinfo.qh>
23 #include <common/notifications/all.qh>
24 #include <common/playerstats.qh>
25 #include <common/util.qh>
27 // =============================================
28 // Server side voting code, reworked by Samual
29 // Last updated: December 27th, 2011
30 // =============================================
32 // Nagger for players to know status of voting
33 bool Nagger_SendEntity(entity this, entity to, float sendflags)
37 WriteHeader(MSG_ENTITY, ENT_CLIENT_NAGGER);
41 // 2 = player needs to ready up
43 // 8 = player needs to vote
53 if (to.ready == 0) nags |= BIT(1);
58 if (to.vote_selection == 0) nags |= BIT(3);
60 if (warmup_stage) nags |= BIT(4);
62 if (sendflags & BIT(6)) nags |= BIT(6);
64 if (sendflags & BIT(7)) nags |= BIT(7);
66 if (!(nags & 4)) // no vote called? send no string
67 nags &= ~(BIT(6) | BIT(7));
69 WriteByte(MSG_ENTITY, nags);
73 WriteByte(MSG_ENTITY, vote_accept_count);
74 WriteByte(MSG_ENTITY, vote_reject_count);
75 WriteByte(MSG_ENTITY, vote_needed_overall);
76 WriteChar(MSG_ENTITY, to.vote_selection);
79 if (nags & BIT(7)) WriteString(MSG_ENTITY, vote_called_display);
83 for (i = 1; i <= maxclients; i += 8)
85 for (f = 0, e = edict_num(i), b = BIT(0); b < BIT(8); b <<= 1, e = nextent(e))
86 if (!IS_REAL_CLIENT(e) || e.ready)
88 WriteByte(MSG_ENTITY, f);
97 Net_LinkEntity(nagger = new_pure(nagger), false, 0, Nagger_SendEntity);
100 void Nagger_VoteChanged()
102 if (nagger) nagger.SendFlags |= BIT(7);
105 void Nagger_VoteCountChanged()
107 if (nagger) nagger.SendFlags |= BIT(6);
110 void Nagger_ReadyCounted()
112 if (nagger) nagger.SendFlags |= BIT(0);
115 // If the vote_caller is still here, return their name, otherwise vote_caller_name
116 string OriginalCallerName()
118 if (IS_REAL_CLIENT(vote_caller)) return playername(vote_caller, false);
119 return vote_caller_name;
122 // =======================
123 // Game logic for voting
124 // =======================
128 FOREACH_CLIENT(true, { it.vote_selection = 0; });
132 strfree(vote_called_command);
133 strfree(vote_called_display);
134 strfree(vote_caller_name);
137 vote_called = VOTE_NULL;
141 vote_parsed_command = string_null;
142 vote_parsed_display = string_null;
144 Nagger_VoteChanged();
147 void VoteStop(entity stopper)
149 bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n");
150 if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
151 // Don't force them to wait for next vote, this way they can e.g. correct their vote.
152 if ((vote_caller) && (stopper == vote_caller)) vote_caller.vote_waittime = time + autocvar_sv_vote_stop;
158 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ^1", vote_called_display, "^2 was accepted\n");
160 if ((vote_called == VOTE_MASTER) && vote_caller) vote_caller.vote_master = 1;
161 else localcmd(strcat(vote_called_command, "\n"));
163 if (vote_caller) vote_caller.vote_waittime = 0; // people like your votes, you don't need to wait to vote again
166 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_ACCEPT);
171 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 was rejected\n");
173 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
178 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 timed out\n");
180 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
183 void VoteSpam(float notvoters, float mincount, string result)
186 strcat("\{1}^2* vote results: ^1", ftos(vote_accept_count)),
187 strcat("^2:^1", ftos(vote_reject_count)),
188 ((mincount >= 0) ? strcat("^2 (^1", ftos(mincount), "^2 needed)") : "^2"),
189 strcat(", ^1", ftos(vote_abstain_count), "^2 didn't care"),
190 strcat(", ^1", ftos(notvoters), strcat("^2 didn't ", ((mincount >= 0) ? "" : "have to "), "vote\n"))));
192 if (autocvar_sv_eventlog)
195 strcat(":vote:v", result, ":", ftos(vote_accept_count)),
196 strcat(":", ftos(vote_reject_count)),
197 strcat(":", ftos(vote_abstain_count)),
198 strcat(":", ftos(notvoters)),
199 strcat(":", ftos(mincount))));
203 #define spectators_allowed (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || intermission_running)))
205 void VoteCount(float first_count)
208 vote_accept_count = vote_reject_count = vote_abstain_count = 0;
210 float vote_player_count = 0, notvoters = 0;
211 float vote_real_player_count = 0, vote_real_accept_count = 0;
212 float vote_real_reject_count = 0, vote_real_abstain_count = 0;
213 float vote_needed_of_voted, final_needed_votes;
214 float vote_factor_overall, vote_factor_of_voted;
216 Nagger_VoteCountChanged();
218 // add up all the votes from each connected client
219 FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_CLIENT(it), {
221 if (IS_PLAYER(it)) ++vote_real_player_count;
222 switch (it.vote_selection)
224 case VOTE_SELECT_REJECT:
225 { ++vote_reject_count;
226 { if (IS_PLAYER(it)) ++vote_real_reject_count; } break;
228 case VOTE_SELECT_ACCEPT:
229 { ++vote_accept_count;
230 { if (IS_PLAYER(it)) ++vote_real_accept_count; } break;
232 case VOTE_SELECT_ABSTAIN:
233 { ++vote_abstain_count;
234 { if (IS_PLAYER(it)) ++vote_real_abstain_count; } break;
240 // Check to see if there are enough players on the server to allow master voting... otherwise, vote master could be used for evil.
241 if ((vote_called == VOTE_MASTER) && autocvar_sv_vote_master_playerlimit > vote_player_count)
243 if (vote_caller) vote_caller.vote_waittime = 0;
244 print_to(vote_caller, "^1There are not enough players on this server to allow you to become vote master.");
249 // 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.
250 if (!spectators_allowed && (vote_real_player_count > 0))
252 vote_accept_count = vote_real_accept_count;
253 vote_reject_count = vote_real_reject_count;
254 vote_abstain_count = vote_real_abstain_count;
255 vote_player_count = vote_real_player_count;
258 // people who have no opinion in any way :D
259 notvoters = (vote_player_count - vote_accept_count - vote_reject_count - vote_abstain_count);
261 // determine the goal for the vote to be passed or rejected normally
262 vote_factor_overall = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
263 vote_needed_overall = floor((vote_player_count - vote_abstain_count) * vote_factor_overall) + 1;
265 // if the vote times out, determine the amount of votes needed of the people who actually already voted
266 vote_factor_of_voted = bound(0.5, autocvar_sv_vote_majority_factor_of_voted, 0.999);
267 vote_needed_of_voted = floor((vote_accept_count + vote_reject_count) * vote_factor_of_voted) + 1;
269 // are there any players at all on the server? it could be an admin vote
270 if (vote_player_count == 0 && first_count)
272 VoteSpam(0, -1, "yes"); // no players at all, just accept it
277 // since there ARE players, finally calculate the result of the vote
278 if (vote_accept_count >= vote_needed_overall)
280 VoteSpam(notvoters, -1, "yes"); // there is enough acceptions to pass the vote
285 if (vote_reject_count > vote_player_count - vote_abstain_count - vote_needed_overall)
287 VoteSpam(notvoters, -1, "no"); // there is enough rejections to deny the vote
292 // there is not enough votes in either direction, now lets just calculate what the voters have said
293 if (time > vote_endtime)
295 final_needed_votes = vote_needed_overall;
297 if (autocvar_sv_vote_majority_factor_of_voted)
299 if (vote_accept_count >= vote_needed_of_voted)
301 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "yes");
306 if (vote_accept_count + vote_reject_count > 0)
308 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "no");
313 final_needed_votes = min(vote_needed_overall, vote_needed_of_voted);
316 // it didn't pass or fail, so not enough votes to even make a decision.
317 VoteSpam(notvoters, final_needed_votes, "timeout");
324 if (vote_endtime > 0) // a vote was called
326 if (time > vote_endtime) // time is up
332 // =======================
333 // Game logic for warmup
334 // =======================
336 // Resets the state of all clients, items, weapons, waypoints, ... of the map.
337 void reset_map(bool dorespawn)
339 if (time <= game_starttime)
343 if (round_handler_IsActive())
344 round_handler_Reset(game_starttime);
347 MUTATOR_CALLHOOK(reset_map_global);
349 FOREACH_ENTITY_FLOAT_ORDERED(pure_data, false,
358 if (it.team_saved) it.team = it.team_saved;
359 if (it.flags & FL_PROJECTILE) delete(it); // remove any projectiles left
362 // Waypoints and assault start come LAST
363 FOREACH_ENTITY_ORDERED(IS_NOT_A_CLIENT(it), {
364 if (it.reset2) it.reset2(it);
367 FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it); });
369 // Moving the player reset code here since the player-reset depends
370 // on spawnpoint entities which have to be reset first --blub
373 if (!MUTATOR_CALLHOOK(reset_map_players))
375 if (restart_mapalreadyrestarted || (time < game_starttime))
377 FOREACH_CLIENT(IS_PLAYER(it),
380 only reset players if a restart countdown is active
381 this can either be due to cvar sv_ready_restart_after_countdown having set
382 restart_mapalreadyrestarted to 1 after the countdown ended or when
383 sv_ready_restart_after_countdown is not used and countdown is still running
385 // NEW: changed behaviour so that it prevents that previous spectators/observers suddenly spawn as players
386 // PlayerScore_Clear(it);
387 CS(it).killcount = 0;
388 // stop the player from moving so that he stands still once he gets respawned
389 it.velocity = '0 0 0';
390 it.avelocity = '0 0 0';
391 CS(it).movement = '0 0 0';
392 PutClientInServer(it);
399 // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set)
400 void ReadyRestart_think(entity this)
402 restart_mapalreadyrestarted = true;
408 // Forces a restart of the game without actually reloading the map // this is a mess...
409 void ReadyRestart_force()
411 if (time <= game_starttime && game_stopped)
414 bprint("^1Server is restarting...\n");
418 // clear overtime, we have to decrease timelimit to its original value again.
419 if (checkrules_overtimesadded > 0 && g_race_qualifying != 2)
420 cvar_set("timelimit", ftos(autocvar_timelimit - (checkrules_overtimesadded * autocvar_timelimit_overtime)));
421 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
423 readyrestart_happened = true;
424 game_starttime = time + RESTART_COUNTDOWN;
426 // clear player attributes
427 FOREACH_CLIENT(IS_PLAYER(it), {
429 CS(it).killcount = 0;
430 float val = PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_ALIVETIME, 0);
431 PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_ALIVETIME, -val);
434 restart_mapalreadyrestarted = false; // reset this var, needed when cvar sv_ready_restart_repeatable is in use
436 // disable the warmup global for the server
437 warmup_stage = 0; // once the game is restarted the game is in match stage
439 // reset the .ready status of all players (also spectators)
440 FOREACH_CLIENT(IS_REAL_CLIENT(it), { it.ready = false; });
442 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
444 // lock teams with lockonrestart
445 if (autocvar_teamplay_lockonrestart && teamplay)
448 bprint("^1The teams are now locked.\n");
451 // initiate the restart-countdown-announcer entity
452 if (sv_ready_restart_after_countdown)
454 entity restart_timer = new_pure(restart_timer);
455 setthink(restart_timer, ReadyRestart_think);
456 restart_timer.nextthink = game_starttime;
459 // after a restart every players number of allowed timeouts gets reset, too
460 if (autocvar_sv_timeout)
462 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { CS(it).allowed_timeouts = autocvar_sv_timeout_number; });
465 if (!sv_ready_restart_after_countdown) reset_map(true);
466 if (autocvar_sv_eventlog) GameLogEcho(":restart");
471 if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || game_stopped || race_completing) localcmd("restart\n");
472 else localcmd("\nsv_hook_gamerestart\n");
474 // Reset ALL scores, but only do that at the beginning of the countdown if sv_ready_restart_after_countdown is off!
475 // Otherwise scores could be manipulated during the countdown.
476 if (!sv_ready_restart_after_countdown) Score_ClearAll();
477 ReadyRestart_force();
480 // Count the players who are ready and determine whether or not to restart the match
483 float ready_needed_factor, ready_needed_count;
484 float t_ready = 0, t_players = 0;
486 FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || it.caplayer == 1), {
488 if (it.ready) ++t_ready;
491 readycount = t_ready;
493 Nagger_ReadyCounted();
495 ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 0.999);
496 ready_needed_count = floor(t_players * ready_needed_factor) + 1;
498 if (readycount >= ready_needed_count) ReadyRestart();
502 // ======================================
503 // Supporting functions for VoteCommand
504 // ======================================
506 float Votecommand_check_assignment(entity caller, float assignment)
508 float from_server = (!caller);
510 if ((assignment == VC_ASGNMNT_BOTH)
511 || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY)
512 || (from_server && assignment == VC_ASGNMNT_SERVERONLY))) return true;
517 string VoteCommand_extractcommand(string input, float startpos, float argc)
521 if ((argc - 1) < startpos) output = "";
522 else output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos));
527 float VoteCommand_checknasty(string vote_command)
529 if ((strstrofs(vote_command, ";", 0) >= 0)
530 || (strstrofs(vote_command, "\n", 0) >= 0)
531 || (strstrofs(vote_command, "\r", 0) >= 0)
532 || (strstrofs(vote_command, "$", 0) >= 0)) return false;
537 float VoteCommand_checkinlist(string vote_command, string list)
539 string l = strcat(" ", list, " ");
541 if (strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0) return true;
546 string ValidateMap(string validated_map, entity caller)
548 validated_map = MapInfo_FixName(validated_map);
552 print_to(caller, "This map is not available on this server.");
556 if (!autocvar_sv_vote_override_mostrecent && caller)
558 if (Map_IsRecent(validated_map))
560 print_to(caller, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
565 if (!MapInfo_CheckMap(validated_map))
567 print_to(caller, strcat("^1Invalid mapname, \"^3", validated_map, "^1\" does not support the current game mode."));
571 return validated_map;
574 float VoteCommand_checkargs(float startpos, float argc)
576 float p, q, check, minargs;
577 string cvarname = strcat("sv_vote_command_restriction_", argv(startpos));
578 string cmdrestriction = ""; // No we don't.
579 string charlist, arg;
582 if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
583 cmdrestriction = cvar_string(cvarname);
585 LOG_INFO("NOTE: ", cvarname, " does not exist, no restrictions will be applied.");
587 if (cmdrestriction == "") return true;
589 ++startpos; // skip command name
591 // check minimum arg count
593 // 0 args: argc == startpos
594 // 1 args: argc == startpos + 1
597 minargs = stof(cmdrestriction);
598 if (argc - startpos < minargs) return false;
600 p = strstrofs(cmdrestriction, ";", 0); // find first semicolon
604 // we know that at any time, startpos <= argc - minargs
605 // so this means: argc-minargs >= startpos >= argc, thus
606 // argc-minargs >= argc, thus minargs <= 0, thus all minargs
607 // have been seen already
609 if (startpos >= argc) // all args checked? GOOD
612 if (p < 0) // no more args? FAIL
614 // exception: exactly minargs left, this one included
615 if (argc - startpos == minargs) break;
621 // cut to next semicolon
622 q = strstrofs(cmdrestriction, ";", p + 1); // find next semicolon
623 if (q < 0) charlist = substring(cmdrestriction, p + 1, -1);
624 else charlist = substring(cmdrestriction, p + 1, q - (p + 1));
626 // in case we ever want to allow semicolons in VoteCommand_checknasty
627 // charlist = strreplace("^^", ";", charlist);
631 // verify the arg only contains allowed chars
632 arg = argv(startpos);
633 checkmate = strlen(arg);
634 for (check = 0; check < checkmate; ++check)
635 if (strstrofs(charlist, substring(arg, check, 1), 0) < 0) return false;
636 // not allowed character
637 // all characters are allowed. FINE.
648 int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
650 string first_command = argv(startpos);
651 int missing_chars = argv_start_index(startpos);
653 if (autocvar_sv_vote_limit > 0 && strlen(vote_command) > autocvar_sv_vote_limit)
656 if (!VoteCommand_checkinlist(first_command, vote_list)) return 0;
658 if (!VoteCommand_checkargs(startpos, argc)) return 0;
660 switch (MUTATOR_CALLHOOK(VoteCommand_Parse, caller, first_command, vote_command, startpos, argc))
662 case MUT_VOTEPARSE_CONTINUE: { break; }
663 case MUT_VOTEPARSE_SUCCESS: { return 1; }
664 case MUT_VOTEPARSE_INVALID: { return -1; }
665 case MUT_VOTEPARSE_UNACCEPTABLE: { return 0; }
668 switch (first_command) // now go through and parse the proper commands to adjust as needed.
671 case "kickban": // catch all kick/kickban commands
673 entity victim = GetIndexedEntity(argc, (startpos + 1));
674 float accepted = VerifyClientEntity(victim, true, false);
678 string reason = "No reason provided";
679 if(argc > next_token)
680 reason = substring(vote_command, argv_start_index(next_token) - missing_chars, -1);
682 string command_arguments = reason;
683 if (first_command == "kickban")
684 command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~");
686 vote_parsed_command = strcat(first_command, " # ", ftos(etof(victim)), " ", command_arguments);
687 vote_parsed_display = sprintf("^1%s #%d ^7%s^1 %s", first_command, etof(victim), victim.netname, reason);
689 else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return 0; }
696 case "gotomap": // re-direct all map selection commands to gotomap
698 vote_command = ValidateMap(argv(startpos + 1), caller);
699 if (!vote_command) return -1;
700 vote_parsed_command = strcat("gotomap ", vote_command);
701 vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
706 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?
708 vote_command = ValidateMap(argv(startpos + 1), caller);
709 if (!vote_command) return -1;
710 vote_parsed_command = strcat("nextmap ", vote_command);
711 vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
718 vote_parsed_command = vote_command;
719 vote_parsed_display = strzone(strcat("^1", vote_command));
729 // =======================
730 // Command Sub-Functions
731 // =======================
733 void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY
737 case CMD_REQUEST_COMMAND:
739 if (!vote_called) { print_to(caller, "^1No vote called."); }
740 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
742 print_to(caller, "^1You have already voted.");
745 else // everything went okay, continue changing vote
747 print_to(caller, "^1You abstained from your vote.");
748 caller.vote_selection = VOTE_SELECT_ABSTAIN;
750 if (!autocvar_sv_vote_singlecount) VoteCount(false); }
756 case CMD_REQUEST_USAGE:
758 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote abstain"));
759 print_to(caller, " No arguments required.");
765 void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH
769 case CMD_REQUEST_COMMAND:
771 float tmp_playercount = 0;
774 vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
776 if (!autocvar_sv_vote_call && caller) { print_to(caller, "^1Vote calling is not allowed."); }
777 else if (!autocvar_sv_vote_gamestart && time < game_starttime)
779 print_to(caller, "^1Vote calling is not allowed before the match has started.");
781 else if (vote_called)
783 print_to(caller, "^1There is already a vote called.");
785 else if (!spectators_allowed && (caller && !IS_PLAYER(caller)))
787 print_to(caller, "^1Only players can call a vote.");
789 else if (caller && !IS_CLIENT(caller))
791 print_to(caller, "^1Only connected clients can vote.");
793 else if (timeout_status)
795 print_to(caller, "^1You can not call a vote while a timeout is active.");
797 else if (caller && (time < caller.vote_waittime))
799 print_to(caller, strcat("^1You have to wait ^2", ftos(ceil(caller.vote_waittime - time)), "^1 seconds before you can again call a vote."));
801 else if (!VoteCommand_checknasty(vote_command))
803 print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
805 else if ((parse_error = VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) <= 0)
808 print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
810 else // everything went okay, continue with calling the vote
812 vote_caller = caller; // remember who called the vote
813 vote_caller_name = strzone(GetCallerName(vote_caller));
814 vote_called = VOTE_NORMAL;
815 vote_called_command = strzone(vote_parsed_command);
816 vote_called_display = strzone(vote_parsed_display);
817 vote_endtime = time + autocvar_sv_vote_timeout;
821 caller.vote_selection = VOTE_SELECT_ACCEPT;
822 caller.vote_waittime = time + autocvar_sv_vote_wait;
826 FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
827 if (tmp_playercount > 1)
828 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
830 bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
831 if (autocvar_sv_eventlog)
832 GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
833 Nagger_VoteChanged();
834 VoteCount(true); // needed if you are the only one
841 case CMD_REQUEST_USAGE:
843 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote call command"));
844 print_to(caller, " Where 'command' is the command to request a vote upon.");
845 print_to(caller, strcat("Examples: ", GetCommandPrefix(caller), " vote call gotomap dance"));
846 print_to(caller, strcat(" ", GetCommandPrefix(caller), " vote call endmatch"));
852 void VoteCommand_master(float request, entity caller, float argc, string vote_command) // CLIENT ONLY
856 case CMD_REQUEST_COMMAND:
858 if (autocvar_sv_vote_master)
860 switch (strtolower(argv(2)))
865 vote_command = VoteCommand_extractcommand(vote_command, 3, argc);
867 if (!caller.vote_master)
868 print_to(caller, "^1You do not have vote master privileges.");
869 else if (!VoteCommand_checknasty(vote_command))
871 print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
873 else if ((parse_error = VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc)) <= 0)
876 print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
878 else // everything went okay, proceed with command
880 localcmd(strcat(vote_parsed_command, "\n"));
881 print_to(caller, strcat("Executing command '", vote_parsed_display, "' on server."));
882 bprint("\{1}^2* ^3", GetCallerName(caller), "^2 used their ^3master^2 status to do \"^2", vote_parsed_display, "^2\".\n");
883 if (autocvar_sv_eventlog)
884 GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display));
892 if (autocvar_sv_vote_master_password == "") { print_to(caller, "^1Login to vote master is not allowed."); }
893 else if (caller.vote_master)
895 print_to(caller, "^1You are already logged in as vote master.");
897 else if (autocvar_sv_vote_master_password != argv(3))
899 print_to(caller, strcat("Rejected vote master login from ", GetCallerName(caller)));
901 else // everything went okay, proceed with giving this player master privilages
903 caller.vote_master = true;
904 print_to(caller, strcat("Accepted vote master login from ", GetCallerName(caller)));
905 bprint("\{1}^2* ^3", GetCallerName(caller), "^2 logged in as ^3master^2\n");
906 if (autocvar_sv_eventlog)
907 GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid)));
913 default: // calling a vote for master
915 if (!autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); }
916 else if (vote_called)
918 print_to(caller, "^1There is already a vote called.");
920 else if (!spectators_allowed && (caller && !IS_PLAYER(caller)))
922 print_to(caller, "^1Only players can call a vote.");
924 else if (timeout_status)
926 print_to(caller, "^1You can not call a vote while a timeout is active.");
928 else // everything went okay, continue with creating vote
930 vote_caller = caller;
931 vote_caller_name = strzone(GetCallerName(vote_caller));
932 vote_called = VOTE_MASTER;
933 vote_called_command = strzone("XXX");
934 vote_called_display = strzone("^3master");
935 vote_endtime = time + autocvar_sv_vote_timeout;
937 caller.vote_selection = VOTE_SELECT_ACCEPT;
938 caller.vote_waittime = time + autocvar_sv_vote_wait;
940 bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote to become ^3master^2.\n");
941 if (autocvar_sv_eventlog)
942 GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
943 Nagger_VoteChanged();
944 VoteCount(true); // needed if you are the only one
951 else { print_to(caller, "^1Master control of voting is not allowed."); }
957 case CMD_REQUEST_USAGE:
959 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote master [action [command | password]]"));
960 print_to(caller, " If action is left blank, it calls a vote for you to become master.");
961 print_to(caller, " Otherwise the actions are either 'do' a command or 'login' as master.");
967 void VoteCommand_no(float request, entity caller) // CLIENT ONLY
971 case CMD_REQUEST_COMMAND:
973 if (!vote_called) { print_to(caller, "^1No vote called."); }
974 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
976 print_to(caller, "^1You have already voted.");
978 else if (((caller == vote_caller) || caller.vote_master) && autocvar_sv_vote_no_stops_vote)
983 else // everything went okay, continue changing vote
985 print_to(caller, "^1You rejected the vote.");
986 caller.vote_selection = VOTE_SELECT_REJECT;
988 if (!autocvar_sv_vote_singlecount)
996 case CMD_REQUEST_USAGE:
998 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote no"));
999 print_to(caller, " No arguments required.");
1005 void VoteCommand_status(float request, entity caller) // BOTH
1009 case CMD_REQUEST_COMMAND:
1011 if (vote_called) print_to(caller, strcat("^7Vote for ", vote_called_display, "^7 called by ^7", OriginalCallerName(), "^7."));
1012 else print_to(caller, "^1No vote called.");
1018 case CMD_REQUEST_USAGE:
1020 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote status"));
1021 print_to(caller, " No arguments required.");
1027 void VoteCommand_stop(float request, entity caller) // BOTH
1031 case CMD_REQUEST_COMMAND:
1033 if (!vote_called) print_to(caller, "^1No vote called.");
1034 else if ((caller == vote_caller) || !caller || caller.vote_master) VoteStop(caller);
1035 else print_to(caller, "^1You are not allowed to stop that vote.");
1040 case CMD_REQUEST_USAGE:
1042 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote stop"));
1043 print_to(caller, " No arguments required.");
1049 void VoteCommand_yes(float request, entity caller) // CLIENT ONLY
1053 case CMD_REQUEST_COMMAND:
1055 if (!vote_called) { print_to(caller, "^1No vote called."); }
1056 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change)
1058 print_to(caller, "^1You have already voted.");
1060 else // everything went okay, continue changing vote
1062 print_to(caller, "^1You accepted the vote.");
1063 caller.vote_selection = VOTE_SELECT_ACCEPT;
1064 msg_entity = caller;
1065 if (!autocvar_sv_vote_singlecount)
1073 case CMD_REQUEST_USAGE:
1075 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote yes"));
1076 print_to(caller, " No arguments required.");
1082 /* use this when creating a new command, making sure to place it in alphabetical order... also,
1083 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
1084 void VoteCommand_(float request)
1088 case CMD_REQUEST_COMMAND:
1095 case CMD_REQUEST_USAGE:
1097 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote ");
1098 print_to(caller, " No arguments required.");
1106 // ================================
1107 // Macro system for vote commands
1108 // ================================
1110 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
1111 #define VOTE_COMMANDS(request, caller, arguments, command) \
1112 VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current vote", VC_ASGNMNT_CLIENTONLY) \
1113 VOTE_COMMAND("call", VoteCommand_call(request, caller, arguments, command), "Create a new vote for players to decide on", VC_ASGNMNT_BOTH) \
1114 VOTE_COMMAND("help", VoteCommand_macro_help(caller, arguments), "Shows this information", VC_ASGNMNT_BOTH) \
1115 VOTE_COMMAND("master", VoteCommand_master(request, caller, arguments, command), "Full control over all voting and vote commands", VC_ASGNMNT_CLIENTONLY) \
1116 VOTE_COMMAND("no", VoteCommand_no(request, caller), "Select no in current vote", VC_ASGNMNT_CLIENTONLY) \
1117 VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current vote", VC_ASGNMNT_BOTH) \
1118 VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \
1119 VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \
1122 void VoteCommand_macro_help(entity caller, float argc)
1124 string command_origin = GetCommandPrefix(caller);
1126 if (argc == 2 || argv(2) == "help") // help display listing all commands
1128 print_to(caller, "\nVoting commands:\n");
1129 #define VOTE_COMMAND(name, function, description, assignment) \
1130 { if (Votecommand_check_assignment(caller, assignment)) { print_to(caller, strcat(" ^2", name, "^7: ", description)); } }
1132 VOTE_COMMANDS(0, caller, 0, "");
1135 print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are listed above.\n"));
1136 print_to(caller, strcat("For help about a specific command, type ", command_origin, " vote help COMMAND"));
1137 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"));
1139 else // usage for individual command
1141 #define VOTE_COMMAND(name, function, description, assignment) \
1142 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(2))) { function; return; } } }
1144 VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
1147 string cvarname = strcat("sv_vote_command_help_", argv(2));
1148 if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
1149 wordwrap_sprint(caller, cvar_string(cvarname), 1000);
1151 print_to(caller, "No documentation exists for this vote");
1155 float VoteCommand_macro_command(entity caller, float argc, string vote_command)
1157 #define VOTE_COMMAND(name, function, description, assignment) \
1158 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } }
1160 VOTE_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, vote_command);
1167 // ======================================
1168 // Main function handling vote commands
1169 // ======================================
1171 void VoteCommand(float request, entity caller, float argc, string vote_command)
1173 // Guide for working with argc arguments by example:
1174 // argc: 1 - 2 - 3 - 4
1175 // argv: 0 - 1 - 2 - 3
1176 // cmd vote - master - login - password
1180 case CMD_REQUEST_COMMAND:
1182 if (VoteCommand_macro_command(caller, argc, vote_command)) return;
1186 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"));
1187 case CMD_REQUEST_USAGE:
1189 VoteCommand_macro_help(caller, argc);