Class xVotingHandler extends xVotingHandlerBase config(xMapVote); struct FGameModeOption { var config string GameName,GameShortName,GameClass,Mutators,Options,Prefix; }; var config array GameModes; var config int LastVotedGameInfo,VoteTime,MaxMapsOnList,VoteNumForOrgy; var config float MidGameVotePct,MapWinPct,MapChangeDelay; var config bool bNoWebAdmin; var config bool bNoMapVoteOrgy; var class BaseMutator; var array AnnouncerCues; var array Maps; var array ActiveVotes; var array ActiveVoters; var int iCurrentHistory,VoteTimeLeft,ShowMenuDelay; var string PendingMapURL; var KFGameReplicationInfo KF; var bool bMapvoteHasEnded,bMapVoteTimer,bHistorySaved; function PostBeginPlay() { local int i,j,z,n,UpV,DownV,Seq,NumPl; local string S,MapFile; local bool ConfigChanged; if (WorldInfo.Game.BaseMutator==None) WorldInfo.Game.BaseMutator = Self; else WorldInfo.Game.BaseMutator.AddMutator(Self); if (bDeleteMe) // This was a duplicate instance of the mutator. return; ConfigChanged = False; MapFile = string(WorldInfo.GetPackageName()); iCurrentHistory = class'xMapVoteHistory'.Static.GetMapHistory(MapFile,WorldInfo.Title); if (LastVotedGameInfo<0 || LastVotedGameInfo>=GameModes.Length) LastVotedGameInfo = 0; if (MapChangeDelay==0) MapChangeDelay = 3; if (GameModes.Length==0) // None specified, so use current settings. { GameModes.Length = 1; GameModes[0].GameName = "Killing Floor"; GameModes[0].GameShortName = "KF"; GameModes[0].GameClass = PathName(WorldInfo.Game.Class); GameModes[0].Mutators = ""; GameModes[0].Prefix = ""; MidGameVotePct = 0.51; MapWinPct = 0.75; VoteTime = 35; ConfigChanged = True; } if (VoteNumForOrgy <= 0) { VoteNumForOrgy = 4; bNoMapVoteOrgy = False; ConfigChanged = True; } if (ConfigChanged) SaveConfig(); // Build maplist. z = 0; for (i=(Class'KFGameInfo'.Default.GameMapCycles.Length-1); i>=0; --i) { for (j=(Class'KFGameInfo'.Default.GameMapCycles[i].Maps.Length-1); j>=0; --j) { if (MaxMapsOnList>0 && Class'KFGameInfo'.Default.GameMapCycles[i].Maps[j]~=MapFile) // If we limit the maps count, remove current map. continue; Maps.Length = z+1; Maps[z].MapName = Class'KFGameInfo'.Default.GameMapCycles[i].Maps[j]; n = class'xMapVoteHistory'.Static.GetMapHistory(Maps[z].MapName,""); class'xMapVoteHistory'.Static.GetHistory(n,UpV,DownV,Seq,NumPl,S); Maps[z].UpVotes = UpV; Maps[z].DownVotes = DownV; Maps[z].Sequence = Seq; Maps[z].NumPlays = NumPl; Maps[z].History = n; Maps[z].MapTitle = S; ++z; } } if (MaxMapsOnList>0) { // Remove random maps from list. while (Maps.Length>MaxMapsOnList) Maps.Remove(Rand(Maps.Length),1); } SetTimer(0.15,false,'SetupBroadcast'); SetTimer(1,true,'CheckEndGameEnded'); } function AddMutator(Mutator M) { if (M!=Self) // Make sure we don't get added twice. { if (M.Class==Class) M.Destroy(); else Super.AddMutator(M); } } function SetupBroadcast() { local xVoteBroadcast B; local WebServer W; local WebAdmin A; local xVoteWebApp xW; local byte i; B = Spawn(class'xVoteBroadcast'); B.Handler = Self; B.NextBroadcaster = WorldInfo.Game.BroadcastHandler; WorldInfo.Game.BroadcastHandler = B; if (!bNoWebAdmin) { foreach AllActors(class'WebServer',W) break; if (W!=None) { for (i=0; (i<10 && A==None); ++i) A = WebAdmin(W.ApplicationObjects[i]); if (A!=None) { xW = new (None) class'xVoteWebApp'; A.addQueryHandler(xW); } else `Log("X-VoteWebAdmin ERROR: No valid WebAdmin application found!"); } else `Log("X-VoteWebAdmin ERROR: No WebServer object found!"); } } final function AddVote(int Count, int MapIndex, int GameIndex) { local int i,j; if (bMapvoteHasEnded) return; for (i=0; i=0; --j) ActiveVoters[j].ClientReceiveVote(GameIndex,MapIndex,ActiveVotes[i].NumVotes); if (ActiveVotes[i].NumVotes<=0) { for (j=(ActiveVoters.Length-1); j>=0; --j) if (ActiveVoters[j].DownloadStage==2 && ActiveVoters[j].DownloadIndex>=i && ActiveVoters[j].DownloadIndex>0) // Make sure client doesn't skip a download at this point. --ActiveVoters[j].DownloadIndex; ActiveVotes.Remove(i,1); } return; } if (Count<=0) return; ActiveVotes.Length = i+1; ActiveVotes[i].GameIndex = GameIndex; ActiveVotes[i].MapIndex = MapIndex; ActiveVotes[i].NumVotes = Count; for (j=(ActiveVoters.Length-1); j>=0; --j) ActiveVoters[j].ClientReceiveVote(GameIndex,MapIndex,Count); } final function LogoutPlayer(PlayerController PC) { local int i; for (i=(ActiveVoters.Length-1); i>=0; --i) if (ActiveVoters[i].PlayerOwner==PC) { ActiveVoters[i].Destroy(); break; } } final function LoginPlayer(PlayerController PC) { local xVotingReplication R; local int i; for (i=(ActiveVoters.Length-1); i>=0; --i) if (ActiveVoters[i].PlayerOwner==PC) return; R = Spawn(class'xVotingReplication',PC); R.VoteHandler = Self; ActiveVoters.AddItem(R); } function NotifyLogout(Controller Exiting) { if (PlayerController(Exiting)!=None) LogoutPlayer(PlayerController(Exiting)); if (NextMutator != None) NextMutator.NotifyLogout(Exiting); } function NotifyLogin(Controller NewPlayer) { if (PlayerController(NewPlayer)!=None) LoginPlayer(PlayerController(NewPlayer)); if (NextMutator != None) NextMutator.NotifyLogin(NewPlayer); } function ClientDownloadInfo(xVotingReplication V) { if (bMapvoteHasEnded) { V.DownloadStage = 255; return; } switch (V.DownloadStage) { case 0: // Game modes. if (V.DownloadIndex>=GameModes.Length) break; V.ClientReceiveGame(V.DownloadIndex,GameModes[V.DownloadIndex].GameName,GameModes[V.DownloadIndex].GameShortName,GameModes[V.DownloadIndex].Prefix); ++V.DownloadIndex; return; case 1: // Maplist. if (V.DownloadIndex>=Maps.Length) break; if (Maps[V.DownloadIndex].MapTitle=="") V.ClientReceiveMap(V.DownloadIndex,Maps[V.DownloadIndex].MapName,Maps[V.DownloadIndex].UpVotes,Maps[V.DownloadIndex].DownVotes,Maps[V.DownloadIndex].Sequence,Maps[V.DownloadIndex].NumPlays); else V.ClientReceiveMap(V.DownloadIndex,Maps[V.DownloadIndex].MapName,Maps[V.DownloadIndex].UpVotes,Maps[V.DownloadIndex].DownVotes,Maps[V.DownloadIndex].Sequence,Maps[V.DownloadIndex].NumPlays,Maps[V.DownloadIndex].MapTitle); ++V.DownloadIndex; return; case 2: // Current votes. if (V.DownloadIndex>=ActiveVotes.Length) break; V.ClientReceiveVote(ActiveVotes[V.DownloadIndex].GameIndex,ActiveVotes[V.DownloadIndex].MapIndex,ActiveVotes[V.DownloadIndex].NumVotes); ++V.DownloadIndex; return; default: V.ClientReady(LastVotedGameInfo); V.DownloadStage = 255; return; } ++V.DownloadStage; V.DownloadIndex = 0; } function ClientCastVote(xVotingReplication V, int GameIndex, int MapIndex, bool bAdminForce) { local int i; if (bMapvoteHasEnded) return; if (bAdminForce && V.PlayerOwner.PlayerReplicationInfo.bAdmin) { SwitchToLevel(GameIndex,MapIndex,true); return; } if (!Class'xUI_MapVote'.Static.BelongsToPrefix(Maps[MapIndex].MapName,GameModes[GameIndex].Prefix)) { V.PlayerOwner.ClientMessage("Error: Can't vote that map (wrong Prefix to that game mode)!"); return; } if (V.CurrentVote[0]>=0) AddVote(-1,V.CurrentVote[1],V.CurrentVote[0]); V.CurrentVote[0] = GameIndex; V.CurrentVote[1] = MapIndex; AddVote(1,MapIndex,GameIndex); for (i=(ActiveVoters.Length-1); i>=0; --i) ActiveVoters[i].ClientNotifyVote(V.PlayerOwner.PlayerReplicationInfo,GameIndex,MapIndex); TallyVotes(); } function ClientRankMap(xVotingReplication V, bool bUp) { class'xMapVoteHistory'.Static.AddMapKarma(iCurrentHistory,bUp); } function ClientDisconnect(xVotingReplication V) { ActiveVoters.RemoveItem(V); if (V.CurrentVote[0]>=0) AddVote(-1,V.CurrentVote[1],V.CurrentVote[0]); TallyVotes(); } final function float GetPctOf(int Nom, int Denom) { local float R; R = float(Nom) / float(Denom); return R; } final function TallyVotes(optional bool bForce) { local int i,NumVotees,c,j; local array Candidates; if (bMapvoteHasEnded) return; NumVotees = ActiveVoters.Length; c = 0; if (bForce) { // First check for highest result. for (i=(ActiveVotes.Length-1); i>=0; --i) c = Max(c,ActiveVotes[i].NumVotes); if (c>0) { // Then check how many votes for the best. for (i=(ActiveVotes.Length-1); i>=0; --i) if (ActiveVotes[i].NumVotes==c) Candidates.AddItem(i); // Finally pick a random winner from the best. c = Candidates[Rand(Candidates.Length)]; // If more then "VoteNumForOrgy" voters and everyone voted same map?!!! Give the mapvote some orgy. if (!bNoMapVoteOrgy && NumVotees >= VoteNumForOrgy && ActiveVotes.Length==1) { for (j=(ActiveVoters.Length-1); j >= 0; --j) ActiveVoters[j].PlayerOwner.ClientPlaySound(AnnouncerCues[13]); } SwitchToLevel(ActiveVotes[c].GameIndex,ActiveVotes[c].MapIndex,false); } else { // Pick a random map to win. c = Rand(Maps.Length); // Pick a random gametype to win along with it. for (i=(GameModes.Length-1); i>=0; --i) if (Class'xUI_MapVote'.Static.BelongsToPrefix(Maps[c].MapName,GameModes[i].Prefix)) Candidates.AddItem(i); if (Candidates.Length==0) // Odd, a map without gametype... i = Rand(GameModes.Length); else i = Candidates[Rand(Candidates.Length)]; SwitchToLevel(i,c,false); } return; } // Check for insta-win vote. for (i=(ActiveVotes.Length-1); i>=0; --i) { c+=ActiveVotes[i].NumVotes; if (GetPctOf(ActiveVotes[i].NumVotes,NumVotees)>=MapWinPct) { if (NumVotees>=4 && ActiveVotes.Length==1) // If more then 4 voters and everyone voted same map?!!! Give the mapvote some orgy. { for (j=(ActiveVoters.Length-1); j>=0; --j) ActiveVoters[j].PlayerOwner.ClientPlaySound(AnnouncerCues[13]); } SwitchToLevel(ActiveVotes[i].GameIndex,ActiveVotes[i].MapIndex,false); return; } } // Check for mid-game voting timer. if (!bMapVoteTimer && NumVotees>0 && GetPctOf(c,NumVotees)>=MidGameVotePct) StartMidGameVote(true); } final function StartMidGameVote(bool bMidGame) { local int i; if (bMapVoteTimer || bMapvoteHasEnded) return; bMapVoteTimer = true; if (bMidGame) { for (i=(ActiveVoters.Length-1); i>=0; --i) ActiveVoters[i].ClientNotifyVoteTime(0); } ShowMenuDelay = 5; VoteTimeLeft = Max(VoteTime,10); SetTimer(1,true); } function CheckEndGameEnded() { if (KF==None) { KF = KFGameReplicationInfo(WorldInfo.GRI); if (KF==None) return; } if (KF.bMatchIsOver) // HACK, since KFGameInfo_Survival doesn't properly notify mutators of this! { if (!bMapVoteTimer) StartMidGameVote(false); ClearTimer('CheckEndGameEnded'); WorldInfo.Game.ClearTimer('ShowPostGameMenu'); } } function bool HandleRestartGame() { if (!bMapVoteTimer) StartMidGameVote(false); return true; } function Timer() { local int i; local SoundCue FX; if (bMapvoteHasEnded) { // NOTE: // "WorldInfo.NetMode != NM_Standalone" prevents cyclic unsuccessful map change in single player mode. // I have not tested how this code will behave if it really fails to change the map. // Most likely there should be another solution here, but for now it will do. if (WorldInfo.NetMode != NM_Standalone && WorldInfo.NextSwitchCountdown<=0.f) // Mapswitch failed, force to random other map. { ActiveVotes.Length = 0; bMapvoteHasEnded = false; TallyVotes(true); } return; } if (ShowMenuDelay>0 && --ShowMenuDelay==0) { for (i=(ActiveVoters.Length-1); i>=0; --i) ActiveVoters[i].ClientOpenMapvote(true); } --VoteTimeLeft; if (VoteTimeLeft==0) { TallyVotes(true); } else if (VoteTimeLeft<=10 || VoteTimeLeft==20 || VoteTimeLeft==30 || VoteTimeLeft==60) { FX = None; if (VoteTimeLeft<=10) FX = AnnouncerCues[VoteTimeLeft-1]; else if (VoteTimeLeft==20) FX = AnnouncerCues[10]; else if (VoteTimeLeft==30) FX = AnnouncerCues[11]; else if (VoteTimeLeft==60) FX = AnnouncerCues[12]; for (i=(ActiveVoters.Length-1); i>=0; --i) { ActiveVoters[i].ClientNotifyVoteTime(VoteTimeLeft); if (FX!=None) ActiveVoters[i].PlayerOwner.ClientPlaySound(FX); } } } final function SwitchToLevel(int GameIndex, int MapIndex, bool bAdminForce) { local int i; local string S; if (bMapvoteHasEnded) return; Default.LastVotedGameInfo = GameIndex; Class.Static.StaticSaveConfig(); bMapvoteHasEnded = true; if (!bAdminForce && !bHistorySaved) { class'xMapVoteHistory'.Static.UpdateMapHistory(Maps[MapIndex].History); class'xMapVoteHistory'.Static.StaticSaveConfig(); bHistorySaved = true; } S = Maps[MapIndex].MapName$" ("$GameModes[GameIndex].GameName$")"; for (i=(ActiveVoters.Length-1); i>=0; --i) { KFPlayerController(ActiveVoters[i].PlayerOwner).ShowConnectionProgressPopup(PMT_AdminMessage,"Switching to level:",S); ActiveVoters[i].ClientNotifyVoteWin(GameIndex,MapIndex,bAdminForce); } PendingMapURL = Maps[MapIndex].MapName$"?Game="$GameModes[GameIndex].GameClass$"?Mutator="$PathName(BaseMutator); if (GameModes[GameIndex].Mutators!="") PendingMapURL $= ","$GameModes[GameIndex].Mutators; if (GameModes[GameIndex].Options!="") PendingMapURL $= "?"$GameModes[GameIndex].Options; `Log("MapVote: Switch map to "$PendingMapURL); SetTimer(FMax(MapChangeDelay,0.1),false,'PendingSwitch'); } function Pendingswitch () { WorldInfo.ServerTravel(PendingMapURL,false); SetTimer(1,true); } final function ParseCommand(string Cmd, PlayerController PC) { if (Cmd~="Help") { PC.ClientMessage("MapVote commands:"); PC.ClientMessage("!MapVote - Show mapvote menu"); PC.ClientMessage("!AddMap - Add map to mapvote"); PC.ClientMessage("!RemoveMap - Remove map from mapvote"); } else if (Cmd~="MapVote") ShowMapVote(PC); else if (!PC.PlayerReplicationInfo.bAdmin && !PC.IsA('MessagingSpectator')) return; else if (Left(Cmd,7)~="AddMap ") { Cmd = Mid(Cmd,7); PC.ClientMessage("Added map '"$Cmd$"'!"); AddMap(Cmd); } else if (Left(Cmd,10)~="RemoveMap ") { Cmd = Mid(Cmd,10); if (RemoveMap(Cmd)) PC.ClientMessage("Removed map '"$Cmd$"'!"); else PC.ClientMessage("Map '"$Cmd$"' not found!"); } } function ShowMapVote(PlayerController PC) { local int i; if (bMapvoteHasEnded) return; for (i=(ActiveVoters.Length-1); i>=0; --i) if (ActiveVoters[i].PlayerOwner==PC) { ActiveVoters[i].ClientOpenMapvote(false); return; } } final function AddMap(string M) { if (Class'KFGameInfo'.Default.GameMapCycles.Length==0) Class'KFGameInfo'.Default.GameMapCycles.Length = 1; Class'KFGameInfo'.Default.GameMapCycles[0].Maps.AddItem(M); Class'KFGameInfo'.Static.StaticSaveConfig(); } final function bool RemoveMap(string M) { local int i,j; for (i=(Class'KFGameInfo'.Default.GameMapCycles.Length-1); i>=0; --i) { for (j=(Class'KFGameInfo'.Default.GameMapCycles[i].Maps.Length-1); j>=0; --j) { if (Class'KFGameInfo'.Default.GameMapCycles[i].Maps[j]~=M) { Class'KFGameInfo'.Default.GameMapCycles[i].Maps.Remove(j,1); Class'KFGameInfo'.Static.StaticSaveConfig(); return true; } } } return false; } defaultproperties { BaseMutator=Class'xVotingHandler' AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_one_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_two_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_three_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_four_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_five_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_six_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_seven_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_eight_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_nine_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_ten_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_20_seconds_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_30_seconds_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_1_minute_Cue') AnnouncerCues.Add(SoundCue'xVoteAnnouncer.VX_HolyShit_Cue') }