]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Finished more refactoring of the rest of the game logic of voting -- next up
authorSamual <samual@xonotic.org>
Mon, 5 Dec 2011 18:36:26 +0000 (13:36 -0500)
committerSamual <samual@xonotic.org>
Mon, 5 Dec 2011 18:36:26 +0000 (13:36 -0500)
comes re-writing the votecount function

qcsrc/server/assault.qc
qcsrc/server/defs.qh
qcsrc/server/vote.qc
qcsrc/server/vote.qh

index 425bbf77933892791bf46b5c96219b50daec758e..2562dca3df803fa93725204eb01f7059ef1cd189 100644 (file)
@@ -372,5 +372,5 @@ void assault_new_round()
 
        // reset the level with a countdown
        cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
-       ReadyRestartForce(); // sets game_starttime
+       ReadyRestart_force(); // sets game_starttime
 }
index 430bf369a3ecb9edf19c6f0c6cc33b2472719f01..6cae6732b57032a022bf0ecaa806d513b3373e36 100644 (file)
@@ -265,11 +265,6 @@ float alreadychangedlevel;
 // footstep interval
 .float nextstep;
 
-.float ready;
-#define RESTART_COUNTDOWN 10
-float restart_mapalreadyrestarted; //bool, indicates whether reset_map() was already executed
-entity restartTimer;
-void restartTimer_Think();
 float blockSpectators; //if set, new or existing spectators or observers will be removed unless they become a player within g_maxplayers_spectator_blocktime seconds
 .float spectatortime; //point in time since the client is spectating or observing
 void checkSpectatorBlock();
index af78b6daf4a8a697bd8adacfe5c3f30c23503798..f5d43a5f33082851478c625b96366f45a265e1e9 100644 (file)
@@ -1,6 +1,6 @@
 // =============================================
 //  Server side voting code, reworked by Samual
-//  Last updated: December 4th, 2011
+//  Last updated: December 6th, 2011
 // =============================================
 
 #define VC_REQUEST_COMMAND 1
@@ -15,9 +15,6 @@
 #define VOTE_SELECT_NULL 0
 #define VOTE_SELECT_ACCEPT 1
 
-string GetKickVoteVictim_newcommand;
-string GetKickVoteVictim_reason;
-
 string vote_parsed_command;
 string vote_parsed_display;
 
@@ -122,228 +119,552 @@ void Nagger_ReadyCounted()
 //  Game logic for voting
 // =======================
 
+void VoteReset() 
+{
+       entity tmp_player;
+
+       FOR_EACH_CLIENT(tmp_player) { tmp_player.vote_selection = 0; }
+
+       if(votecalled)
+       {
+               strunzone(votecalledvote);
+               strunzone(votecalledvote_display);
+       }
+
+       votecalled = FALSE;
+       votecalledmaster = FALSE;
+       votefinished = 0;
+       votecalledvote = string_null;
+       votecalledvote_display = string_null;
+       
+       vote_parsed_command = string_null;
+       vote_parsed_display = string_null;
+
+       Nagger_VoteChanged();
+}
+
 void VoteStop(entity stopper) 
 {
        bprint("\{1}^2* ^3", VoteCommand_getname(stopper), "^2 stopped ^3", VoteCommand_getname(votecaller), "^2's vote\n");
        if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid))); }
        
        // Don't force them to wait for next vote, this way they can e.g. correct their vote.
-       if(votecaller)
-               if(stopper == votecaller) 
-                       votecaller.vote_next = time + autocvar_sv_vote_stop;
+       if((votecaller) && (stopper == votecaller)) { votecaller.vote_next = time + autocvar_sv_vote_stop; }
 
        VoteReset();
 }
 
-
-// ======================================
-//  Supporting functions for VoteCommand
-// ======================================
-
-float Votecommand_check_assignment(entity caller, float assignment)
+void VoteThink() 
 {
-       float from_server = (!caller);
-       
-       if((assignment == VC_ASGNMNT_BOTH) 
-               || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY) 
-               || (from_server && assignment == VC_ASGNMNT_SERVERONLY)))
+       if(votefinished > 0) // a vote was called
+       if(time > votefinished) // time is up
        {
-               return TRUE;
+               VoteCount();
        }
-
-       return FALSE;
-}
-
-string VoteCommand_getprefix(entity caller)
-{
-       if(caller)
-               return "cmd";
-       else
-               return "sv_cmd";
-}
-
-string VoteCommand_getname(entity caller)
-{
-       if(caller)
-               return caller.netname;
-       else
-               return ((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
+       
+       return;
 }
 
-string VoteCommand_extractcommand(string input, float startpos, float argc
+void VoteAccept(
 {
-       string output;
+       bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
        
-       if((argc - 1) < startpos)
-               output = "";
+       if(votecalledmaster && votecaller)
+               votecaller.vote_master = 1;
        else
-               output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos));
-               
-       print("VoteCommand_parse: '", output, "'. \n");
-       return output;
+               localcmd(strcat(votecalledvote, "\n"));
+       
+       if(votecaller) { votecaller.vote_next = 0; } // people like your votes, you don't need to wait to vote again // todo separate anti-spam even for succeeded votes
+
+       VoteReset();
+       Announce("voteaccept");
 }
 
-float VoteCommand_checknasty(string vote_command)
+void VoteReject() 
 {
-       if((strstrofs(vote_command, ";", 0) >= 0)
-               || (strstrofs(vote_command, "\n", 0) >= 0)
-               || (strstrofs(vote_command, "\r", 0) >= 0)
-               || (strstrofs(vote_command, "$", 0) >= 0))
-               return TRUE;
-               
-       return FALSE;
+       bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
+       VoteReset();
+       Announce("votefail");
 }
 
-float VoteCommand_checkinlist(string vote_command, string list)
+void VoteTimeout() 
 {
-       string l = strcat(" ", list, " ");
-       
-       if(strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0)
-               return TRUE;
-       
-       // if gotomap is allowed, chmap is too, and vice versa
-       if(vote_command == "gotomap")
-               if(strstrofs(l, " chmap ", 0) >= 0)
-                       return TRUE;
-                       
-       if(vote_command == "chmap")
-               if(strstrofs(l, " gotomap ", 0) >= 0)
-                       return TRUE;
-       
-       return FALSE;
+       bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
+       VoteReset();
+       Announce("votefail");
 }
 
-string ValidateMap(string m, entity e)
+void VoteSpam(float notvoters, float mincount, string result)
 {
-       m = MapInfo_FixName(m);
-       if(!m)
+       string s;
+       if(mincount >= 0)
        {
-               print_to(e, "This map is not available on this server.");
-               return string_null;
+               s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
+               s = strcat(s, ftos(vote_nocount), "^2 (^1");
+               s = strcat(s, ftos(mincount), "^2 needed), ^1");
+               s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
+               s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
        }
-       if(!autocvar_sv_vote_override_mostrecent)
-               if(Map_IsRecent(m))
-               {
-                       print_to(e, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
-                       return string_null;
-               }
-       if(!MapInfo_CheckMap(m))
+       else
        {
-               print_to(e, strcat("^1Invalid mapname, \"^3", m, "^1\" does not support the current game mode."));
-               return string_null;
+               s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
+               s = strcat(s, ftos(vote_nocount), "^2, ^1");
+               s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
+               s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
        }
-
-       return m;
-}
-
-float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
-{
-       string first_command;
-       entity victim;
        
-       first_command = argv(startpos);
-
-       if not(VoteCommand_checkinlist(vote_command, vote_list))
-               return FALSE;
-
-       if((argc - 1) < startpos) // These commands won't work without arguments
-       {
-               switch(first_command)
-               {
-                       case "map":
-                       case "chmap":
-                       case "gotomap":
-                       case "kick":
-                       case "kickban":
-                               return FALSE;
-                               
-                       default: { break; }
-               }
-       }
+       bprint(s);
        
-       switch(first_command) // now go through and parse the proper commands to adjust as needed.
+       if(autocvar_sv_eventlog)
        {
-               case "kick":
-               case "kickban": // catch all kick/kickban commands
-               {
-                       victim = edict_num(GetFilteredNumber(substring(vote_command, argv_start_index(startpos + 1), argv_end_index(-1) - argv_start_index(startpos + 1))));
-                       if not(victim) { return FALSE; }
-                       // TODO: figure out how kick/kickban/ban commands work and re-write this to fit around them
-                       vote_parsed_command = vote_command;
-                       vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", "todo");
-                       
-                       break;
-               }
-               
-               case "map":
-               case "chmap":
-               case "gotomap": // re-direct all map selection commands to gotomap
-               {
-                       vote_command = ValidateMap(substring(vote_command, argv_start_index(startpos + 1), argv_end_index(-1) - argv_start_index(startpos + 1)), caller);
-                       if not(vote_command) { return FALSE; }
-                       vote_parsed_command = strcat("gotomap ", vote_command);
-                       vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
-                       
-                       break;
-               }
-               
-               default: 
-               { 
-                       vote_parsed_command = vote_command;
-                       vote_parsed_display = strzone(strcat("^1", vote_command));
-                       break; 
-               }
+               s = strcat(":vote:v", result, ":", ftos(vote_yescount));
+               s = strcat(s, ":", ftos(vote_nocount));
+               s = strcat(s, ":", ftos(vote_abstaincount));
+               s = strcat(s, ":", ftos(notvoters));
+               s = strcat(s, ":", ftos(mincount));
+               GameLogEcho(s);
        }
-
-       return TRUE;
 }
 
+void VoteCount() 
+{
+       float playercount;
+       playercount = 0;
+       vote_yescount = 0;
+       vote_nocount = 0;
+       vote_abstaincount = 0;
+       entity player;
+       //same for real players
+       float realplayercount;
+       float realplayeryescount;
+       float realplayernocount;
+       float realplayerabstaincount;
+       realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
 
-// =======================
-//  Command Sub-Functions
-// =======================
+       Nagger_VoteCountChanged();
 
-void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY
-{
-       switch(request)
+       FOR_EACH_REALCLIENT(player)
        {
-               case VC_REQUEST_COMMAND:
-               {
-                       if not(votecalled) { print_to(caller, "^1No vote called."); }
-                       else if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { print_to(caller, "^1You have already voted."); }
-                       
-                       else // everything went okay, continue changing vote
-                       {
-                               print_to(caller, "^1You abstained from your vote.");
-                               caller.vote_selection = VOTE_SELECT_ABSTAIN;
-                               msg_entity = caller;
-                               if(!autocvar_sv_vote_singlecount) { VoteCount(); }
-                       }
-                       
-                       return;
+               if(player.vote_selection == -1) {
+                       ++vote_nocount;
+               } else if(player.vote_selection == 1) {
+                       ++vote_yescount;
+               } else if(player.vote_selection == -2) {
+                       ++vote_abstaincount;
                }
-                       
-               default:
-               case VC_REQUEST_USAGE:
-               {
-                       print("\nUsage:^3 vote abstain\n");
-                       print("  No arguments required.\n");
-                       return;
+               ++playercount;
+               //do the same for real players
+               if(player.classname == "player") {
+                       if(player.vote_selection == -1) {
+                               ++realplayernocount;
+                       } else if(player.vote_selection == 1) {
+                               ++realplayeryescount;
+                       } else if(player.vote_selection == -2) {
+                               ++realplayerabstaincount;
+                       }
+                       ++realplayercount;
                }
        }
-}
 
-void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH
-{
-       switch(request)
-       {
-               case VC_REQUEST_COMMAND:
-               {
-                       float spectators_allowed = ((autocvar_sv_vote_nospectators != 2) || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage));
-                       float tmp_playercount;
-                       entity tmp_player;
-                       
-                       vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
+       //in tournament mode, if we have at least one player then don't make the vote dependent on spectators (so specs don't have to press F1)
+       if(autocvar_sv_vote_nospectators)
+       if(realplayercount > 0) {
+               vote_yescount = realplayeryescount;
+               vote_nocount = realplayernocount;
+               vote_abstaincount = realplayerabstaincount;
+               playercount = realplayercount;
+       }
+
+       float votefactor, simplevotefactor;
+       votefactor = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
+       simplevotefactor = autocvar_sv_vote_simple_majority_factor;
+
+       // FIXME this number is a guess
+       vote_needed_absolute = floor((playercount - vote_abstaincount) * votefactor) + 1;
+       if(simplevotefactor)
+       {
+               simplevotefactor = bound(votefactor, simplevotefactor, 0.999);
+               vote_needed_simple = floor((vote_yescount + vote_nocount) * simplevotefactor) + 1;
+       }
+       else
+               vote_needed_simple = 0;
+
+       if(votecalledmaster
+          && playercount == 1) {
+               // if only one player is on the server becoming vote
+               // master is not allowed.  This could be used for
+               // trolling or worse. 'self' is the user who has
+               // called the vote because this function is called
+               // by SV_ParseClientCommand. Maybe all voting should
+               // be disabled for a single player?
+               print_to(votecaller, "^1You are the only player on this server so you can not become vote master.");
+               if(votecaller) {
+                       votecaller.vote_next = 0;
+               }
+               VoteReset();
+       } else {
+               if(vote_yescount >= vote_needed_absolute)
+               {
+                       VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "yes");
+                       VoteAccept();
+               }
+               else if(vote_nocount > playercount - vote_abstaincount - vote_needed_absolute) // that means, vote_yescount cannot reach vote_needed_absolute any more
+               {
+                       VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "no");
+                       VoteReject();
+               }
+               else if(time > votefinished)
+               {
+                       if(simplevotefactor)
+                       {
+                               string result;
+                               if(vote_yescount >= vote_needed_simple)
+                                       result = "yes";
+                               else if(vote_yescount + vote_nocount > 0)
+                                       result = "no";
+                               else
+                                       result = "timeout";
+                               VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, min(vote_needed_absolute, vote_needed_simple), result);
+                               if(result == "yes")
+                                       VoteAccept();
+                               else if(result == "no")
+                                       VoteReject();
+                               else
+                                       VoteTimeout();
+                       }
+                       else
+                       {
+                               VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, vote_needed_absolute, "timeout");
+                               VoteTimeout();
+                       }
+               }
+       }
+}
+
+
+// =======================
+//  Game logic for warmup
+// =======================
+
+// Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set)
+void ReadyRestart_think() 
+{
+       restart_mapalreadyrestarted = 1;
+       reset_map(TRUE);
+       Score_ClearAll();
+       remove(self);
+       
+       return;
+}
+
+// Forces a restart of the game without actually reloading the map // this is a mess...
+void ReadyRestart_force()
+{
+       entity tmp_player, restart_timer;
+
+       bprint("^1Server is restarting...\n");
+
+       VoteReset();
+
+       // clear overtime, we have to decrease timelimit to its original value again.
+       if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) { cvar_set("timelimit", ftos(autocvar_timelimit - (checkrules_overtimesadded * autocvar_timelimit_overtime))); }
+
+       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
+
+       // disable the warmup global for the server
+       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(tmp_player) { tmp_player.ready = 0; }
+       readycount = 0;
+       Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
+
+       // lock teams with lockonrestart
+       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)
+       {
+               restart_timer = spawn();
+               restart_timer.think = ReadyRestart_think;
+               restart_timer.nextthink = game_starttime;
+       }
+
+       // after a restart every players number of allowed timeouts gets reset, too
+       if(autocvar_sv_timeout) { FOR_EACH_REALPLAYER(tmp_player) { tmp_player.allowedTimeouts = autocvar_sv_timeout_number; } }
+
+       //reset map immediately if this cvar is not set
+       if not(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");
+
+       // 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 not(autocvar_sv_ready_restart_after_countdown) { Score_ClearAll(); }
+
+       ReadyRestart_force();
+       
+       return;
+}
+
+// Count the players who are ready and determine whether or not to restart the match
+void ReadyCount()
+{
+       entity tmp_player;
+       float t_ready, t_players;
+
+       FOR_EACH_REALPLAYER(tmp_player)
+       {
+               ++t_players;
+               if(tmp_player.ready) { ++t_ready; }
+       }
+
+       readycount = t_ready;
+
+       Nagger_ReadyCounted();
+
+       // TODO: check percentage of ready players
+       if(t_ready) // at least one is ready
+       if(t_ready == t_players) // and, everyone is ready
+               ReadyRestart();
+               
+       return;
+}
+
+
+// ======================================
+//  Supporting functions for VoteCommand
+// ======================================
+
+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)))
+       {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+string VoteCommand_getprefix(entity caller)
+{
+       if(caller)
+               return "cmd";
+       else
+               return "sv_cmd";
+}
+
+string VoteCommand_getname(entity caller)
+{
+       if(caller)
+               return caller.netname;
+       else
+               return ((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
+}
+
+string VoteCommand_extractcommand(string input, float startpos, float argc) 
+{
+       string output;
+       
+       if((argc - 1) < startpos)
+               output = "";
+       else
+               output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos));
+               
+       print("VoteCommand_parse: '", output, "'. \n");
+       return output;
+}
+
+float VoteCommand_checknasty(string vote_command)
+{
+       if((strstrofs(vote_command, ";", 0) >= 0)
+               || (strstrofs(vote_command, "\n", 0) >= 0)
+               || (strstrofs(vote_command, "\r", 0) >= 0)
+               || (strstrofs(vote_command, "$", 0) >= 0))
+               return TRUE;
+               
+       return FALSE;
+}
+
+float VoteCommand_checkinlist(string vote_command, string list)
+{
+       string l = strcat(" ", list, " ");
+       
+       if(strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0)
+               return TRUE;
+       
+       // if gotomap is allowed, chmap is too, and vice versa
+       if(vote_command == "gotomap")
+               if(strstrofs(l, " chmap ", 0) >= 0)
+                       return TRUE;
+                       
+       if(vote_command == "chmap")
+               if(strstrofs(l, " gotomap ", 0) >= 0)
+                       return TRUE;
+       
+       return FALSE;
+}
+
+string ValidateMap(string validated_map, entity caller)
+{
+       validated_map = MapInfo_FixName(validated_map);
+       
+       if(!validated_map)
+       {
+               print_to(caller, "This map is not available on this server.");
+               return string_null;
+       }
+       
+       if(!autocvar_sv_vote_override_mostrecent && caller)
+       {
+               if(Map_IsRecent(validated_map))
+               {
+                       print_to(caller, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
+                       return string_null;
+               }
+       }
+       
+       if(!MapInfo_CheckMap(validated_map))
+       {
+               print_to(caller, strcat("^1Invalid mapname, \"^3", validated_map, "^1\" does not support the current game mode."));
+               return string_null;
+       }
+
+       return validated_map;
+}
+
+float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
+{
+       string first_command;
+       entity victim;
+       
+       first_command = argv(startpos);
+
+       if not(VoteCommand_checkinlist(vote_command, vote_list))
+               return FALSE;
+
+       if((argc - 1) < startpos) // These commands won't work without arguments
+       {
+               switch(first_command)
+               {
+                       case "map":
+                       case "chmap":
+                       case "gotomap":
+                       case "kick":
+                       case "kickban":
+                               return FALSE;
+                               
+                       default: { break; }
+               }
+       }
+       
+       switch(first_command) // now go through and parse the proper commands to adjust as needed.
+       {
+               case "kick":
+               case "kickban": // catch all kick/kickban commands
+               {
+                       victim = edict_num(GetFilteredNumber(substring(vote_command, argv_start_index(startpos + 1), argv_end_index(-1) - argv_start_index(startpos + 1))));
+                       if not(victim) { return FALSE; }
+                       // TODO: figure out how kick/kickban/ban commands work and re-write this to fit around them
+                       vote_parsed_command = vote_command;
+                       vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", "todo");
+                       
+                       break;
+               }
+               
+               case "map":
+               case "chmap":
+               case "gotomap": // re-direct all map selection commands to gotomap
+               {
+                       vote_command = ValidateMap(substring(vote_command, argv_start_index(startpos + 1), argv_end_index(-1) - argv_start_index(startpos + 1)), caller);
+                       if not(vote_command) { return FALSE; }
+                       vote_parsed_command = strcat("gotomap ", vote_command);
+                       vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
+                       
+                       break;
+               }
+               
+               default: 
+               { 
+                       vote_parsed_command = vote_command;
+                       vote_parsed_display = strzone(strcat("^1", vote_command));
+                       
+                       break; 
+               }
+       }
+
+       return TRUE;
+}
+
+
+// =======================
+//  Command Sub-Functions
+// =======================
+
+void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY
+{
+       switch(request)
+       {
+               case VC_REQUEST_COMMAND:
+               {
+                       if not(votecalled) { print_to(caller, "^1No vote called."); }
+                       else if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { print_to(caller, "^1You have already voted."); }
+                       
+                       else // everything went okay, continue changing vote
+                       {
+                               print_to(caller, "^1You abstained from your vote.");
+                               caller.vote_selection = VOTE_SELECT_ABSTAIN;
+                               msg_entity = caller;
+                               if(!autocvar_sv_vote_singlecount) { VoteCount(); }
+                       }
+                       
+                       return;
+               }
+                       
+               default:
+               case VC_REQUEST_USAGE:
+               {
+                       print("\nUsage:^3 vote abstain\n");
+                       print("  No arguments required.\n");
+                       return;
+               }
+       }
+}
+
+void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH
+{
+       switch(request)
+       {
+               case VC_REQUEST_COMMAND:
+               {
+                       float spectators_allowed = ((autocvar_sv_vote_nospectators != 2) || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage));
+                       float tmp_playercount;
+                       entity tmp_player;
+                       
+                       vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
                        
                        if not(autocvar_sv_vote_call || !caller) { print_to(caller, "^1Vote calling is not allowed."); }
                        else if(votecalled) { print_to(caller, "^1There is already a vote called."); }
@@ -658,165 +979,45 @@ float VoteCommand_macro_command(entity caller, float argc, string vote_command)
        #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_COMMAND, caller, argc, vote_command)
-       #undef VOTE_COMMAND
-       
-       return FALSE;
-}
-
-
-// ======================================
-//  Main function handling vote commands
-// ======================================
-
-void VoteCommand(float request, entity caller, float argc, string vote_command) 
-{
-       // Guide for working with argc arguments by example:
-       // argc:   1    - 2      - 3     - 4
-       // argv:   0    - 1      - 2     - 3 
-       // cmd     vote - master - login - password
-       
-       switch(request)
-       {
-               case VC_REQUEST_COMMAND:
-               {
-                       if(VoteCommand_macro_command(caller, argc, vote_command))
-                               return;
-               }
-                       
-               default:
-                       print_to(caller, strcat("Unknown vote command", ((argv(1) != "") ? strcat(" \"", argv(1), "\"") : ""), ". For a list of supported commands, try ", VoteCommand_getprefix(caller), " help.\n"));
-               case VC_REQUEST_USAGE:
-               {
-                       VoteCommand_macro_help(caller, argc);
-                       return;
-               }
-       }
-}
-
-// =======================
-//  Game logic for voting
-// =======================
-
-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");
+       VOTE_COMMANDS(VC_REQUEST_COMMAND, caller, argc, vote_command)
+       #undef VOTE_COMMAND
+       
+       return FALSE;
 }
 
-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();
-}
+// ======================================
+//  Main function handling vote commands
+// ======================================
 
-/**
- * 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()
+void VoteCommand(float request, entity caller, float argc, string vote_command) 
 {
-       entity tmp_player;
-       float t_ready, t_players;
-
-       FOR_EACH_REALPLAYER(tmp_player)
+       // Guide for working with argc arguments by example:
+       // argc:   1    - 2      - 3     - 4
+       // argv:   0    - 1      - 2     - 3 
+       // cmd     vote - master - login - password
+       
+       switch(request)
        {
-               ++t_players;
-               if(tmp_player.ready) { ++t_ready; }
+               case VC_REQUEST_COMMAND:
+               {
+                       if(VoteCommand_macro_command(caller, argc, vote_command))
+                               return;
+               }
+                       
+               default:
+                       print_to(caller, strcat("Unknown vote command", ((argv(1) != "") ? strcat(" \"", argv(1), "\"") : ""), ". For a list of supported commands, try ", VoteCommand_getprefix(caller), " help.\n"));
+               case VC_REQUEST_USAGE:
+               {
+                       VoteCommand_macro_help(caller, argc);
+                       return;
+               }
        }
-
-       readycount = t_ready;
-
-       Nagger_ReadyCounted();
-
-       // TODO: Add ability to 
-       if(t_ready) // at least one is ready
-       if(t_ready == t_players) // 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;
-}
+// =======================
+//  Game logic for voting
+// =======================
 
 void VoteHelp(entity e) {
        string vmasterdis;
@@ -849,209 +1050,4 @@ void VoteHelp(entity e) {
        print_to(e, strcat("^7If neither the vote will timeout after ", ftos(autocvar_sv_vote_timeout), "^7 seconds."));
        print_to(e, "^7You can call a vote for or execute these commands:");
        print_to(e, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
-}
-
-void VoteThink() {
-       if(votefinished > 0) // a vote was called
-       if(time > votefinished) // time is up
-       {
-               VoteCount();
-       }
-}
-
-void VoteReset() {
-       entity player;
-
-       FOR_EACH_CLIENT(player)
-       {
-               player.vote_selection = 0;
-       }
-
-       if(votecalled)
-       {
-               strunzone(votecalledvote);
-               strunzone(votecalledvote_display);
-       }
-
-       votecalled = FALSE;
-       votecalledmaster = FALSE;
-       votefinished = 0;
-       votecalledvote = string_null;
-       votecalledvote_display = string_null;
-
-       Nagger_VoteChanged();
-}
-
-void VoteAccept() {
-       bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
-       if(votecalledmaster)
-       {
-               if(votecaller) {
-                       votecaller.vote_master = 1;
-               }
-       } else {
-               localcmd(strcat(votecalledvote, "\n"));
-       }
-       if(votecaller) {
-               votecaller.vote_next = 0; // people like your votes,
-                                         // no wait for next vote
-       }
-       VoteReset();
-       Announce("voteaccept");
-}
-
-void VoteReject() {
-       bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
-       VoteReset();
-       Announce("votefail");
-}
-
-void VoteTimeout() {
-       bprint("\{1}^2* ^3", VoteCommand_getname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
-       VoteReset();
-       Announce("votefail");
-}
-
-void VoteSpam(float notvoters, float mincount, string result)
-{
-       string s;
-       if(mincount >= 0)
-       {
-               s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
-               s = strcat(s, ftos(vote_nocount), "^2 (^1");
-               s = strcat(s, ftos(mincount), "^2 needed), ^1");
-               s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
-               s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
-       }
-       else
-       {
-               s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
-               s = strcat(s, ftos(vote_nocount), "^2, ^1");
-               s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
-               s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
-       }
-       bprint(s);
-       if(autocvar_sv_eventlog)
-       {
-               s = strcat(":vote:v", result, ":", ftos(vote_yescount));
-               s = strcat(s, ":", ftos(vote_nocount));
-               s = strcat(s, ":", ftos(vote_abstaincount));
-               s = strcat(s, ":", ftos(notvoters));
-               s = strcat(s, ":", ftos(mincount));
-               GameLogEcho(s);
-       }
-}
-
-void VoteCount() {
-       float playercount;
-       playercount = 0;
-       vote_yescount = 0;
-       vote_nocount = 0;
-       vote_abstaincount = 0;
-       entity player;
-       //same for real players
-       float realplayercount;
-       float realplayeryescount;
-       float realplayernocount;
-       float realplayerabstaincount;
-       realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
-
-       Nagger_VoteCountChanged();
-
-       FOR_EACH_REALCLIENT(player)
-       {
-               if(player.vote_selection == -1) {
-                       ++vote_nocount;
-               } else if(player.vote_selection == 1) {
-                       ++vote_yescount;
-               } else if(player.vote_selection == -2) {
-                       ++vote_abstaincount;
-               }
-               ++playercount;
-               //do the same for real players
-               if(player.classname == "player") {
-                       if(player.vote_selection == -1) {
-                               ++realplayernocount;
-                       } else if(player.vote_selection == 1) {
-                               ++realplayeryescount;
-                       } else if(player.vote_selection == -2) {
-                               ++realplayerabstaincount;
-                       }
-                       ++realplayercount;
-               }
-       }
-
-       //in tournament mode, if we have at least one player then don't make the vote dependent on spectators (so specs don't have to press F1)
-       if(autocvar_sv_vote_nospectators)
-       if(realplayercount > 0) {
-               vote_yescount = realplayeryescount;
-               vote_nocount = realplayernocount;
-               vote_abstaincount = realplayerabstaincount;
-               playercount = realplayercount;
-       }
-
-       float votefactor, simplevotefactor;
-       votefactor = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
-       simplevotefactor = autocvar_sv_vote_simple_majority_factor;
-
-       // FIXME this number is a guess
-       vote_needed_absolute = floor((playercount - vote_abstaincount) * votefactor) + 1;
-       if(simplevotefactor)
-       {
-               simplevotefactor = bound(votefactor, simplevotefactor, 0.999);
-               vote_needed_simple = floor((vote_yescount + vote_nocount) * simplevotefactor) + 1;
-       }
-       else
-               vote_needed_simple = 0;
-
-       if(votecalledmaster
-          && playercount == 1) {
-               // if only one player is on the server becoming vote
-               // master is not allowed.  This could be used for
-               // trolling or worse. 'self' is the user who has
-               // called the vote because this function is called
-               // by SV_ParseClientCommand. Maybe all voting should
-               // be disabled for a single player?
-               print_to(votecaller, "^1You are the only player on this server so you can not become vote master.");
-               if(votecaller) {
-                       votecaller.vote_next = 0;
-               }
-               VoteReset();
-       } else {
-               if(vote_yescount >= vote_needed_absolute)
-               {
-                       VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "yes");
-                       VoteAccept();
-               }
-               else if(vote_nocount > playercount - vote_abstaincount - vote_needed_absolute) // that means, vote_yescount cannot reach vote_needed_absolute any more
-               {
-                       VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "no");
-                       VoteReject();
-               }
-               else if(time > votefinished)
-               {
-                       if(simplevotefactor)
-                       {
-                               string result;
-                               if(vote_yescount >= vote_needed_simple)
-                                       result = "yes";
-                               else if(vote_yescount + vote_nocount > 0)
-                                       result = "no";
-                               else
-                                       result = "timeout";
-                               VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, min(vote_needed_absolute, vote_needed_simple), result);
-                               if(result == "yes")
-                                       VoteAccept();
-                               else if(result == "no")
-                                       VoteReject();
-                               else
-                                       VoteTimeout();
-                       }
-                       else
-                       {
-                               VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, vote_needed_absolute, "timeout");
-                               VoteTimeout();
-                       }
-               }
-       }
-}
+}
\ No newline at end of file
index e5233b074e06b8f058bbe7db4404c658c9674bc1..1dc639d171da75070eda3f1ec67abdd580d2a349 100644 (file)
@@ -25,7 +25,14 @@ void VoteStop(entity stopper);
 void VoteSpam(float notvoters, float mincount, string result);
 void VoteCount();
 
+// =========================
+//  warmup and nagger stuff
+// =========================
+
+#define RESTART_COUNTDOWN 10
 entity nagger;
+.float ready;
 float readycount;
 float readyrestart_happened;
+float restart_mapalreadyrestarted; // bool, indicates whether reset_map() was already executed
 void ReadyCount();
\ No newline at end of file