/*
        Dessloks's Friendly Fire Monitor (FFMon)
        Version 1.30

        See readme, changelog and howto for more information.
*/

#pragma dynamic  24576 // 16384

#include <core>
#include <console>
#include <string>
#include <admin>
#include <adminlib>

#define ACCESS_FFMON          ACCESS_BAN

#define ACTION_NONE           0
#define ACTION_KICK           1
#define ACTION_BAN            2
#define ACTION_SLAP           4
#define ACTION_BURY           8
#define ACTION_CHICKEN       16
#define ACTION_OVERRIDE_BAN  32
#define ACTION_SLAY          64

/* If you want to use adminmod to change to the next map when a skunk is detected
   set this define to 1.  Using adminmod for changing to the nextmap seems to get the
   map cycle out of order.  The alternative is to set a server cvar that makes the engine
   think the round is over and switch to the next map.
*/
#define FORCE_NEXTMAP_ADMINMOD 0

/* If you want to turn the debugging on, which logs to the server console */
/* and to your log files, set this to 1.  0 is off. */
#define DEBUG 0

/* To use adminmod's beta menu feature, set this to 1.  For adminmod 2.50.50 you need
   to put amv_enable_beta "menu 1" in your adminmod.cfg or server.cfg file.
   Set to 0 to use the old experimental method. */
#define USE_ADMINMOD_MENUS 1


#define ACCESS_CONSOLE 131072

new STRING_VERSION[MAX_DATA_LENGTH] = "$Revision: 1.30 beta$";

#define TD_CLEAN_SLATE  0
#define TK_CLEAN_SLATE  0

#define USER_ID_INVALID -1

#define SKUNK_TYPE_NORMAL       0
#define SKUNK_TYPE_TRADITIONAL  1

new g_TdCount[MAX_PLAYERS + 1]   = {TD_CLEAN_SLATE,...};
new g_TkCount[MAX_PLAYERS + 1]   = {TK_CLEAN_SLATE,...};
new g_tkedby[MAX_PLAYERS + 1]    = {USER_ID_INVALID,...};
new g_Chickened[MAX_PLAYERS + 1] = {0};

// TK revenge action stored here if the attacker is dead
// revenge is taken out the start of the next round
new g_TkRevenge[MAX_PLAYERS + 1] = {ACTION_NONE};

new g_RoundStart     = 0;
new g_RoundSpawn     = 0;
new g_RoundStartTimer= 0;
new g_VoteStarted    = 0;
new g_CtScore        = 0;
new g_TScore         = 0;
new g_DebugMessages  = 0;

new g_LastTkBan[MAX_AUTHID_LENGTH];

/* Menu defines and variables */
#define MAX_TK_OPTIONS        10

#define MENU_STATE_OPEN       0
#define MENU_STATE_KILLED     1

new g_TkMenuAction[MAX_TK_OPTIONS + 1][MAX_TEXT_LENGTH];
new g_MenuStates[MAX_PLAYERS + 1][3];

/*
*
*  Configuration variables available through commands and their default values
*
*/
new g_Enabled               = 1;     /* Everything enabled by default */
new g_TkProtection          = 1;     /* TK Protection on/off */
new g_TkLimit               = 3;     /* TK limit before banning */
new g_TkLimitBanTime        = 15;    /* amount of time to ban a TK abuser */
new g_TkMenu                = 1;     /* show the tk menu */
new g_TkReset               = 0;     /* reset TK counts at end of each round */

new g_TkSave                = 0;     /* save TKs? */
new g_TkSaveTime            = 24;    /* number of hours to keep track of TKs */
new g_TkSaveLimit           = 9;     /* how many TKs allowed in TkSaveTime */
new g_TkSaveBanTime         = 1440;  /* how long to ban when TkLimitSaved reached (def: 1440 min = 24 hours) */
new g_TkSavePurgeTime       = 24;    /* how many hours between periodic purges of tk data */

new g_TdProtection          = 1;     /* TD protection on/off */
new g_TdLimit               = 7;     /* TD limit before = 1 TK */
new g_TdAction              = ACTION_SLAY; /* action for td abusers */

new g_AllowPunish           = 76;    /* allow kick, ban, slap, bury and overrideban, chicken off by default */
new g_UserSlapCount         = 1;     /* number of times to slap when using slaptk */
new g_UserOverrideLevel     = 0;     /* access level required to override--by default everybody can */
new g_BuryGlowTime          = 30;    /* amount of time to glow after burying somebody */

new g_RoundStartTime        = 8;    /* amount of time in seconds to special check for TD/TK */
new g_RoundStartBanTime     = 30;   /* ban time for TKs in start of round */
new g_RoundStartBanTk       = 1;     /* whether or not to ban TKers in start of round */
new g_RoundStartTdSlap      = 1;     /* whether or not to slap TD during start of round */
new g_RoundStartTdSlapTimes = 5;     /* number of times to slap a TDer in start of round */
new g_RoundStartTdAction    = ACTION_BAN; /* action to take against TD abusers during round start */
new g_RoundStartTdCount     = 3;     /* TD limit during round start before action kicks in */
new g_RoundStartTdBanTime   = 2;     /* amount of time to ban somebody who TDs for TdCount if ACTION_BAN */

new g_SpawnShotProtection   = 0;     /* whether or not to use spawn shot protection */
new g_SpawnShotAction       = ACTION_KICK; /* action to take against spawn shooters */
new g_SpawnShotTime         = 2;     /* number of seconds to protect spawn shooting */
new g_SpawnShotBanTime      = 10;    /* amount of time to ban spawn shooters */

new g_SkunkProtection       = 0;     /* switch to next map if one team dominates */
new g_SkunkType             = SKUNK_TYPE_NORMAL;     /* determine type of skunk to detect */
new g_SkunkLimit            = 10;    /* number of wins difference to consider a skunk */

new g_Immunity              = 0;     /* admin immunity */
new g_ImmunityLevel         = 65536; /* immunity level */

new g_DisplayScores         = 0;     /* display scores at the end of each round */
new g_LogMessages           = 1;     /* turn off FFMon message logging to log files */
new g_DisplayConsgreet      = 0;     /* turn on/off console greeting at connect */
new g_BlockAttackMessages   = 1;     /* block detailed attack messages from being written to the log files */

new g_StartVote             = 0;     /* start a vote before mp_winlimit */
new g_StartVoteRound        = 0;     /* number of rounds before mp_winlimit to start a vote */

#if DEBUG == 1
new g_HeapStart = 0;
#endif

/*
*
*
*  Helper functions
*
*/

HeapCheckStart()
{
    #if DEBUG == 1
    g_HeapStart = heapspace();
    #endif
}

HeapCheckStop(sFunctionName[])
{
    strlen(sFunctionName);

    #if DEBUG == 1
    new sDebugMessage[MAX_DATA_LENGTH];
    new iHeap2;
    iHeap2 = heapspace();
    snprintf (sDebugMessage,
              MAX_DATA_LENGTH,
              "%s: Heapspace started with %i, ended with %i.",
              sFunctionName,
              g_HeapStart,
              iHeap2);
    LogMessage(sDebugMessage);
    #endif
}

DebugMessage(sMessage[])
{
    if (g_DebugMessages)
    {
        LogMessage(sMessage);
    }
}

LogMessage(sMessage[])
{
    new sRealMessage[MAX_DATA_LENGTH];

    if (g_LogMessages)
    {
        snprintf(sRealMessage, MAX_DATA_LENGTH, "[FFMon] %s", sMessage);
        log(sRealMessage);
    }
}

SayMessage(sMessage[])
{
    new sRealMessage[MAX_DATA_LENGTH];

    snprintf(sRealMessage, MAX_DATA_LENGTH, "<FFMon> %s", sMessage);

    //typesay( sRealMessage, 10, 255, 255, 255 );
    say(sRealMessage);
}

IsImmune(sUser[])
{
    new iImmune = 0;

    if (g_Immunity)
    {
        if (access(g_ImmunityLevel, sUser) != 0)
        {
            iImmune = 1;
        }
    }

    return iImmune;
}

//
// Generate the file name for this user
//
GetTkFileName(sWONID[], sFileName[])
{
    snprintf(sFileName, MAX_DATA_LENGTH, "ffmon/%s.tks", sWONID);
}

//
// remove the tk file for this WONID
//
RemoveTkFile(sWONID[])
{
    new sFileName[MAX_DATA_LENGTH];

    GetTkFileName(sWONID, sFileName);

    deletefile(sFileName);

    RemoveFromMasterTkFile(sWONID);
}

//
// This function should only be called after AddTk, RemoveTk
// or AgeTks.  That will ensure that the list is up to date
// since all this function does is get a line count
//
GetTkCount(sWONID[])
{
    new sFileName[MAX_DATA_LENGTH];

    GetTkFileName(sWONID, sFileName);

    return filesize(sFileName);
}

//
// Read in all currently active TKs for the given file
//
ReadTkInfo(sFileName[], iTks[], iStartTk, iFileOffset)
{
    new sData[MAX_DATA_LENGTH];
    new iCurLine = iFileOffset;
    new iCurTime = systemtime();
    new iCurPos = iStartTk;

    if (fileexists(sFileName))
    {
        while (readfile(sFileName, sData, iCurLine, MAX_DATA_LENGTH))
        {
            new iTkTime = strtonum(sData);
            if (iTkTime + (g_TkSaveTime * 60 * 60) > iCurTime)
            {
                // tk hasn't expired yet.  save
                iTks[iCurPos] = iTkTime;
            }
            else
            {
                // tks are in order in the file, so once we find one that is
                // expired, they all will be expired after this one.
                // so we are done
                break;
            }

            iCurLine++;
            iCurPos++;
        }
    }

    return iCurPos;
}

//
// returns true if user has reached the limit
//
AddTk(sWONID[])
{
    new sFileName[MAX_DATA_LENGTH];
    new iTks[MAX_DATA_LENGTH] = {0};
    new iCount = 0;

    if (g_TkSave)
    {
        GetTkFileName(sWONID, sFileName);

        // stick the new one in the head of the list
        iTks[0] = systemtime();

        // read the data from the file, starting at the first line
        iCount = ReadTkInfo(sFileName, iTks, 1, 1);

        // now save the TK info
        SaveTkInfo(sFileName, iTks, iCount);

        // add to the master list
        AddToMasterTkFile(sWONID);
    }
}

//
// Function removes the last TK from the file
//
RemoveTk(sWONID[])
{
    // we pass in 2 so that we skip the first entry in the file
    // the first entry in the file is always the most recent
    AgeTksEx(sWONID, 2);
}

AgeTks(sWONID[])
{
    // pass in 1 for the offset meaning to start with the first
    // entry in the file, not skipping any
    AgeTksEx(sWONID, 1);
}

//
// age the TKs in the file.  Offset specifies where to start
// in the file.
//
AgeTksEx(sWONID[], iOffset, iRemove = 1)
{
    new sFileName[MAX_DATA_LENGTH];
    new iTks[MAX_DATA_LENGTH] = {0};
    new iCount = 0;

    GetTkFileName(sWONID, sFileName);

    if (fileexists(sFileName))
    {
        // read the info from the file for active TKs
        iCount = ReadTkInfo(sFileName, iTks, 0, iOffset);

        SaveTkInfo(sFileName, iTks, iCount);

        if ( (iCount == 0) &&
             (iRemove) )
        {
            RemoveFromMasterTkFile(sWONID);
        }
    }
}

//
// Save off the TK info
// if there isn't any, remove the file
//
SaveTkInfo(sFileName[], iTks[], iCount)
{
    new sData[MAX_DATA_LENGTH];

    if (iCount > 0)
    {
        // clear the file
        resetfile(sFileName);

        // write out all the TKs for this ID
        for (new i = 0; i < iCount; i++)
        {
            snprintf(sData, MAX_DATA_LENGTH, "%d", iTks[i]);

            writefile(sFileName, sData);
        }
    }
    else
    {
        // no more TKs so just delete the file
        deletefile(sFileName);
    }
}

CheckPeriodicPurge()
{
    // maybe this should be a vault.ini entry
    new sVaultData[MAX_DATA_LENGTH];
    new iCurTime = systemtime();
    new iLastTime = iCurTime;

    if (get_vaultdata("FFMON_TKSAVELASTPURGE", sVaultData, MAX_DATA_LENGTH ) != 0)
    {
        iLastTime = strtonum(sVaultData);

        // check to see if the purge time has elapsed or the maximum
        // amount of time between purges has elapsed.  This check forces
        // a purge at least once a week.
        if ( ((iLastTime + (g_TkSavePurgeTime * 60 * 60)) < iCurTime) ||
             ((iLastTime + (7 * 24 * 60 * 60)) < iCurTime) )
        {
            // time to do a purge
            PurgeTkFiles();
            UpdateLastPurgeTime();
        }
    }
    else
    {
        // first time
        UpdateLastPurgeTime();
    }
}

UpdateLastPurgeTime()
{
    SetVaultInt("FFMON_TKSAVELASTPURGE", systemtime());
}

//
// Walk all of the tk files and age them... we do this
// once a day to try and keep the number of files down.
//
PurgeTkFiles()
{
    new sMasterFilename[MAX_TEXT_LENGTH];
    new sFilename[MAX_TEXT_LENGTH];
    new sData[MAX_DATA_LENGTH];
    new iCurLine = 1;
    new iCurTime = systemtime();
    new iTotal = 0;
    new iRemoved = 0;

    // get the current master file name
    GetMasterTkFileName(sMasterFilename);

    if (fileexists(sMasterFilename))
    {
        while (readfile(sMasterFilename, sData, iCurLine, MAX_DATA_LENGTH))
        {
            if (strlen(sData) > 0)
            {
                iTotal++;

                // get the filename for later
                GetTkFileName(sData, sFilename);

                // age the tks for this guy
                AgeTksEx(sData, 1, 0);

                // if the file is gone, then remove from the master list
                if (!fileexists(sFilename))
                {
                    writefile(sMasterFilename, "", iCurLine);
                    iRemoved++;
                }
            }

            iCurLine++;
        }
    }

    snprintf(sData,
             MAX_DATA_LENGTH,
             "%d of %d entries removed in %d seconds w/%d players online",
             iRemoved,
             iTotal,
             iCurTime - systemtime(),
             playercount());
    LogMessage(sData);
}

//
// add this WONID to the master file that keeps track of all the
// TK files we have.  We do this so we can purge the files at
// some later date.  The reason we need this stupid master file
// is because no functionality exists to walk the files in the
// directory on linux.
//
AddToMasterTkFile(sWONID[])
{
    new sFilename[MAX_TEXT_LENGTH];
    new sData[MAX_DATA_LENGTH];
    new iCurLine = 1;
    new iFound = 0;
    new iEmptyLine = -1;

    // get the current master file name
    GetMasterTkFileName(sFilename);

    if (fileexists(sFilename))
    {
        while (readfile(sFilename, sData, iCurLine, MAX_DATA_LENGTH))
        {
            new sMessage[MAX_DATA_LENGTH];

            if (streq(sData, sWONID))
            {
                // already in the file
                iFound = 1;
                break;
            }

            new iLen = strlen(sData);

            if ( (iLen == 0) &&
                 (iEmptyLine == -1) )
            {
                // found an open slot
                // save this line for later
                snprintf(sMessage, MAX_DATA_LENGTH, "AddToMasterTkFile - Found empty line %d", iCurLine);
                LogMessage(sMessage);

                iEmptyLine = iCurLine;
            }

            iCurLine++;
        }
    }

    // did not find anyplace for it, append
    if (!iFound)
    {
        DebugMessage("AddToMasterTkFile - new AuthID, adding to file");
        writefile(sFilename, sWONID, iEmptyLine);
    }
}

//
// remove a wonid from the file
// because file access is limited to a few functions
// we just set the line to be blank if we remove it
//
RemoveFromMasterTkFile(sWONID[])
{
    new sFilename[MAX_TEXT_LENGTH];
    new sData[MAX_DATA_LENGTH];
    new iCurLine = 1;

    // get the current master file name
    GetMasterTkFileName(sFilename);

    if (fileexists(sFilename))
    {
        while (readfile(sFilename, sData, iCurLine, MAX_DATA_LENGTH))
        {
            if (streq(sData, sWONID))
            {
                // already in the file
                writefile(sFilename, "", iCurLine);
                break;
            }

            iCurLine++;
        }
    }
}

GetMasterTkFileName(sFilename[])
{
    strcpy(sFilename, "ffmon\tks.def", MAX_TEXT_LENGTH);
}

#if USE_ADMINMOD_MENUS != 1
BindToMenuselect(iUserIndex)
{
    new sTarget[MAX_NAME_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];
    new iSessionID;
    new iTeam;
    new iWONID;
    new iDead;

    playerinfo(iUserIndex, sTarget, MAX_NAME_LENGTH, iSessionID, iWONID, iTeam, iDead, sAuthID);

    execclient(sTarget, "bind 1 ^"menuselect 1^";bind 2 ^"menuselect 2^";bind 3 ^"menuselect 3^"");
    execclient(sTarget, "bind 4 ^"menuselect 4^";bind 5 ^"menuselect 5^";bind 6 ^"menuselect 6^"");
    execclient(sTarget, "bind 7 ^"menuselect 7^";bind 8 ^"menuselect 8^";bind 9 ^"menuselect 9^"");
    execclient(sTarget, "bind 0 ^"menuselect 0^"");
}

BindToSlot(iUserIndex)
{
    new sTarget[MAX_NAME_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];
    new iSessionID;
    new iTeam;
    new iWONID;
    new iDead;

    playerinfo(iUserIndex, sTarget, MAX_NAME_LENGTH, iSessionID, iWONID, iTeam, iDead, sAuthID);

    execclient(sTarget, "bind 1 ^"slot1^";bind 2 ^"slot2^";bind 3 ^"slot3^"");
    execclient(sTarget, "bind 4 ^"slot4^";bind 5 ^"slot5^";bind 6 ^"slot6^"");
    execclient(sTarget, "bind 7 ^"slot7^";bind 8 ^"slot8^";bind 9 ^"slot9^"");
    execclient(sTarget, "bind 0 ^"slot10^"");
}
#endif

ResetMenu(iUserIndex)
{
    // reset some menu states
    g_MenuStates[iUserIndex][MENU_STATE_OPEN] = 0;
    g_MenuStates[iUserIndex][MENU_STATE_KILLED] = 0;
}

BuildTkMenu(sMenu[])
{
    new sMenuTemp[MAX_TEXT_LENGTH];
    new iMenuNum = 1;
    new iAction = 0;

    new mKeys[10] = {1,2,4,8,16,32,64,128,256,512};

    // forgive is always available
    snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Forgive^n", iMenuNum);
    strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);

    g_TkMenuAction[iMenuNum - 1] = "say forgive";
    iAction += mKeys[iMenuNum - 1];
    iMenuNum++;

    // slay
    if ((g_AllowPunish & ACTION_SLAY) == ACTION_SLAY)
    {
        snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Slay^n", iMenuNum);
        strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);

        g_TkMenuAction[iMenuNum - 1] = "say slay";
        iAction += mKeys[iMenuNum - 1];

        iMenuNum++;
    }

    if ((g_AllowPunish & ACTION_KICK) == ACTION_KICK)
    {
        snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Kick^n", iMenuNum);
        strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);

        g_TkMenuAction[iMenuNum - 1] = "say kick";
        iAction += mKeys[iMenuNum - 1];

        iMenuNum++;
    }

    if ((g_AllowPunish & ACTION_SLAP) == ACTION_SLAP)
    {
        snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Slap^n", iMenuNum);
        strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);

        g_TkMenuAction[iMenuNum - 1] = "say slap";
        iAction += mKeys[iMenuNum - 1];

        iMenuNum++;
    }

    if ((g_AllowPunish & ACTION_BAN) == ACTION_BAN)
    {
        snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Ban^n", iMenuNum);
        strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);

        g_TkMenuAction[iMenuNum - 1] = "say ban";
        iAction += mKeys[iMenuNum - 1];

        iMenuNum++;
    }

    if ((g_AllowPunish & ACTION_BURY) == ACTION_BURY)
    {
        snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Bury^n", iMenuNum);
        strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);

        g_TkMenuAction[iMenuNum - 1] = "say bury";
        iAction += mKeys[iMenuNum - 1];

        iMenuNum++;
    }

    if ((g_AllowPunish & ACTION_CHICKEN) == ACTION_CHICKEN)
    {
        snprintf(sMenuTemp, MAX_TEXT_LENGTH, "%d. Chicken^n", iMenuNum);
        strcat(sMenu, sMenuTemp, MAX_TEXT_LENGTH);
        g_TkMenuAction[iMenuNum - 1] = "say chicken";
        iAction += mKeys[iMenuNum - 1];

        iMenuNum++;
    }

    // put in the quit option
    strcat(sMenu, "^n0. Quit^n", MAX_TEXT_LENGTH);
    iAction += mKeys[9];

    // fill in a blank entry to mark the end of the list
    g_TkMenuAction[iMenuNum - 1] = "";

    return iAction;
}

DisplayTkMenu(iUserIndex)
{
    new iSessionID = 0;
    new iWONID = 0;
    new iTeam = 0;
    new iDead = 0;
    new iActionKeys = 0;

    new sMenuText[MAX_TEXT_LENGTH];
    new sUserName[MAX_NAME_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];

    // Put in the title
    strcpy(sMenuText, "TK Options^n^n", MAX_TEXT_LENGTH);

    // Get the menu string and action value
    iActionKeys = BuildTkMenu(sMenuText);

    playerinfo(iUserIndex, sUserName, MAX_NAME_LENGTH, iSessionID, iWONID, iTeam, iDead, sAuthID);

    g_MenuStates[iUserIndex][MENU_STATE_OPEN] = 1;

    // display the menu
#if USE_ADMINMOD_MENUS == 1
    menu(sUserName, sMenuText, iActionKeys);
#else
    messageex(sUserName, sMenuText, print_tty);
    set_timer("TkMenuTimer", 8, 1, sUserName);

    iActionKeys += 1;
#endif
}

ClearTkMenu(iUserIndex)
{
    if (g_MenuStates[iUserIndex][MENU_STATE_OPEN] == 1)
    {
        // clear the variables
        ResetMenu(iUserIndex);

#if USE_ADMINMOD_MENUS != 1
        new iSessionID = 0;
        new iWONID = 0;
        new iTeam = 0;
        new iDead = 0;

        new sUserName[MAX_NAME_LENGTH];
        new sAuthID[MAX_AUTHID_LENGTH];

        // rebind the keys
        BindToSlot(iUserIndex);

        // clear the menu
        playerinfo(iUserIndex, sUserName, MAX_NAME_LENGTH, iSessionID, iWONID, iTeam, iDead, sAuthID);
        messageex(sUserName, "", print_tty);
#endif
    }
}

public TkMenuTimer(Timer, Repeat, HLName, HLParam)
{
    new iUserIndex;

    new sName[MAX_NAME_LENGTH];
    convert_string(HLParam, sName, MAX_NAME_LENGTH);

    get_userindex(sName, iUserIndex);

    ClearTkMenu(iUserIndex);
}

ShowScore()
{
    new iScoreDisplayed = PLUGIN_CONTINUE;
    new s[MAX_DATA_LENGTH];

    if (g_DisplayScores)
    {
        snprintf(s,
                 MAX_DATA_LENGTH,
                 "Terrorists %d -- Counter-terrorists %d",
                 g_TScore,
                 g_CtScore);
        SayMessage(s);
        iScoreDisplayed = PLUGIN_HANDLED;
    }

    return iScoreDisplayed;
}

BanUser(sWONID[], iBanTime, iAllowOverride = 1)
{
    ban(sWONID, iBanTime, uid_wonID);

    if (iAllowOverride)
    {
        strcpy(g_LastTkBan, sWONID, MAX_AUTHID_LENGTH);
    }
}

UnbanUser(sWONID[])
{
    unban(sWONID);
    strcpy(g_LastTkBan, "", MAX_AUTHID_LENGTH);
}

stock DoAction(sWONID[], iAction, iBanTime)
{
    new iResult;

    switch (iAction)
    {
        case ACTION_KICK:
        {
            kick(sWONID);
            iResult = 1;
        }

        case ACTION_SLAP:
        {
            for (new i = 0; i <= g_UserSlapCount; i++)
                slap(sWONID);

            iResult = 1;
        }

        case ACTION_SLAY:
        {
            slay(sWONID);
            iResult = 1;
        }

        case ACTION_BAN:
        {
            BanUser(sWONID, iBanTime);
            iResult = 1;
        }

        case ACTION_BURY:
        {
            BuryUser(sWONID);
            iResult = 1;
        }

        case ACTION_CHICKEN:
        {
            ChickenUser(sWONID);
            iResult = 1;
        }

        default:
            iResult = 0;
    }

    return iResult;
}

GetActionString(iAction, sAction[])
{
    switch (iAction)
    {
        case ACTION_KICK:
            strcpy(sAction, "kicked", MAX_TEXT_LENGTH);

        case ACTION_SLAP:
            strcpy(sAction, "slapped", MAX_TEXT_LENGTH);

        case ACTION_SLAY:
            strcpy(sAction, "executed", MAX_TEXT_LENGTH);

        case ACTION_BAN:
            strcpy(sAction, "banned", MAX_TEXT_LENGTH);

        case ACTION_BURY:
            strcpy(sAction, "buried", MAX_TEXT_LENGTH);

        case ACTION_CHICKEN:
            strcpy(sAction, "chickened", MAX_TEXT_LENGTH);

        default:
            strcpy(sAction, "none", MAX_TEXT_LENGTH);
    }
}

ChickenUser(sWONID[])
{
    new sCommand[MAX_DATA_LENGTH];
    new iIndex;

    snprintf( sCommand,
              MAX_DATA_LENGTH,
              "%s 10",
              sWONID);
    plugin_exec("admin_chicken", sCommand);

    get_userindex(sWONID, iIndex);

    g_Chickened[iIndex] = 1;
}

UnchickenUsers()
{
    new i;

    for (i = 1; i <= maxplayercount(); i++)
    {
        UnchickenUser(i);
    }
}

UnchickenUser(iIndex)
{
    if (g_Chickened[iIndex])
    {
        new sName[MAX_NAME_LENGTH];
        new sAuthID[MAX_AUTHID_LENGTH];
        new iUserID;
        new iWONID;
        new iTeam;
        new iDead;

        playerinfo(iIndex, sName,  MAX_NAME_LENGTH,  iUserID,  iWONID,  iTeam, iDead, sAuthID);

        plugin_exec("admin_unchicken", sName);
        g_Chickened[iIndex] = 0;
    }
}

BuryUser(sWONID[])
{
    new x;
    new y;
    new z;

    execclient(sWONID, "slot1"); // drop his first weapon
    execclient(sWONID, "+attack");
    execclient(sWONID, "-attack");
    execclient(sWONID, "drop");
    execclient(sWONID, "slot2"); // drop his pistol (if any)
    execclient(sWONID, "+attack");
    execclient(sWONID, "-attack");
    execclient(sWONID, "drop");
    execclient(sWONID, "slot5"); // drop the bomb (if any)
    execclient(sWONID, "+attack");
    execclient(sWONID, "-attack");
    execclient(sWONID, "drop");
    execclient(sWONID, "say Help! I've been buried and I can't get up!");

    if (g_BuryGlowTime > 0)
    {
        if (set_timer("bury_noglow", g_BuryGlowTime, 1, sWONID))
            glow(sWONID, 0, 255, 0);
    }

    get_userorigin(sWONID, x, y, z);
    teleport(sWONID, x, y, (z-20));
}

public bury_noglow(Timer, Repeat, HLName, HLParam)
{
    new sName[MAX_NAME_LENGTH];
    convert_string(HLParam, sName, MAX_NAME_LENGTH);

    glow(sName, 0, 0, 0);

    return PLUGIN_HANDLED;
}

ParseAction(sAction[])
{
    new iAdd = 1;
    new iPunish = 0;

    // default is to try to add the action to the allowed list
    if (sAction[0] == '-')
    {
        iAdd = 0;
    }

    // remove the leading + or -
    strtrim(sAction, "+-", 0);

    // get the action
    iPunish = check_action(sAction);
    if (iPunish != 0)
    {
        if (iAdd)
        {
            // if the action isn't in the list then add it in
            if ((g_AllowPunish & iPunish) != iPunish)
            {
                g_AllowPunish += iPunish;
            }
        }
        else
        {
            // if the action is in the list then subtract it out
            if ((g_AllowPunish & iPunish) == iPunish)
            {
                g_AllowPunish -= iPunish;
            }
        }
    }
}

check_action( sAction[] )
{
    new iResult = 0;

    if (strlen(sAction) > 0)
    {
        if (strncmp(sAction, "kick", 4) == 0)
        {
            iResult = ACTION_KICK;
        }
        else
        if (strncmp(sAction, "ban", 3) == 0)
        {
            iResult = ACTION_BAN;
        }
        else
        if (strncmp(sAction, "slay", 4) == 0)
        {
            iResult = ACTION_SLAY;
        }
        else
        if (strncmp(sAction, "chicken", 7) == 0)
        {
            iResult = ACTION_CHICKEN;
        }
        else
        if (strncmp(sAction, "slap", 4) == 0)
        {
            iResult = ACTION_SLAP;
        }
        else
        if (strncmp(sAction, "bury", 4) == 0)
        {
            iResult = ACTION_BURY;
        }
        else
        if (strncmp(sAction, "overrideban", 11) == 0)
        {
            iResult = ACTION_OVERRIDE_BAN;
        }
    }

    return iResult;
}

BuildPunnishmentList(sOut[])
{
    strcpy(sOut, "", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_SLAY) == ACTION_SLAY)
        strcat(sOut, "slay", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_KICK) == ACTION_KICK)
        strcat(sOut, " kick", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_SLAP) == ACTION_SLAP)
        strcat(sOut, " slap", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_BAN) == ACTION_BAN)
        strcat(sOut, " ban", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_BURY) == ACTION_BURY)
        strcat(sOut, " bury", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_CHICKEN) == ACTION_CHICKEN)
        strcat(sOut, " chicken", MAX_TEXT_LENGTH);

    if ((g_AllowPunish & ACTION_OVERRIDE_BAN) == ACTION_OVERRIDE_BAN)
        strcat(sOut, " overrideban", MAX_TEXT_LENGTH);
}

stock SetVaultInt(sParam[], iValue)
{
    new sVaultData[MAX_DATA_LENGTH];

    snprintf(sVaultData, MAX_DATA_LENGTH, "%d", iValue);
    set_vaultdata(sParam, sVaultData);
}

stock SetVaultParam(sParam[], iValue)
{
    if (iValue)
        set_vaultdata(sParam, "on");
    else
        set_vaultdata(sParam, "off");
}

stock LoadConfiguration()
{
    new sVaultData[MAX_DATA_LENGTH];

    if (get_vaultdata("FFMON_ENABLED", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_Enabled = check_param(sVaultData);

    if (get_vaultdata("FFMON_TK", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkProtection = check_param(sVaultData);

    if (get_vaultdata("FFMON_TKLIMIT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkLimit = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TKBANTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkLimitBanTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TKMENU", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkMenu = check_param(sVaultData);

    if (get_vaultdata("FFMON_TKSAVE", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkSave = check_param(sVaultData);

    if (get_vaultdata("FFMON_TKSAVETIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkSaveTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TKSAVELIMIT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkSaveLimit = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TKSAVEBANTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkSaveBanTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TKSAVEPURGETIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkSavePurgeTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TKRESET", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TkReset = check_param(sVaultData);

    if (get_vaultdata("FFMON_TD", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TdProtection = check_param(sVaultData);

    if (get_vaultdata("FFMON_TDLIMIT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TdLimit = strtonum(sVaultData);

    if (get_vaultdata("FFMON_TDACTION", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_TdAction = strtonum(sVaultData);

    if (get_vaultdata("FFMON_ALLOWPUNISH", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_AllowPunish = strtonum(sVaultData);

    if (get_vaultdata("FFMON_USERSLAPCOUNT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_UserSlapCount = strtonum(sVaultData);

    if (get_vaultdata("FFMON_OVERRIDELEVEL", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_UserOverrideLevel = strtonum(sVaultData);

    if (get_vaultdata("FFMON_BURYGLOWTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_BuryGlowTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RS", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RSBANTK", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartBanTk = check_param(sVaultData);

    if (get_vaultdata("FFMON_RSBANTKTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartBanTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RSTDSLAP", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartTdSlap = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RSTDSLAPCOUNT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartTdSlapTimes = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RSTDACTION", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartTdAction = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RSTDCOUNT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartTdCount = strtonum(sVaultData);

    if (get_vaultdata("FFMON_RSTDBANTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_RoundStartTdBanTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_SS", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SpawnShotProtection = check_param(sVaultData);

    if (get_vaultdata("FFMON_SSTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SpawnShotTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_SSACTION", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SpawnShotAction = strtonum(sVaultData);

    if (get_vaultdata("FFMON_SSBANTIME", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SpawnShotBanTime = strtonum(sVaultData);

    if (get_vaultdata("FFMON_SKUNK", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SkunkProtection = check_param(sVaultData);

    if (get_vaultdata("FFMON_SKUNKTYPE", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SkunkType = strtonum(sVaultData);

    if (get_vaultdata("FFMON_SKUNKLIMIT", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_SkunkLimit = strtonum(sVaultData);

    if (get_vaultdata("FFMON_IMMUNITY", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_Immunity = check_param(sVaultData);

    if (get_vaultdata("FFMON_IMMUNITYLEVEL", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_ImmunityLevel = strtonum(sVaultData);

    if (get_vaultdata("FFMON_SCORES", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_DisplayScores = check_param(sVaultData);

    if (get_vaultdata("FFMON_LOG", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_LogMessages = check_param(sVaultData);

    if (get_vaultdata("FFMON_CONSGREET", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_DisplayConsgreet = check_param(sVaultData);

    if (get_vaultdata("FFMON_BLOCKATTACK", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_BlockAttackMessages = check_param(sVaultData);

    if (get_vaultdata("FFMON_STARTVOTE", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_StartVote = check_param(sVaultData);

    if (get_vaultdata("FFMON_STARTVOTEROUND", sVaultData, MAX_DATA_LENGTH ) != 0)
        g_StartVoteRound = strtonum(sVaultData);

    //LogMessage("Configuration values loaded.");
}

stock SaveConfiguration()
{
    SetVaultParam("FFMON_ENABLED", g_Enabled);
    SetVaultParam("FFMON_TK", g_TkProtection);
    SetVaultParam("FFMON_DEBUG", g_DebugMessages);
    SetVaultInt("FFMON_TKLIMIT", g_TkLimit);
    SetVaultInt("FFMON_TKBANTIME", g_TkLimitBanTime);
    SetVaultParam("FFMON_TKMENU", g_TkMenu);
    SetVaultParam("FFMON_TKRESET", g_TkReset);
    SetVaultParam("FFMON_TKSAVE", g_TkSave);
    SetVaultInt("FFMON_TKSAVETIME", g_TkSaveTime);
    SetVaultInt("FFMON_TKSAVELIMIT", g_TkSaveLimit);
    SetVaultInt("FFMON_TKSAVEBANTIME", g_TkSaveBanTime);
    SetVaultInt("FFMON_TKSAVEPURGETIME", g_TkSavePurgeTime);
    SetVaultParam("FFMON_TD", g_TdProtection);
    SetVaultInt("FFMON_TDLIMIT", g_TdLimit);
    SetVaultInt("FFMON_TDACTION", g_TdAction);
    SetVaultInt("FFMON_ALLOWPUNISH", g_AllowPunish);
    SetVaultInt("FFMON_USERSLAPCOUNT", g_UserSlapCount);
    SetVaultInt("FFMON_OVERRIDELEVEL", g_UserOverrideLevel);
    SetVaultInt("FFMON_BURYGLOWTIME", g_BuryGlowTime);
    SetVaultInt("FFMON_RS", g_RoundStartTime);
    SetVaultParam("FFMON_RSBANTK", g_RoundStartBanTk);
    SetVaultInt("FFMON_RSBANTKTIME", g_RoundStartBanTime);
    SetVaultInt("FFMON_RSTDSLAP", g_RoundStartTdSlap);
    SetVaultInt("FFMON_RSTDSLAPCOUNT", g_RoundStartTdSlapTimes);
    SetVaultInt("FFMON_RSTDACTION", g_RoundStartTdAction);
    SetVaultInt("FFMON_RSTDCOUNT", g_RoundStartTdCount);
    SetVaultInt("FFMON_RSTDBANTIME", g_RoundStartTdBanTime);
    SetVaultParam("FFMON_SS", g_SpawnShotProtection);
    SetVaultInt("FFMON_SSTIME", g_SpawnShotTime);
    SetVaultInt("FFMON_SSACTION", g_SpawnShotAction);
    SetVaultInt("FFMON_SSBANTIME", g_SpawnShotBanTime);
    SetVaultParam("FFMON_SKUNK", g_SkunkProtection);
    SetVaultInt("FFMON_SKUNKTYPE", g_SkunkType);
    SetVaultInt("FFMON_SKUNKLIMIT", g_SkunkLimit);
    SetVaultParam("FFMON_IMMUNITY", g_Immunity);
    SetVaultInt("FFMON_IMMUNITYLEVEL", g_ImmunityLevel);
    SetVaultParam("FFMON_SCORES", g_DisplayScores);
    SetVaultParam("FFMON_LOG", g_LogMessages);
    SetVaultParam("FFMON_CONSGREET", g_DisplayConsgreet);
    SetVaultParam("FFMON_BLOCKATTACK", g_BlockAttackMessages);
    SetVaultParam("FFMON_STARTVOTE", g_StartVote);
    SetVaultInt("FFMON_STARTVOTEROUND", g_StartVoteRound);

    //LogMessage("Configuration values saved.");
}

ShowNextMap()
{
    new s[MAX_DATA_LENGTH];
    new sMessage[MAX_TEXT_LENGTH];

    nextmap(s, MAX_DATA_LENGTH);

    snprintf(sMessage, MAX_TEXT_LENGTH, "The next map will be %s.", s);

    SayMessage(sMessage);
}

DisplayTk()
{
    new sName[MAX_NAME_LENGTH];
    new iUserID;
    new iWONID;
    new iTeam;
    new s[MAX_TEXT_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];
    new i;
    new iDead;
    new iMaxPlayers = maxplayercount();

    for (i = 1; i <= iMaxPlayers; i++)
    {
        /* get the players name for this slot */
        if (playerinfo(i, sName,  MAX_NAME_LENGTH,  iUserID,  iWONID,  iTeam, iDead, sAuthID))
        {
            if (iUserID != 0)
            {
                snprintf(s,
                         MAX_TEXT_LENGTH,
                         "FFMon: %s: %d TK(s).",
                         sName,
                         g_TkCount[i]);
                selfmessage(s);
            }
        }
    }
}

DisplayTd()
{
    new sName[MAX_NAME_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];
    new iUserID;
    new iWONID;
    new iTeam;
    new iDead;
    new s[MAX_TEXT_LENGTH];
    new i;
    new iMaxPlayers = maxplayercount();

    for (i = 1; i <= iMaxPlayers; i++)
    {
        /* get the players name for this slot */
        if (playerinfo(i, sName, MAX_NAME_LENGTH, iUserID, iWONID, iTeam, iDead, sAuthID))
        {
            if (iUserID != 0)
            {
                snprintf(s,
                         MAX_TEXT_LENGTH,
                         "FFMon: %s: %d TD(s).",
                         sName,
                         g_TdCount[i]);
                selfmessage(s);
            }
        }
    }
}

DisplayConsgreet()
{
    new s[MAX_TEXT_LENGTH];

    if (g_DisplayConsgreet)
    {
        consgreet("");
        consgreet("Friendly Fire Monitor by Desslok <desslok@taradox.com>.");
        consgreet("");

        if (g_TkProtection)
        {
            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "%d unforgiven TKs: %d min ban.",
                     g_TkLimit,
                     g_TkLimitBanTime);
            consgreet(s);
        }

        if (g_TdProtection)
        {
            new sAction[MAX_TEXT_LENGTH];

            GetActionString(g_TdAction, sAction);

            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "%d team attacks: %s.",
                     g_TdLimit,
                     sAction);
            consgreet(s);
        }

        if (g_RoundStartBanTk || g_RoundStartTdSlap || g_RoundStartTdAction)
        {
            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "Round start: %d sec.",
                     g_RoundStartTime);
            consgreet(s);
        }

        if (g_RoundStartBanTk)
        {
            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "Round start TKs: %d min ban.",
                     g_RoundStartBanTime);
            consgreet(s);
        }

        if (g_RoundStartTdSlap)
        {
            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "Round start team attacks: %d slaps.",
                     g_RoundStartTdSlapTimes);
            consgreet(s);
        }

        if (g_RoundStartTdAction)
        {
            new sAction[MAX_TEXT_LENGTH];

            GetActionString(g_RoundStartTdAction, sAction);

            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "Round start %d team attacks: %s.",
                     g_RoundStartTdCount,
                     sAction);
            consgreet(s);
        }

        if (g_SpawnShotProtection && g_SpawnShotAction)
        {
            new sAction[MAX_TEXT_LENGTH];

            GetActionString(g_SpawnShotAction, sAction);

            snprintf(s,
                     MAX_TEXT_LENGTH,
                     "Spawn shooters in first %d sec get %s.",
                     g_SpawnShotTime,
                     sAction);
            consgreet(s);
        }

        consgreet("");
        strcpy(s, "TK options: forgive", MAX_TEXT_LENGTH);

        if ((g_AllowPunish & ACTION_SLAY) == ACTION_SLAY)
            strcat(s, " slay", MAX_TEXT_LENGTH);

        if ((g_AllowPunish & ACTION_KICK) == ACTION_KICK)
            strcat(s, " kick", MAX_TEXT_LENGTH);

        if ((g_AllowPunish & ACTION_BAN) == ACTION_BAN)
            strcat(s, " ban", MAX_TEXT_LENGTH);

        if ((g_AllowPunish & ACTION_SLAP) == ACTION_SLAP)
            strcat(s, " slap", MAX_TEXT_LENGTH);

        if ((g_AllowPunish & ACTION_BURY) == ACTION_BURY)
            strcat(s, " bury", MAX_TEXT_LENGTH);

        if ((g_AllowPunish & ACTION_CHICKEN) == ACTION_CHICKEN)
            strcat(s, " chicken", MAX_TEXT_LENGTH);

        consgreet(s);
        consgreet("Say overrideban to remove an unjustified ban.");
    }
}

DisplayHelp()
{
    selfmessage("FFMon Public Commands");
    selfmessage("---------------------");
    selfmessage("admin_ffmon status - show status");
    selfmessage("admin_ffmon showtk - show TK counts");
    selfmessage("admin_ffmon showtd - show TD counts");
    selfmessage("");
    selfmessage("FFMon Admin Commands (typing any command with no value will show its current setting)");
    selfmessage("--------------------");
    selfmessage("admin_ffmon on|off - turn on or off FFMon");
    selfmessage("admin_ffmon help - show help");
    selfmessage("admin_ffmon tk on|off (default: on) - TK monitoring");
    selfmessage("admin_ffmon tklimit <value> (default: 3) - TK limit before banning");
    selfmessage("admin_ffmon tkbantime <value> (default: 30) - ban time for TK abusers");
    selfmessage("admin_ffmon tkmenu on|off (default: off) - TK experimental menu");
    selfmessage("admin_ffmon tkreset on|off (default: off) - reset TK counts at end of each round");
    selfmessage("admin_ffmon td on|off (default: on) - TD monitoring");
    selfmessage("admin_ffmon tdlimit <value> (default: 5) - TD limit before slaying");
    selfmessage("admin_ffmon tdaction slay|kick|ban|bury|chicken (default: slay) - action when users hit tdlimit");
    selfmessage("");
    selfmessage("admin_ffmon allowpunish <kick|ban|slap|bury|chicken|overrideban|all> (default: all - chicken)");
    selfmessage("admin_ffmon userslapcount <value> (default: 1) - number of times to slap when slaptk is used");
    selfmessage("admin_ffmon overridelevel <value> (default: 0) - level req to override bans--anybody by default");
    selfmessage("admin_ffmon buryglowtime <value> (default: 30) - amount of time to glow after a bury");
    selfmessage("");
    selfmessage("admin_ffmon rs <value> (default: 10) - seconds to monitor at round start, 0 to disable");
    selfmessage("admin_ffmon rsbantk on|off (default: on) - ban TKers immediately during round start time");
    selfmessage("admin_ffmon rsbantktime <value> (default: 300) - minutes to ban round start TKers");
    selfmessage("admin_ffmon rstdslap on|off (default: on) - slap TDers during round start time");
    selfmessage("admin_ffmon rstdslapcount <value> (default: 5) - # of times to slap TDers during round start");
    selfmessage("admin_ffmon rstdaction none|slay|kick|ban (default: ban) - action to take against TD abusers");
    selfmessage("admin_ffmon rstdcount <value> (default: 3) - limit for TD abusers at round start");
    selfmessage("admin_ffmon rstdbantime <value> (default: 2) - minutes to ban a TD abuser");
    selfmessage("");
    selfmessage("admin_ffmon ss on|off (default: off) - Spawn shot monitoring");
    selfmessage("admin_ffmon sstime <value> (default: 2) - seconds to monitor spawn shooting");
    selfmessage("admin_ffmon ssaction none|slay|kick|ban|bury (default: kick) - action against spawn shooters");
    selfmessage("admin_ffmon ssbantime <value> (default: 5) - minutes to ban spawn shooters");
    selfmessage("");
    selfmessage("admin_ffmon skunk on|off (default: on) - Skunk monitoring");
    selfmessage("admin_ffmon skunktype 0|1 (default: 0) - 0=normal,1=trad: trad skunk only if losing team has 0");
    selfmessage("admin_ffmon skunklimit <value> (default: 10) - Score difference limit for a skunk");
    selfmessage("");
    selfmessage("admin_ffmon immunity on|off (default: off) - immunity for admins from TK/TD");
    selfmessage("admin_ffmon immunitylevel <value> (default: 65536) - level required for immunity");
    selfmessage("");
    selfmessage("admin_ffmon scores on|off (default: on) - Display team scores at end round (incl win notifications)");
    selfmessage("admin_ffmon log on|off (default: on) - FFMon info message to log files");
    selfmessage("admin_ffmon consgreet on|off (default: on) - console greeting at connect time");
    selfmessage("admin_ffmon blockattack on|off (default: on) - block attack messages from to log files");
    selfmessage("");
    selfmessage("admin_ffmon startvote on|off (default: on) - start an hlds vote before mp_winlimit");
    selfmessage("admin_ffmon startvoteround <value> (default: 2) - number of rnds before mp_winlimit to start vote");
}

SetLogDetail()
{
    new iCurLogDetail = getvar("mp_logdetail");
    new iLogDetail = 0;

    // try to be smart about this so we don't enable more log detail
    // than we actually need for performance reasons
    if (g_SpawnShotProtection)
    {
        iLogDetail += 1;
    }

    if (g_TkProtection || g_TdProtection || g_RoundStartBanTk)
    {
        iLogDetail += 2;
    }

    if (iCurLogDetail < iLogDetail)
    {
        new s[MAX_TEXT_LENGTH];

        snprintf(s, MAX_TEXT_LENGTH, "mp_logdetail %d", iLogDetail);
        exec(s);
    }

    if (g_BlockAttackMessages)
    {
        // block attack info from being written to the log file
        exec("logd_block 58");
    }
}

ForceNextMap()
{
    /* Since adminmod will report a different nextmap
       then what the HL engine will use as the nextmap
       we will just set mp_maxrounds to 1 so that the engine
       will trigger a map change.  This should prevent the
       map cycle from gettin too out of sync and should
       make map changing more like a normal map switch.
    */

#if FORCE_NEXTMAP_ADMINMOD == 1
    // adminmod way of changing maps
    nextmap(s, MAX_DATA_LENGTH);
    changelevel(s, 4);
#else
    // store the current maxround value so it can be restored later.
    SetVaultInt("FFMON_FORCENEXTMAP", getvar("mp_maxrounds"));
    exec("mp_maxrounds 1");
#endif
}

ForceNextMapUndo()
{

#if FORCE_NEXTMAP_ADMINMOD == 0
    // return mp_maxrounds to what it was before in case we used it to switch maps
    new VaultData[MAX_DATA_LENGTH];
    new iVaultData = -1;

    if (get_vaultdata("FFMON_FORCENEXTMAP", VaultData, MAX_DATA_LENGTH ) != 0)
        iVaultData = strtonum(VaultData);

    if (iVaultData != 9999)
    {
        new s[MAX_TEXT_LENGTH];

        snprintf(s, MAX_TEXT_LENGTH, "mp_maxrounds %d", iVaultData);
        exec(s);

        SetVaultInt("FFMON_FORCENEXTMAP", 9999);
    }
#endif

}

DisplayParamState(sDesc[], iValue)
{
    new sState[MAX_DATA_LENGTH] = "OFF";
    new s[MAX_TEXT_LENGTH];

    if (iValue)
    {
        sState = "ON";
    }

    snprintf(s, MAX_TEXT_LENGTH, "FFMon: %s is %s.", sDesc, sState);
    selfmessage(s);
}

DisplayParamValue(sDesc[], iValue)
{
    new s[MAX_TEXT_LENGTH];

    snprintf(s, MAX_TEXT_LENGTH, "FFMon: %s is %d.", sDesc, iValue);
    selfmessage(s);
}

DisplayParamString(sDesc[], sValue[])
{
    new s[MAX_TEXT_LENGTH];

    snprintf(s, MAX_TEXT_LENGTH, "FFMon: %s: %s.", sDesc, sValue);
    selfmessage(s);
}

public roundstartover()
{
    DebugMessage("Round start time complete.");

    new i;

    g_RoundStart = 0;

    for (i = 1; i <= MAX_PLAYERS; i++)
    {
        g_tkedby[i] = USER_ID_INVALID;
    }
}

public roundspawntimer()
{
    DebugMessage("Spawn time complete.");

    g_RoundSpawn = 0;
}

//
// I'm having problems with timers from adminmod.  In some cases I
// do not receive a callback for the timer and therefore never mark
// the end of the round start or spawn times.  So instead of setting
// a timer, we will detect the beginning of the round events
// manually... kinda stinks but it should work.  This function will
// be called from the checktd and checktk routines before any
// processing is done.
//
CheckTimers()
{
    if (g_RoundStart || g_RoundSpawn)
    {
	new iSystemTime = systemtime();

        if ((g_RoundSpawn) &&
            (iSystemTime > g_RoundStartTimer + g_SpawnShotTime))
        {
            roundspawntimer();
        }

        if ((g_RoundStart) &&
            (iSystemTime > g_RoundStartTimer + g_RoundStartTime))
        {
            roundstartover();
        }
    }
}

CheckTkLimit(iID, sWONID[], sName[])
{
    new iHandled = 0;

    if ( (GetTkCount(sWONID) >= g_TkSaveLimit) &&
         (!IsImmune(sName)) )
    {
        new sMessage[MAX_TEXT_LENGTH];

        BanUser(sWONID, g_TkSaveBanTime, 0);

        snprintf( sMessage,
                  MAX_TEXT_LENGTH,
                  "%s has TKed %i times in %d hours. Banned for %i min.",
                  sName,
                  g_TkSaveLimit,
                  g_TkSaveTime,
                  g_TkSaveBanTime );

        //typesay( sMessage, 10, 255, 255, 255 );
        SayMessage(sMessage);

        LogMessage("Multiple TKer (saved) banned.");

        RemoveTkFile(sWONID);

        iHandled = 1;
    }

    if ( (g_TkCount[iID] >= g_TkLimit) &&
         (!IsImmune(sName)) &&
         !iHandled )
    {
        new sMessage[MAX_TEXT_LENGTH];

        BanUser(sWONID, g_TkLimitBanTime);

        snprintf( sMessage,
                  MAX_TEXT_LENGTH,
                  "%s has TKed %i times and was banned for %i min.",
                  sName,
                  g_TkLimit,
                  g_TkLimitBanTime );

        //typesay( sMessage, 10, 255, 255, 255 );
        SayMessage(sMessage);

        LogMessage("Multiple TKer banned.");

        iHandled = 1;
    }

    return iHandled;
}

/*
*
*
*   LogD specific functions
*
*
*
*/

public ffmon_checktk(HLCommand, HLData, HLUserName, UserIndex)
{
    if (!g_Enabled)
        return PLUGIN_CONTINUE;

    HeapCheckStart();

    new iIDA;
    new sIDA[MAX_NUMBER_LENGTH];
    new iUserIDA;
    new iWONIDA;
    new iTeamA;
    new sNameA[MAX_NAME_LENGTH];
    new sAuthIDA[MAX_AUTHID_LENGTH];

    new iIDV;
    new sIDV[MAX_NUMBER_LENGTH];
    new iUserIDV;
    new iWONIDV;
    new iTeamV;
    new sNameV[MAX_NAME_LENGTH];
    new sAuthIDV[MAX_AUTHID_LENGTH];

    new sData[MAX_DATA_LENGTH];
    new sMessage[MAX_TEXT_LENGTH];

    new iDead;

    CheckTimers();

    convert_string(HLData, sData, MAX_DATA_LENGTH);
    strsplit(sData, " ", sIDA, 3, sIDV, 3 );

    iIDA = strtonum(sIDA);
    iIDV = strtonum(sIDV);

    if (iIDA == 0)
    {
        LogMessage("ffmon_checktk attacker ID is 0");
        return PLUGIN_CONTINUE;
    }

    if (iIDV == 0)
    {
        LogMessage("ffmon_checktk victim ID is 0");
        return PLUGIN_CONTINUE;
    }

    if (!playerinfo(iIDA, sNameA, MAX_NAME_LENGTH, iUserIDA, iWONIDA, iTeamA, iDead, sAuthIDA))
    {
        LogMessage("ffmon_checktk attacker playerinfo failed");
        return PLUGIN_CONTINUE;
    }

    if (!playerinfo(iIDV, sNameV, MAX_NAME_LENGTH, iUserIDV, iWONIDV, iTeamV, iDead, sAuthIDV))
    {
        LogMessage("ffmon_checktk victim playerinfo failed");
        return PLUGIN_CONTINUE;
    }

    /*
     *
     * check to see if this is a spawn killer
     * This is most useful for maps like awp_map and what not
     *
     */
    if (iTeamV != iTeamA)
    {
        HeapCheckStop("ffmon_checktk");

        if (g_RoundSpawn && g_SpawnShotProtection)
        {
            if (!IsImmune(sNameA))
            {
                if (DoAction(sAuthIDA, g_SpawnShotAction, g_SpawnShotBanTime))
                {
                    new sAction[MAX_TEXT_LENGTH];

                    GetActionString(g_SpawnShotAction, sAction);

                    snprintf( sMessage,
                              MAX_TEXT_LENGTH,
                              "%s was %s for spawn killing in first %i sec.",
                              sNameA,
                              sAction,
                              g_SpawnShotTime);
                    //typesay( sMessage, 10, 255, 255, 255 );
                    SayMessage(sMessage);

                    snprintf(sMessage, MAX_TEXT_LENGTH, "Spawn killer %s.", sAction);
                    LogMessage(sMessage);
                }
            }
        }

        return PLUGIN_CONTINUE;
    }

    if (g_RoundStart && g_RoundStartBanTk)
    {
        if (!IsImmune(sNameA))
        {
            BanUser(sAuthIDA, g_RoundStartBanTime);

            snprintf( sMessage,
                      MAX_TEXT_LENGTH,
                      "%s was banned for %i min for TKing in first %i sec.",
                      sNameA,
                      g_RoundStartBanTime,
                      g_RoundStartTime);

            //typesay( sMessage, 10, 255, 255, 255 );
            SayMessage(sMessage);

            LogMessage("Round start TKer banned.");
        }
    }
    else
    {
        if (!g_TkProtection)
            return PLUGIN_CONTINUE;

        g_tkedby[iIDV] = iIDA;
        g_TkCount[iIDA] += 1;
        AddTk(sAuthIDA);

        if (!CheckTkLimit(iIDA, sAuthIDA, sNameA))
        {
            new sMessage2[MAX_TEXT_LENGTH];

            snprintf( sMessage,
                      MAX_TEXT_LENGTH,
                      "%s TK Warning %i of %i... you don't want to get to %i.",
                      sNameA,
                      g_TkCount[iIDA],
                      g_TkLimit,
                      g_TkLimit );
            typesay( sMessage, 10, 255, 255, 255 );
            //SayMessage(sMessage);

            snprintf( sMessage,
                      MAX_TEXT_LENGTH,
                      "%s TKed you.  ",
                      sNameA);

            strcpy(sMessage2, "Options: forgive", MAX_TEXT_LENGTH);

            // build the string that shows what options the user has
            if ((g_AllowPunish & ACTION_SLAY) == ACTION_SLAY)
                strcat(sMessage2, " slay", MAX_TEXT_LENGTH);

            if ((g_AllowPunish & ACTION_KICK) == ACTION_KICK)
                strcat(sMessage2, " kick", MAX_TEXT_LENGTH);

            if ((g_AllowPunish & ACTION_BAN) == ACTION_BAN)
                strcat(sMessage2, " ban", MAX_TEXT_LENGTH);

            if ((g_AllowPunish & ACTION_SLAP) == ACTION_SLAP)
                strcat(sMessage2, " slap", MAX_TEXT_LENGTH);

            if ((g_AllowPunish & ACTION_BURY) == ACTION_BURY)
                strcat(sMessage2, " bury", MAX_TEXT_LENGTH);

            if ((g_AllowPunish & ACTION_CHICKEN) == ACTION_CHICKEN)
                strcat(sMessage2, " chicken", MAX_TEXT_LENGTH);

            // for a pretty print (tsay) set iPrintType = 1
            new iPrintType = 0;
            if (iPrintType)
            {
                strcat(sMessage, "^n", MAX_TEXT_LENGTH);
                strcat(sMessage, sMessage2, MAX_TEXT_LENGTH);
                messageex(sAuthIDV, sMessage, print_pretty);
            }
            else
            {
                // normal chat message
                messageex(sAuthIDV, sMessage, print_chat);
                messageex(sAuthIDV, sMessage2, print_chat);
            }

            if (g_TkMenu)
            {
                DisplayTkMenu(iIDV);

#if USE_ADMINMOD_MENUS != 1
                BindToMenuselect(iIDV);
#endif
            }
        }

        HeapCheckStop("ffmon_checktk");
    }

    return PLUGIN_HANDLED;
}

public ffmon_checktd(HLCommand, HLData, HLUserName, UserIndex)
{
    if (!g_Enabled)
        return PLUGIN_CONTINUE;

    HeapCheckStart();

    new iIDA;
    new iIDV;
    new iUserIDA;
    new iUserIDV;
    new iWONIDA;
    new iWONIDV;
    new iTeamA;
    new iTeamV;
    new iDead;

    new sIDA[MAX_NUMBER_LENGTH];
    new sIDV[MAX_NUMBER_LENGTH];
    new sData[MAX_DATA_LENGTH];
    new sNameA[MAX_NAME_LENGTH];
    new sNameV[MAX_NAME_LENGTH];
    new sAuthIDA[MAX_AUTHID_LENGTH];
    new sAuthIDV[MAX_AUTHID_LENGTH];
    new sTDMessage[MAX_DATA_LENGTH];

    new i;

    CheckTimers();

    convert_string(HLData, sData, MAX_DATA_LENGTH);
    strsplit(sData, " ", sIDA, 3, sIDV, 3 );

    iIDA = strtonum( sIDA );
    iIDV = strtonum( sIDV );

    if (iIDA == 0)
    {
        LogMessage("ffmon_checktd attacker ID is 0");
        return PLUGIN_CONTINUE;
    }

    if (iIDV == 0)
    {
        LogMessage("ffmon_checktd victim ID is 0");
        return PLUGIN_CONTINUE;
    }

    if (!playerinfo(iIDA, sNameA, MAX_NAME_LENGTH, iUserIDA, iWONIDA, iTeamA, iDead, sAuthIDA))
    {
        LogMessage("ffmon_checktd attacker playerinfo failed");
        return PLUGIN_CONTINUE;
    }

    if (!playerinfo(iIDV, sNameV, MAX_NAME_LENGTH, iUserIDV, iWONIDV, iTeamV, iDead, sAuthIDV))
    {
        LogMessage("ffmon_checktd victim playerinfo failed");
        return PLUGIN_CONTINUE;
    }

    // check to see if the player hurt themselves
    if (iIDA == iIDV)
        return PLUGIN_CONTINUE;

    if ( (iTeamV != iTeamA) )
    {
        // spawn shot -- if dead then the spawn kill will get it
        if (g_RoundSpawn && g_SpawnShotProtection && !iDead)
        {
            if ( (!IsImmune(sNameA)) &&
                 (DoAction(sAuthIDA, g_SpawnShotAction, g_SpawnShotBanTime)) )
            {
                new sAction[MAX_TEXT_LENGTH];

                GetActionString(g_SpawnShotAction, sAction);

                snprintf( sTDMessage,
                          MAX_TEXT_LENGTH,
                          "%s was %s for spawn shooting in first %i sec.",
                          sNameA,
                          sAction,
                          g_SpawnShotTime);
                //typesay( sTDMessage, 10, 255, 255, 255 );
                SayMessage(sTDMessage);

                snprintf(sTDMessage, MAX_TEXT_LENGTH, "Spawn shooter %s.", sAction);
                LogMessage(sTDMessage);
            }
        }

        return PLUGIN_CONTINUE;
    }

    if (!g_TdProtection)
        return PLUGIN_CONTINUE;

    g_TdCount[iIDA] += 1;

    snprintf(sTDMessage, MAX_DATA_LENGTH, "%s,\nTHAT'S YOUR TEAMMATE, HOLD YOUR FIRE.", sNameA);
    messageex(sNameA, sTDMessage, print_pretty);

    /* special handling if this is the beginning of the round */
    if (g_RoundStart)
    {
        if ( (g_RoundStartTdSlap) &&
             (!IsImmune(sNameA)) )
        {
            for (i = 0; i < g_RoundStartTdSlapTimes; i++)
                slap(sAuthIDA);
        }

        if ( (g_TdCount[iIDA] == g_RoundStartTdCount) &&
             (!IsImmune(sNameA)) )
        {
            if (DoAction(sAuthIDA, g_RoundStartTdAction, g_RoundStartTdBanTime))
            {
                new sAction[MAX_TEXT_LENGTH];

                GetActionString(g_RoundStartTdAction, sAction);

                snprintf( sTDMessage,
                          MAX_TEXT_LENGTH,
                          "%s was %s for team attacking %d times in first %i sec.",
                          sNameA,
                          sAction,
                          g_RoundStartTdCount,
                          g_RoundStartTime);
                //typesay( sTDMessage, 10, 255, 255, 255 );
                SayMessage(sTDMessage);

                snprintf(sTDMessage, MAX_TEXT_LENGTH, "Round start TD abuser %s.", sAction);
                LogMessage(sTDMessage);
            }
        }
    }

    /* has the user reached their TD limit? */
    if ( (g_TdCount[iIDA] == g_TdLimit) &&
         (!IsImmune(sNameA)) )
    {
        if (DoAction(sAuthIDA, g_TdAction, g_TkLimitBanTime))
        {
            new sAction[MAX_TEXT_LENGTH];

            GetActionString(g_TdAction, sAction);

            snprintf( sTDMessage,
                      MAX_TEXT_LENGTH,
                      "%s was %s for team attacking %d times.",
                      sNameA,
                      sAction,
                      g_TdLimit);

            //typesay( sTDMessage, 10, 255, 255, 255 );
            SayMessage(sTDMessage);

            g_TdCount[iIDA] = 0;
            g_TkCount[iIDA] += 1;
            AddTk(sAuthIDA);

            CheckTkLimit(iIDA, sAuthIDA, sNameA);
        }
    }

    HeapCheckStop("ffmon_checktd");

    return PLUGIN_HANDLED;
}

public ffmon_worldaction(HLCommand, HLData, HLUserName, UserIndex)
{
    if (!g_Enabled)
        return PLUGIN_CONTINUE;

    HeapCheckStart();

    new i;
    new sParams[MAX_DATA_LENGTH];
    convert_string(HLData, sParams, MAX_DATA_LENGTH);

    if (streq(sParams, "Round_Start"))
    {
        UnchickenUsers();

        for (i = 1; i <= MAX_PLAYERS; i++)
        {
            // clear team attack counts
            g_TdCount[i] = 0;

            if (g_TkReset)
            {
                g_TkCount[i] = 0;
            }

            // do any cached revenge actions
            if (g_TkRevenge[i] != ACTION_NONE)
            {
                new sName[MAX_NAME_LENGTH];
                new sAuthID[MAX_AUTHID_LENGTH];
                new sAction[MAX_TEXT_LENGTH];
                new sData[MAX_DATA_LENGTH];
                new iUserID;
                new iWONID;
                new iTeam;
                new iDead;

                if (playerinfo(i, sName,  MAX_NAME_LENGTH,  iUserID,  iWONID,  iTeam, iDead, sAuthID))
                {
                    GetActionString(g_TkRevenge[i], sAction);
                    DoAction(sAuthID, g_TkRevenge[i], 5);

                    snprintf(sData,
                             MAX_DATA_LENGTH,
                             "%s was %s as TK revenge.",
                             sName,
                             sAction);
                    SayMessage(sData);
                }

                g_TkRevenge[i] = ACTION_NONE;
            }
        }

        /* old timer code
        if (set_timer("roundstartover", g_RoundStartTime, 0))
        {
            g_RoundStart = 1;
        }
        else
        {
            LogMessage("Round start timer failed!");
        }

        if (set_timer("roundspawntimer", g_SpawnShotTime, 0))
        {
            g_RoundSpawn = 1;
        }
        else
        {
            LogMessage("Spawn timer failed!");
        }
        */

        // new manual timer code
        g_RoundStartTimer = systemtime();
        g_RoundStart = 1;
        g_RoundSpawn = 1;
    }

    HeapCheckStop("ffmon_worldaction");

    return PLUGIN_HANDLED;
}

public ffmon_teamaction(HLCommand, HLData, HLUserName, UserIndex)
{
    if (!g_Enabled)
        return PLUGIN_CONTINUE;

    new sParams[MAX_DATA_LENGTH];
    new sTeam[MAX_DATA_LENGTH];
    new sAction[MAX_DATA_LENGTH];

    convert_string(HLData, sParams, MAX_DATA_LENGTH);
    strbreak(sParams, sTeam, sAction, MAX_DATA_LENGTH);

    if ( (strmatch(sAction, "CTs_Win", strlen("CTs_Win"))) ||
         (strmatch(sAction, "Terrorists_Win", strlen("Terrorists_Win"))) ||
         (strmatch(sAction, "Bomb_Defused", strlen("Bomb_Defused"))) ||
         (strmatch(sAction, "Target_Bombed", strlen("Target_Bombed"))) ||
         (strmatch(sAction, "Target_Saved", strlen("Target_Saved"))) ||
         (strmatch(sAction, "Hostages_Not_Rescued", strlen("Hostages_Not_Rescued"))) ||
         (strmatch(sAction, "All_Hostages_Rescued", strlen("All_Hostages_Rescued"))) ||
         (strmatch(sAction, "VIP_Assassinated", strlen("All_Hostages_Rescued"))) ||
         (strmatch(sAction, "VIP_Escaped", strlen("All_Hostages_Rescued"))) )
    {
        new sScores[MAX_DATA_LENGTH];
        new sCtScore[MAX_DATA_LENGTH];
        new sTScore[MAX_DATA_LENGTH];
        new sSkip[MAX_DATA_LENGTH];
        new s[MAX_DATA_LENGTH];
        new sWinning[MAX_DATA_LENGTH];
        new sLosing[MAX_DATA_LENGTH];
        new c;
        new iDiff;
        new iWinLimit;
        new iWinning;
        new iLosing;

        strbreak(sAction, sSkip, sScores, MAX_DATA_LENGTH);
        strbreak(sScores, sCtScore, sTScore, MAX_DATA_LENGTH);

        c = strchr(sCtScore, '#') + 1;
        g_CtScore = strtonum(sCtScore[c]);

        c = strchr(sTScore, '#') + 1;
        g_TScore = strtonum(sTScore[c]);

        ShowScore();

        if (g_CtScore > g_TScore)
        {
            iDiff = g_CtScore - g_TScore;
            iWinning = g_CtScore;
            iLosing = g_TScore;
            strcpy(sWinning, "Counter-terrorist", MAX_DATA_LENGTH);
            strcpy(sLosing, "Terrorist", MAX_DATA_LENGTH);
        }
        else
        {
            iDiff = g_TScore - g_CtScore;
            iWinning = g_TScore;
            iLosing = g_CtScore;
            strcpy(sLosing, "Counter-terrorist", MAX_DATA_LENGTH);
            strcpy(sWinning, "Terrorist", MAX_DATA_LENGTH);
        }

        iWinLimit = getvar("mp_winlimit");

        if ( (iDiff == (g_SkunkLimit - 1)) &&
             (iWinning != iWinLimit) &&
             (g_SkunkProtection) &&
             ( (g_SkunkType == SKUNK_TYPE_NORMAL) ||
               ((g_SkunkType == SKUNK_TYPE_TRADITIONAL) &&
                (iLosing == 0)) ) )
        {
            /* announce possible skunk */
            snprintf(s,
                     MAX_DATA_LENGTH,
                     "Possible skunk by the %ss next round.",
                     sWinning);
            //typesay( s, 10, 255, 255, 255 );
            SayMessage(s);
        }
        else
        if ( (iDiff == g_SkunkLimit) &&
             (g_SkunkProtection) &&
             ( (g_SkunkType == SKUNK_TYPE_NORMAL) ||
               ((g_SkunkType == SKUNK_TYPE_TRADITIONAL) &&
                (iLosing == 0)) ) )
        {
            /* annouce skunk and go to the next map */
            snprintf(s,
                     MAX_DATA_LENGTH,
                     "%ss skunk the %ss!",
                     sWinning,
                     sLosing);
            //typesay( s, 10, 255, 255, 255 );
            SayMessage(s);

            ShowNextMap();
            ForceNextMap();
        }
        else
        if ( (iWinLimit > 0) &&
             (iWinning  == (iWinLimit - 1)) &&
             (iLosing == (iWinLimit - 1)) &&
             (g_DisplayScores) )
        {
            snprintf(s,
                     MAX_DATA_LENGTH,
                     "SUDDEN DEATH!! Winner take all!",
                     sWinning);
            //typesay( s, 10, 255, 255, 255 );
            SayMessage(s);
        }
        else
        if ( (iWinLimit > 0) &&
             (iWinning == (iWinLimit - 1)) &&
             (g_DisplayScores) )
        {
            snprintf(s,
                     MAX_DATA_LENGTH,
                     "Possible %s victory next round.",
                     sWinning);
            //typesay( s, 10, 255, 255, 255 );
            SayMessage(s);
        }
        else
        if ( (iWinLimit > 0) &&
             (iWinning >= (iWinLimit)) &&
             (g_DisplayScores) )
        {
            snprintf(s,
                     MAX_DATA_LENGTH,
                     "%ss win!!!",
                     sWinning);
            //typesay( s, 10, 255, 255, 255 );
            SayMessage(s);

            ShowNextMap();
        }

        // start a map vote if requested
        if ( (g_StartVote) &&
             (!g_VoteStarted) &&
             ((iWinLimit - iWinning) == g_StartVoteRound) )
        {
            DebugMessage("Starting map vote due to round count");
            plugin_exec("admin_startvote", "");
            g_VoteStarted = 1;
        }
    }

    return PLUGIN_CONTINUE;
}

/*
*
*
*  Adminmod functions
*
*/

public HandleSay(HLCommand, HLData, HLUserName, UserIndex)
{
    if (!g_Enabled)
        return PLUGIN_CONTINUE;

    new iUserIDA;
    new iWONIDA;
    new iTeamA;
    new sNameA[MAX_NAME_LENGTH];
    new sNameV[MAX_NAME_LENGTH];
    new sAuthIDA[MAX_AUTHID_LENGTH];
    new sAuthIDV[MAX_AUTHID_LENGTH];
    new sData[MAX_DATA_LENGTH];
    new iUserIDV;
    new iWONIDV;
    new iTeamV;
    new iDeadA;
    new iDeadV;
    new iReturn = PLUGIN_CONTINUE;

    convert_string(HLData, sData, MAX_DATA_LENGTH);
    strstripquotes(sData);

    sData[0] = sData[0] | 32;

    if ( (sData[0] != 'f') &&
         (sData[0] != 'p') &&
         (sData[0] != 'k') &&
         (sData[0] != 'b') &&
         (sData[0] != 's') &&
         (sData[0] != 'c') &&
         (sData[0] != 'o') &&
         (sData[0] != '!')  )
        return PLUGIN_CONTINUE;

    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         (!playerinfo(g_tkedby[UserIndex], sNameA, MAX_NAME_LENGTH, iUserIDA, iWONIDA, iTeamA, iDeadA, sAuthIDA)) )
    {
        LogMessage("HandleSay attacker playerinfo failed");
        return PLUGIN_CONTINUE;
    }

    if (!playerinfo(UserIndex, sNameV, MAX_NAME_LENGTH, iUserIDV, iWONIDV, iTeamV, iDeadV, sAuthIDV))
    {
        LogMessage("HandleSay victim playerinfo failed");
        return PLUGIN_CONTINUE;
    }

    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData, "forgive", 7) == 0) ||
          (strncasecmp(sData, "!forgive", 8) == 0)) )
    {
        g_TkCount[g_tkedby[UserIndex]] -= 1;
        RemoveTk(sAuthIDA);

        g_tkedby[UserIndex] = USER_ID_INVALID;

        snprintf( sData,
                  MAX_DATA_LENGTH,
                  "%s decided to forgive %s and forfeit revenge.",
                  sNameV,
                  sNameA);
        SayMessage(sData);

        iReturn = PLUGIN_HANDLED;
    }
    else
    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData,"slay",4) == 0) ||
          (strncasecmp(sData,"!slay",5) == 0) ||
          (strncasecmp(sData,"punishtk",8) == 0) ||
          (strncasecmp(sData,"!punishtk",9) == 0)) )
    {
        if ( (g_AllowPunish & ACTION_SLAY) &&
             (!IsImmune(sNameA)) )
        {
            if (!iDeadA)
            {
                slay(sAuthIDA);

                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s decided to have %s executed for TK revenge.",
                          sNameV,
                          sNameA);
            }
            else
            {
                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s is dead.  Revenge will happen next round.",
                          sNameA);

                g_TkRevenge[g_tkedby[UserIndex]] = ACTION_SLAY;
            }

            g_tkedby[UserIndex] = USER_ID_INVALID;

            SayMessage(sData);

            iReturn = PLUGIN_HANDLED;
        }
    }
    else
    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData,"ban",3) == 0) ||
          (strncasecmp(sData,"!ban",4) == 0)) )
    {
       if ( (g_AllowPunish & ACTION_BAN) &&
             (!IsImmune(sNameA)) )
        {
            BanUser(sAuthIDA, g_TkLimitBanTime);

            g_tkedby[UserIndex] = USER_ID_INVALID;

            snprintf( sData,
                      MAX_DATA_LENGTH,
                      "%s decided to have %s banned for TK revenge.",
                      sNameV,
                      sNameA);
            SayMessage(sData);

            iReturn = PLUGIN_HANDLED;
        }
    }
    else
    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData,"kick",4) == 0) ||
          (strncasecmp(sData,"!kick",5) == 0)) )
    {
        if ( (g_AllowPunish & ACTION_KICK) &&
             (!IsImmune(sNameA)) )
        {
            kick(sAuthIDA);
            g_tkedby[UserIndex] = USER_ID_INVALID;

            snprintf( sData,
                      MAX_DATA_LENGTH,
                      "%s decided to have %s kicked for TK revenge.",
                      sNameV,
                      sNameA);
            SayMessage(sData);

            iReturn = PLUGIN_HANDLED;
        }
    }
    else
    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData,"slap",4) == 0) ||
          (strncasecmp(sData,"!slap",5) == 0)) )
    {
        if ( (g_AllowPunish & ACTION_SLAP) &&
             (!IsImmune(sNameA)) )
        {
            if (!iDeadA)
            {
                new i;

                for (i = 0; i <= g_UserSlapCount; i++)
                    slap(sAuthIDA);

                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s decided to have %s slapped for TK revenge.",
                          sNameV,
                          sNameA);
            }
            else
            {
                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s is dead.  Revenge will happen next round.",
                          sNameA);

                g_TkRevenge[g_tkedby[UserIndex]] = ACTION_SLAP;
            }

            g_tkedby[UserIndex] = USER_ID_INVALID;

            SayMessage(sData);

            iReturn = PLUGIN_HANDLED;
        }
    }
    else
    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData,"bury",4) == 0) ||
          (strncasecmp(sData,"!bury",5) == 0)) )
    {
        if ( (g_AllowPunish & ACTION_BURY) &&
             (!IsImmune(sNameA)) )
        {
            if (!iDeadA)
            {
                BuryUser(sAuthIDA);

                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s decided to have %s buried for TK revenge.",
                          sNameV,
                          sNameA);
            }
            else
            {
                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s is dead.  Revenge will happen next round.",
                          sNameA);

                g_TkRevenge[g_tkedby[UserIndex]] = ACTION_BURY;
            }

            g_tkedby[UserIndex] = USER_ID_INVALID;

            SayMessage(sData);

            iReturn = PLUGIN_HANDLED;
        }
    }
    else
    if ( (g_tkedby[UserIndex] != USER_ID_INVALID) &&
         ((strncasecmp(sData,"chicken",7) == 0) ||
          (strncasecmp(sData,"!chicken",8) == 0)) )
    {
        if ( (g_AllowPunish & ACTION_CHICKEN) &&
             (!IsImmune(sNameA)) )
        {
            if (!iDeadA)
            {
                ChickenUser(sAuthIDA);

                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s decided to have %s turned into a chicken for TK revenge.",
                          sNameV,
                          sNameA);
            }
            else
            {
                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s is dead.  Revenge will happen next round.",
                          sNameA);

                g_TkRevenge[g_tkedby[UserIndex]] = ACTION_CHICKEN;
            }

            g_tkedby[UserIndex] = USER_ID_INVALID;

            SayMessage(sData);

            iReturn = PLUGIN_HANDLED;
        }
    }
    else
    if ((strncasecmp(sData,"score",5) == 0) ||
        (strncasecmp(sData,"!score",6) == 0))
    {
        return ShowScore();
    }
    else
    if ((strncasecmp(sData,"overrideban",11) == 0) ||
        (strncasecmp(sData,"!overrideban",12) == 0))
    {
        if (strlen(g_LastTkBan) != 0)
        {
            if ( (g_AllowPunish & ACTION_OVERRIDE_BAN) &&
                 (access(g_UserOverrideLevel, sNameV) != 0) )
            {
                UnbanUser(g_LastTkBan);

                snprintf( sData,
                          MAX_DATA_LENGTH,
                          "%s removed the last ban.",
                          sNameV);
                SayMessage(sData);

                LogMessage("Last ban overridden.");

                iReturn = PLUGIN_HANDLED;
            }
        }
    }

    return iReturn;
}

public plugin_connect(HLUserName, HLIP, UserIndex)
{
    new i;

    if (UserIndex >= 1 && UserIndex <= MAX_PLAYERS)
    {
        g_TkCount[UserIndex] = TK_CLEAN_SLATE;

        // load the tk count from the file
        new sName[MAX_NAME_LENGTH];
        new sAuthID[MAX_AUTHID_LENGTH];
        new iUserID;
        new iWONID;
        new iTeam;
        new iDead;

        playerinfo(UserIndex, sName, MAX_NAME_LENGTH, iUserID, iWONID, iTeam, iDead, sAuthID);

        AgeTks(sAuthID);

        for (i = 1; i <= MAX_PLAYERS; i++)
        {
            if (g_tkedby[i] == UserIndex)
            {
                g_tkedby[i] = USER_ID_INVALID;
            }
        }

        g_tkedby[UserIndex] = USER_ID_INVALID;
        g_TdCount[UserIndex] = TD_CLEAN_SLATE;
        g_TkRevenge[UserIndex] = ACTION_NONE;

        ResetMenu(UserIndex);
    }

    DisplayConsgreet();

    return PLUGIN_CONTINUE;
}

public plugin_disconnect(HLUserName, UserIndex)
{
    new i;

    if (UserIndex >= 1 && UserIndex <= MAX_PLAYERS)
    {
        g_TkCount[UserIndex] = TK_CLEAN_SLATE;

        for (i = 1; i <= MAX_PLAYERS; i++)
        {
            if (g_tkedby[i] == UserIndex)
            {
                g_tkedby[i] = USER_ID_INVALID;
            }
        }

        g_tkedby[UserIndex] = USER_ID_INVALID;
        g_TdCount[UserIndex] = TD_CLEAN_SLATE;
        g_TkRevenge[UserIndex] = ACTION_NONE;

        UnchickenUser(UserIndex);
        ClearTkMenu(UserIndex);
    }

    return PLUGIN_CONTINUE;
}


public admin_ffmon(HLCommand, HLData, HLUserName, UserIndex)
{
    new sData[MAX_DATA_LENGTH];
    new sCommand[MAX_DATA_LENGTH];
    new sArgument[MAX_DATA_LENGTH];
    new iDisplayStatus = 0;

    convert_string(HLData, sData, MAX_DATA_LENGTH);

    strinit(sCommand);
    strinit(sArgument);

    if (!strlen(sData))
    {
        if (!access(ACCESS_FFMON, ""))
        {
            iDisplayStatus = 1;
        }
        else
        {
            DisplayHelp();
            return PLUGIN_HANDLED;
        }
    }
    else
    {
        strsplit(sData, " ", sCommand, MAX_DATA_LENGTH, sArgument, MAX_DATA_LENGTH);
    }

    if (streq(sCommand, "status"))
    {
        iDisplayStatus = 1;
    }
    else
    if (streq(sCommand, "showtk"))
    {
        DisplayTk();
        return PLUGIN_HANDLED;
    }
    else
    if (streq(sCommand, "showtd"))
    {
        DisplayTd();
        return PLUGIN_HANDLED;
    }

    if (!access(ACCESS_FFMON, ""))
    {
        selfmessage("FFMon: You are not allowed to use this command");
        return PLUGIN_HANDLED;
    }

    if (streq(sCommand, "help"))
    {
        DisplayHelp();
        return PLUGIN_HANDLED;
    }
    else
    if (streq(sCommand, "on"))
    {
        g_Enabled = 1;
        SaveConfiguration();
        iDisplayStatus = 1;
    }
    else
    if (streq(sCommand, "off"))
    {
        g_Enabled = 0;
        SaveConfiguration();
        iDisplayStatus = 1;
    }

    if (iDisplayStatus)
    {
        DisplayParamState("FFMon", g_Enabled);
    }

    if (streq(sCommand, "debug") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_DebugMessages = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Debug messages", g_DebugMessages);
    }

    if (streq(sCommand, "tk") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkProtection = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("TK monitoring", g_TkProtection);
    }

    if (streq(sCommand, "tklimit") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkLimit = strtonum(sArgument);
            SaveConfiguration();
        }

        if (g_TkLimit < 1)
            g_TkLimit = 1;

        DisplayParamValue("TK limit before banning", g_TkLimit);
    }

    if (streq(sCommand, "tkbantime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkLimitBanTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("TK limit ban time", g_TkLimitBanTime);
    }

    if (streq(sCommand, "tkmenu") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkMenu = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("TK menu", g_TkMenu);
    }

    if (streq(sCommand, "tkreset") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkReset = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("TK reset", g_TkReset);
    }

    if (streq(sCommand, "tksave") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkSave = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("TK save", g_TkSave);
    }

    if (streq(sCommand, "tksavetime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkSaveTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("TK save time", g_TkSaveTime);
    }

    if (streq(sCommand, "tksavelimit") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkSaveLimit = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("TK save limit", g_TkSaveLimit);
    }

    if (streq(sCommand, "tksavebantime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkSaveBanTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("TK save ban time", g_TkSaveBanTime);
    }

    if (streq(sCommand, "tksavepurgetime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TkSavePurgeTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("TK save purge time", g_TkSavePurgeTime);
    }

    if (streq(sCommand, "td") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TdProtection = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("TD monitoring", g_TdProtection);
    }

    if (streq(sCommand, "tdlimit") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TdLimit = strtonum(sArgument);
            SaveConfiguration();
        }

        if (g_TdLimit < 1)
            g_TdLimit = 1;

        DisplayParamValue("TD limit before slaying", g_TdLimit);
    }

    if (streq(sCommand, "tdaction") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_TdAction = check_action(sArgument);
            SaveConfiguration();
        }

        new sAction[MAX_TEXT_LENGTH];
        GetActionString(g_TdAction, sAction);

        DisplayParamString("TD abuser", sAction);
    }

    if (streq(sCommand, "allowpunish") || iDisplayStatus)
    {
        new sCurrent[MAX_DATA_LENGTH];
        new sNext[MAX_DATA_LENGTH];

        if (strlen(sArgument))
        {
            strsep(sData, " ", sCommand, MAX_DATA_LENGTH, sArgument, MAX_DATA_LENGTH);

            // walk the command line added each option
            while (strlen(sArgument))
            {
                strsep(sArgument, " ", sCurrent, MAX_DATA_LENGTH, sNext, MAX_DATA_LENGTH);

                // do the appropriate thing with this action
                ParseAction(sCurrent);

                // move to the next item
                strcpy(sArgument, sNext, MAX_DATA_LENGTH);
            }

            SaveConfiguration();
        }

        sCurrent = "";
        BuildPunnishmentList(sCurrent);

        DisplayParamString("Punishments allowed", sCurrent);
    }

    if (streq(sCommand, "userslapcount") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_UserSlapCount = strtonum(sArgument);
            SaveConfiguration();
        }

        if (g_UserSlapCount < 1)
            g_UserSlapCount = 1;

        DisplayParamValue("User slap count after TK", g_UserSlapCount);
    }

    if (streq(sCommand, "overridelevel") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_UserOverrideLevel = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("User override access level", g_UserOverrideLevel);
    }

    if (streq(sCommand, "buryglowtime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_BuryGlowTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Glow time after a bury", g_BuryGlowTime);
    }

    if (streq(sCommand, "rs") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Round start time", g_RoundStartTime);
    }

    if (streq(sCommand, "rsbantk") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartBanTk = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Round start TK banning", g_RoundStartBanTk);
    }

    if (streq(sCommand, "rsbantktime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartBanTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Round start TK ban time", g_RoundStartBanTime);
    }

    if (streq(sCommand, "rstdslap") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartTdSlap = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Round start TD slapping", g_RoundStartTdSlap);
    }

    if (streq(sCommand, "rstdslapcount") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartTdSlapTimes = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Round start TD slap count", g_RoundStartTdSlapTimes);
    }

    if (streq(sCommand, "rstdaction") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartTdAction = check_action(sArgument);
            SaveConfiguration();
        }

        new sAction[MAX_TEXT_LENGTH];
        GetActionString(g_RoundStartTdAction, sAction);

        DisplayParamString("Round start TD abuser", sAction);
    }

    if (streq(sCommand, "rstdcount") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartTdCount = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Round start TD count for action", g_RoundStartTdCount);
    }

    if (streq(sCommand, "rstdbantime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_RoundStartTdBanTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Round start TD ban time", g_RoundStartTdBanTime);
    }

    if (streq(sCommand, "ss") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SpawnShotProtection = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Spawn shot monitoring", g_SpawnShotProtection);
    }

    if (streq(sCommand, "sstime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SpawnShotTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Spawn shot monitor time", g_SpawnShotTime);
    }

    if (streq(sCommand, "ssaction") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SpawnShotAction = check_action(sArgument);
            SaveConfiguration();
        }

        new sAction[MAX_TEXT_LENGTH];
        GetActionString(g_SpawnShotAction, sAction);

        DisplayParamString("Spawn shooters", sAction);
    }

    if (streq(sCommand, "ssbantime") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SpawnShotBanTime = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Spawn shot ban time", g_SpawnShotBanTime);
    }

    if (streq(sCommand, "skunk") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SkunkProtection = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Skunk monitoring", g_SkunkProtection);
    }

    if (streq(sCommand, "skunktype") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SkunkType = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Skunk type", g_SkunkType);
    }

    if (streq(sCommand, "skunklimit") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_SkunkLimit = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Skunk limit", g_SkunkLimit);
    }

    if (streq(sCommand, "immunity") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_Immunity = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Admin immunity", g_Immunity);
    }

    if (streq(sCommand, "immunitylevel") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_ImmunityLevel = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Immunity level", g_ImmunityLevel);
    }

    if (streq(sCommand, "scores") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_DisplayScores = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Team score display", g_DisplayScores);
    }

    if (streq(sCommand, "log") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_LogMessages = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Message logging", g_LogMessages);
    }

    if (streq(sCommand, "consgreet") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_DisplayConsgreet = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Console greeting", g_DisplayConsgreet);
    }

    if (streq(sCommand, "blockattack") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_BlockAttackMessages = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Attack message blocking", g_BlockAttackMessages);
    }

    if (streq(sCommand, "startvote") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_StartVote = check_param(sArgument);
            SaveConfiguration();
        }

        DisplayParamState("Round vote starting", g_StartVote);
    }

    if (streq(sCommand, "startvoteround") || iDisplayStatus)
    {
        if (strlen(sArgument))
        {
            g_StartVoteRound = strtonum(sArgument);
            SaveConfiguration();
        }

        DisplayParamValue("Start vote round", g_StartVoteRound);
    }

    return PLUGIN_HANDLED;
}

public menuselect(HLCommand, HLData, HLUserName, UserIndex)
{
    if (!g_Enabled)
        return PLUGIN_CONTINUE;

    new iSessionID = 0;
    new iWONID = 0;
    new iTeam = 0;
    new iDead = 0;
    new sName[MAX_NAME_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];

    new Data[MAX_DATA_LENGTH];

    if (!g_TkMenu)
        return PLUGIN_CONTINUE;

    convert_string(HLData, Data, MAX_DATA_LENGTH);
    new iMenu = strtonum(Data);

    if (g_MenuStates[UserIndex][MENU_STATE_OPEN] == 0)
    {
        /*User was not in the menu*/
        g_MenuStates[UserIndex][MENU_STATE_KILLED] = 0;

        return PLUGIN_CONTINUE;
    }

    if (g_MenuStates[UserIndex][MENU_STATE_KILLED] == 1)
    {
        /*User was in menu system when a vote got called*/
        g_MenuStates[UserIndex][MENU_STATE_KILLED] = 0;

        DisplayTkMenu(UserIndex);

        return PLUGIN_CONTINUE;
    }

    if (iMenu > 10 || iMenu < 0)
    {
        /*There is no case for this, someone else can deal with it*/
        return PLUGIN_CONTINUE;
    }

    playerinfo(UserIndex, sName, MAX_NAME_LENGTH, iSessionID, iWONID, iTeam, iDead, sAuthID);

    ClearTkMenu(UserIndex);

    if (iMenu > 0 && iMenu < MAX_TK_OPTIONS)
    {
        // do whatever action they chose
        execclient(sName, g_TkMenuAction[iMenu - 1]);
    }

    return PLUGIN_HANDLED;
}

/*

    Watch for vote commands and lurk around them

*/
public handlevote(HLCommand, HLData, HLUserName, UserIndex)
{
    new sName[MAX_NAME_LENGTH];
    new sAuthID[MAX_AUTHID_LENGTH];
    new iSessionID;
    new iTeam;
    new iWONID;
    new iDead;
    new i;

    for (i = 1; i <= maxplayercount(); i++)
    {
        strinit(sName);
        if (playerinfo(i, sName, MAX_NAME_LENGTH, iSessionID, iWONID, iTeam, iDead, sAuthID) == 1)
        {
            g_MenuStates[i][MENU_STATE_KILLED] = 1;
        }
    }

    return PLUGIN_CONTINUE;
}

public resetstate(HLCommand, HLData, HLUserName, UserIndex)
{
    ResetMenu(UserIndex);

    return PLUGIN_CONTINUE;
}

public admin_ffmon_purge(HLCommand, HLData, HLUserName, UserIndex)
{
    PurgeTkFiles();
}

public admin_ffmon_reload(HLCommand, HLData, HLUserName, UserIndex)
{
    LoadConfiguration();
}

public plugin_init()
{
    plugin_registerinfo("FFMon", "Helps manage a Friendly Fire server. Special edited for THH server.", STRING_VERSION);

    plugin_registercmd("ffmon_checktk",     "ffmon_checktk", ACCESS_CONSOLE);
    plugin_registercmd("ffmon_worldaction", "ffmon_worldaction",   ACCESS_CONSOLE);
    plugin_registercmd("ffmon_teamaction",  "ffmon_teamaction",   ACCESS_CONSOLE);
    plugin_registercmd("ffmon_checktd",     "ffmon_checktd", ACCESS_CONSOLE);

    plugin_registercmd("admin_ffmon", "admin_ffmon", ACCESS_FFMON, "admin_ffmon");
    plugin_registercmd("admin_ffmon_purge", "admin_ffmon_purge", ACCESS_FFMON, "admin_ffmon_purge");
    plugin_registercmd("admin_ffmon_reload", "admin_ffmon_reload", ACCESS_FFMON, "admin_ffmon_reload");
    plugin_registercmd("say","HandleSay",ACCESS_ALL);

    /* menu specific handlers */
    plugin_registercmd("admin_vote_map", "handlevote", ACCESS_ALL, "");
    plugin_registercmd("admin_vote_kick", "handlevote", ACCESS_ALL, "");
    plugin_registercmd("admin_vsay", "handlevote", ACCESS_ALL, "");

    plugin_registercmd("menuselect", "menuselect", ACCESS_ALL);

    plugin_registercmd("radio1", "resetstate", ACCESS_ALL);
    plugin_registercmd("radio2", "resetstate", ACCESS_ALL);
    plugin_registercmd("radio3", "resetstate", ACCESS_ALL);
    plugin_registercmd("buy", "resetstate", ACCESS_ALL);
    plugin_registercmd("buyequip", "resetstate", ACCESS_ALL);
    plugin_registercmd("chooseteam", "resetstate", ACCESS_ALL);
    /* end menu specific */

    exec( "logd_reg 57 admin_command ffmon_checktk" );
    exec( "logd_reg 58 admin_command ffmon_checktd" );
    exec( "logd_reg 61 admin_command ffmon_teamaction" );
    exec( "logd_reg 62 admin_command ffmon_worldaction" );

    LoadConfiguration();
    SetLogDetail();
    ForceNextMapUndo();

    CheckPeriodicPurge();

    g_VoteStarted = 0;
    g_CtScore     = 0;
    g_TScore      = 0;

    return PLUGIN_CONTINUE;
}

