+// =============================================
+// Server side voting code, reworked by Samual
+// Last updated: December 4th, 2011
+// =============================================
+
+#define VC_REQUEST_COMMAND 1
+#define VC_REQUEST_USAGE 2
+
+#define VC_ASGNMNT_BOTH 1
+#define VC_ASGNMNT_CLIENTONLY 2
+#define VC_ASGNMNT_SERVERONLY 3
+
+
+// ============================
+// Misc. Supporting Functions
+// ============================
+
+float Votecommand_check_assignment(entity caller, float assignment)
+{
+ float from_server = (!caller);
+
+ if((assignment == VC_ASGNMNT_BOTH)
+ || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY)
+ || (from_server && assignment == VC_ASGNMNT_SERVERONLY)))
+ {
+ print("check_assignment returned true\n");
+ return TRUE;
+ }
+
+ print("check_assignment returned false\n");
+ return FALSE;
+}
+
+string VoteCommand_getprefix(entity caller)
+{
+ if(caller)
+ return "cmd";
+ else
+ return "sv_cmd";
+}
+
+float Nagger_SendEntity(entity to, float sendflags)
+{
+ float nags, i, f, b;
+ entity e;
+ WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
+
+ // bits:
+ // 1 = ready
+ // 2 = player needs to ready up
+ // 4 = vote
+ // 8 = player needs to vote
+ // 16 = warmup
+ // sendflags:
+ // 64 = vote counts
+ // 128 = vote string
+
+ nags = 0;
+ if(readycount)
+ {
+ nags |= 1;
+ if(to.ready == 0)
+ nags |= 2;
+ }
+ if(votecalled)
+ {
+ nags |= 4;
+ if(to.vote_vote == 0)
+ nags |= 8;
+ }
+ if(inWarmupStage)
+ nags |= 16;
+
+ if(sendflags & 64)
+ nags |= 64;
+
+ if(sendflags & 128)
+ nags |= 128;
+
+ if(!(nags & 4)) // no vote called? send no string
+ nags &~= (64 | 128);
+
+ WriteByte(MSG_ENTITY, nags);
+
+ if(nags & 64)
+ {
+ WriteByte(MSG_ENTITY, vote_yescount);
+ WriteByte(MSG_ENTITY, vote_nocount);
+ WriteByte(MSG_ENTITY, vote_needed_absolute);
+ WriteChar(MSG_ENTITY, to.vote_vote);
+ }
+
+ if(nags & 128)
+ WriteString(MSG_ENTITY, votecalledvote_display);
+
+ if(nags & 1)
+ {
+ for(i = 1; i <= maxclients; i += 8)
+ {
+ for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
+ if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
+ f |= b;
+ WriteByte(MSG_ENTITY, f);
+ }
+ }
+
+ return TRUE;
+}
+
+void Nagger_Init()
+{
+ Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
+}
+
+void Nagger_VoteChanged()
+{
+ if(nagger)
+ nagger.SendFlags |= 128;
+}
+
+void Nagger_VoteCountChanged()
+{
+ if(nagger)
+ nagger.SendFlags |= 64;
+}
+
+void Nagger_ReadyCounted()
+{
+ if(nagger)
+ nagger.SendFlags |= 1;
+}
+
+void ReadyRestartForce()
+{
+ local entity e;
+
+ bprint("^1Server is restarting...\n");
+
+ VoteReset();
+
+ // clear overtime
+ if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
+ //we have to decrease timelimit to its original value again!!
+ float newTL;
+ newTL = autocvar_timelimit;
+ newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
+ cvar_set("timelimit", ftos(newTL));
+ }
+
+ checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
+
+
+ readyrestart_happened = 1;
+ game_starttime = time;
+ if(!g_ca && !g_arena)
+ game_starttime += RESTART_COUNTDOWN;
+ restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
+
+ inWarmupStage = 0; //once the game is restarted the game is in match stage
+
+ //reset the .ready status of all players (also spectators)
+ FOR_EACH_CLIENTSLOT(e)
+ e.ready = 0;
+ readycount = 0;
+ Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
+
+ if(autocvar_teamplay_lockonrestart && teamplay) {
+ lockteams = 1;
+ bprint("^1The teams are now locked.\n");
+ }
+
+ //initiate the restart-countdown-announcer entity
+ if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
+ {
+ restartTimer = spawn();
+ restartTimer.think = restartTimer_Think;
+ restartTimer.nextthink = game_starttime;
+ }
+
+ //after a restart every players number of allowed timeouts gets reset, too
+ if(autocvar_sv_timeout)
+ {
+ FOR_EACH_REALPLAYER(e)
+ e.allowedTimeouts = autocvar_sv_timeout_number;
+ }
+
+ //reset map immediately if this cvar is not set
+ if (!autocvar_sv_ready_restart_after_countdown)
+ reset_map(TRUE);
+
+ if(autocvar_sv_eventlog)
+ GameLogEcho(":restart");
+}
+
+void ReadyRestart()
+{
+ // no arena, assault support yet...
+ if(g_arena | g_assault | gameover | intermission_running | race_completing)
+ localcmd("restart\n");
+ else
+ localcmd("\nsv_hook_gamerestart\n");
+
+ ReadyRestartForce();
+
+ // reset ALL scores, but only do that at the beginning
+ //of the countdown if sv_ready_restart_after_countdown is off!
+ //Otherwise scores could be manipulated during the countdown!
+ if (!autocvar_sv_ready_restart_after_countdown)
+ Score_ClearAll();
+}
+
+/**
+ * Counts how many players are ready. If not enough players are ready, the function
+ * does nothing. If all players are ready, the timelimit will be extended and the
+ * restart_countdown variable is set to allow other functions like PlayerPostThink
+ * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
+ * is not set the map will be resetted.
+ *
+ * Function is called after the server receives a 'ready' sign from a player.
+ */
+void ReadyCount()
+{
+ local entity e;
+ local float r, p;
+
+ r = p = 0;
+
+ FOR_EACH_REALPLAYER(e)
+ {
+ p += 1;
+ if(e.ready)
+ r += 1;
+ }
+
+ readycount = r;
+
+ Nagger_ReadyCounted();
+
+ if(r) // at least one is ready
+ if(r == p) // and, everyone is ready
+ ReadyRestart();
+}
+
+/**
+ * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
+ * is set)
+ */
+void restartTimer_Think() {
+ restart_mapalreadyrestarted = 1;
+ reset_map(TRUE);
+ Score_ClearAll();
+ remove(self);
+ return;
+}
+
float VoteCheckNasty(string cmd)
{
if(strstrofs(cmd, ";", 0) >= 0)
return TRUE;
}
-float GameCommand_Vote(string s, entity e) {
- local float playercount;
- float argc;
- argc = tokenize_console(s);
- if(argv(0) == "help") {
- print_to(e, " vote COMMANDS ARGUMENTS. See 'vhelp' for more info.");
- return TRUE;
- } else if(argv(0) == "vote") {
- if(argv(1) == "") {
- print_to(e, "^1You have to supply a vote command. See 'vhelp' for more info.");
- } else if(argv(1) == "help") {
- VoteHelp(e);
- } else if(argv(1) == "status") {
- if(votecalled) {
- print_to(e, strcat("^7Vote for ", votecalledvote_display, "^7 called by ^7", VoteNetname(votecaller), "^7."));
- } else {
- print_to(e, "^1No vote called.");
- }
- } else if(argv(1) == "call") {
- if(!e || autocvar_sv_vote_call) {
- if(autocvar_sv_vote_nospectators && e && e.classname != "player") {
- print_to(e, "^1Error: Only players can call a vote."); // TODO invent a cvar name for allowing votes by spectators during warmup anyway
- }
- else if(timeoutStatus) { //don't allow a vote call during a timeout
- print_to(e, "^1Error: You can not call a vote while a timeout is active.");
- }
- else if(votecalled) {
- print_to(e, "^1There is already a vote called.");
- } else {
- local string vote;
- vote = VoteParse(s, argc);
- if(vote == "") {
- print_to(e, "^1Your vote is empty. See 'vhelp' for more info.");
- } else if(e
- && time < e.vote_next) {
- print_to(e, strcat("^1You have to wait ^2", ftos(ceil(e.vote_next - time)), "^1 seconds before you can again call a vote."));
- } else if(VoteCheckNasty(vote)) {
- print_to(e, "Syntax error in command. See 'vhelp' for more info.");
- } else if(RemapVote(vote, "vcall", e)) {
- votecalledvote = strzone(RemapVote_vote);
- votecalledvote_display = strzone(RemapVote_display);
- votecalled = TRUE;
- votecalledmaster = FALSE;
- votefinished = time + autocvar_sv_vote_timeout;
- votecaller = e; // remember who called the vote
- if(e) {
- e.vote_vote = 1; // of course you vote yes
- e.vote_next = time + autocvar_sv_vote_wait;
- }
- bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote for ", votecalledvote_display, "\n");
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
- Nagger_VoteChanged();
- VoteCount(); // needed if you are the only one
- msg_entity = e;
-
- local entity player;
- FOR_EACH_REALCLIENT(player)
- {
- ++playercount;
- }
- if(playercount > 1) // don't announce a "vote now" sound if player is alone
- Announce("votecall");
- } else {
- print_to(e, "^1This vote is not ok. See 'vhelp' for more info.");
- }
- }
- } else {
- print_to(e, "^1Vote calling is NOT allowed.");
- }
- } else if(argv(1) == "stop") {
- if(!votecalled) {
- print_to(e, "^1No vote called.");
- } else if(e == votecaller) { // the votecaller can stop a vote
- VoteStop(e);
- } else if(!e) { // server admin / console can too
- VoteStop(e);
- } else if(e.vote_master) { // masters can too
- VoteStop(e);
- } else {
- print_to(e, "^1You are not allowed to stop that Vote.");
- }
- } else if(argv(1) == "master") {
- if(autocvar_sv_vote_master) {
- if(votecalled) {
- print_to(e, "^1There is already a vote called.");
- } else {
- votecalled = TRUE;
- votecalledmaster = TRUE;
- votecalledvote = strzone("XXX");
- votecalledvote_display = strzone("^3master");
- votefinished = time + autocvar_sv_vote_timeout;
- votecaller = e; // remember who called the vote
- if(e) {
- e.vote_vote = 1; // of course you vote yes
- e.vote_next = time + autocvar_sv_vote_wait;
- }
- bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote to become ^3master^2.\n");
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
- Nagger_VoteChanged();
- VoteCount(); // needed if you are the only one
- }
- } else {
- print_to(e, "^1Vote to become master is NOT allowed.");
- }
- } else if(argv(1) == "do") {
- if(!e || e.vote_master) {
- local string dovote;
- dovote = VoteParse(s, argc);
- if(dovote == "") {
- print_to(e, "^1Your command was empty. See 'vhelp' for more info.");
- } else if(VoteCheckNasty(dovote)) {
- print_to(e, "Syntax error in command. See 'vhelp' for more info.");
- } else if(RemapVote(dovote, "vdo", e)) { // strcat seems to be necessary
- bprint("\{1}^2* ^3", VoteNetname(e), "^2 used their ^3master^2 status to do \"^2", RemapVote_display, "^2\".\n");
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":vote:vdo:", ftos(e.playerid), ":", RemapVote_display));
- localcmd(strcat(RemapVote_vote, "\n"));
- } else {
- print_to(e, "^1This command is not ok. See 'vhelp' for more info.");
- }
- } else {
- print_to(e, "^1You are NOT a master. You might need to login or vote to become master first. See 'vhelp' for more info.");
- }
- } else if(argv(1) == "login") {
- local string masterpwd;
- masterpwd = autocvar_sv_vote_master_password;
- if(masterpwd != "") {
- local float granted;
- granted = (masterpwd == argv(2));
- if (e)
- e.vote_master = granted;
- if(granted) {
- print("Accepted master login from ", VoteNetname(e), "\n");
- bprint("\{1}^2* ^3", VoteNetname(e), "^2 logged in as ^3master^2\n");
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":vote:vlogin:", ftos(e.playerid)));
- }
- else
- print("REJECTED master login from ", VoteNetname(e), "\n");
- }
- else
- print_to(e, "^1Login to become master is NOT allowed.");
- } else if(argv(1) == "yes") {
- if(!votecalled) {
- print_to(e, "^1No vote called.");
- } else if (!e) {
- print_to(e, "^1You can't vote from the server console.");
- } else if(e.vote_vote == 0
- || autocvar_sv_vote_change) {
- msg_entity = e;
- print_to(e, "^1You accepted the vote.");
- e.vote_vote = 1;
- if(!autocvar_sv_vote_singlecount) {
- VoteCount();
- }
- } else {
- print_to(e, "^1You have already voted.");
- }
- } else if(argv(1) == "no") {
- if(!votecalled) {
- print_to(e, "^1No vote called.");
- } else if (!e) {
- print_to(e, "^1You can't vote from the server console.");
- } else if(e.vote_vote == 0
- || autocvar_sv_vote_change) {
- msg_entity = e;
- print_to(e, "^1You rejected the vote.");
- e.vote_vote = -1;
- if(!autocvar_sv_vote_singlecount) {
- VoteCount();
- }
- } else {
- print_to(e, "^1You have already voted.");
- }
- } else if(argv(1) == "abstain" || argv(1) == "dontcare") {
- if(!votecalled) {
- print_to(e, "^1No vote called.");
- } else if (!e) {
- print_to(e, "^1You can't vote from the server console.");
- } else if(e.vote_vote == 0
- || autocvar_sv_vote_change) {
- msg_entity = e;
- print_to(e, "^1You abstained from your vote.");
- e.vote_vote = -2;
- if(!autocvar_sv_vote_singlecount) {
- VoteCount();
- }
- } else {
- print_to(e, "^1You have already voted.");
- }
- } else {
- // ignore this?
- print_to(e, "^1Unknown vote command.");
+
+// =======================
+// Command Sub-Functions
+// =======================
+
+void VoteCommand_abstain(float request, entity caller)
+{
+ switch(request)
+ {
+ case VC_REQUEST_COMMAND:
+ {
+
+ return;
+ }
+
+ default:
+ case VC_REQUEST_USAGE:
+ {
+ print("\nUsage:^3 vote \n");
+ print(" No arguments required.\n");
+ return;
}
- return TRUE;
}
+}
+
+void VoteCommand_stop(float request, entity caller)
+{
+ switch(request)
+ {
+ case VC_REQUEST_COMMAND:
+ {
+
+ return;
+ }
+
+ default:
+ case VC_REQUEST_USAGE:
+ {
+ print("\nUsage:^3 vote \n");
+ print(" No arguments required.\n");
+ return;
+ }
+ }
+}
+
+/* use this when creating a new command, making sure to place it in alphabetical order.
+void VoteCommand_(float request)
+{
+ switch(request)
+ {
+ case VC_REQUEST_COMMAND:
+ {
+
+ return;
+ }
+
+ default:
+ case VC_REQUEST_USAGE:
+ {
+ print("\nUsage:^3 vote \n");
+ print(" No arguments required.\n");
+ return;
+ }
+ }
+}
+*/
+
+
+// ==================================
+// Macro system for server commands
+// ==================================
+
+// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
+#define VOTE_COMMANDS(request,caller,arguments) \
+ VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current poll", VC_ASGNMNT_CLIENTONLY) \
+ VOTE_COMMAND("force", VoteCommand_force(request, caller), "Force a result of a vote", VC_ASGNMNT_SERVERONLY) \
+ VOTE_COMMAND("help", VoteCommand_macro_help(caller), "Shows this information", VC_ASGNMNT_BOTH) \
+ VOTE_COMMAND("no", VoteCommand_no(request, caller), "Vote no in current poll", VC_ASGNMNT_CLIENTONLY) \
+ VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current poll", VC_ASGNMNT_BOTH) \
+ VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \
+ VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Vote yes in current poll", VC_ASGNMNT_CLIENTONLY) \
+ //VOTE_COMMAND("", VoteCommand_(request, caller, arguments), "", ) \
+ /* nothing */
+
+void VoteCommand_macro_help(entity caller)
+{
+ print("\nUsage:^3 ", VoteCommand_getprefix(caller), " vote COMMAND...^7, where possible commands are:\n");
+
+ #define VOTE_COMMAND(name,function,description,assignment) \
+ { if(Votecommand_check_assignment(caller, assignment)) { print(" ^2", name, "^7: ", description, "\n"); } }
+
+ VOTE_COMMANDS(0, caller, 0)
+ #undef VOTE_COMMAND
+
+ return;
+}
+
+float VoteCommand_macro_command(entity caller, float argc)
+{
+ #define VOTE_COMMAND(name,function,description,assignment) \
+ { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(0))) { function; return TRUE; } } }
+
+ VOTE_COMMANDS(VC_REQUEST_COMMAND, caller, argc)
+ #undef VOTE_COMMAND
+
+ return FALSE;
+}
+
+float VoteCommand_macro_usage(entity caller, float argc)
+{
+ #define VOTE_COMMAND(name,function,description,assignment) \
+ { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(1))) { function; return TRUE; } } }
+
+ VOTE_COMMANDS(VC_REQUEST_USAGE, caller, argc)
+ #undef VOTE_COMMAND
+
return FALSE;
}
+
+// ======================================
+// Main function handling vote commands
+// ======================================
+
+void VoteCommand(entity caller, float argc)
+{
+ if(strtolower(argv(0)) == "help")
+ {
+ if(argc == 1)
+ {
+ VoteCommand_macro_help(caller);
+ return;
+ }
+ else if(VoteCommand_macro_usage(caller, argc))
+ {
+ return;
+ }
+ }
+ else if(VoteCommand_macro_command(caller, argc))
+ {
+ return;
+ }
+
+ // nothing above caught the command, must be invalid
+ //print("Unknown server command", ((command != "") ? strcat(" \"", command, "\"") : ""), ". For a list of supported commands, try sv_cmd help.\n");
+}
+
void VoteHelp(entity e) {
- local string vmasterdis;
+ string vmasterdis;
if(!autocvar_sv_vote_master) {
vmasterdis = " ^1(disabled)";
}
- local string vlogindis;
+ string vlogindis;
if("" == autocvar_sv_vote_master_password) {
vlogindis = " ^1(disabled)";
}
- local string vcalldis;
+ string vcalldis;
if(!autocvar_sv_vote_call) {
vcalldis = " ^1(disabled)";
}
}
void VoteReset() {
- local entity player;
+ entity player;
FOR_EACH_CLIENT(player)
{
}
void VoteCount() {
- local float playercount;
+ float playercount;
playercount = 0;
vote_yescount = 0;
vote_nocount = 0;
vote_abstaincount = 0;
- local entity player;
+ entity player;
//same for real players
- local float realplayercount;
- local float realplayeryescount;
- local float realplayernocount;
- local float realplayerabstaincount;
+ float realplayercount;
+ float realplayeryescount;
+ float realplayernocount;
+ float realplayerabstaincount;
realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
Nagger_VoteCountChanged();