class ScrnBalanceVoting extends ScrnVotingOptions;

var ScrnBalance Mut;

const VOTE_PERKLOCK     = 0;
const VOTE_PERKUNLOCK   = 1;
const VOTE_PAUSE        = 2;
const VOTE_ENDTRADE     = 3;
const VOTE_BLAME        = 4;
const VOTE_KICK         = 5;
const VOTE_BORING       = 6;
const VOTE_SPAWN        = 7;
const VOTE_ENDWAVE      = 8;
const VOTE_SPEC         = 9;
const VOTE_READY        = 10;
const VOTE_UNREADY      = 11;
const VOTE_TEAMLOCK     = 12;
const VOTE_TEAMUNLOCK   = 13;
const VOTE_INVITE       = 14;
const VOTE_FF           = 15;
const VOTE_MAPRESTART   = 16;
const VOTE_FAKEDPLAYERS = 17;
const VOTE_FAKEDCOUNT   = 18;
const VOTE_FAKEDHEALTH  = 19;
const VOTE_DIFF         = 20;
const VOTE_RKILL        = 100;

var localized string strCantEndTrade;
var localized string strTooLate;
var localized string strGamePaused, strSecondsLeft, strGameUnPaused, strPauseTraderOnly;
var localized string viResume, viEndTrade, viDifficulty;
var localized string strZedSpawnsDoubled;
var localized string strSquadNotFound, strCantSpawnSquadNow, strSquadList;
var localized string strNotInStoryMode, strNotInTSC;
var localized string strCantEndWaveNow, strEndWavePenalty;
var localized string strRCommands;

//variables for GamePaused state
var int PauseTime;
var transient bool bPauseable;
var transient string msgPause;

var string Reason; // reason why voting was started (e.g. kick player for being noob)

var transient float LastBlameVoteTime;

function class<ScrnVeterancyTypes> FindPerkByName(PlayerController Sender, string VeterancyNameOrIndex)
{
    local int i;
    local ClientPerkRepLink L;
    local class<ScrnVeterancyTypes> Perk;
    local string s1, s2;

    // log("FindPerkByName("$Sender$", "$VeterancyNameOrIndex$")", 'ScrnBalance');

    if ( Sender == none || VeterancyNameOrIndex == "" || SRStatsBase(Sender.SteamStatsAndAchievements) == none )
        return none;
    L = SRStatsBase(Sender.SteamStatsAndAchievements).Rep;
    if ( L == none )
        return none;

    i = int(VeterancyNameOrIndex);
    if ( i > 0 && i <= L.CachePerks.Length )
        return class<ScrnVeterancyTypes>(L.CachePerks[i-1].PerkClass);
    // log("CachePerks.Length="$L.CachePerks.Length, 'ScrnBalance');
    for ( i = 0; i < L.CachePerks.Length; ++i ) {
        Perk = class<ScrnVeterancyTypes>(L.CachePerks[i].PerkClass);
        if ( Perk != none ) {
            // log(GetItemName(String(Perk.class)) @ Perk.default.VeterancyNameOrIndex, 'ScrnBalance');
            if ( GetItemName(String(Perk.class)) ~= VeterancyNameOrIndex || Perk.default.VeterancyName ~= VeterancyNameOrIndex
                    || (Divide(Perk.default.VeterancyName, " ", s1, s2) && (VeterancyNameOrIndex ~= s1 || VeterancyNameOrIndex ~= s2)) )
                return Perk;
        }
    }

    return none;
}


function SendSquadList(PlayerController Sender)
{
    local int i;
    local string s;

    if ( Mut.Squads.Length == 0 ) {
        Sender.ClientMessage(strOptionDisabled);
        return;
    }

    s = strSquadList @ Mut.Squads[0].SquadName;
    for ( i=1; i<Mut.Squads.Length; ++i ) {
        s = s $ ", " $ Mut.Squads[i].SquadName;
    }

    Sender.ClientMessage(s);
}

static function bool TryStrToInt(string str, out int val)
{
    val = int(str);
    return val != 0 || str == "0";
}

function int GetVoteIndex(PlayerController Sender, string Key, out string Value, out string VoteInfo)
{
    local int result, v;
    local class<ScrnVeterancyTypes> Perk;

    if ( Key == "LOCKPERK" ) {
        if ( !Mut.bAllowLockPerkVote ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }
        Perk = FindPerkByName(Sender, Value);
        if ( Perk == none )
            return VOTE_ILLEGAL;
        if ( Perk.default.bLocked )
            return VOTE_NOEFECT;

        result = VOTE_PERKLOCK;
        Value = Perk.default.VeterancyName;
    }
    else if ( Key == "UNLOCKPERK" ) {
        Perk = FindPerkByName(Sender, Value);
        if ( Perk == none )
            return VOTE_ILLEGAL;
        if ( !Perk.default.bLocked )
            return VOTE_NOEFECT;

        result = VOTE_PERKUNLOCK;
        Value = Perk.default.VeterancyName;
    }
    else if ( Key == "PAUSE" || (Level.Pauser != none && Key == "RESUME") ) {
        if ( !Mut.bAllowPauseVote ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }
        if ( Mut.bPauseTraderOnly && !Mut.KF.bTradingDoorsOpen && Mut.KF.IsInState('MatchInProgress') ) {
            Sender.ClientMessage(strPauseTraderOnly);
            return VOTE_NOEFECT;
        }
        result = VOTE_PAUSE;
        if ( Value == "" )
            Value = "60";
        else {
            v = int(Value);
            if ( v <= 0 )
                v = 60;
            Value = string(v);
        }
        if ( Level.Pauser != none )
            VoteInfo = viResume;
    }
    else if ( Key == "ENDTRADE" || (Key == "END" && Value == "TRADE") ) {
        if ( Mut.bStoryMode ) {
            Sender.ClientMessage(strNotInStoryMode);
            return VOTE_NOEFECT;
        }
        if ( Mut.KF.bWaveInProgress|| Mut.KF.WaveCountDown < 10 || Mut.SkippedTradeTimeMult < 0 ) {
            Sender.ClientMessage(strCantEndTrade);
            return VOTE_NOEFECT;
        }
        result = VOTE_ENDTRADE;
        Value = "";
        VoteInfo = viEndTrade;
    }
    else if ( Key == "BLAME" ) {
        if ( !Mut.bAllowBlameVote ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }
        if ( Level.TimeSeconds - LastBlameVoteTime < Mut.BlameVoteCoolDown ) {
            Sender.ClientMessage(strNotAvaliableATM);
            return VOTE_LOCAL;
        }

        if ( Value == "" ) {
            SendPlayerList(Sender);
            return VOTE_LOCAL;
        }

        Reason = "";
        if ( Left(Value, 1) == "\"" ) {
            // don't look for player in quoted string - just blame it :)
            VotingHandler.VotedPlayer = none;
            Value = Mid(Value, 1);
            Divide(Value, "\"", Value, Reason);
            VotingHandler.VotedPlayer = none;
        }
        else {
            Divide(Value, " ", Value, Reason);
            if ( Value ~= "ALL" || Value ~= "TEAM" ) {
                Value = "TEAM";
                VoteInfo = "Blame Team";
                VotingHandler.VotedPlayer = none;
            }
            else {
                if ( Value ~= "ME" )
                    VotingHandler.VotedPlayer = Sender;
                else
                    VotingHandler.VotedPlayer = FindPlayer(Value);
                if ( VotingHandler.VotedPlayer == none ) {
                    Sender.ClientMessage(strPlayerNotFound);
                    SendPlayerList(Sender);
                    return VOTE_ILLEGAL;
                }
                Value = Mut.ColoredPlayerName(VotingHandler.VotedPlayer.PlayerReplicationInfo);
                VoteInfo = "Blame " $ Value;
            }
        }
        LastBlameVoteTime = Level.TimeSeconds;
        result = VOTE_BLAME;
    }
    else if ( Key == "SPEC" ) {
        if ( Level.Game.AccessControl == none
                || Level.Game.NumSpectators > Level.Game.MaxSpectators
                || (!Mut.bAllowKickVote && !Sender.PlayerReplicationInfo.bAdmin) )
        {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }

        if ( Value == "" ) {
            SendPlayerList(Sender);
            return VOTE_LOCAL;
        }

        Reason = "";
        Divide(Value, " ", Value, Reason);
        VotingHandler.VotedPlayer = FindPlayer(Value);
        if ( VotingHandler.VotedPlayer == none ) {
            Sender.ClientMessage(strPlayerNotFound);
            SendPlayerList(Sender);
            return VOTE_ILLEGAL;
        }
        Value = Mut.ColoredPlayerName(VotingHandler.VotedPlayer.PlayerReplicationInfo);
        VoteInfo = "Spectate " $ Value;
        result = VOTE_SPEC;
    }
    else if ( Key == "KICK" ) {
        if ( Level.Game.AccessControl == none || (!Mut.bAllowKickVote && !Sender.PlayerReplicationInfo.bAdmin) ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }

        if ( Value == "" ) {
            SendPlayerList(Sender);
            return VOTE_LOCAL;
        }

        Reason = "";
        Divide(Value, " ", Value, Reason);
        VotingHandler.VotedPlayer = FindPlayer(Value);
        if ( VotingHandler.VotedPlayer == none ) {
            Sender.ClientMessage(strPlayerNotFound);
            SendPlayerList(Sender);
            return VOTE_ILLEGAL;
        }
        Value = Mut.ColoredPlayerName(VotingHandler.VotedPlayer.PlayerReplicationInfo);
        VoteInfo = "Kick " $ Value;
        result = VOTE_KICK;
    }
    else if ( Key == "INVITE" ) {
        if ( !Mut.CheckScrnGT(Sender) )
            return VOTE_LOCAL;

        if ( Value == "" ) {
            SendPlayerList(Sender);
            return VOTE_LOCAL;
        }

        Reason = "";
        Divide(Value, " ", Value, Reason);
        VotingHandler.VotedPlayer = FindPlayer(Value);
        if ( VotingHandler.VotedPlayer == none ) {
            Sender.ClientMessage(strPlayerNotFound);
            SendPlayerList(Sender);
            return VOTE_ILLEGAL;
        }
        else if ( Mut.ScrnGT.IsInvited(VotingHandler.VotedPlayer) ) {
            VotingHandler.VotedPlayer = none;
            return VOTE_NOEFECT;
        }
        Value = Mut.ColoredPlayerName(VotingHandler.VotedPlayer.PlayerReplicationInfo);
        VoteInfo = "Invite " $ Value;
        result = VOTE_INVITE;
    }
    else if ( Key == "BORING" ) {
        if ( Mut.bStoryMode ) {
            Sender.ClientMessage(strNotInStoryMode);
            return VOTE_LOCAL;
        }
        else if ( !Mut.bAllowBoringVote && !Sender.PlayerReplicationInfo.bAdmin ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_LOCAL;
        }
        else if ( Mut.KF.bTradingDoorsOpen || Mut.KF.KFLRules.WaveSpawnPeriod < 0.5 ) {
            Sender.ClientMessage(strNotAvaliableATM);
            return VOTE_LOCAL;
        }

        Value = "";
        VoteInfo = "Game is BORING";
        result = VOTE_BORING;
    }
    else if ( Key == "SPAWN" ) {
        if ( Mut.bStoryMode && !Mut.bBeta ) {
            Sender.ClientMessage(strNotInStoryMode);
            return VOTE_NOEFECT;
        }
        if ( Mut.Squads.Length == 0 ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }
        if ( Value == "" ) {
            SendSquadList(Sender);
            return VOTE_LOCAL;
        }
        if ( Mut.FindSquad(Value) == -1 ) {
            Sender.ClientMessage(strSquadNotFound);
            return VOTE_ILLEGAL;
        }
        if ( Mut.KF.TotalMaxMonsters < 10 ) {
            Sender.ClientMessage(strCantSpawnSquadNow);
            return VOTE_NOEFECT;
        }
        result = VOTE_SPAWN;
    }
    else if ( Key == "ENDWAVE" || (Key == "END" && Value == "WAVE") ) {
        VotingHandler.VotedTeam = Sender.PlayerReplicationInfo.Team;
        if ( VotingHandler.VotedTeam == none )
            return VOTE_ILLEGAL;

        if ( Mut.KF.NumMonsters == 0 )
            return VOTE_NOEFECT;

        if ( Mut.MaxVoteKillMonsters == 0 ) {
            Sender.ClientMessage(strOptionDisabled);
            return VOTE_NOEFECT;
        }
        if ( Mut.KF.TotalMaxMonsters > 0 || Mut.KF.NumMonsters > Mut.MaxVoteKillMonsters
                || Mut.GameRules.bFinalWave || !CanEndWave() )
        {
            Sender.ClientMessage(strCantEndWaveNow);
            return VOTE_NOEFECT;
        }
        return VOTE_ENDWAVE;
    }
    else if ( Key == "READY" ) {
        if ( !Level.Game.bWaitingToStartMatch )
            return VOTE_NOEFECT;

        return VOTE_READY;
    }
    else if ( Key == "UNREADY" ) {
        if ( !Level.Game.bWaitingToStartMatch )
            return VOTE_NOEFECT;

        return VOTE_UNREADY;
    }
    else if ( Key == "LOCKTEAM" || Key == "LOCKTEAMS" ) {
        if ( !Mut.CheckScrnGT(Sender) )
            return VOTE_LOCAL;
        else if ( Mut.bTeamsLocked )
            return VOTE_NOEFECT;
        else if ( !Sender.PlayerReplicationInfo.bAdmin && (Mut.ScrnGT.WaveNum+1) < Mut.ScrnGT.RelativeWaveNum(Mut.LockTeamMinWave) ) {
            Sender.ClientMessage(strNotAvaliableATM);
            return VOTE_LOCAL;
        }
        return VOTE_TEAMLOCK;
    }
    else if ( Key == "UNLOCKTEAM" || Key == "UNLOCKTEAMS" ) {
        if ( !Mut.CheckScrnGT(Sender) )
            return VOTE_LOCAL;
        else if ( !Mut.bTeamsLocked )
            return VOTE_NOEFECT;
        return VOTE_TEAMUNLOCK;
    }
    else if ( Key == "FF" ) {
        if ( Mut.bTSCGame ) {
            Sender.ClientMessage(strNotInTSC);
            return VOTE_NOEFECT;
        }
        if ( Level.GRI.bMatchHasBegun ) {
            Sender.ClientMessage(strNotAvaliableATM);
            return VOTE_LOCAL;
        }
        if ( right(Value, 1) == "%" )
            Value = left(Value, len(Value)-1);
        if ( !TryStrToInt(Value, v) || v < Mut.MinVoteFF || v > Mut.MaxVoteFF )
            return VOTE_ILLEGAL;
        if ( v == int(Mut.KF.FriendlyFireScale*100) )
            return VOTE_NOEFECT;
        if ( v == 0 )
            VoteInfo = "Friendly Fire OFF";
        else
            VoteInfo = "Friendly Fire "$v$"%";
        return VOTE_FF;
    }
    else if ( Key == "MAP" && Value == "RESTART" ) {
        result = VOTE_MAPRESTART;
    }
    else if ( Key == "FAKED" || Key == "FAKEDPLAYERS" ) {
        if ( !Mut.CheckScrnGT(Sender) )
            return VOTE_LOCAL;
        if ( !TryStrToInt(Value, v) || v < 0 || v > 32 )
            return VOTE_ILLEGAL;
        if ( Mut.ScrnGT.ScrnGRI.FakedPlayers == v && Mut.ScrnGT.ScrnGRI.FakedAlivePlayers == v )
            return VOTE_NOEFECT;

        if ( v <= 1 )
            VoteInfo = "Faked Players OFF";
        else
            VoteInfo = v $ " Faked Players";

        return VOTE_FAKEDPLAYERS;
    }
    else if ( Key == "FAKEDCOUNT" ) {
        if ( !Mut.CheckScrnGT(Sender) )
            return VOTE_LOCAL;
        if ( !TryStrToInt(Value, v) || v < 0 || v > 32 )
            return VOTE_ILLEGAL;
        if ( Mut.ScrnGT.ScrnGRI.FakedPlayers == v )
            return VOTE_NOEFECT;

        if ( v <= 1 )
            VoteInfo = "Faked Zed Count OFF";
        else
            VoteInfo = v $ "p Faked Zed Count";

        return VOTE_FAKEDCOUNT;
    }
    else if ( Key == "FAKEDHEALTH" ) {
        if ( !Mut.CheckScrnGT(Sender) )
            return VOTE_LOCAL;
        if ( !TryStrToInt(Value, v) || v < 0 || v > 32 )
            return VOTE_ILLEGAL;
        if ( Mut.ScrnGT.ScrnGRI.FakedAlivePlayers == v )
            return VOTE_NOEFECT;

        if ( v <= 1 )
            VoteInfo = "Faked Zed Health OFF";
        else
            VoteInfo = v $ "p Faked Zed Health";

        return VOTE_FAKEDHEALTH;
    }
    else if ( Key == "DIFF" || Key == "DIFFICULTY" ) {
        if ( Value == "" ) {
            Sender.ClientMessage("Usage: MVOTE DIFF DEFAULT|NORMAL|HARD|SUI|SUI+|HOE|HOE+");
            return VOTE_LOCAL;
        }
        v = GetDifficulty(Value);
        if ( v != 0 && v < Mut.MinVoteDifficulty || v > 8 )
            return VOTE_ILLEGAL;
        if ( v == Mut.Persistence.Difficulty )
            return VOTE_NOEFECT;
        VoteInfo = Value @ viDifficulty;
        Value = string(v);
        return VOTE_DIFF;
    }
    else if ( Key == "R_KILL" ) {
        if ( !Sender.PlayerReplicationInfo.bAdmin || Mut.SrvTourneyMode == 0 ) {
            Sender.ClientMessage(strRCommands);
            return VOTE_LOCAL;
        }

        if ( Value == "" ) {
            SendPlayerList(Sender);
            return VOTE_LOCAL;
        }

        Reason = "";
        Divide(Value, " ", Value, Reason);
        VotingHandler.VotedPlayer = FindPlayer(Value);
        if ( VotingHandler.VotedPlayer == none ) {
            Sender.ClientMessage(strPlayerNotFound);
            SendPlayerList(Sender);
            return VOTE_ILLEGAL;
        }
        Value = Mut.ColoredPlayerName(VotingHandler.VotedPlayer.PlayerReplicationInfo);
        VoteInfo = "Referee KILL " $ Value;
        result = VOTE_RKILL;
    }
    else
        return VOTE_UNKNOWN;

    return result;
}


function ApplyVoteValue(int VoteIndex, string VoteValue)
{
    local class<ScrnVeterancyTypes> Perk;
    local ScrnCustomPRI ScrnPRI;

    switch ( VoteIndex ) {
        case VOTE_PERKLOCK: case VOTE_PERKUNLOCK:
            Perk = FindPerkByName(VotingHandler.VoteInitiator, VoteValue);
            if ( Perk != none ) {
                Mut.LockPerk(Perk, VoteIndex == VOTE_PERKLOCK);
            }
            break;
        case VOTE_PAUSE:
            if (Level.Pauser == none ) {
                //pause game
                if ( Mut.bPauseTraderOnly && !Mut.KF.bTradingDoorsOpen && Mut.KF.IsInState('MatchInProgress') ) {
                    VotingHandler.BroadcastMessage(strPauseTraderOnly);
                    return;
                }
                PauseTime = int(VoteValue);
                if ( PauseTime <= 0 )
                    PauseTime = 60;
                GotoState('GamePaused', 'Begin');
                //Enable('Tick');
            }
            else {
                //resume game
                Level.Pauser = none;
                VotingHandler.BroadcastMessage(strGameUnpaused);
                //Disable('Tick');
            }
            break;
        case VOTE_ENDTRADE:
            if ( Mut.KF.bWaveInProgress || Mut.KF.WaveCountDown < 6 ) {
                VotingHandler.BroadcastMessage(strTooLate);
                return;
            }
            Mut.TradeTimeAddSeconds = max(float(Mut.KF.WaveCountDown - 5) * Mut.SkippedTradeTimeMult, 0);
            Mut.KF.WaveCountDown = 6; // need to left at least 6 to execute kfgametype.timer() events
            break;
        case VOTE_BLAME:
            if ( VotingHandler.VotedPlayer != none )
                VoteValue = Mut.ColoredPlayerName(VotingHandler.VotedPlayer.PlayerReplicationInfo);
            LastBlameVoteTime = Level.TimeSeconds;
            if ( Reason == "" )
                VotingHandler.BroadcastMessage(VoteValue $ " blamed");
            else
                VotingHandler.BroadcastMessage(VoteValue $ " blamed for " $Reason);

            //achievement
            if ( IsGoodReason(Reason) && VotingHandler.VoteInitiator != none
                    && SRStatsBase(VotingHandler.VoteInitiator.SteamStatsAndAchievements) != none )
            {
                class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
                    SRStatsBase(VotingHandler.VoteInitiator.SteamStatsAndAchievements).Rep, 'Blame55p', 1);
                if ( VotingHandler.VotedPlayer == VotingHandler.VoteInitiator )
                    class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
                        SRStatsBase(VotingHandler.VoteInitiator.SteamStatsAndAchievements).Rep, 'BlameMe', 1);
                else if ( ScrnPlayerController(VotingHandler.VotedPlayer) != none && ScrnPlayerController(VotingHandler.VotedPlayer).BeggingForMoney >= 3 )
                {
                    class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
                        SRStatsBase(VotingHandler.VoteInitiator.SteamStatsAndAchievements).Rep, 'SellCrap', 1);
                    VotingHandler.VotedPlayer.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnFakedAchMsg', 1);
                    ScrnPlayerController(VotingHandler.VotedPlayer).BeggingForMoney = 0;
                }
            }

            if ( VotingHandler.VotedPlayer != none ) {
                ScrnPRI = class'ScrnCustomPRI'.static.FindMe(VotingHandler.VotedPlayer.PlayerReplicationInfo);
                if ( ScrnPRI != none ) {
                    VotingHandler.VotedPlayer.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnBlamedMsg', ScrnPRI.BlameCounter++); // more blame = bigger shit

                    //achievement
                    if ( ScrnPRI.BlameCounter == 5
                            && SRStatsBase(VotingHandler.VotedPlayer.SteamStatsAndAchievements) != none )
                        class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
                            SRStatsBase(VotingHandler.VotedPlayer.SteamStatsAndAchievements).Rep, 'MaxBlame', 1);
                }
                else
                    VotingHandler.VotedPlayer.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnBlamedMsg');
            }

            if ( VoteValue ~= "TEAM" || VoteValue ~= "ALL" ) {
                BlameAll();
                //achievement
                if ( IsGoodReason(Reason) && VotingHandler.VoteInitiator != none
                        && SRStatsBase(VotingHandler.VoteInitiator.SteamStatsAndAchievements) != none )
                    class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
                        SRStatsBase(VotingHandler.VoteInitiator.SteamStatsAndAchievements).Rep, 'BlameTeam', 1);
            }
            else if ( VoteValue ~= "Baron" || (VotingHandler.VotedPlayer != none && VotingHandler.VotedPlayer.GetPlayerIDHash() == "76561198006289592") ) {
                Mut.BroadcastFakedAchievement(0); // blame Baron :)
                // blame the one who blamed baron
                if ( VotingHandler.VoteInitiator != none && VotingHandler.VotedPlayer != VotingHandler.VoteInitiator ) {
                    VotingHandler.VotedPlayer = VotingHandler.VoteInitiator;
                    Reason = "Blaming Baron";
                    ApplyVoteValue(VoteIndex, VoteValue);
                }
            }
            else if ( VoteValue ~= "TWI" || VoteValue ~= "Tripwire" ) {
                Mut.BroadcastFakedAchievement(3); // blame Tripwire :)
            }
            else
                BlameMonster(VoteValue);

            break;
        case VOTE_SPEC:
            if ( VotingHandler.VotedPlayer != none && !VotingHandler.VotedPlayer.PlayerReplicationInfo.bAdmin ) {
                if ( Mut.ScrnGT != none )
                    Mut.ScrnGT.UninvitePlayer(VotingHandler.VotedPlayer);
                VotingHandler.VotedPlayer.BecomeSpectator();
            }
            break;
        case VOTE_KICK:
            if ( VotingHandler.VotedPlayer != none && !VotingHandler.VotedPlayer.PlayerReplicationInfo.bAdmin && NetConnection(VotingHandler.VotedPlayer.Player)!=None ) {
                if ( Mut.ScrnGT != none )
                    Mut.ScrnGT.UninvitePlayer(VotingHandler.VotedPlayer);

                if ( Reason == "" ) {
                    VotingHandler.BroadcastMessage(VoteValue $ " kicked");
                    VotingHandler.VotedPlayer.ClientNetworkMessage("AC_Kicked", "Team Vote");
                }
                else {
                    VotingHandler.BroadcastMessage(VoteValue $ " kicked for " $Reason);
                    VotingHandler.VotedPlayer.ClientNetworkMessage("AC_Kicked", Reason);
                }

                if (VotingHandler.VotedPlayer.Pawn != none && Vehicle(VotingHandler.VotedPlayer.Pawn) == none)
                    VotingHandler.VotedPlayer.Pawn.Destroy();
                if (VotingHandler.VotedPlayer != None)
                    VotingHandler.VotedPlayer.Destroy();
            }
            break;
        case VOTE_BORING:
            Mut.KF.KFLRules.WaveSpawnPeriod *= 0.5;
            VotingHandler.BroadcastMessage(strZedSpawnsDoubled $ " ("$Mut.KF.KFLRules.WaveSpawnPeriod$")");
            break;

        case VOTE_SPAWN:
            Mut.SpawnSquad(VoteValue);
            break;

        case VOTE_ENDWAVE:
            if ( Mut.KF.TotalMaxMonsters > 0 || Mut.KF.NumMonsters > Mut.MaxVoteKillMonsters
                    || Mut.GameRules.bFinalWave )
            {
                return;
            }
            DoEndWave();
            break;
        case VOTE_READY:
            SetReady(VotingHandler.VotedTeam.TeamIndex, true);
            break;
        case VOTE_UNREADY:
            SetReady(VotingHandler.VotedTeam.TeamIndex, false);
            break;
        case VOTE_TEAMLOCK:
            Mut.ScrnGT.LockTeams();
            break;
        case VOTE_TEAMUNLOCK:
            Mut.ScrnGT.UnlockTeams();
            break;
        case VOTE_INVITE:
            Mut.ScrnGT.InvitePlayer(VotingHandler.VotedPlayer);
            break;
        case VOTE_FF:
            Mut.KF.FriendlyFireScale = float(VoteValue)/100.0;
            if ( TSCGame(Mut.KF) != none )
                TSCGame(Mut.KF).HdmgScale = Mut.KF.FriendlyFireScale;
            break;
        case VOTE_MAPRESTART:
            Level.ServerTravel("?restart",false);
            break;
        case VOTE_FAKEDPLAYERS:
            Mut.ScrnGT.ScrnGRI.FakedPlayers = byte(VoteValue);
            Mut.ScrnGT.ScrnGRI.FakedAlivePlayers = byte(VoteValue);
            break;
        case VOTE_FAKEDCOUNT:
            Mut.ScrnGT.ScrnGRI.FakedPlayers = byte(VoteValue);
            break;
        case VOTE_FAKEDHEALTH:
            Mut.ScrnGT.ScrnGRI.FakedAlivePlayers = byte(VoteValue);
            break;
        case VOTE_DIFF:
            Mut.Persistence.Difficulty = byte(VoteValue);
            Mut.Persistence.SaveConfig();
            break;

        case VOTE_RKILL:
            if ( VotingHandler.VotedPlayer != none && VotingHandler.VotedPlayer.Pawn != none ) {
                VotingHandler.VotedPlayer.Pawn.Suicide();
                if ( Reason == "" ) {
                    VotingHandler.BroadcastMessage(VoteValue $ " killed by referee");
                }
                else {
                    VotingHandler.BroadcastMessage(VoteValue $ " killed by referee for " $Reason);
                }
            }
            break;
    }
}

function SetReady(byte TeamIndex, bool bReady)
{
    local Controller C;

    for ( C = Level.ControllerList; C != None; C = C.NextController )
    {
        if ( C.PlayerReplicationInfo != none && C.bIsPlayer && PlayerController(C) != none
                && C.PlayerReplicationInfo.Team != none
                && C.PlayerReplicationInfo.bWaitingPlayer && !C.PlayerReplicationInfo.bOnlySpectator )
        {
            C.PlayerReplicationInfo.bReadyToPlay = bReady;
        }
    }
}

function class<KFMonster> Str2Monster(string MonsterName)
{
    MonsterName = caps(MonsterName);

    if ( MonsterName == "CLOT" )
        return class'ZombieClot';
    if ( MonsterName == "CRAWLER" )
        return class'ZombieCrawler';
    if ( MonsterName == "STALKER" )
        return class'ZombieStalker';
    if ( MonsterName == "BLOAT" )
        return class'ZombieBloat';
    if ( MonsterName == "GOREFAST" )
        return class'ZombieGorefast';
    if ( MonsterName == "SIREN" )
        return class'ZombieSiren';
    if ( MonsterName == "HUSK" )
        return class'ZombieHusk';
    if ( MonsterName == "SCRAKE" || MonsterName == "SC" )
        return class'ZombieScrake';
    if ( MonsterName == "FLESHPOUND" || MonsterName == "FP" )
        return class'ZombieFleshpound';
    if ( MonsterName == "PAT" || MonsterName == "PATRIARCH" || MonsterName == "BOSS" )
        return class'ZombieBoss';

    return none;
}

function BlameAll()
{
    local Controller P;
    local ScrnPlayerController Player;
    local ScrnCustomPRI ScrnPRI;

    for ( P = Level.ControllerList; P != none; P = P.nextController ) {
        Player = ScrnPlayerController(P);
        if ( Player != none ) {
            ScrnPRI = class'ScrnCustomPRI'.static.FindMe(Player.PlayerReplicationInfo);
            if ( ScrnPRI != none ) {
                Player.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnBlamedMsg', ScrnPRI.BlameCounter++); // more blame = bigger shit
                if ( Player.PlayerReplicationInfo != none && Player.PlayerReplicationInfo.PlayerName ~= "Baron" )
                    Mut.BroadcastFakedAchievement(0); // blame Baron :)
            }
        }
    }
}


// returns false if monster not found or can't be blamed
function bool BlameMonster(String MonsterName)
{
    local class<KFMonster> MC;
    local Controller P;
    local ScrnPlayerController Player;

    MC = Str2Monster(MonsterName);
    // don't blame Stalkers and Patriarch, because shit on their head could reveal
    // their positions
    if ( MC == none || MC == class'ZombieStalker' ||  MC == class'ZombieBoss' )
        return false;

    for ( P = Level.ControllerList; P != none; P = P.nextController ) {
        Player = ScrnPlayerController(P);
        if ( Player != none ) {
            Player.ClientMonsterBlamed(MC);
        }
    }
    return true;
}

static function bool IsGoodReason(string Reason)
{
    local string a, b;

    // good reason contains from at least 2 words, one of them is at least 6 character long
    // "n00b" and "screw you" aren't good reasons :)
    return Divide(Reason, " ", a, b) && ( len(a) >= 6 || len(b) >= 6);
}

function bool CanEndWave()
{
    local Controller C;
    local MonsterController MC;
    local Monster M;
    local KFHumanPawn P;
    local int TotalHP;

    for ( C = Level.ControllerList; C != None; C = C.NextController ) {
        MC = MonsterController(C);
        M = Monster(C.Pawn);
        if ( MC==none || M == none )
            continue;

        TotalHP += M.default.Health;

        if ( Mut.bVoteKillCheckVisibility ) {
            foreach VisibleCollidingActors(class'KFHumanPawn', P, 1000) {
                if ( P.Health > 0 && P.Controller != none && P.Controller.bIsPlayer && KF_StoryNPC(P) == none && MC.CanSee(P) )
                    return false;
            }
        }
    }
    return TotalHP <= Mut.MaxVoteKillHP;
}

function DoEndWave()
{
    local Monster M;
    local int Penalty, TotalPenalty;

    foreach DynamicActors(class'Monster', M) {
        Penalty = max(M.ScoringValue * Mut.VoteKillPenaltyMult, 0);
        VotingHandler.VotedTeam.Score -= Penalty;
        TotalPenalty += Penalty;
        M.KilledBy(M);
    }
    if ( TotalPenalty > 0 )
        VotingHandler.BroadcastMessage(strEndWavePenalty $ TotalPenalty);
}

function byte GetDifficulty(out string DiffStr)
{
    if ( DiffStr == "0" || DiffStr == "DEFAULT" || DiffStr == "OFF" ) {
        DiffStr = "Default";
        return 2;
    }
    else if ( DiffStr == "2" || DiffStr == "NORMAL" ) {
        DiffStr = "Normal";
        return 2;
    }
    else if ( DiffStr == "4" || DiffStr == "HARD" ) {
        DiffStr = "Hard";
        return 4;
    }
    else if ( DiffStr == "5" || DiffStr == "SUICIDAL" || DiffStr == "SUI" ) {
        DiffStr = "Suicidal";
        return 5;
    }
    else if ( DiffStr == "6" || DiffStr == "SUICIDAL+" || DiffStr == "SUI+" ) {
        DiffStr = "Suicidal+";
        return 6;
    }
    else if ( DiffStr == "7" || DiffStr == "HELLONEARTH" || DiffStr == "HOE" ) {
        DiffStr = "HoE";
        return 7;
    }
    else if ( DiffStr == "8" || DiffStr == "HELLONEARTH+" || DiffStr == "HOE+" ) {
        DiffStr = "HoE+";
        return 8;
    }
    return 255;
}

state GamePaused
{
Begin:
    Level.Pauser = VotingHandler.VoteInitiator.PlayerReplicationInfo;

    if ( Level.Pauser != none ) {
        // tell players that game is paused
        msgPause = strGamePaused;
        ReplaceText(msgPause, "%s", string(PauseTime));
        VotingHandler.BroadcastMessage(msgPause);

        // wait for pause time ends or game resumes by other source (e.g. admin)
        while ( PauseTime > 0 && Level.Pauser != none ) {
            if ( PauseTime <= 5 )
                VotingHandler.BroadcastMessage(String(PauseTime));
            else if ( (PauseTime%30 == 0) || (PauseTime < 30 && PauseTime%10 == 0) )
                VotingHandler.BroadcastMessage(PauseTime @ strSecondsLeft);
            //log(Level.TimeSeconds @ "Sleep", 'ScrnBalance');
            sleep(1.0);
            PauseTime--;
        }
        // resume game after pause time ends
        if ( Level.Pauser != none ) {
            Level.Pauser = none;
            VotingHandler.BroadcastMessage(strGameUnpaused);
        }
    }
    GotoState('');
}

defaultproperties
{
     strCantEndTrade="Can not end trade time at the current moment"
     strTooLate="Too late"
     strGamePaused="Game paused for %s seconds"
     strSecondsLeft="seconds left"
     strGameUnPaused="Game resumed"
     strPauseTraderOnly="Game can be paused only during trader time!"
     viResume="RESUME GAME"
     viEndTrade="END TRADER TIME"
     viDifficulty="Difficulty"
     strZedSpawnsDoubled="ZED spawn rate doubled!"
     strSquadNotFound="Monster squad with a given name not found"
     strCantSpawnSquadNow="Can not spawn monsters at this moment"
     strSquadList="Avaliable Squads:"
     strNotInStoryMode="Not avaliable in Story Mode"
     strNotInTSC="Not avaliable in TSC"
     strCantEndWaveNow="Can't end the wave now"
     strEndWavePenalty="Team charged for premature wave end with $"
     strRCommands="R_* commands can be executed only by Referee (Admin rights + Tourney Mode)"
     HelpInfo(0)="%gLOCKPERK%w|%gUNLOCKPERK %y<perk_name> %w Disables/Enables perk at the end of the wave"
     HelpInfo(1)="%gLOCKTEAM%w|%gUNLOCKTEAM %w Locks/Unlocks teams. Only invited players may join locked team."
     HelpInfo(2)="%gPAUSE %yX %w Pause the game for X seconds"
     HelpInfo(3)="%gEND TRADE %w Immediately end current trader time and start next wave"
     HelpInfo(4)="%gEND WAVE %w Kills last stuck zeds to end the wave"
     HelpInfo(5)="%gBLAME %y<player_name> %b[<reason>] %w Blame player [for the <reason>]"
     HelpInfo(6)="%gSPEC %y<player_name> %b[<reason>] %w Move player to spectators"
     HelpInfo(7)="%gKICK %y<player_name> %b[<reason>] %w Kick player [for the <reason>]"
     HelpInfo(8)="%gINVITE %y<player_name> %w Invite player to join locked team."
     HelpInfo(9)="%gBORING %w Doubles ZED spawn rate"
     HelpInfo(10)="%gSPAWN %y<squad_name> %w Spawns zed squad"
     HelpInfo(11)="%gREADY%w|%gUNREADY %w Makes everybody ready/unready to play"
     HelpInfo(12)="%gFF %yX %w Set Friendly Fire to X%"
     HelpInfo(13)="%gMAP RESTART %w Restarts current map"
     HelpInfo(14)="%gFAKED %yX %w Set Faked Players to X (FAKEDCOUNT+FAKEDHEALTH)"
     HelpInfo(15)="%gFAKEDCOUNT %yX %w Set Faked Players for zed count calculation"
     HelpInfo(16)="%gFAKEDHEALTH %yX %w Set Faked Players for zed health calculation"
     HelpInfo(17)="%gDIFF %yX %w Changes map difficulty (2-8) for the next map"
     bAlwaysTick=True
}
