class ScrnVotingHandlerMut extends Mutator Config(ScrnVoting); var globalconfig int VoteCountDown; var globalconfig float VotePercent, VotePercentCountDown; const VERSION = 40600; var localized string strVersion; var protected bool bVoteInProgress; var protected int VoteSecondsLeft; var protected array VotersYes, VotersNo; var localized string strVoteInitiated, strVotePassed, strVoteFailed, strVoteFailedInitiator, strVoteFailedPlayer, strVoteTimeout, strVoteInProgress, strNoVoteInProgress, strAlreadyVoted, strVoteUnknown, strVoteIllegal, strVoteHasNoEffect, strForcedByAdmin, strVotedYes, strVotedNo, strVoteStatus, strSpectatorsCantVote, strOtherTeamVote; var localized string strTimeout, strPlayerBlocked; var string VoteInfo; // user-friendly information about current vore var protected int VoteIndex; var protected string VoteValue; // value to be set, if vote passes var protected ScrnVotingOptions CurrentVotingObject; //objects that controls current vote value var transient PlayerController VoteInitiator; var transient TeamInfo VotedTeam; var PlayerController TeamCaptains[2]; var transient PlayerController VotedPlayer; var protected transient bool bVotedPlayer; // if true, VotedPlayer is used in the current vote var protected array VotingOptions; var array HelpInfo; var private bool bHelpPrepared; var class VHReplicationInfoClass; var VHReplicationInfo VHRI; var byte VoteID; var globalconfig float VoteCoolDown; var PlayerController FailedVoter; var float FailedVoterBlockTime; // FailedVoter is unable to start a new vote till this time // Players who can't start voting. Can be used by voting options var array BlockedVoters; struct SVoteGroup { var String GroupName; var ScrnVotingOptions VO; }; var array VoteGroups; static function string GetVersionStr() { local String msg, s; local int v, sub_v; msg = default.strVersion; v = VERSION / 100; sub_v = VERSION % 100; s = String(int(v%100)); if ( len(s) == 1 ) s = "0" $ s; if ( sub_v > 0 ) s @= "(BETA "$sub_v$")"; ReplaceText(msg, "%n", s); s = String(v/100); ReplaceText(msg, "%m",s); return msg; } function Mutate(string MutateString, PlayerController Sender) { if ( MutateString ~= "vote" ) Vote("", Sender); else if ( left(MutateString, 5) ~= "vote " ) Vote(Right(MutateString, len(MutateString)-5), Sender); else { super.Mutate(MutateString, Sender); if ( MutateString ~= "version" ) Sender.ClientMessage(GetVersionStr()); } } static function ScrnVotingHandlerMut GetVotingHandler(GameInfo Game) { local Mutator mut; for ( mut = Game.BaseMutator; mut != none; mut = mut.NextMutator ) { if ( ScrnVotingHandlerMut(mut) != none ) return ScrnVotingHandlerMut(mut); } return none; } function ScrnVotingOptions AddVotingOptions(class VO_Class) { local int i; local ScrnVotingOptions VO; // don't add same class twice for ( i=0; i < VotingOptions.length; ++i ) { if ( VotingOptions[i].class == VO_Class ) return VotingOptions[i]; } VO = spawn(VO_Class, self); if ( VO != none ) { VO.VotingHandler = self; VotingOptions[VotingOptions.length] = VO; VO.InitGroups(); } return VO; } function RemoveVotingOptions(class VO_Class, optional bool bChildsToo) { local int i, j; for ( i=0; i < VotingOptions.length; ++i ) { if ( VotingOptions[i].class == VO_Class || (bChildsToo && ClassIsChildOf(VotingOptions[i].class, VO_Class)) ) { // unregister groups too for ( j=0; j 0 && left(str,1) == " " ) str = right(str, --l); while ( l > 0 && right(str,1) == " " ) str = left(str, --l); return str; } function bool IsVoteInProgress() { return bVoteInProgress; } function string GetVoteStatus(optional PlayerController Voter, optional bool bVotedYes) { local string result; if ( !bVoteInProgress ) return strNoVoteInProgress; if ( Voter != none && Voter.PlayerReplicationInfo != none ) { if ( bVotedYes ) result = strVotedYes; else result = strVotedNo; ReplaceText(result, "%p", Voter.PlayerReplicationInfo.PlayerName); } else { result = strVoteStatus; } ReplaceText(result, "%v", VoteInfo); ReplaceText(result, "%y", String(VotersYes.length)); ReplaceText(result, "%n", String(VotersNo.length)); return result; } // returns number of players who are able to vote function int MaxVoters() { local int result; if ( VotedTeam != none ) result = VotedTeam.Size; else result = Level.Game.NumPlayers; return max(result, 1); } function Vote(string VoteString, PlayerController Sender) { local int i, idx; local string k, v, g; local ScrnVotingOptions VO; if ( Sender == none || Sender.PlayerReplicationInfo == none ) return; // wtf? VoteString = caps(Trim(VoteString)); if ( VoteString == "" || VoteString == "HELP" || VoteString ~= "INFO" || VoteString ~= "?") { SendHelp(Sender); } else if ( VoteString == "STATUS" ) { Sender.ClientMessage(GetVoteStatus()); } else if ( Sender.PlayerReplicationInfo.bOnlySpectator && !Sender.PlayerReplicationInfo.bAdmin ) { Sender.ClientMessage(strSpectatorsCantVote); } else if ( VoteString == "YES" || VoteString == "TRYYES" ) { if ( bVoteInProgress ) { if ( Sender.PlayerReplicationInfo.bAdmin ) { VotePassed(Sender.PlayerReplicationInfo.PlayerName); return; } else if ( VotedTeam != none ) { if ( VotedTeam != Sender.PlayerReplicationInfo.Team ) { if ( VoteString != "TRYYES" ) Sender.ClientMessage(strOtherTeamVote); return; } else if ( VotedTeam.TeamIndex < 2 && TeamCaptains[VotedTeam.TeamIndex] == Sender ) { VotePassed(Sender.PlayerReplicationInfo.PlayerName); return; } } for ( i = 0; i < VotersNo.length; i++ ) { if ( VotersNo[i] == Sender ) { VotersNo.remove(i, 1); break; } } for ( i = 0; i < VotersYes.length; i++ ) { if ( VotersYes[i] == Sender ) { Sender.ClientMessage(strAlreadyVoted); return; } } VotersYes[VotersYes.length] = Sender; BroadcastMessage(GetVoteStatus(Sender, true)); if ( float(VotersYes.length) / MaxVoters() * 100.0 >= VotePercent ) VotePassed(); else { VHRI.UpdateVoteStatus(self, VHRI.VS_INPROGRESS, VoteInfo, VotersYes.length, VotersNo.length, VoteID); } } else if ( VoteString != "TRYYES" ) Sender.ClientMessage(strNoVoteInProgress); } else if ( VoteString == "NO" || VoteString == "TRYNO" ) { if ( bVoteInProgress ) { if ( Sender.PlayerReplicationInfo.bAdmin ) { VoteInfo @= strForcedByAdmin; ReplaceText(VoteInfo, "%p", Sender.PlayerReplicationInfo.PlayerName); VoteFailed(); return; } else if ( VotedTeam != none ) { if ( VotedTeam != Sender.PlayerReplicationInfo.Team ) { if ( VoteString != "TRYNO" ) Sender.ClientMessage(strOtherTeamVote); return; } else if ( VotedTeam.TeamIndex < 2 && TeamCaptains[VotedTeam.TeamIndex] == Sender ) { VoteInfo @= strForcedByAdmin; ReplaceText(VoteInfo, "%p", Sender.PlayerReplicationInfo.PlayerName); VoteFailed(); return; } } for ( i = 0; i < VotersYes.length; i++ ) { if ( VotersYes[i] == Sender ) { VotersYes.remove(i, 1); break; } } for ( i = 0; i < VotersNo.length; i++ ) { if ( VotersNo[i] == Sender ) { Sender.ClientMessage(strAlreadyVoted); return; } } VotersNo[VotersNo.length] = Sender; BroadcastMessage(GetVoteStatus(Sender, false)); if ( float(MaxVoters() - VotersNo.length) / MaxVoters() * 100.0 < VotePercent ) VoteFailed(); // vote fails when it can't theoretically pass the vote else VHRI.UpdateVoteStatus(self, VHRI.VS_INPROGRESS, VoteInfo, VotersYes.length, VotersNo.length, VoteID); } else if ( VoteString != "TRYNO" ) Sender.ClientMessage(strNoVoteInProgress); } else if ( bVoteInProgress ) Sender.ClientMessage(strVoteInProgress); else if ( !MayStartVoting(Sender)) Sender.ClientMessage(strPlayerBlocked); else { VoteInfo = ""; VotedPlayer = none; VotedTeam = none; if ( Divide(VoteString, " ", k, v) ) { k = trim(k); v = trim(v); } else { k = VoteString; v = ""; } // check if first is a group VO = GetGroupOptions(k); if ( VO != none ) { // first word is a group, then second is a key and others - value g = k; if ( Divide(v, " ", k, v) ) { k = trim(k); v = trim(v); } else { k = v; v = ""; } if ( k == "" || k == "HELP" || k ~= "?" ) VO.SendGroupHelp(Sender, g); else { idx = VO.GetGroupVoteIndex(Sender, g, k, v, VoteInfo); switch (idx) { case VO.VOTE_UNKNOWN: Sender.ClientMessage(strVoteIllegal); VO.SendGroupHelp(Sender, g); break; case VO.VOTE_ILLEGAL: Sender.ClientMessage(strVoteIllegal); break; case VO.VOTE_NOEFECT: Sender.ClientMessage(strVoteHasNoEffect); break; case VO.VOTE_LOCAL: return; default: CurrentVotingObject = VO; VoteIndex = idx; VoteValue = v; if ( VoteInfo == "" ) VoteInfo = g @ k @ v; StartVoting(Sender); } } } else { // no group found - do global vote check for ( i = 0; i < VotingOptions.length; ++i ) { if ( VotingOptions[i] == none || !VotingOptions[i].bAcceptsGlobalVotes ) continue; VO = VotingOptions[i]; idx = VO.GetVoteIndex(Sender, k, v, VoteInfo); if ( idx != VO.VOTE_UNKNOWN ) { switch (idx) { case VO.VOTE_ILLEGAL: Sender.ClientMessage(strVoteIllegal); break; case VO.VOTE_NOEFECT: Sender.ClientMessage(strVoteHasNoEffect); break; case VO.VOTE_LOCAL: return; default: CurrentVotingObject = VO; VoteIndex = idx; VoteValue = v; if ( VoteInfo == "" ) VoteInfo = k @ v; StartVoting(Sender); } return; } } if ( VO != none && idx == VO.VOTE_UNKNOWN ) { Sender.ClientMessage(strVoteUnknown); } } } } function bool MayStartVoting(PlayerController Sender) { local int i; if ( Sender.PlayerReplicationInfo == none ) return false; // admins can always vote if ( Sender.PlayerReplicationInfo.bAdmin ) return true; // spectators can't vote if ( Sender.PlayerReplicationInfo.bOnlySpectator ) return false; if ( Level.Game.NumPlayers == 1 ) return true; if ( Sender == FailedVoter && Level.TimeSeconds < FailedVoterBlockTime ) return false; for ( i=0; i= VotePercentCountDown ) { VotePassed(strTimeout); } else { EndVoting(); BroadcastMessage(strVoteTimeout); VHRI.UpdateVoteStatus(self, VHRI.VS_FAILED, VoteInfo, VotersYes.length, VotersNo.length, VoteID); } } } function EndVoting() { bVoteInProgress = false; SetTimer(0, false); } function VotePassed(optional String ForcedByPlayerName) { local string msg; if ( !bVoteInProgress ) return; EndVoting(); if ( VoteInitiator == none ) { BroadcastMessage(chr(27)$chr(200)$chr(1)$chr(1)$strVoteFailedInitiator); VHRI.UpdateVoteStatus(self, VHRI.VS_FAILED, VoteInfo, VotersYes.length, VotersNo.length, VoteID); return; } CurrentVotingObject.ApplyVoteValue(VoteIndex, VoteValue); msg = strVotePassed; ReplaceText(msg, "%v", VoteInfo); if ( ForcedByPlayerName != "" ) { msg @= strForcedByAdmin; ReplaceText(msg, "%p", ForcedByPlayerName); } BroadcastMessage(chr(27)$chr(1)$chr(200)$chr(1)$msg); VHRI.UpdateVoteStatus(self, VHRI.VS_PASSED, VoteInfo, VotersYes.length, VotersNo.length, VoteID); } function VoteFailed() { if ( VoteCoolDown > 0 ) { FailedVoter = VoteInitiator; FailedVoterBlockTime = Level.TimeSeconds + VoteCoolDown; } EndVoting(); BroadcastMessage(chr(27)$chr(200)$chr(1)$chr(1)$strVoteFailed); VHRI.UpdateVoteStatus(self, VHRI.VS_FAILED, VoteInfo, VotersYes.length, VotersNo.length, VoteID); } static function string ParseHelpLine(string s) { ReplaceText(s, "%r", chr(27)$chr(200)$chr(1)$chr(1)); ReplaceText(s, "%g", chr(27)$chr(1)$chr(200)$chr(1)); ReplaceText(s, "%b", chr(27)$chr(1)$chr(100)$chr(200)); ReplaceText(s, "%w", chr(27)$chr(200)$chr(200)$chr(200)); ReplaceText(s, "%y", chr(27)$chr(200)$chr(200)$chr(1)); ReplaceText(s, "%p", chr(27)$chr(200)$chr(1)$chr(200)); ReplaceText(s, "%k", chr(27)$chr(64)$chr(64)$chr(64)); return s; } function PrepareHelp() { local int i, j, l; local ScrnVotingOptions VO; bHelpPrepared = true; l = HelpInfo.length; // add help infos from voting options for ( j=0; j < VotingOptions.length; ++j ) { VO = VotingOptions[j]; if ( VO != none) { HelpInfo.insert(l, VO.HelpInfo.length); for ( i = 0; i < VO.HelpInfo.length; ++i) { HelpInfo[l++] = VO.HelpInfo[i]; } } } for ( i = 0; i < l; ++i) { HelpInfo[i] = ParseHelpLine(HelpInfo[i]); } } function SendHelp(PlayerController Sender) { local int i; if ( !bHelpPrepared ) PrepareHelp(); for ( i = 0; i < HelpInfo.length; ++i) Sender.ClientMessage(HelpInfo[i]); } simulated function PostBeginPlay() { if ( Level.NetMode == NM_Client ) { log("Voting Handler should be used only on the server side", class.outer.name); Destroy(); return; } VHRI = Spawn(VHReplicationInfoClass, self); VHRI.mutRef = Self; } function ServerTraveling(string URL, bool bItems) { if (NextMutator != None) NextMutator.ServerTraveling(URL,bItems); if ( VHRI != none ) { VHRI.Destroy(); VHRI = none; } } //returns true, if specifield vote is in progress //pass -1 to VIndex to check all votes from specified ScrnVotingOptions object function bool IsMyVotingRunning(ScrnVotingOptions VO, int VIndex) { return bVoteInProgress && CurrentVotingObject == VO && (VoteIndex == VIndex || VIndex == -1); } defaultproperties { VoteCountDown=70 VotePercent=51.000000 VotePercentCountDown=51.000000 strVersion="ScrN Voting Handler v%m.%n" strVoteInitiated="%p initiated a vote: %v." strVotePassed="Vote passed: %v" strVoteFailed="Vote failed" strVoteFailedInitiator="Vote failed due to initiator's disconnect" strVoteFailedPlayer="Vote failed due to player's disconnect" strVoteTimeout="Vote failed due to timeout" strVoteInProgress="Another vote in progress" strNoVoteInProgress="No vote in progress" strAlreadyVoted="You've already voted" strVoteUnknown="Unknown vote" strVoteIllegal="Illegal vote arguments" strVoteHasNoEffect="Vote has no effect" strForcedByAdmin="(forced by %p)" strVotedYes="%p voted YES on: %v (+%y -%n)" strVotedNo="%p voted NO on: %v (+%y -%n)" strVoteStatus="Current vote: %v (+%y -%n)" strSpectatorsCantVote="Spectators can not vote!" strOtherTeamVote="Can't participate in other team's voting!" strTimeout="TIMEOUT" strPlayerBlocked="You are disallowed to start a voting" HelpInfo(0)="%bVoting Options:" HelpInfo(1)="%gHELP %w Show this information" HelpInfo(2)="%gYES%w|%gNO %w Accept|Decline current vote" HelpInfo(3)="%gSTATUS %w Show status of the current vote" VHReplicationInfoClass=Class'ScrnVotingHandlerV4.VHReplicationInfo' VoteCoolDown=10.000000 bAddToServerPackages=True GroupName="SCRN-VOTE" FriendlyName="ScrN Voting Handler" Description="Allows voting via MUTATE VOTE console command" }